The current XML protocol is based on the old "version 3" binary protocol with some structuring. See the end of this document for a critique.
This document describes version 3 of the protocol. This number is sent as the PROTOCOL-VERSION in the HELLO message.
Control flow is on the pattern
EVENT (COMMAND RESPONSE?)* COMMAND: Opera
(aka "the debugging host") sends an event to the debugger which may
issue commands to Opera (some of which have responses), and finally
issues a command to relinquish control until the next event. The only
command that relinquishes control is "continue".
The initial event is "hello" which is sent once Opera discovers that a debugger is present.
There is one exception to this scheme. While the debugger is not in control it may send a "break" command to Opera to force it to stop execution, and signal breakpoint events for all threads.
A "runtime" represents a document context in which threads execute. There is a one-to-one mapping between runtimes and HTML documents.
A "thread" represents a thread of execution in a runtime. A "parent" thread can be preempted by another "child" thread in order to respond to a priority event; the parent thread will not run again until the child thread has finished.
A "script" is a pair (id, source code) where the id is globally unique and the source code is Unicode text.
An "object" is represented by a globally unique ID that is assigned by the debugger.
Globally unique means unique within a running Opera session; that is from starting Opera to stopping it.
The main principle is that the data should be self-describing. This means that even enumerated values are passed as strings and that element names are verbose.
Here is the grammar describing the data. Some of the data elements are described in more detail below.
### # The protocol can mostly be described as a context-free grammar of # data flowing past an observer on the wire, where HELLO and EVENT # flows from host to client, and COMMAND flows from client to HOST. # PROTOCOL ::= HELLO (COMMAND | EVENT)* ; ### # Events (messages from debugging host to debugger) # EVENT ::= RUNTIME-STARTED | RUNTIME-STOPPED | NEW-SCRIPT | PARSE-ERROR | THREAD-STARTED | THREAD-FINISHED | THREAD-STOPPED-AT | EVAL-REPLY | EXAMINE-REPLY | BACKTRACE-REPLY | RUNTIMES-REPLY | HANDLE-EVENT | OBJECT-SELECTED ; HELLO ::= "<hello>" PROTOCOL-VERSION # from debugging host OPERATING-SYSTEM PLATFORM USER-AGENT # the one that's statically configured "</hello>" ; RUNTIME-STARTED ::= "<runtime-started>" RUNTIME "</runtime-started>" ; RUNTIME-STOPPED ::= "<runtime-stopped>" RUNTIME-ID "</runtime-stopped>" ; NEW-SCRIPT ::= "<new-script>" RUNTIME-ID SCRIPT-ID SCRIPT-TYPE SCRIPT-DATA URI? # present if SCRIPT-TYPE is "linked" "</new-script>" ; # OFFSET represents the character offset in the script where the parse error occured OFFSET ::= "<offset>" UNSIGNED "</offset>" ; # CONTEXT describes in what context the error occured CONTEXT ::= "<context>" TEXT "</context>" ; # DESCRIPTION contains the human-readable description of the parse error DESCRIPTION ::= "<description>" TEXT "</description>" ; PARSE-ERROR ::= "<parse-error>" RUNTIME-ID SCRIPT-ID LINE-NUMBER OFFSET CONTEXT DESCRIPTION "</parse-error>" ; THREAD-STARTED ::= "<thread-started>" RUNTIME-ID THREAD-ID PARENT-THREAD-ID THREAD-TYPE EVENT-DESC? # present if THREAD-TYPE is "event" "</thread-started>" ; THREAD-FINISHED ::= "<thread-finished>" RUNTIME-ID THREAD-ID STATUS "</thread-finished>" ; # BREAKPOINT-ID is present if and only if STOPPED-REASON is "breakpoint" THREAD-STOPPED-AT ::= "<thread-stopped-at>" RUNTIME-ID THREAD-ID SCRIPT-ID LINE-NUMBER STOPPED-REASON BREAKPOINT-ID? "</thread-stopped-at>" ; # If STATUS is "completed" or "unhandled-exception", then # VALUE-DATA will be present. EVAL-REPLY ::= "<eval-reply>" TAG STATUS VALUE-DATA? "</eval-reply>" ; EXAMINE-REPLY ::= "<examine-reply>" TAG OBJECT* "</examine-reply>" ; # Frames are in innermost-first order BACKTRACE-REPLY ::= "<backtrace-reply>" TAG FRAME* "</backtrace-reply>" ; RUNTIMES-REPLY ::= "<runtimes-reply>" TAG RUNTIME* "</runtimes-reply>" ; # This event is issued for XML events on the host, if a corresponding # ADD-EVENT-HANDLER has been issued earlier by the client. # OBJECT-ID refers to the target of the event. HANDLE-EVENT ::= "<handle-event>" OBJECT-ID HANDLER-ID EVENT-TYPE "</handle-event>" ; # Some hosts send this event to indicate that an object was selected for # debugging, e.g., if the debugger was started by right-clicking an element # and clicking "inspect" in the context menu, this event will be sent # right after startup. A client may safely choose to ignore this event. OBJECT-SELECTED ::= "<object-selected>" OBJECT-ID WINDOW-ID "</object-selected>" ; ### # Commands (messages from debugger to debugging host) # COMMAND ::= RUNTIMES | CONTINUE | EVAL | EXAMINE-FRAME | EXAMINE-OBJECTS | SPOTLIGHT-OBJECT | ADD-BREAKPOINT | REMOVE-BREAKPOINT | ADD-EVENT-HANDLER | REMOVE-EVENT-HANDLER | SET-CONFIGURATION | BACKTRACE | BREAK ; RUNTIMES ::= "<runtimes>" TAG CREATE-ALL-RUNTIMES? RUNTIME-ID* # list the ones you want to see, or none if you want all "</runtimes>" ; # Create runtimes for all documents. Runtimes are normally not created for documents # without ECMAScript. CREATE-ALL-RUNTIMES ::= "<create-all-runtimes />" ; CONTINUE ::= "<continue>" RUNTIME-ID THREAD-ID MODE "</continue>" ; # SCRIPT-DATA represents a script to be executed; PROPERTY values # represent variables to set. # If THREAD-ID, code is evaluated in the global scope. EVAL ::= "<eval>" TAG RUNTIME-ID THREAD-ID FRAME-ID SCRIPT-DATA PROPERTY* "</eval>" ; EXAMINE-FRAME ::= "<examine-frame>" TAG RUNTIME-ID THREAD-ID FRAME-ID "</examine-frame>" ; EXAMINE-OBJECTS ::= "<examine-objects>" TAG RUNTIME-ID OBJECT-ID+ "</examine-objects>" ; # Using OBJECT-ID == 0 clears the spotlight. # If SCROLL-INTO-VIEW is present, the object will be scrolled into the view (at least part of it), # otherwise the viewport will remain where it is. SPOTLIGHT-OBJECT ::= "<spotlight-object>" OBJECT-ID SCROLL-INTO-VIEW? "</spotlight-object>" ; SCROLL-INTO-VIEW ::= "<scroll-into-view />" ; # The SOURCE-POSITION element defines how # to set the breakpoint. ADD-BREAKPOINT ::= "<add-breakpoint>" BREAKPOINT-ID SOURCE-POSITION "</add-breakpoint>" ; REMOVE-BREAKPOINT ::= "<remove-breakpoint>" BREAKPOINT-ID "</remove-breakpoint>" ; # Add an event handler. This will generate a HANDLE-EVENT event every time the XML event defined # by the pair (NAMESPACE, EVENT-TYPE) reaches the object defined by OBJECT-ID in the capturing # phase. XML events are defined in http://www.w3.org/TR/xml-events # # HANDLER-ID is set by the client and is referred to by both client and host. # NAMESPACE of the event: if empty, it will match any namespace. # PREVENT-DEFAULT prevents the default event handler from running. # STOP-PROPAGATION stops propagation of the event beyond this OBJECT-ID (it will however run for # all handlers on the object). ADD-EVENT-HANDLER ::= "<add-event-handler>" HANDLER-ID OBJECT-ID NAMESPACE EVENT-TYPE PREVENT-DEFAULT STOP-PROPAGATION "</add-event-handler>" ; REMOVE-EVENT-HANDLER ::= "<remove-event-handler>" HANDLER-ID "</remove-event-handler>" ; SET-CONFIGURATION ::= "<set-configuration>" STOP-AT+ "</set-configuration>" ; # If MAXFRAMES is omitted, all frames are returned. BACKTRACE ::= "<backtrace>" TAG RUNTIME-ID THREAD-ID MAXFRAMES? "</backtrace>" ; BREAK ::= "<break>" RUNTIME-ID THREAD-ID "</break>" ; ### # Basis-data # EVENT-DESC ::= "<event-desc>" NAMESPACE EVENT-TYPE "</event-desc>" ; PREVENT-DEFAULT ::= "<prevent-default>" YESNO # default is yes "</prevent-default>" ; STOP-PROPAGATION ::= "<stop-propagation>" YESNO # default is yes "</stop-propagation>" ; # If DATA-TYPE is ... then ... is present: # "object", OBJECT-ID # "number", STRING # "string", STRING # "boolean", STRING ("true" or "false") # Otherwise ("undefined" or "null"), only DATA-TYPE is present. VALUE-DATA ::= "<value-data>" DATA-TYPE ( OBJECT-VALUE | STRING )? "</value-data>" ; OBJECT-VALUE ::= "<object-value>" OBJECT-ID PROTOTYPE-ID? OBJECT-ATTRIBUTES NAME? "</object-value>" ; RUNTIME ::= "<runtime>" RUNTIME-ID HTML-FRAME-PATH WINDOW-ID # the ID of the window OBJECT-ID # the 'global' object URI # the document's URI "</runtime>" ; FRAME ::= "<frame>" FUNCTION-ID ARGUMENT-OBJECT VARIABLE-OBJECT THIS-OBJECT SOURCE-POSITION? OBJECT-VALUE* "</frame>" ; # Default values are NO for every STOP-TYPE, except # "script", which is YES. STOP-AT ::= "<stop-at>" YESNO STOP-TYPE "</stop-at>" ; # Set this value to 0 to get all frames. MAXFRAMES ::= "<maxframes>" UNSIGNED "</maxframes>" ; SOURCE-POSITION ::= "<source-position>" SCRIPT-ID LINE-NUMBER "</source-position>" ; SCRIPT-DATA ::= "<script-data>" TEXT "</script-data>" ; OBJECT ::= "<object>" OBJECT-VALUE PROPERTY* "</object>" ; PROPERTY ::= "<property>" OBJECT-ID? # if you want to set a property on an object PROPERTY-NAME VALUE-DATA "</property>" ; PROPERTY-NAME ::= "<property-name>" TEXT "</property-name>" ; OBJECT-ATTRIBUTES ::= "<object-attributes>" OBJECT-ATTRIBUTE* "</object-attributes>" ; OBJECT-ATTRIBUTE ::= "<iscallable/>" | "<isfunction/>" ; NAME ::= CLASS-NAME | FUNCTION-NAME ; CLASS-NAME ::= "<class-name>" TEXT "</class-name>" ; FUNCTION-NAME ::= "<function-name>" TEXT "</function-name>" ; PROTOCOL-VERSION ::= "<protocol-version>" UNSIGNED "</protocol-version>" ; OPERATING-SYSTEM ::= "<operating-system>" TEXT "</operating-system>" ; PLATFORM ::= "<platform>" TEXT "</platform>" ; USER-AGENT ::= "<user-agent>" TEXT "</user-agent>" ; HTML-FRAME-PATH ::= "<html-frame-path>" TEXT "</html-frame-path>" ; STOP-TYPE ::= "<stop-type>" ( "script" | "exception" | "error" | "abort" ) "</stop-type>" ; SCRIPT-TYPE ::= "<script-type>" ( "inline" | "event" | "linked" | "timeout" | "java" | "generated" | "unknown" ) "</script-type>" ; THREAD-TYPE ::= "<thread-type>" ( "inline" | "event" | "linked" | "timeout" | "java" | "unknown" ) "</thread-type>" ; NAMESPACE ::= "<namespace>" TEXT "</namespace>" ; # The event type is e.g., "click", "mousemove" # More examples are at http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html # Exactly which events are implemented depends on the host, and is not defined in this protocol. EVENT-TYPE ::= "<event-type>" TEXT "</event-type>" ; STATUS ::= "<status>" ( "completed" | "unhandled-exception" | "aborted" | "cancelled-by-scheduler" ) "</status>" ; DATA-TYPE ::= "<data-type>" ( "number" | "boolean" | "string" | "null" | "undefined" | "object" ) "</data-type>" ; MODE ::= "<mode>" ( "run" | "step-into-call" | "step-next-line" | "step-out-of-call" ) "</mode>" ; # "broken" is sent in response to a BREAK command. # "breakpoint" is sent when the script hits a debugger-set breakpoint. STOPPED-REASON ::= "<stopped-reason>" ( "broken" | "function-return" | "exception" | "debugger statement" | "breakpoint" | "unknown" ) "</stopped-reason>" ; LINE-NUMBER ::= "<line-number>" UNSIGNED "</line-number>" ; ARGUMENT-OBJECT ::= "<argument-object>" UNSIGNED "</argument-object>" ; VARIABLE-OBJECT ::= "<variable-object>" UNSIGNED "</variable-object>" ; THIS-OBJECT ::= "<this-object>" UNSIGNED "</this-object>" ; ### # Generic context-free data: # # A TAG represents a value passed from the client to the host and # returned from the host with a reply. # TAG ::= "<tag>" UNSIGNED "</tag>" ; YESNO ::= "<yes/>" | "<no/>" ; STRING ::= "<string>" TEXT "</string>" ; URI ::= "<uri>" TEXT "</uri>" ; ### # Identifiers: Most of these are globally unique (they're just the # integer representation of some pointer). THREAD-ID and PARENT-THREAD-ID # are relative to a RUNTIME-ID. FRAME-ID is relative to the current stack # height in a stopped thread: 0 being the top-most frame (i.e., the most # recently called), 1 being the caller for that, and so on. # RUNTIME-ID ::= "<runtime-id>" UNSIGNED "</runtime-id>" ; OBJECT-ID ::= "<object-id>" UNSIGNED "</object-id>" ; # The window ID is shared across scope. Notably, it's the same as in the console logger and window manager # INTERNAL: The value is from Window::id WINDOW-ID ::= "<window-id>" UNSIGNED "</window-id>" ; PROTOTYPE-ID ::= "<prototype-id>" UNSIGNED "</prototype-id>" ; FUNCTION-ID ::= "<function-id>" UNSIGNED "</function-id>" ; SCRIPT-ID ::= "<script-id>" UNSIGNED "</script-id>" ; BREAKPOINT-ID ::= "<breakpoint-id>" UNSIGNED "</breakpoint-id>" ; HANDLER-ID ::= "<handler-id>" UNSIGNED "</handler-id>" ; FRAME-ID ::= "<frame-id>" UNSIGNED "</frame-id>" ; THREAD-ID ::= "<thread-id>" UNSIGNED "</thread-id>" ; PARENT-THREAD-ID ::= "<parent-thread-id>" UNSIGNED "</parent-thread-id>" ; ### # Primitive data: # # You may *NOT* assume that an UNSIGNED received from the host fits # in 32 bits, but you may assume that 64 bits is enough. # # You must *NOT* send an UNSIGNED to the host that does not fit in 32 # bits unless it was received from the host. # UNSIGNED ::= [0-9]+ ; TEXT ::= BASE64-ENCODED-DATA | textual-data ; BASE64-ENCODED-DATA ::= "<base64-encoded-data>" textual-data "</base64-encoded-data>" ;
The USER-AGENT value is not actually static but can be different with every request. It is strictly speaking an attribute of the script, not of the debugging host. In practice we cannot know the user agent used for all scripts, but for "inline" and "linked" we do.
The tagging system is probably too weak to support multiple clients: tags will need to contain information about the specific client that sent the command. There are other ways to fix this, e.g., by making one (designated) client send a message on behalf of another, but fixing the tagging would probably be better.
(The tagging failure may be a generic weakness in the way the protocols work, but most services won't have multiple clients so it is not so visible elsewhere.)