This is a DRAFT.
Comments are welcome on dev-tech-js-engine(at)lists.mozilla.org; or, you can send them directly to me at jimb(at)mozilla.com.
- The debugging API must support remote debugging. Mobile devices often have restricted user interfaces; it should be possible for the debugger's user interface to run on a workstation or laptop, while inspecting a debuggee on the mobile device.
- The debugging API should be prepared to support separate content processes, if Mozilla implements them.
(Even though it hasn't been implemented yet, this description uses the present tense, for clarity and ease of transition to summary documentation.)
Every server provides a root actor that can provide global information about the application ("I am a web browser"), and enumerate the potential debuggees present in the application—tabs, worker threads, chrome, and so on—each of which is represented by its own actor.
The server interacts with debuggees running in other threads simply by passing entire JSON packets between the client and actor code running on those threads. Thus, all inter-thread communication is handled via the protocol, permitting thread actors and the interfaces they use to be single-threaded and simplifying their implementation. Communication with subprocesses can be handled the same way.
The js::dbg2 interface provides functions to:
- select code of interest to the developer (everything in a tab; a selected frame within a tab; chrome; and so on),
- establish breakpoints, watchpoints, and other sorts of monitoring, and be notified when events of interest occur,
Remote debugging, in which the debugger's user interface can run in a separate process from the debuggee and communicates with the debuggee over a stream connection, addresses many of our goals at once:
- A debugger running in a separate process from the debuggee is easier to make robust. The debugger's user interface and the debuggee need not share an event loop or a chrome DOM tree.
- Remote debugging eases mobile development. The debugger could run on a desktop computer, and operate on a debuggee on a mobile device.
- The remote protocol can handle almost all inter-thread communication. Each actor runs on the same thread as the debuggee it represents, so actor/debuggee interactions are intra-thread, and need not worry about synchronization or shared state. Actors and the application's main server interact only by exchanging protocol packets. The debugger
user interface simply needs to be able to talk to more than one agent at a time.
(Note that some operations are inherently cross-thread: enumerating currently running threads; thread creation notifications; the initial attachment of the debugger to a thread. But once a thread has been attached to, all subsequent communication can be via the remote protocol.)
The js::dbg2 Interfaces and jsd2IDebuggerService
Like jsd, js::dbg2 provides grip objects that refer to values in the debuggee. The debugger can inspect the object's properties, their attributes, and so on via the grip without accidentally invoking getters or setters, making it easier to write secure and robust debuggers.
Tasks and Estimates
Note that all estimates include time to write unit tests providing full code and branch coverage for new code.
- JS_CopyScript JSAPI function (8 days)
- Implement, document, and test a function that makes a fresh, deep copy
of a JSScript object, suitable for execution in a thread or global object
different than the original JSScript.
For various reasons, SpiderMonkey is moving towards restricting each JSScript to be used with a single global object (the next task; see details there). Before we can impose this requirement, we must make it possible for embedders to comply with it by providing a function which copies a JSScript object.
- Associate JSScripts with specific global objects (5 days)
- Add a 'global' field to JSScript, and change JS_ExecuteScript to clone
JSScript objects if necessary to match the global object passed.
This is needed to allow us to enumerate all the scripts in use by a particular global object, along with several other current SpiderMonkey goals; see bug 563375#c4. We can accomplish this by having JS_ExecuteScript use copies of JSScripts owned by globals other than the one passed to it.
- Change JSRuntime::scriptFilenameTable to use js::HashMap (3 days)
- Since subsequent tasks will involve changing the data structures used to store script source URLs, we should grant ourselves the benefits of strict typing provided by the new js::HashMap template.
- Create name-to-script mapping (8 days)
- Adapt the existing hash table of script names to also function as a map from script names to scripts. This entails adding links to JSScript objects, arranging for entries in scriptFilenameTable to head chains of scripts, and having garbage collection properly remove scripts from their names' lists.
- Script URL enumeration (5 days)
- Define a function to enumerate the URLs of all scripts associated
with a given global object.
Debugger user interfaces need to be able to present the user with a list of the scripts in use by a particular page or origin, so that the user can browse their source code, set breakpoints, and so on. These lists should include only those scripts in use by the page or origin being debugged.
- Draft C++ js::dbg2 breakpoint API (3 days)
- Write a C++ API declaring:
- A class representing a position at which a breakpoint can be set,
expressed in terms of textual positions (URL, line, and column) or in terms
of function names (a global object, a series of containing function names,
and a final function name), or in terms of specific function objects.
The API should permit the "grammar" of breakpoint locations to be extended in the future (to describe, say, function-valued properties in object literals).
These should be designed such that, in normal, efficent use, no explicit storage management (new/delete) is required.
URLs in breakpoint locations should be represented as entries in the runtime's scriptFilenameTable. This means that, given a breakpoint location, we have immediate access to the list of JSScripts derived from the source code to which the location refers.
If possible, the URL/line/column variant of this type should be suitable for use by the js::dbg2 stack frame type to represent source positions; we should not need two distinct types that represent locations in source code.
- A class representing a breakpoint, js::dbg2::Breakpoint, which can be inserted in or removed from a debugging sphere. This API will not be concerned with breakpoint conditions, ignore counts, and such; those behaviors must be implemented by the client of the js::dbg2 interface.
- A stub js::dbg2::Sphere class, sufficient for bootstrapping, constructed from a given global object.
- Debugging sphere member functions for enumerating the currently inserted breakpoints.
- A class representing a position at which a breakpoint can be set, expressed in terms of textual positions (URL, line, and column) or in terms of function names (a global object, a series of containing function names, and a final function name), or in terms of specific function objects.
- Implement Breakpoint Location Classes (5 days)
- Implement the classes described above describing breakpoint locations. There may be some tricky work here, as we want to have entries in the scriptFilenameTable that are live because they are referred to by breakpoint location objects, not scripts, and have entries cleaned up as appropriate.
- Implement js::dbg2::Breakpoint(15 days)
- Implement the js::dbg2::Breakpoint class, including insertion and
removal. This entails:
- turning the various sorts of breakpoint locations into JSScript,offset pairs
- searching JSScript lists to insert and remove traps
- managing multiple breakpoints set at the same bytecode
- inserting traps for existing breakpoints into newly loaded code (pending breakpoints)
- coping with scripts being garbage collected
- interlocking with JägerMonkey to insure that breakpoints are never set in functions that have JM frames on the stack
- Use function start positions when re-setting breakpoints (8 days)
- When re-loading a previously loaded script, we should use our knowledge of function boundaries to improve our accuracy as we re-set breakpoints in the new script. If all changes to a script lie outside a given function's definition, then treating the breakpoint as if it were set relative to the function's start, rather than at an absolute line and column, will allow us to find a better location for it in the new script.
- Expand source notes to carry column information (8 days)
- Extend the source notes attached to JSScripts to carry both line and
column information. This allows debugging of poorly-formatted code such as
that produced by script compressors or obfuscators. The bytecode compiler
already tracks column numbers; they're simply not recorded in the source
Note that this need not imply any increase in the size of notes for normally formatted source code: the granularity of the features distinguished by the source annotations (that is, statements) need not change. Only if there were multiple statements or functions on the same line would column numbers be needed to distinguish them.