Confirmed users
496
edits
(Refined and expanded.) |
|||
Line 15: | Line 15: | ||
For example, a debugger might connect to a browser, ask the root actor to list the browser's tabs, and present this list to the developer. If the developer chooses some tabs to debug, then the debugger sends "attach" requests to the actors representing those tabs, to begin debugging. Both artifacts of the program being debugged, like JavaScript objects and stack frames, and artifacts of the debugging machinery, like breakpoints and watchpoints, are actors to which packets can be addressed. | For example, a debugger might connect to a browser, ask the root actor to list the browser's tabs, and present this list to the developer. If the developer chooses some tabs to debug, then the debugger sends "attach" requests to the actors representing those tabs, to begin debugging. Both artifacts of the program being debugged, like JavaScript objects and stack frames, and artifacts of the debugging machinery, like breakpoints and watchpoints, are actors to which packets can be addressed. | ||
All actors form a tree, with the root actor as the root. Closing communications with an actor closes communications with all its descendants. The root actor has no owner, and lives as long as the underlying connection to the client does; when the underlying connection is closed, all actor names are closed. These limits on the lifetimes of actor names allow the protocol to mention actors freely, without forcing the client to explicitly free every actor that has ever been mentioned. | |||
Note that the actor hierarchy does not, in general, correspond to any particular hierarchy appearing in the debuggee. For example, although web workers are arranged in a hierarchy, all actors representing web worker threads are direct children of the root actor: one might want to detach from a parent worker while continuing to debug one of its children, so it doesn't make sense to close communications with a child worker when one closes communicatinos with its parent. | |||
<i>(We are stealing the "actor" terminology from Mozilla's [[IPDL]], to mean, roughly, "things participating in the protocol". However, IPDL does much more with the idea than we do: it treats both client and server as collections of actors, and uses that detail to statically verify properties of the protocol. In contrast, the debugging protocol simply wants a consistent way to indicate the entities to which packets are directed.)</i> | <i>(We are stealing the "actor" terminology from Mozilla's [[IPDL]], to mean, roughly, "things participating in the protocol". However, IPDL does much more with the idea than we do: it treats both client and server as collections of actors, and uses that detail to statically verify properties of the protocol. In contrast, the debugging protocol simply wants a consistent way to indicate the entities to which packets are directed.)</i> | ||
Line 27: | Line 29: | ||
{ "to": <i>actor</i>, "type": <i>type</i>, ... } | { "to": <i>actor</i>, "type": <i>type</i>, ... } | ||
where <i>actor</i> is the actor to whom the packet is directed—actor names are always | where <i>actor</i> is the actor to whom the packet is directed—actor names are always integers—and <i>type</i> is a string specifying what sort of packet it is. Additional properties may be present, depending on <i>type</i>. | ||
Every packet sent from the server has the form: | Every packet sent from the server has the form: | ||
Line 51: | Line 53: | ||
{ "from":0, "application-type":<i>app-type</i>, "traits":<i>traits</i>, ...} | { "from":0, "application-type":<i>app-type</i>, "traits":<i>traits</i>, ...} | ||
The root actor's name is always zero. <i>app-type</i> is a | The root actor's name is always zero. <i>app-type</i> is a string indicating what sort of program the server represents. There may be more properties present, depending on <i>app-type</i>. | ||
<i>traits</i> is | <i>traits</i> is an object describing protocol variants this server supports that are not convenient for the client to detect otherwise. The property names present indicate what traits the server has; the properties' values depend on their names. This version of the protocol defines no traits, so <i>traits</i> must be an object with no properties, {}. | ||
For web browsers, the introductory packet should have the following form: | For web browsers, the introductory packet should have the following form: | ||
Line 61: | Line 63: | ||
== Listing Top-Level Browsing Contexts == | == Listing Top-Level Browsing Contexts == | ||
To get a list of the top-level browsing contexts present in a browser, a client should send a | To get a list of the top-level browsing contexts (tabs) present in a browser, a client should send a request like the following to the root actor: | ||
{ "to":0, "type":"list-contexts" } | { "to":0, "type":"list-contexts" } | ||
The | The reply should have the form: | ||
{ "from":0, "contexts":[<i>context</i>...] } | { "from":0, "contexts":[<i>context</i>...], selected:<i>index</i> } | ||
<tt>Contexts</tt> is a list with one element for each top-level browsing context present in the browser, and <i>index</i> is the index within that list of the browsing context the user is currently interacting with. Each <i>context</i> has the following form: | |||
{ "actor":<i>actor</i>, "title":<i>title</i>, "url":<i>url</i> } | { "actor":<i>actor</i>, "title":<i>title</i>, "url":<i>url</i> } | ||
Line 91: | Line 93: | ||
= Interacting with Thread-Like Actors = | = Interacting with Thread-Like Actors = | ||
Actors representing independent threads of JavaScript execution, like browsing contexts and web workers, are collectively known as "threads". Interactions with actors representing threads follow a more | Actors representing independent threads of JavaScript execution, like browsing contexts and web workers, are collectively known as "threads". Interactions with actors representing threads follow a more complicated communication pattern. | ||
A thread is always in one of the following states: | A thread is always in one of the following states: | ||
* <b>Detached</b>: the thread is running freely, and not presently interacting with the debugger. Detached threads run, encounter errors, and exit without exchanging any sort of messages with the debugger. A debugger can attach to a thread, putting it in the | * <b>Detached</b>: the thread is running freely, and not presently interacting with the debugger. Detached threads run, encounter errors, and exit without exchanging any sort of messages with the debugger. A debugger can attach to a thread, putting it in the <b>Running</b> state. Or, a detached thread may exit on its own, entering the <b>Exited</b> state. | ||
* <b>Running</b>: the thread is running under the debugger's observation, executing JavaScript code or possibly blocked waiting for input. It will report exceptions, breakpoint hits, watchpoint hits, and other interesting events to the client, and enter the | * <b>Running</b>: the thread is running under the debugger's observation, executing JavaScript code or possibly blocked waiting for input. It will report exceptions, breakpoint hits, watchpoint hits, and other interesting events to the client, and enter the <b>Paused</b> state. The debugger can also interrupt a running thread; this elicits a response and puts the thread in the <b>Paused</b> state. A running thread may also exit, entering the "exited" state. | ||
* <b>Paused</b>: the thread has reported a pause to the client and is awaiting further instructions. In this state, a thread can accept requests and send replies. If the client asks the thread to continue or step, it returns to the | * <b>Paused</b>: the thread has reported a pause to the client and is awaiting further instructions. In this state, a thread can accept requests and send replies. If the client asks the thread to continue or step, it returns to the <b>Running</b> state. | ||
* <b>Exited</b>: the thread has ceased execution, and will disappear. The resources of the underlying thread may have been freed; this state really indicates that the actor's name is not yet available for reuse. When the actor receives a "release" packet, the name may be reused. | * <b>Exited</b>: the thread has ceased execution, and will disappear. The resources of the underlying thread may have been freed; this state really indicates that the actor's name is not yet available for reuse. When the actor receives a "release" packet, the name may be reused. | ||
Line 109: | Line 111: | ||
* At no point may either client or server send an unbounded number of packets without receiving a packet from its counterpart. This avoids deadlock without requiring either side to buffer an arbitrary number of packets per actor. | * At no point may either client or server send an unbounded number of packets without receiving a packet from its counterpart. This avoids deadlock without requiring either side to buffer an arbitrary number of packets per actor. | ||
* In states where a transition can be initiated by either the debugger or the thread, it is always clear to the debugger which state the thread actually entered, and for what reason.<p>For example, if the debugger interrupts a running thread, it cannot be sure whether the thread stopped because of the interruption, paused of its own accord (to report a watchpoint hit, say), or exited. However, the next packet the debugger receives will either be "interrupted", "paused", or "exited", resolving the ambiguity.</p><p>Similarly, when the debugger attaches to a thread, it cannot be sure whether it has succeeded in attaching to the thread, or whether the thread exited before the attachment request arrived. However, in either case the debugger can expect a disambiguating response: if the attach suceeded, it receives an "attached" packet; and in the second case, it receives an "exit" packet.</p><p>To support this property, the thread ignores certain debugger packets in some states (the "interrupt" packet in the | * In states where a transition can be initiated by either the debugger or the thread, it is always clear to the debugger which state the thread actually entered, and for what reason.<p>For example, if the debugger interrupts a running thread, it cannot be sure whether the thread stopped because of the interruption, paused of its own accord (to report a watchpoint hit, say), or exited. However, the next packet the debugger receives will either be "interrupted", "paused", or "exited", resolving the ambiguity.</p><p>Similarly, when the debugger attaches to a thread, it cannot be sure whether it has succeeded in attaching to the thread, or whether the thread exited before the attachment request arrived. However, in either case the debugger can expect a disambiguating response: if the attach suceeded, it receives an "attached" packet; and in the second case, it receives an "exit" packet.</p><p>To support this property, the thread ignores certain debugger packets in some states (the "interrupt" packet in the <b>Paused</b> and <b>Exited</b> states, for exmple). These cases all handle situations where the ignored packet was preempted by some thread action.</p> | ||
Note that the rules here apply to the client's interactions with each thread agent separately. A client may send an "interrupt" to one thread agent while awaiting a reply to a request sent to a different thread agent. | Note that the rules here apply to the client's interactions with each thread agent separately. A client may send an "interrupt" to one thread agent while awaiting a reply to a request sent to a different thread agent. | ||
Line 117: | Line 119: | ||
To attach to a thread, the client sends a packet of the form: | To attach to a thread, the client sends a packet of the form: | ||
{ "to":<i>thread</i>, "type":"attach", "pause-for":<i> | { "to":<i>thread</i>, "type":"attach", "pause-for":<i>pause-types</i> } | ||
This tells the thread to continue to run, but asks it to pause if any of | Here, <i>thread</i> is the actor representing the thread, perhaps a browsing context from a "list-contexts" reply. This tells the thread to continue to run, but asks it to pause if any of the event described by <i>pause-types</i> occurs. The form of <i>pause-types</i> is described in [[#Pause_Types]]. | ||
the event described by <i> | |||
described in [[# | |||
The thread responds in one of two ways: | The thread responds in one of two ways: | ||
Line 127: | Line 127: | ||
{ "from":<i>thread</i>, "type":"attached" } | { "from":<i>thread</i>, "type":"attached" } | ||
This indicates that the thread received the <tt>attach</tt> packet, and | This indicates that the thread received the <tt>attach</tt> packet, and will continue to run, reporting events of interest to the debugger. The thread is now in the <b>Running</b> state. The actor name <i>thread</i> remains valid until the client detaches from the thread or acknowledges a thread exit. | ||
will continue to run, reporting events of interest to the debugger. The | |||
thread is now in the <b>Running</b> state. The actor name <i>thread</i> remains | |||
valid until the client detaches from the thread or acknowledges a thread | |||
exit. | |||
{ "from":<i>thread</i>, "type":"exited" } | { "from":<i>thread</i>, "type":"exited" } | ||
This indicates that the thread exited before receiving the <tt>attach</tt> | This indicates that the thread exited before receiving the <tt>attach</tt> packet. The thread is now in the <b>Exited</b> state. The client must respond to this with a <tt>release</tt> packet; see [[#Exiting_Threads|Exiting Threads]]. | ||
packet. The thread is now in the <b>Exited</b> state. The client must | |||
respond to this with a <tt>release</tt> packet; see [[#Exiting_Threads|Exiting Threads]]. | |||
== Detaching From a Thread == | == Detaching From a Thread == | ||
Line 149: | Line 143: | ||
{ "from":<i>thread</i>, "type":"detached" } | { "from":<i>thread</i>, "type":"detached" } | ||
This indicates that the client has detached from the thread. The thread is | This indicates that the client has detached from the thread. The thread is now in the <b>Detached</b> state: it can run freely, and no longer reports events to the client. The actor name <i>thread</i> is released and available for reuse. | ||
now in the <b>Detached</b> state: it can run freely, and no longer reports | |||
events to the client. The actor name <i>thread</i> is released and | |||
available for reuse. | |||
{ "from":<i>thread</i>, "type":"paused", ... } | { "from":<i>thread</i>, "type":"paused", ... } | ||
{ "from":<i>thread</i>, "type":"detached" } | { "from":<i>thread</i>, "type":"detached" } | ||
This series of packets indicates that the thread paused of its own accord | This series of packets indicates that the thread paused of its own accord (for the reason given by the additional properties of the "paused" packet), and only then received the "detach" packet. As above, this indicates that the thread is in the <b>Detached</b> state, and the actor name is available for reuse. | ||
(for the reason given by the additional properties of the "paused" packet), | |||
and only then received the "detach" packet. As above, this indicates that | |||
the thread is in the <b>Detached</b> state, and the actor name is available | |||
for reuse. | |||
{ "from":<i>thread</i>, "type":"exited" } | { "from":<i>thread</i>, "type":"exited" } | ||
This indicates that the thread exited on its own before receiving the | This indicates that the thread exited on its own before receiving the "detach" packet. The client should follow by sending a "release" packet; see [[#Exiting_Threads|Exiting Threads]], below. | ||
"detach" packet. The client should follow by sending a "release" packet; see [[#Exiting_Threads|Exiting Threads]], below. | |||
== Running Threads == | == Running Threads == | ||
Once the client has attached to a thread, it is in the <b>Running</b> | Once the client has attached to a thread, it is in the <b>Running</b> state. In this state, four things can happen: | ||
state. In this state, four things can happen: | |||
* The thread can hit a breakpoint or watchpoint, or encounter some other condition of interest to the client. | * The thread can hit a breakpoint or watchpoint, or encounter some other condition of interest to the client. | ||
Line 178: | Line 163: | ||
* The client can interrupt the running thread. | * The client can interrupt the running thread. | ||
Note that a client action can occur simultaneously with a thread action. | Note that a client action can occur simultaneously with a thread action. The protocol is designed to avoid ambiguities when both client and thread act simultaneously. | ||
The protocol is designed to avoid ambiguities when both client and thread | |||
act simultaneously. | |||
== Thread Pauses == | == Thread Pauses == | ||
If the thread pauses to report an interesting event to the client, it sends | If the thread pauses to report an interesting event to the client, it sends a packet of the form: | ||
a packet of the form: | |||
{ "from":<i>thread</i>, "type":"paused", "frame":<i>frame</i>, " | { "from":<i>thread</i>, "type":"paused", "actor":<i>actor</i>, "frame":<i>frame</i>, "why":<i>reason</i> } | ||
This indicates that the thread is | This indicates that the thread has entered the <b>Paused</b> state, and explains where and why. | ||
<i> | |||
[[#Frames|frames]], below. The <i>reason</i> value describes why the thread | <i>Actor</i> is an actor representing this specific pause of the thread. The pause actor lives until the thread leaves the <b>Paused</b> state. Actors referring to stack frames, values, and other entities uncovered during this pause are all children of this actor; when the thread resumes, those actors are automatically freed. This relieves the client from the responsibility to explicitly close communications with every actor mentioned in a pause. If a client wishes to hold a reference to a JavaScript value across pauses, then it must create its own grip on the value, parented by the thread actor, using the [[#Keeping_Values|"keep-value"]] packet. | ||
paused. It has one of the following forms: | |||
<i>Frame</i> describes the top frame on the JavaScript stack; see [[#Frames|frames]], below. The <i>reason</i> value describes why the thread paused. It has one of the following forms: | |||
{ "type":"breakpoint", "actor":<i>actor</i> } | { "type":"breakpoint", "actor":<i>actor</i> } | ||
The thread stopped at the breakpoint represented by the actor named | The thread stopped at the breakpoint represented by the actor named <i>actor</i>. | ||
<i>actor</i>. | |||
{ "type":"watchpoint", "actor":<i>watchpoint</i> } | { "type":"watchpoint", "actor":<i>watchpoint</i> } | ||
The thread stopped at the breakpoint represented by the actor named | The thread stopped at the breakpoint represented by the actor named <i>actor</i>. | ||
<i>actor</i>. | |||
{ "type":"stepped" } | { "type":"stepped" } | ||
The client had asked the thread to step to the next statement, and the | The client had asked the thread to step to the next statement, and the thread completed that step. | ||
thread completed that step. | |||
{ "type":"pre-call" } | { "type":"pre-call" } | ||
The client had asked the thread to pause before making each function call, | The client had asked the thread to pause before making each function call, and the thread is about to call a function. Single-stepping the thread will a place it at the head of the function's code, with all arguments, local variables, and local functions bound. | ||
and the thread is about to call a function. Single-stepping the thread will | |||
place it at the head of the function's code, with all arguments, local | |||
variables, and local functions bound. | |||
{ "type":"pre-return" } | { "type":"pre-return" } | ||
The client had asked the thread to pause before returning from functions, | The client had asked the thread to pause before returning from functions, and the thread is about to return from a function. Single-stepping the thread will return the thread to the calling frame. | ||
and the thread is about to return from a function. Single-stepping the | |||
thread will return the thread to the calling frame. | |||
{ "type":"pre-throw", "exception":<i>grip</i> } | { "type":"pre-throw", "exception":<i>grip</i> } | ||
The client had asked the thread to pause before throwing an exception; | The client had asked the thread to pause before throwing an exception; <i>grip</i> is the name of an actor representing the exception value being thrown; its parent is the pause actor. Control is still at the point of the throw; it has not yet passed to a catch clause. Single-stepping this thread will report either a "caught" or "uncaught" pause. | ||
<i>grip</i> is the name of an actor representing the exception value being | |||
thrown. Control is still at the point of the throw; it has not yet passed | |||
to a catch clause. Single-stepping this thread will report either a | |||
"caught" or "uncaught" pause. | |||
{ "type":"caught", "exception":<i>grip</i> } | { "type":"caught", "exception":<i>grip</i> } | ||
The client stepped the thread from a "pre-throw" pause, and a catch clause | The client stepped the thread from a "pre-throw" pause, and a catch clause has been found for the exception referred to by <i>grip</i>, whose parent is the pause actor; control is stopped at the head of the catch clause, with catch variable bindings made. If the catch is conditional, control is at the beginning of the condition. | ||
has been found for the exception referred to by <i>grip</i>; control is | |||
stopped at the head of the catch clause, with catch variable bindings made. | |||
If the catch is conditional, control is at the beginning of the condition. | |||
{ "type":uncaught", "exception":<i>grip</i> } | { "type":uncaught", "exception":<i>grip</i> } | ||
The client stepped the thread from a "pre-throw" pause, and no catch clause | The client stepped the thread from a "pre-throw" pause, and no catch clause was found for the exception. <i>Grip</i> is as above. <i>(I'm not sure which code the thread is executing at this point; we might as well reveal SpiderMonkey's natural behavior.)</i> | ||
was found for the exception. <i>(I'm not sure which code the thread is | |||
executing at this point; we might as well reveal SpiderMonkey's natural | |||
behavior.)</i> | |||
{ "type":"pre-throw-by-guard", "exception":<i>grip</i> } | { "type":"pre-throw-by-guard", "exception":<i>grip</i> } | ||
The thread had been stopped in a conditional guard, and the client asked | The thread had been stopped in a conditional guard, and the client asked the thread to continue but pause before throwing an exception. The guard condition evaluated to false, and the thread is about to re-throw the exception value, <i>grip</i>. | ||
the thread to continue but pause before throwing an exception. The guard | |||
condition evaluated to false, and the thread is about to re-throw the | |||
exception value, <i>grip</i>. | |||
== Resuming a Thread == | == Resuming a Thread == | ||
If a thread is in the <b>Paused</b> state, the client can resume it by | If a thread is in the <b>Paused</b> state, the client can resume it by sending a packet of the following form: | ||
sending a packet of the following form: | |||
{ "to":<i>thread</i>, "type":" | { "to":<i>thread</i>, "type":"resume", "pause-for":<i>pause-types</i> } | ||
This puts the thread in the <b>Running</b> state, but asks it to pause if | This puts the thread in the <b>Running</b> state, but asks it to pause if any of the event described by <i>pause-types</i> occurs. The form of <i>pause-types</i> is described in [[#Pause_Types]]. | ||
any of the event described by <i> | |||
<i> | |||
== Interrupting a Thread == | == Interrupting a Thread == | ||
If a thread is in the <b>Running</b> state, the client can cause it to | If a thread is in the <b>Running</b> state, the client can cause it to pause where it is by sending a packet of the following form: | ||
pause where it is by sending a packet of the following form: | |||
{ "to":<i>thread</i>, "type":"interrupt" } | { "to":<i>thread</i>, "type":"interrupt" } | ||
Line 273: | Line 231: | ||
{ "from":<i>thread</i>, "type":"interrupted" } | { "from":<i>thread</i>, "type":"interrupted" } | ||
This indicates that the thread stopped due to the client's | This indicates that the thread stopped due to the client's <tt>interrupt</tt> packet, and is now in the <b>Paused</b> state. | ||
<tt>interrupt</tt> packet, and is now in the <b>Paused</b> state. | |||
{ "from":<i>thread</i>, "type":"paused", "frame":<i>frame</i>, " | { "from":<i>thread</i>, "type":"paused", "frame":<i>frame</i>, "why":<i>reason</i> } | ||
This indicates that the thread stopped of its own accord before receiving | This indicates that the thread stopped of its own accord before receiving the client's <tt>interrupt</tt> packet, and is now in the <b>Paused</b> state. The meanings of the "paused" packet's properties are as for an ordinary pause. The thread will ignore the client's interrupt packet when it receives it. | ||
the client's <tt>interrupt</tt> packet, and is now in the <b>Paused</b> | |||
state. The meanings of the | |||
ordinary pause. The thread will ignore the client's interrupt packet when | |||
it receives it. | |||
{ "from":<i>thread</i>, "type":"exited" } | { "from":<i>thread</i>, "type":"exited" } | ||
This indicates that the thread exited before receiving the client's | This indicates that the thread exited before receiving the client's <tt>interrupt</tt> packet, and is now in the <b>Exited</b> state. See [[#Exiting_Threads|Exiting Threads]], below. | ||
<tt>interrupt</tt> packet, and is now in the <b>Exited</b> state. See | |||
[[#Exiting_Threads|Exiting Threads]], below. | |||
== Exiting Threads == | == Exiting Threads == | ||
Line 300: | Line 251: | ||
{ "to":<i>thread</i>, "type":"release" } | { "to":<i>thread</i>, "type":"release" } | ||
This allows the thread actor name, <i>thread</i>, to be reused for other actors. | This acknowledges the exit and allows the thread actor name, <i>thread</i>, to be reused for other actors. | ||
= | == Pause Types == | ||
The <tt>pause-for</tt> property of a <tt>attach</tt> and <tt>resume</tt> packets specifies which kinds of events in the thread the client is interested in hearing about. The <i>pause-types</i> value is an object each of whose properties names a particular kind of pause, as appearing in the "type" field of a "paused" packet. | |||
For example, the following <i>resume</i> packet would instruct <i>thread</i> to continue until the current function call is about to return, or throw an exception: | |||
{ "to":<i>thread</i>, "type":"resume", "pause-for": { "pre-call":true, "pre-throw":true } } | |||
Certain pause types cause other pause types to be included automatically: | |||
{| | |||
!Pause type | |||
!Also implies | |||
|- | |||
| <tt>pre-throw</tt> | |||
| <tt>pre-throw-by-guard</tt> | |||
|} | |||
= Grips = | = Grips = | ||
== Long-term Value References == | |||
= Frames = | = Frames = |