Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit 03a5004

Browse files
authored
When the world stops (#2990)
1 parent 399b863 commit 03a5004

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed

docs/updates/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ Here are some highlights from the week:
8888
* :police_car: License checking for our dependencies
8989
* :nail_care: Lots of UI polish
9090

91+
### [When the World Stops](./when-the-world-stops.md)
92+
93+
One of the most interesting debugger questions is what happens when the debugger stops? This post is a quick run-through that will give you some context so that you can dig in and answer your own questions.
9194

9295
### [May 23rd](./updates-5-23-2017.md)
9396

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
### When the world stops
2+
3+
4+
One of the most interesting questions is what happens when the debugger stops? Basically, what happens when the world pauses?
5+
6+
Here is a quick run-through that will help give you some context so that you can dig in and answer your own questions.
7+
8+
There are three characters in the story: the client, server, and engine.
9+
When the page pauses, the engine pauses the page, tells the server,
10+
and the server tells the client.
11+
12+
This post is going to focus on the work the server does when the page pauses. The code included has been simplified a bit to remove some of the incidental complexity.
13+
14+
---
15+
16+
The [server][script.js] has several page lifecycle hooks that are called when
17+
something happens. `onDebuggerStatement` is one of these hooks!
18+
19+
```js
20+
onDebuggerStatement: function (frame) {
21+
return this._pauseAndRespond(frame, { type: "debuggerStatement" });
22+
}
23+
```
24+
25+
The [server][script.js]'s main job is to tell the client that the page is paused.
26+
You can see below, that the `_paused` function is building a `packet`, this packet
27+
has all of the data the client needs to inform the programmer about the pause state.
28+
The `_paused` function also does one other interesting thing here as well. When the function is triggered, it registers all of the relevant actors in the story and adds them to various "actor pools".
29+
The server registers all of the actors so that if the client has any follow up questions,
30+
the server will know who to follow up with.
31+
32+
The server is like a detective, it wants to know who all the major witnesses are!
33+
34+
35+
```js
36+
_paused: function (frame) {
37+
this._state = "paused";
38+
39+
40+
this._pausePool = new ActorPool(this.conn);
41+
this.conn.addActorPool(this._pausePool);
42+
43+
// Give children of the pause pool a quick link back to the
44+
// thread...
45+
this._pausePool.threadActor = this;
46+
this._pauseActor = new PauseActor(this._pausePool);
47+
this._pausePool.addActor(this._pauseActor);
48+
49+
// Update the list of frames.
50+
let poppedFrames = this._updateFrames();
51+
52+
// Send off the paused packet and spin an event loop.
53+
let packet = { from: this.actorID,
54+
type: "paused",
55+
actor: this._pauseActor.actorID };
56+
if (frame) {
57+
packet.frame = this._createFrameActor(frame).form();
58+
}
59+
60+
if (poppedFrames) {
61+
packet.poppedFrames = poppedFrames;
62+
}
63+
64+
return packet;
65+
}
66+
```
67+
68+
Lets continue digging into how the server builds the pause packet for the client.
69+
We already know quite a bit already. We know the `type` is "paused", it has a `from` and an `actor` field which is used as a return address :). We also know that there are two types of frame fields: `frame` and `poppedFrames`. Lets focus on the `frame` field because it is simpler.
70+
71+
We get the `frame` packet data by asking for the frame actor's `form`.
72+
The form function is defined in the Frame actor [class][frame.js].
73+
Lets slow down and take a look at the data the frame actor puts in its form.
74+
It's fascinating!
75+
76+
It starts with the `actor` and `type` field for record keeping. It then sets up `callee`, `environment`, `arguments`, `where`, and `oldest` fields. Oldest, what's that? :) It's neat to think of a frame as potentially a function frame, at which point we care about the callee and arguments data.
77+
That's cool, but where's the good stuff? Where are the variables and scope data kept? All of that is kept in an environment actor and fetched through a similar `form` function. Lets keep on digging!
78+
79+
```js
80+
form: function () {
81+
let threadActor = this.threadActor;
82+
let form = { actor: this.actorID,
83+
type: this.frame.type };
84+
85+
if (this.frame.type === "call") {
86+
form.callee = createValueGrip(this.frame.callee);
87+
}
88+
89+
if (this.frame.environment) {
90+
form.environment = threadActor.createEnvironmentActor(this.frame.environment).form()
91+
}
92+
93+
form.arguments = this._args();
94+
95+
let location = this.threadActor.sources.getFrameLocation(this.frame);
96+
form.where = {
97+
source: location.generatedSourceActor.form(),
98+
line: location.generatedLine,
99+
column: location.generatedColumn
100+
};
101+
102+
if (!this.frame.older) {
103+
form.oldest = true;
104+
}
105+
106+
return form;
107+
}
108+
```
109+
110+
Alright, we've arrived at a frame environment and we're asking it for its form.
111+
Environment is just a fancy word for a scope, so all that's happening is the frame is saying, "yo scope, whatcha got for me"? Our main goal here is to see where the variables are documented.
112+
So lets walk through what it's doing. First the environment notes its type. Then it does some sneaky recursion to inquire into its parent scope's data. Let's not get nerd sniped! Then it gets object and function data, remember we could be in a function or object scope, right?!?!
113+
Then we get bindings... Bindings are exactly what we want here. A good way to think of bindings are: variables are bound to scopes in JS, ergo variables are in the bindings thing! Lets go a bit deeper!
114+
115+
```js
116+
form: function () {
117+
let form = { actor: this.actorID };
118+
119+
// What is this environment's type?
120+
if (this.obj.type == "declarative") {
121+
form.type = this.obj.callee ? "function" : "block";
122+
} else {
123+
form.type = this.obj.type;
124+
}
125+
126+
// Does this environment have a parent?
127+
if (this.obj.parent) {
128+
form.parent = this.threadActor.createEnvironmentActor(this.obj.parent).form();
129+
}
130+
131+
// Does this environment reflect the properties of an object as variables?
132+
if (this.obj.type == "object" || this.obj.type == "with") {
133+
form.object = createValueGrip(this.obj.object);
134+
}
135+
136+
// Is this the environment created for a function call?
137+
if (this.obj.callee) {
138+
form.function = createValueGrip(this.obj.callee);
139+
}
140+
141+
// Shall we list this environment's bindings?
142+
if (this.obj.type == "declarative") {
143+
form.bindings = this.bindings();
144+
}
145+
146+
return form;
147+
}
148+
```
149+
150+
We've landed in the Environment's bindings function and it's glorious. We're
151+
finally looking for variables, and not just variables but Arguments as well. It
152+
doesn't get much better than this! I think the coolest thing about this code is
153+
we've reached a local floor for the server where we can go no deeper.
154+
At the very bottom of this descent, script, frame, environment (scope), bindings,
155+
we have a call for the variable `getVariable(name)`. This is a [call][var] that makes the leap back into the engine and out of the server!
156+
157+
```js
158+
bindings: function () {
159+
let bindings = { arguments: [], variables: {} };
160+
161+
const parameterNames = this.obj.callee ? this.obj.callee.parameterNames : [];
162+
163+
for (let name of parameterNames) {
164+
let arg = {};
165+
let value = this.obj.getVariable(name);
166+
167+
let desc = {
168+
value: value,
169+
configurable: false,
170+
enumerable: true
171+
};
172+
173+
let descForm = {
174+
enumerable: true,
175+
configurable: desc.configurable
176+
};
177+
descForm.value = createValueGrip(desc.value);
178+
arg[name] = descForm;
179+
bindings.arguments.push(arg);
180+
}
181+
182+
for (let name of this.obj.names()) {
183+
let value = this.obj.getVariable(name);
184+
185+
let desc = {
186+
value: value,
187+
configurable: false,
188+
enumerable: true
189+
};
190+
191+
let descForm = {
192+
enumerable: true,
193+
configurable: desc.configurable
194+
};
195+
196+
descForm.value = createValueGrip(desc.value);
197+
bindings.variables[name] = descForm;
198+
}
199+
200+
return bindings;
201+
}
202+
```
203+
204+
---
205+
206+
We have gone as deep as we care to for the time being. We started at a humble lifecycle hook `onDebuggerStatement` and ended with a call for variable data in a scope. I hope you enjoyed this story and are curious to do your own digging. We're always available in [slack] and would love to hear from you.
207+
208+
209+
[Debugger.cpp]:https://github.com/mozilla/gecko-dev/blob/master/js/src/vm/Debugger.cpp
210+
[script.js]:https://github.com/mozilla/gecko-dev/blob/master/devtools/server/actors/script.js
211+
[frame.js]:https://github.com/mozilla/gecko-dev/blob/master/devtools/server/actors/frame.js
212+
[environment.js]:https://github.com/mozilla/gecko-dev/blob/master/devtools/server/actors/environment.js
213+
[var]:http://searchfox.org/mozilla-central/source/js/src/vm/Debugger.cpp#11528
214+
[slack]:https://devtools-html.slack.com

0 commit comments

Comments
 (0)