toolkit/devtools/client/dbg-client.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     8 var Ci = Components.interfaces;
     9 var Cc = Components.classes;
    10 var Cu = Components.utils;
    11 var Cr = Components.results;
    12 // On B2G scope object misbehaves and we have to bind globals to `this`
    13 // in order to ensure theses variable to be visible in transport.js
    14 this.Ci = Ci;
    15 this.Cc = Cc;
    16 this.Cu = Cu;
    17 this.Cr = Cr;
    19 this.EXPORTED_SYMBOLS = ["DebuggerTransport",
    20                          "DebuggerClient",
    21                          "RootClient",
    22                          "debuggerSocketConnect",
    23                          "LongStringClient",
    24                          "EnvironmentClient",
    25                          "ObjectClient"];
    27 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    28 Cu.import("resource://gre/modules/NetUtil.jsm");
    29 Cu.import("resource://gre/modules/Services.jsm");
    30 Cu.import("resource://gre/modules/Timer.jsm");
    32 let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js").Promise;
    33 const { defer, resolve, reject } = promise;
    35 XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
    36                                    "@mozilla.org/network/socket-transport-service;1",
    37                                    "nsISocketTransportService");
    39 XPCOMUtils.defineLazyModuleGetter(this, "console",
    40                                   "resource://gre/modules/devtools/Console.jsm");
    42 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
    43                                   "resource://gre/modules/devtools/Loader.jsm");
    45 Object.defineProperty(this, "WebConsoleClient", {
    46   get: function () {
    47     return devtools.require("devtools/toolkit/webconsole/client").WebConsoleClient;
    48   },
    49   configurable: true,
    50   enumerable: true
    51 });
    53 Components.utils.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
    54 this.makeInfallible = DevToolsUtils.makeInfallible;
    56 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
    58 function dumpn(str)
    59 {
    60   if (wantLogging) {
    61     dump("DBG-CLIENT: " + str + "\n");
    62   }
    63 }
    65 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
    66   .getService(Ci.mozIJSSubScriptLoader);
    67 loader.loadSubScript("resource://gre/modules/devtools/server/transport.js", this);
    69 /**
    70  * Add simple event notification to a prototype object. Any object that has
    71  * some use for event notifications or the observer pattern in general can be
    72  * augmented with the necessary facilities by passing its prototype to this
    73  * function.
    74  *
    75  * @param aProto object
    76  *        The prototype object that will be modified.
    77  */
    78 function eventSource(aProto) {
    79   /**
    80    * Add a listener to the event source for a given event.
    81    *
    82    * @param aName string
    83    *        The event to listen for.
    84    * @param aListener function
    85    *        Called when the event is fired. If the same listener
    86    *        is added more than once, it will be called once per
    87    *        addListener call.
    88    */
    89   aProto.addListener = function (aName, aListener) {
    90     if (typeof aListener != "function") {
    91       throw TypeError("Listeners must be functions.");
    92     }
    94     if (!this._listeners) {
    95       this._listeners = {};
    96     }
    98     this._getListeners(aName).push(aListener);
    99   };
   101   /**
   102    * Add a listener to the event source for a given event. The
   103    * listener will be removed after it is called for the first time.
   104    *
   105    * @param aName string
   106    *        The event to listen for.
   107    * @param aListener function
   108    *        Called when the event is fired.
   109    */
   110   aProto.addOneTimeListener = function (aName, aListener) {
   111     let l = (...args) => {
   112       this.removeListener(aName, l);
   113       aListener.apply(null, args);
   114     };
   115     this.addListener(aName, l);
   116   };
   118   /**
   119    * Remove a listener from the event source previously added with
   120    * addListener().
   121    *
   122    * @param aName string
   123    *        The event name used during addListener to add the listener.
   124    * @param aListener function
   125    *        The callback to remove. If addListener was called multiple
   126    *        times, all instances will be removed.
   127    */
   128   aProto.removeListener = function (aName, aListener) {
   129     if (!this._listeners || !this._listeners[aName]) {
   130       return;
   131     }
   132     this._listeners[aName] =
   133       this._listeners[aName].filter(function (l) { return l != aListener });
   134   };
   136   /**
   137    * Returns the listeners for the specified event name. If none are defined it
   138    * initializes an empty list and returns that.
   139    *
   140    * @param aName string
   141    *        The event name.
   142    */
   143   aProto._getListeners = function (aName) {
   144     if (aName in this._listeners) {
   145       return this._listeners[aName];
   146     }
   147     this._listeners[aName] = [];
   148     return this._listeners[aName];
   149   };
   151   /**
   152    * Notify listeners of an event.
   153    *
   154    * @param aName string
   155    *        The event to fire.
   156    * @param arguments
   157    *        All arguments will be passed along to the listeners,
   158    *        including the name argument.
   159    */
   160   aProto.notify = function () {
   161     if (!this._listeners) {
   162       return;
   163     }
   165     let name = arguments[0];
   166     let listeners = this._getListeners(name).slice(0);
   168     for each (let listener in listeners) {
   169       try {
   170         listener.apply(null, arguments);
   171       } catch (e) {
   172         // Prevent a bad listener from interfering with the others.
   173         DevToolsUtils.reportException("notify event '" + name + "'", e);
   174       }
   175     }
   176   }
   177 }
   179 /**
   180  * Set of protocol messages that affect thread state, and the
   181  * state the actor is in after each message.
   182  */
   183 const ThreadStateTypes = {
   184   "paused": "paused",
   185   "resumed": "attached",
   186   "detached": "detached"
   187 };
   189 /**
   190  * Set of protocol messages that are sent by the server without a prior request
   191  * by the client.
   192  */
   193 const UnsolicitedNotifications = {
   194   "consoleAPICall": "consoleAPICall",
   195   "eventNotification": "eventNotification",
   196   "fileActivity": "fileActivity",
   197   "lastPrivateContextExited": "lastPrivateContextExited",
   198   "logMessage": "logMessage",
   199   "networkEvent": "networkEvent",
   200   "networkEventUpdate": "networkEventUpdate",
   201   "newGlobal": "newGlobal",
   202   "newScript": "newScript",
   203   "newSource": "newSource",
   204   "tabDetached": "tabDetached",
   205   "tabListChanged": "tabListChanged",
   206   "reflowActivity": "reflowActivity",
   207   "addonListChanged": "addonListChanged",
   208   "tabNavigated": "tabNavigated",
   209   "pageError": "pageError",
   210   "documentLoad": "documentLoad",
   211   "enteredFrame": "enteredFrame",
   212   "exitedFrame": "exitedFrame",
   213   "appOpen": "appOpen",
   214   "appClose": "appClose",
   215   "appInstall": "appInstall",
   216   "appUninstall": "appUninstall"
   217 };
   219 /**
   220  * Set of pause types that are sent by the server and not as an immediate
   221  * response to a client request.
   222  */
   223 const UnsolicitedPauses = {
   224   "resumeLimit": "resumeLimit",
   225   "debuggerStatement": "debuggerStatement",
   226   "breakpoint": "breakpoint",
   227   "DOMEvent": "DOMEvent",
   228   "watchpoint": "watchpoint",
   229   "exception": "exception"
   230 };
   232 /**
   233  * Creates a client for the remote debugging protocol server. This client
   234  * provides the means to communicate with the server and exchange the messages
   235  * required by the protocol in a traditional JavaScript API.
   236  */
   237 this.DebuggerClient = function (aTransport)
   238 {
   239   this._transport = aTransport;
   240   this._transport.hooks = this;
   242   // Map actor ID to client instance for each actor type.
   243   this._threadClients = new Map;
   244   this._addonClients = new Map;
   245   this._tabClients = new Map;
   246   this._tracerClients = new Map;
   247   this._consoleClients = new Map;
   249   this._pendingRequests = [];
   250   this._activeRequests = new Map;
   251   this._eventsEnabled = true;
   253   this.compat = new ProtocolCompatibility(this, []);
   254   this.traits = {};
   256   this.request = this.request.bind(this);
   257   this.localTransport = this._transport.onOutputStreamReady === undefined;
   259   /*
   260    * As the first thing on the connection, expect a greeting packet from
   261    * the connection's root actor.
   262    */
   263   this.mainRoot = null;
   264   this.expectReply("root", (aPacket) => {
   265     this.mainRoot = new RootClient(this, aPacket);
   266     this.notify("connected", aPacket.applicationType, aPacket.traits);
   267   });
   268 }
   270 /**
   271  * A declarative helper for defining methods that send requests to the server.
   272  *
   273  * @param aPacketSkeleton
   274  *        The form of the packet to send. Can specify fields to be filled from
   275  *        the parameters by using the |args| function.
   276  * @param telemetry
   277  *        The unique suffix of the telemetry histogram id.
   278  * @param before
   279  *        The function to call before sending the packet. Is passed the packet,
   280  *        and the return value is used as the new packet. The |this| context is
   281  *        the instance of the client object we are defining a method for.
   282  * @param after
   283  *        The function to call after the response is received. It is passed the
   284  *        response, and the return value is considered the new response that
   285  *        will be passed to the callback. The |this| context is the instance of
   286  *        the client object we are defining a method for.
   287  */
   288 DebuggerClient.requester = function (aPacketSkeleton,
   289                                      { telemetry, before, after }) {
   290   return DevToolsUtils.makeInfallible(function (...args) {
   291     let histogram, startTime;
   292     if (telemetry) {
   293       let transportType = this._transport.onOutputStreamReady === undefined
   294         ? "LOCAL_"
   295         : "REMOTE_";
   296       let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
   297         + transportType + telemetry + "_MS";
   298       histogram = Services.telemetry.getHistogramById(histogramId);
   299       startTime = +new Date;
   300     }
   301     let outgoingPacket = {
   302       to: aPacketSkeleton.to || this.actor
   303     };
   305     let maxPosition = -1;
   306     for (let k of Object.keys(aPacketSkeleton)) {
   307       if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
   308         let { position } = aPacketSkeleton[k];
   309         outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
   310         maxPosition = Math.max(position, maxPosition);
   311       } else {
   312         outgoingPacket[k] = aPacketSkeleton[k];
   313       }
   314     }
   316     if (before) {
   317       outgoingPacket = before.call(this, outgoingPacket);
   318     }
   320     this.request(outgoingPacket, DevToolsUtils.makeInfallible(function (aResponse) {
   321       if (after) {
   322         let { from } = aResponse;
   323         aResponse = after.call(this, aResponse);
   324         if (!aResponse.from) {
   325           aResponse.from = from;
   326         }
   327       }
   329       // The callback is always the last parameter.
   330       let thisCallback = args[maxPosition + 1];
   331       if (thisCallback) {
   332         thisCallback(aResponse);
   333       }
   335       if (histogram) {
   336         histogram.add(+new Date - startTime);
   337       }
   338     }.bind(this), "DebuggerClient.requester request callback"));
   340   }, "DebuggerClient.requester");
   341 };
   343 function args(aPos) {
   344   return new DebuggerClient.Argument(aPos);
   345 }
   347 DebuggerClient.Argument = function (aPosition) {
   348   this.position = aPosition;
   349 };
   351 DebuggerClient.Argument.prototype.getArgument = function (aParams) {
   352   if (!(this.position in aParams)) {
   353     throw new Error("Bad index into params: " + this.position);
   354   }
   355   return aParams[this.position];
   356 };
   358 DebuggerClient.prototype = {
   359   /**
   360    * Connect to the server and start exchanging protocol messages.
   361    *
   362    * @param aOnConnected function
   363    *        If specified, will be called when the greeting packet is
   364    *        received from the debugging server.
   365    */
   366   connect: function (aOnConnected) {
   367     this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
   368       this.traits = aTraits;
   369       if (aOnConnected) {
   370         aOnConnected(aApplicationType, aTraits);
   371       }
   372     });
   374     this._transport.ready();
   375   },
   377   /**
   378    * Shut down communication with the debugging server.
   379    *
   380    * @param aOnClosed function
   381    *        If specified, will be called when the debugging connection
   382    *        has been closed.
   383    */
   384   close: function (aOnClosed) {
   385     // Disable detach event notifications, because event handlers will be in a
   386     // cleared scope by the time they run.
   387     this._eventsEnabled = false;
   389     if (aOnClosed) {
   390       this.addOneTimeListener('closed', function (aEvent) {
   391         aOnClosed();
   392       });
   393     }
   395     const detachClients = (clientMap, next) => {
   396       const clients = clientMap.values();
   397       const total = clientMap.size;
   398       let numFinished = 0;
   400       if (total == 0) {
   401         next();
   402         return;
   403       }
   405       for (let client of clients) {
   406         let method = client instanceof WebConsoleClient ? "close" : "detach";
   407         client[method](() => {
   408           if (++numFinished === total) {
   409             clientMap.clear();
   410             next();
   411           }
   412         });
   413       }
   414     };
   416     detachClients(this._consoleClients, () => {
   417       detachClients(this._threadClients, () => {
   418         detachClients(this._tabClients, () => {
   419           detachClients(this._addonClients, () => {
   420             this._transport.close();
   421             this._transport = null;
   422           });
   423         });
   424       });
   425     });
   426   },
   428   /*
   429    * This function exists only to preserve DebuggerClient's interface;
   430    * new code should say 'client.mainRoot.listTabs()'.
   431    */
   432   listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
   434   /*
   435    * This function exists only to preserve DebuggerClient's interface;
   436    * new code should say 'client.mainRoot.listAddons()'.
   437    */
   438   listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
   440   /**
   441    * Attach to a tab actor.
   442    *
   443    * @param string aTabActor
   444    *        The actor ID for the tab to attach.
   445    * @param function aOnResponse
   446    *        Called with the response packet and a TabClient
   447    *        (which will be undefined on error).
   448    */
   449   attachTab: function (aTabActor, aOnResponse) {
   450     if (this._tabClients.has(aTabActor)) {
   451       let cachedTab = this._tabClients.get(aTabActor);
   452       let cachedResponse = {
   453         cacheEnabled: cachedTab.cacheEnabled,
   454         javascriptEnabled: cachedTab.javascriptEnabled,
   455         traits: cachedTab.traits,
   456       };
   457       setTimeout(() => aOnResponse(cachedResponse, cachedTab), 0);
   458       return;
   459     }
   461     let packet = {
   462       to: aTabActor,
   463       type: "attach"
   464     };
   465     this.request(packet, (aResponse) => {
   466       let tabClient;
   467       if (!aResponse.error) {
   468         tabClient = new TabClient(this, aResponse);
   469         this._tabClients.set(aTabActor, tabClient);
   470       }
   471       aOnResponse(aResponse, tabClient);
   472     });
   473   },
   475   /**
   476    * Attach to an addon actor.
   477    *
   478    * @param string aAddonActor
   479    *        The actor ID for the addon to attach.
   480    * @param function aOnResponse
   481    *        Called with the response packet and a AddonClient
   482    *        (which will be undefined on error).
   483    */
   484   attachAddon: function DC_attachAddon(aAddonActor, aOnResponse) {
   485     let packet = {
   486       to: aAddonActor,
   487       type: "attach"
   488     };
   489     this.request(packet, aResponse => {
   490       let addonClient;
   491       if (!aResponse.error) {
   492         addonClient = new AddonClient(this, aAddonActor);
   493         this._addonClients[aAddonActor] = addonClient;
   494         this.activeAddon = addonClient;
   495       }
   496       aOnResponse(aResponse, addonClient);
   497     });
   498   },
   500   /**
   501    * Attach to a Web Console actor.
   502    *
   503    * @param string aConsoleActor
   504    *        The ID for the console actor to attach to.
   505    * @param array aListeners
   506    *        The console listeners you want to start.
   507    * @param function aOnResponse
   508    *        Called with the response packet and a WebConsoleClient
   509    *        instance (which will be undefined on error).
   510    */
   511   attachConsole:
   512   function (aConsoleActor, aListeners, aOnResponse) {
   513     let packet = {
   514       to: aConsoleActor,
   515       type: "startListeners",
   516       listeners: aListeners,
   517     };
   519     this.request(packet, (aResponse) => {
   520       let consoleClient;
   521       if (!aResponse.error) {
   522         if (this._consoleClients.has(aConsoleActor)) {
   523           consoleClient = this._consoleClients.get(aConsoleActor);
   524         } else {
   525           consoleClient = new WebConsoleClient(this, aResponse);
   526           this._consoleClients.set(aConsoleActor, consoleClient);
   527         }
   528       }
   529       aOnResponse(aResponse, consoleClient);
   530     });
   531   },
   533   /**
   534    * Attach to a global-scoped thread actor for chrome debugging.
   535    *
   536    * @param string aThreadActor
   537    *        The actor ID for the thread to attach.
   538    * @param function aOnResponse
   539    *        Called with the response packet and a ThreadClient
   540    *        (which will be undefined on error).
   541    * @param object aOptions
   542    *        Configuration options.
   543    *        - useSourceMaps: whether to use source maps or not.
   544    */
   545   attachThread: function (aThreadActor, aOnResponse, aOptions={}) {
   546     if (this._threadClients.has(aThreadActor)) {
   547       setTimeout(() => aOnResponse({}, this._threadClients.get(aThreadActor)), 0);
   548       return;
   549     }
   551    let packet = {
   552       to: aThreadActor,
   553       type: "attach",
   554       options: aOptions
   555     };
   556     this.request(packet, (aResponse) => {
   557       if (!aResponse.error) {
   558         var threadClient = new ThreadClient(this, aThreadActor);
   559         this._threadClients.set(aThreadActor, threadClient);
   560       }
   561       aOnResponse(aResponse, threadClient);
   562     });
   563   },
   565   /**
   566    * Attach to a trace actor.
   567    *
   568    * @param string aTraceActor
   569    *        The actor ID for the tracer to attach.
   570    * @param function aOnResponse
   571    *        Called with the response packet and a TraceClient
   572    *        (which will be undefined on error).
   573    */
   574   attachTracer: function (aTraceActor, aOnResponse) {
   575     if (this._tracerClients.has(aTraceActor)) {
   576       setTimeout(() => aOnResponse({}, this._tracerClients.get(aTraceActor)), 0);
   577       return;
   578     }
   580     let packet = {
   581       to: aTraceActor,
   582       type: "attach"
   583     };
   584     this.request(packet, (aResponse) => {
   585       if (!aResponse.error) {
   586         var traceClient = new TraceClient(this, aTraceActor);
   587         this._tracerClients.set(aTraceActor, traceClient);
   588       }
   589       aOnResponse(aResponse, traceClient);
   590     });
   591   },
   593   /**
   594    * Release an object actor.
   595    *
   596    * @param string aActor
   597    *        The actor ID to send the request to.
   598    * @param aOnResponse function
   599    *        If specified, will be called with the response packet when
   600    *        debugging server responds.
   601    */
   602   release: DebuggerClient.requester({
   603     to: args(0),
   604     type: "release"
   605   }, {
   606     telemetry: "RELEASE"
   607   }),
   609   /**
   610    * Send a request to the debugging server.
   611    *
   612    * @param aRequest object
   613    *        A JSON packet to send to the debugging server.
   614    * @param aOnResponse function
   615    *        If specified, will be called with the response packet when
   616    *        debugging server responds.
   617    */
   618   request: function (aRequest, aOnResponse) {
   619     if (!this.mainRoot) {
   620       throw Error("Have not yet received a hello packet from the server.");
   621     }
   622     if (!aRequest.to) {
   623       let type = aRequest.type || "";
   624       throw Error("'" + type + "' request packet has no destination.");
   625     }
   627     this._pendingRequests.push({ to: aRequest.to,
   628                                  request: aRequest,
   629                                  onResponse: aOnResponse });
   630     this._sendRequests();
   631   },
   633   /**
   634    * Send pending requests to any actors that don't already have an
   635    * active request.
   636    */
   637   _sendRequests: function () {
   638     this._pendingRequests = this._pendingRequests.filter((request) => {
   639       if (this._activeRequests.has(request.to)) {
   640         return true;
   641       }
   643       this.expectReply(request.to, request.onResponse);
   644       this._transport.send(request.request);
   646       return false;
   647     });
   648   },
   650   /**
   651    * Arrange to hand the next reply from |aActor| to |aHandler|.
   652    *
   653    * DebuggerClient.prototype.request usually takes care of establishing
   654    * the handler for a given request, but in rare cases (well, greetings
   655    * from new root actors, is the only case at the moment) we must be
   656    * prepared for a "reply" that doesn't correspond to any request we sent.
   657    */
   658   expectReply: function (aActor, aHandler) {
   659     if (this._activeRequests.has(aActor)) {
   660       throw Error("clashing handlers for next reply from " + uneval(aActor));
   661     }
   662     this._activeRequests.set(aActor, aHandler);
   663   },
   665   // Transport hooks.
   667   /**
   668    * Called by DebuggerTransport to dispatch incoming packets as appropriate.
   669    *
   670    * @param aPacket object
   671    *        The incoming packet.
   672    * @param aIgnoreCompatibility boolean
   673    *        Set true to not pass the packet through the compatibility layer.
   674    */
   675   onPacket: function (aPacket, aIgnoreCompatibility=false) {
   676     let packet = aIgnoreCompatibility
   677       ? aPacket
   678       : this.compat.onPacket(aPacket);
   680     resolve(packet).then(aPacket => {
   681       if (!aPacket.from) {
   682         DevToolsUtils.reportException(
   683           "onPacket",
   684           new Error("Server did not specify an actor, dropping packet: " +
   685                     JSON.stringify(aPacket)));
   686         return;
   687       }
   689       // If we have a registered Front for this actor, let it handle the packet
   690       // and skip all the rest of this unpleasantness.
   691       let front = this.getActor(aPacket.from);
   692       if (front) {
   693         front.onPacket(aPacket);
   694         return;
   695       }
   697       let onResponse;
   698       // See if we have a handler function waiting for a reply from this
   699       // actor. (Don't count unsolicited notifications or pauses as
   700       // replies.)
   701       if (this._activeRequests.has(aPacket.from) &&
   702           !(aPacket.type in UnsolicitedNotifications) &&
   703           !(aPacket.type == ThreadStateTypes.paused &&
   704             aPacket.why.type in UnsolicitedPauses)) {
   705         onResponse = this._activeRequests.get(aPacket.from);
   706         this._activeRequests.delete(aPacket.from);
   707       }
   709       // Packets that indicate thread state changes get special treatment.
   710       if (aPacket.type in ThreadStateTypes &&
   711           this._threadClients.has(aPacket.from)) {
   712         this._threadClients.get(aPacket.from)._onThreadState(aPacket);
   713       }
   714       // On navigation the server resumes, so the client must resume as well.
   715       // We achieve that by generating a fake resumption packet that triggers
   716       // the client's thread state change listeners.
   717       if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
   718           this._tabClients.has(aPacket.from) &&
   719           this._tabClients.get(aPacket.from).thread) {
   720         let thread = this._tabClients.get(aPacket.from).thread;
   721         let resumption = { from: thread._actor, type: "resumed" };
   722         thread._onThreadState(resumption);
   723       }
   724       // Only try to notify listeners on events, not responses to requests
   725       // that lack a packet type.
   726       if (aPacket.type) {
   727         this.notify(aPacket.type, aPacket);
   728       }
   730       if (onResponse) {
   731         onResponse(aPacket);
   732       }
   734       this._sendRequests();
   735     }, ex => DevToolsUtils.reportException("onPacket handler", ex));
   736   },
   738   /**
   739    * Called by DebuggerTransport when the underlying stream is closed.
   740    *
   741    * @param aStatus nsresult
   742    *        The status code that corresponds to the reason for closing
   743    *        the stream.
   744    */
   745   onClosed: function (aStatus) {
   746     this.notify("closed");
   747   },
   749   /**
   750    * Actor lifetime management, echos the server's actor pools.
   751    */
   752   __pools: null,
   753   get _pools() {
   754     if (this.__pools) {
   755       return this.__pools;
   756     }
   757     this.__pools = new Set();
   758     return this.__pools;
   759   },
   761   addActorPool: function (pool) {
   762     this._pools.add(pool);
   763   },
   764   removeActorPool: function (pool) {
   765     this._pools.delete(pool);
   766   },
   767   getActor: function (actorID) {
   768     let pool = this.poolFor(actorID);
   769     return pool ? pool.get(actorID) : null;
   770   },
   772   poolFor: function (actorID) {
   773     for (let pool of this._pools) {
   774       if (pool.has(actorID)) return pool;
   775     }
   776     return null;
   777   },
   779   /**
   780    * Currently attached addon.
   781    */
   782   activeAddon: null
   783 }
   785 eventSource(DebuggerClient.prototype);
   787 // Constants returned by `FeatureCompatibilityShim.onPacketTest`.
   788 const SUPPORTED = 1;
   789 const NOT_SUPPORTED = 2;
   790 const SKIP = 3;
   792 /**
   793  * This object provides an abstraction layer over all of our backwards
   794  * compatibility, feature detection, and shimming with regards to the remote
   795  * debugging prototcol.
   796  *
   797  * @param aFeatures Array
   798  *        An array of FeatureCompatibilityShim objects
   799  */
   800 function ProtocolCompatibility(aClient, aFeatures) {
   801   this._client = aClient;
   802   this._featuresWithUnknownSupport = new Set(aFeatures);
   803   this._featuresWithoutSupport = new Set();
   805   this._featureDeferreds = Object.create(null)
   806   for (let f of aFeatures) {
   807     this._featureDeferreds[f.name] = defer();
   808   }
   809 }
   811 ProtocolCompatibility.prototype = {
   812   /**
   813    * Returns a promise that resolves to true if the RDP supports the feature,
   814    * and is rejected otherwise.
   815    *
   816    * @param aFeatureName String
   817    *        The name of the feature we are testing.
   818    */
   819   supportsFeature: function (aFeatureName) {
   820     return this._featureDeferreds[aFeatureName].promise;
   821   },
   823   /**
   824    * Force a feature to be considered unsupported.
   825    *
   826    * @param aFeatureName String
   827    *        The name of the feature we are testing.
   828    */
   829   rejectFeature: function (aFeatureName) {
   830     this._featureDeferreds[aFeatureName].reject(false);
   831   },
   833   /**
   834    * Called for each packet received over the RDP from the server. Tests for
   835    * protocol features and shims packets to support needed features.
   836    *
   837    * @param aPacket Object
   838    *        Packet received over the RDP from the server.
   839    */
   840   onPacket: function (aPacket) {
   841     this._detectFeatures(aPacket);
   842     return this._shimPacket(aPacket);
   843   },
   845   /**
   846    * For each of the features we don't know whether the server supports or not,
   847    * attempt to detect support based on the packet we just received.
   848    */
   849   _detectFeatures: function (aPacket) {
   850     for (let feature of this._featuresWithUnknownSupport) {
   851       try {
   852         switch (feature.onPacketTest(aPacket)) {
   853         case SKIP:
   854           break;
   855         case SUPPORTED:
   856           this._featuresWithUnknownSupport.delete(feature);
   857           this._featureDeferreds[feature.name].resolve(true);
   858           break;
   859         case NOT_SUPPORTED:
   860           this._featuresWithUnknownSupport.delete(feature);
   861           this._featuresWithoutSupport.add(feature);
   862           this.rejectFeature(feature.name);
   863           break;
   864         default:
   865           DevToolsUtils.reportException(
   866             "PC__detectFeatures",
   867             new Error("Bad return value from `onPacketTest` for feature '"
   868                       + feature.name + "'"));
   869         }
   870       } catch (ex) {
   871         DevToolsUtils.reportException("PC__detectFeatures", ex);
   872       }
   873     }
   874   },
   876   /**
   877    * Go through each of the features that we know are unsupported by the current
   878    * server and attempt to shim support.
   879    */
   880   _shimPacket: function (aPacket) {
   881     let extraPackets = [];
   883     let loop = function (aFeatures, aPacket) {
   884       if (aFeatures.length === 0) {
   885         for (let packet of extraPackets) {
   886           this._client.onPacket(packet, true);
   887         }
   888         return aPacket;
   889       } else {
   890         let replacePacket = function (aNewPacket) {
   891           return aNewPacket;
   892         };
   893         let extraPacket = function (aExtraPacket) {
   894           extraPackets.push(aExtraPacket);
   895           return aPacket;
   896         };
   897         let keepPacket = function () {
   898           return aPacket;
   899         };
   900         let newPacket = aFeatures[0].translatePacket(aPacket,
   901                                                      replacePacket,
   902                                                      extraPacket,
   903                                                      keepPacket);
   904         return resolve(newPacket).then(loop.bind(null, aFeatures.slice(1)));
   905       }
   906     }.bind(this);
   908     return loop([f for (f of this._featuresWithoutSupport)],
   909                 aPacket);
   910   }
   911 };
   913 /**
   914  * Interface defining what methods a feature compatibility shim should have.
   915  */
   916 const FeatureCompatibilityShim = {
   917   // The name of the feature
   918   name: null,
   920   /**
   921    * Takes a packet and returns boolean (or promise of boolean) indicating
   922    * whether the server supports the RDP feature we are possibly shimming.
   923    */
   924   onPacketTest: function (aPacket) {
   925     throw new Error("Not yet implemented");
   926   },
   928   /**
   929    * Takes a packet actually sent from the server and decides whether to replace
   930    * it with a new packet, create an extra packet, or keep it.
   931    */
   932   translatePacket: function (aPacket, aReplacePacket, aExtraPacket, aKeepPacket) {
   933     throw new Error("Not yet implemented");
   934   }
   935 };
   937 /**
   938  * Creates a tab client for the remote debugging protocol server. This client
   939  * is a front to the tab actor created in the server side, hiding the protocol
   940  * details in a traditional JavaScript API.
   941  *
   942  * @param aClient DebuggerClient
   943  *        The debugger client parent.
   944  * @param aForm object
   945  *        The protocol form for this tab.
   946  */
   947 function TabClient(aClient, aForm) {
   948   this.client = aClient;
   949   this._actor = aForm.from;
   950   this._threadActor = aForm.threadActor;
   951   this.javascriptEnabled = aForm.javascriptEnabled;
   952   this.cacheEnabled = aForm.cacheEnabled;
   953   this.thread = null;
   954   this.request = this.client.request;
   955   this.traits = aForm.traits || {};
   956 }
   958 TabClient.prototype = {
   959   get actor() { return this._actor },
   960   get _transport() { return this.client._transport; },
   962   /**
   963    * Attach to a thread actor.
   964    *
   965    * @param object aOptions
   966    *        Configuration options.
   967    *        - useSourceMaps: whether to use source maps or not.
   968    * @param function aOnResponse
   969    *        Called with the response packet and a ThreadClient
   970    *        (which will be undefined on error).
   971    */
   972   attachThread: function(aOptions={}, aOnResponse) {
   973     if (this.thread) {
   974       setTimeout(() => aOnResponse({}, this.thread), 0);
   975       return;
   976     }
   978     let packet = {
   979       to: this._threadActor,
   980       type: "attach",
   981       options: aOptions
   982     };
   983     this.request(packet, (aResponse) => {
   984       if (!aResponse.error) {
   985         this.thread = new ThreadClient(this, this._threadActor);
   986         this.client._threadClients.set(this._threadActor, this.thread);
   987       }
   988       aOnResponse(aResponse, this.thread);
   989     });
   990   },
   992   /**
   993    * Detach the client from the tab actor.
   994    *
   995    * @param function aOnResponse
   996    *        Called with the response packet.
   997    */
   998   detach: DebuggerClient.requester({
   999     type: "detach"
  1000   }, {
  1001     before: function (aPacket) {
  1002       if (this.thread) {
  1003         this.thread.detach();
  1005       return aPacket;
  1006     },
  1007     after: function (aResponse) {
  1008       this.client._tabClients.delete(this.actor);
  1009       return aResponse;
  1010     },
  1011     telemetry: "TABDETACH"
  1012   }),
  1014   /**
  1015    * Reload the page in this tab.
  1016    */
  1017   reload: DebuggerClient.requester({
  1018     type: "reload"
  1019   }, {
  1020     telemetry: "RELOAD"
  1021   }),
  1023   /**
  1024    * Navigate to another URL.
  1026    * @param string url
  1027    *        The URL to navigate to.
  1028    */
  1029   navigateTo: DebuggerClient.requester({
  1030     type: "navigateTo",
  1031     url: args(0)
  1032   }, {
  1033     telemetry: "NAVIGATETO"
  1034   }),
  1036   /**
  1037    * Reconfigure the tab actor.
  1039    * @param object aOptions
  1040    *        A dictionary object of the new options to use in the tab actor.
  1041    * @param function aOnResponse
  1042    *        Called with the response packet.
  1043    */
  1044   reconfigure: DebuggerClient.requester({
  1045     type: "reconfigure",
  1046     options: args(0)
  1047   }, {
  1048     telemetry: "RECONFIGURETAB"
  1049   }),
  1050 };
  1052 eventSource(TabClient.prototype);
  1054 function AddonClient(aClient, aActor) {
  1055   this._client = aClient;
  1056   this._actor = aActor;
  1057   this.request = this._client.request;
  1060 AddonClient.prototype = {
  1061   get actor() { return this._actor; },
  1062   get _transport() { return this._client._transport; },
  1064   /**
  1065    * Detach the client from the addon actor.
  1067    * @param function aOnResponse
  1068    *        Called with the response packet.
  1069    */
  1070   detach: DebuggerClient.requester({
  1071     type: "detach"
  1072   }, {
  1073     after: function(aResponse) {
  1074       if (this._client.activeAddon === this._client._addonClients[this.actor]) {
  1075         this._client.activeAddon = null
  1077       delete this._client._addonClients[this.actor];
  1078       return aResponse;
  1079     },
  1080     telemetry: "ADDONDETACH"
  1081   })
  1082 };
  1084 /**
  1085  * A RootClient object represents a root actor on the server. Each
  1086  * DebuggerClient keeps a RootClient instance representing the root actor
  1087  * for the initial connection; DebuggerClient's 'listTabs' and
  1088  * 'listChildProcesses' methods forward to that root actor.
  1090  * @param aClient object
  1091  *      The client connection to which this actor belongs.
  1092  * @param aGreeting string
  1093  *      The greeting packet from the root actor we're to represent.
  1095  * Properties of a RootClient instance:
  1097  * @property actor string
  1098  *      The name of this child's root actor.
  1099  * @property applicationType string
  1100  *      The application type, as given in the root actor's greeting packet.
  1101  * @property traits object
  1102  *      The traits object, as given in the root actor's greeting packet.
  1103  */
  1104 function RootClient(aClient, aGreeting) {
  1105   this._client = aClient;
  1106   this.actor = aGreeting.from;
  1107   this.applicationType = aGreeting.applicationType;
  1108   this.traits = aGreeting.traits;
  1111 RootClient.prototype = {
  1112   constructor: RootClient,
  1114   /**
  1115    * List the open tabs.
  1117    * @param function aOnResponse
  1118    *        Called with the response packet.
  1119    */
  1120   listTabs: DebuggerClient.requester({ type: "listTabs" },
  1121                                      { telemetry: "LISTTABS" }),
  1123   /**
  1124    * List the installed addons.
  1126    * @param function aOnResponse
  1127    *        Called with the response packet.
  1128    */
  1129   listAddons: DebuggerClient.requester({ type: "listAddons" },
  1130                                        { telemetry: "LISTADDONS" }),
  1132   /*
  1133    * Methods constructed by DebuggerClient.requester require these forwards
  1134    * on their 'this'.
  1135    */
  1136   get _transport() { return this._client._transport; },
  1137   get request()    { return this._client.request;    }
  1138 };
  1140 /**
  1141  * Creates a thread client for the remote debugging protocol server. This client
  1142  * is a front to the thread actor created in the server side, hiding the
  1143  * protocol details in a traditional JavaScript API.
  1145  * @param aClient DebuggerClient|TabClient
  1146  *        The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
  1147  *        for chrome debuggers).
  1148  * @param aActor string
  1149  *        The actor ID for this thread.
  1150  */
  1151 function ThreadClient(aClient, aActor) {
  1152   this._parent = aClient;
  1153   this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
  1154   this._actor = aActor;
  1155   this._frameCache = [];
  1156   this._scriptCache = {};
  1157   this._pauseGrips = {};
  1158   this._threadGrips = {};
  1159   this.request = this.client.request;
  1162 ThreadClient.prototype = {
  1163   _state: "paused",
  1164   get state() { return this._state; },
  1165   get paused() { return this._state === "paused"; },
  1167   _pauseOnExceptions: false,
  1168   _ignoreCaughtExceptions: false,
  1169   _pauseOnDOMEvents: null,
  1171   _actor: null,
  1172   get actor() { return this._actor; },
  1174   get compat() { return this.client.compat; },
  1175   get _transport() { return this.client._transport; },
  1177   _assertPaused: function (aCommand) {
  1178     if (!this.paused) {
  1179       throw Error(aCommand + " command sent while not paused. Currently " + this._state);
  1181   },
  1183   /**
  1184    * Resume a paused thread. If the optional aLimit parameter is present, then
  1185    * the thread will also pause when that limit is reached.
  1187    * @param [optional] object aLimit
  1188    *        An object with a type property set to the appropriate limit (next,
  1189    *        step, or finish) per the remote debugging protocol specification.
  1190    *        Use null to specify no limit.
  1191    * @param function aOnResponse
  1192    *        Called with the response packet.
  1193    */
  1194   _doResume: DebuggerClient.requester({
  1195     type: "resume",
  1196     resumeLimit: args(0)
  1197   }, {
  1198     before: function (aPacket) {
  1199       this._assertPaused("resume");
  1201       // Put the client in a tentative "resuming" state so we can prevent
  1202       // further requests that should only be sent in the paused state.
  1203       this._state = "resuming";
  1205       if (this._pauseOnExceptions) {
  1206         aPacket.pauseOnExceptions = this._pauseOnExceptions;
  1208       if (this._ignoreCaughtExceptions) {
  1209         aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
  1211       if (this._pauseOnDOMEvents) {
  1212         aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
  1214       return aPacket;
  1215     },
  1216     after: function (aResponse) {
  1217       if (aResponse.error) {
  1218         // There was an error resuming, back to paused state.
  1219         this._state = "paused";
  1221       return aResponse;
  1222     },
  1223     telemetry: "RESUME"
  1224   }),
  1226   /**
  1227    * Reconfigure the thread actor.
  1229    * @param object aOptions
  1230    *        A dictionary object of the new options to use in the thread actor.
  1231    * @param function aOnResponse
  1232    *        Called with the response packet.
  1233    */
  1234   reconfigure: DebuggerClient.requester({
  1235     type: "reconfigure",
  1236     options: args(0)
  1237   }, {
  1238     telemetry: "RECONFIGURETHREAD"
  1239   }),
  1241   /**
  1242    * Resume a paused thread.
  1243    */
  1244   resume: function (aOnResponse) {
  1245     this._doResume(null, aOnResponse);
  1246   },
  1248   /**
  1249    * Step over a function call.
  1251    * @param function aOnResponse
  1252    *        Called with the response packet.
  1253    */
  1254   stepOver: function (aOnResponse) {
  1255     this._doResume({ type: "next" }, aOnResponse);
  1256   },
  1258   /**
  1259    * Step into a function call.
  1261    * @param function aOnResponse
  1262    *        Called with the response packet.
  1263    */
  1264   stepIn: function (aOnResponse) {
  1265     this._doResume({ type: "step" }, aOnResponse);
  1266   },
  1268   /**
  1269    * Step out of a function call.
  1271    * @param function aOnResponse
  1272    *        Called with the response packet.
  1273    */
  1274   stepOut: function (aOnResponse) {
  1275     this._doResume({ type: "finish" }, aOnResponse);
  1276   },
  1278   /**
  1279    * Interrupt a running thread.
  1281    * @param function aOnResponse
  1282    *        Called with the response packet.
  1283    */
  1284   interrupt: DebuggerClient.requester({
  1285     type: "interrupt"
  1286   }, {
  1287     telemetry: "INTERRUPT"
  1288   }),
  1290   /**
  1291    * Enable or disable pausing when an exception is thrown.
  1293    * @param boolean aFlag
  1294    *        Enables pausing if true, disables otherwise.
  1295    * @param function aOnResponse
  1296    *        Called with the response packet.
  1297    */
  1298   pauseOnExceptions: function (aPauseOnExceptions,
  1299                                aIgnoreCaughtExceptions,
  1300                                aOnResponse) {
  1301     this._pauseOnExceptions = aPauseOnExceptions;
  1302     this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
  1304     // If the debuggee is paused, we have to send the flag via a reconfigure
  1305     // request.
  1306     if (this.paused) {
  1307       this.reconfigure({
  1308         pauseOnExceptions: aPauseOnExceptions,
  1309         ignoreCaughtExceptions: aIgnoreCaughtExceptions
  1310       }, aOnResponse);
  1311       return;
  1313     // Otherwise send the flag using a standard resume request.
  1314     this.interrupt(aResponse => {
  1315       if (aResponse.error) {
  1316         // Can't continue if pausing failed.
  1317         aOnResponse(aResponse);
  1318         return;
  1320       this.resume(aOnResponse);
  1321     });
  1322   },
  1324   /**
  1325    * Enable pausing when the specified DOM events are triggered. Disabling
  1326    * pausing on an event can be realized by calling this method with the updated
  1327    * array of events that doesn't contain it.
  1329    * @param array|string events
  1330    *        An array of strings, representing the DOM event types to pause on,
  1331    *        or "*" to pause on all DOM events. Pass an empty array to
  1332    *        completely disable pausing on DOM events.
  1333    * @param function onResponse
  1334    *        Called with the response packet in a future turn of the event loop.
  1335    */
  1336   pauseOnDOMEvents: function (events, onResponse) {
  1337     this._pauseOnDOMEvents = events;
  1338     // If the debuggee is paused, the value of the array will be communicated in
  1339     // the next resumption. Otherwise we have to force a pause in order to send
  1340     // the array.
  1341     if (this.paused) {
  1342       setTimeout(() => onResponse({}), 0);
  1343       return;
  1345     this.interrupt(response => {
  1346       // Can't continue if pausing failed.
  1347       if (response.error) {
  1348         onResponse(response);
  1349         return;
  1351       this.resume(onResponse);
  1352     });
  1353   },
  1355   /**
  1356    * Send a clientEvaluate packet to the debuggee. Response
  1357    * will be a resume packet.
  1359    * @param string aFrame
  1360    *        The actor ID of the frame where the evaluation should take place.
  1361    * @param string aExpression
  1362    *        The expression that will be evaluated in the scope of the frame
  1363    *        above.
  1364    * @param function aOnResponse
  1365    *        Called with the response packet.
  1366    */
  1367   eval: DebuggerClient.requester({
  1368     type: "clientEvaluate",
  1369     frame: args(0),
  1370     expression: args(1)
  1371   }, {
  1372     before: function (aPacket) {
  1373       this._assertPaused("eval");
  1374       // Put the client in a tentative "resuming" state so we can prevent
  1375       // further requests that should only be sent in the paused state.
  1376       this._state = "resuming";
  1377       return aPacket;
  1378     },
  1379     after: function (aResponse) {
  1380       if (aResponse.error) {
  1381         // There was an error resuming, back to paused state.
  1382         this._state = "paused";
  1384       return aResponse;
  1385     },
  1386     telemetry: "CLIENTEVALUATE"
  1387   }),
  1389   /**
  1390    * Detach from the thread actor.
  1392    * @param function aOnResponse
  1393    *        Called with the response packet.
  1394    */
  1395   detach: DebuggerClient.requester({
  1396     type: "detach"
  1397   }, {
  1398     after: function (aResponse) {
  1399       this.client._threadClients.delete(this.actor);
  1400       this._parent.thread = null;
  1401       return aResponse;
  1402     },
  1403     telemetry: "THREADDETACH"
  1404   }),
  1406   /**
  1407    * Request to set a breakpoint in the specified location.
  1409    * @param object aLocation
  1410    *        The source location object where the breakpoint will be set.
  1411    * @param function aOnResponse
  1412    *        Called with the thread's response.
  1413    */
  1414   setBreakpoint: function ({ url, line, column, condition }, aOnResponse) {
  1415     // A helper function that sets the breakpoint.
  1416     let doSetBreakpoint = function (aCallback) {
  1417       const location = {
  1418         url: url,
  1419         line: line,
  1420         column: column
  1421       };
  1423       let packet = {
  1424         to: this._actor,
  1425         type: "setBreakpoint",
  1426         location: location,
  1427         condition: condition
  1428       };
  1429       this.client.request(packet, function (aResponse) {
  1430         // Ignoring errors, since the user may be setting a breakpoint in a
  1431         // dead script that will reappear on a page reload.
  1432         if (aOnResponse) {
  1433           let root = this.client.mainRoot;
  1434           let bpClient = new BreakpointClient(
  1435             this.client,
  1436             aResponse.actor,
  1437             location,
  1438             root.traits.conditionalBreakpoints ? condition : undefined
  1439           );
  1440           aOnResponse(aResponse, bpClient);
  1442         if (aCallback) {
  1443           aCallback();
  1445       }.bind(this));
  1446     }.bind(this);
  1448     // If the debuggee is paused, just set the breakpoint.
  1449     if (this.paused) {
  1450       doSetBreakpoint();
  1451       return;
  1453     // Otherwise, force a pause in order to set the breakpoint.
  1454     this.interrupt(function (aResponse) {
  1455       if (aResponse.error) {
  1456         // Can't set the breakpoint if pausing failed.
  1457         aOnResponse(aResponse);
  1458         return;
  1460       doSetBreakpoint(this.resume.bind(this));
  1461     }.bind(this));
  1462   },
  1464   /**
  1465    * Release multiple thread-lifetime object actors. If any pause-lifetime
  1466    * actors are included in the request, a |notReleasable| error will return,
  1467    * but all the thread-lifetime ones will have been released.
  1469    * @param array actors
  1470    *        An array with actor IDs to release.
  1471    */
  1472   releaseMany: DebuggerClient.requester({
  1473     type: "releaseMany",
  1474     actors: args(0),
  1475   }, {
  1476     telemetry: "RELEASEMANY"
  1477   }),
  1479   /**
  1480    * Promote multiple pause-lifetime object actors to thread-lifetime ones.
  1482    * @param array actors
  1483    *        An array with actor IDs to promote.
  1484    */
  1485   threadGrips: DebuggerClient.requester({
  1486     type: "threadGrips",
  1487     actors: args(0)
  1488   }, {
  1489     telemetry: "THREADGRIPS"
  1490   }),
  1492   /**
  1493    * Return the event listeners defined on the page.
  1495    * @param aOnResponse Function
  1496    *        Called with the thread's response.
  1497    */
  1498   eventListeners: DebuggerClient.requester({
  1499     type: "eventListeners"
  1500   }, {
  1501     telemetry: "EVENTLISTENERS"
  1502   }),
  1504   /**
  1505    * Request the loaded sources for the current thread.
  1507    * @param aOnResponse Function
  1508    *        Called with the thread's response.
  1509    */
  1510   getSources: DebuggerClient.requester({
  1511     type: "sources"
  1512   }, {
  1513     telemetry: "SOURCES"
  1514   }),
  1516   _doInterrupted: function (aAction, aError) {
  1517     if (this.paused) {
  1518       aAction();
  1519       return;
  1521     this.interrupt(function (aResponse) {
  1522       if (aResponse) {
  1523         aError(aResponse);
  1524         return;
  1526       aAction();
  1527       this.resume();
  1528     }.bind(this));
  1529   },
  1531   /**
  1532    * Clear the thread's source script cache. A scriptscleared event
  1533    * will be sent.
  1534    */
  1535   _clearScripts: function () {
  1536     if (Object.keys(this._scriptCache).length > 0) {
  1537       this._scriptCache = {}
  1538       this.notify("scriptscleared");
  1540   },
  1542   /**
  1543    * Request frames from the callstack for the current thread.
  1545    * @param aStart integer
  1546    *        The number of the youngest stack frame to return (the youngest
  1547    *        frame is 0).
  1548    * @param aCount integer
  1549    *        The maximum number of frames to return, or null to return all
  1550    *        frames.
  1551    * @param aOnResponse function
  1552    *        Called with the thread's response.
  1553    */
  1554   getFrames: DebuggerClient.requester({
  1555     type: "frames",
  1556     start: args(0),
  1557     count: args(1)
  1558   }, {
  1559     telemetry: "FRAMES"
  1560   }),
  1562   /**
  1563    * An array of cached frames. Clients can observe the framesadded and
  1564    * framescleared event to keep up to date on changes to this cache,
  1565    * and can fill it using the fillFrames method.
  1566    */
  1567   get cachedFrames() { return this._frameCache; },
  1569   /**
  1570    * true if there are more stack frames available on the server.
  1571    */
  1572   get moreFrames() {
  1573     return this.paused && (!this._frameCache || this._frameCache.length == 0
  1574           || !this._frameCache[this._frameCache.length - 1].oldest);
  1575   },
  1577   /**
  1578    * Ensure that at least aTotal stack frames have been loaded in the
  1579    * ThreadClient's stack frame cache. A framesadded event will be
  1580    * sent when the stack frame cache is updated.
  1582    * @param aTotal number
  1583    *        The minimum number of stack frames to be included.
  1584    * @param aCallback function
  1585    *        Optional callback function called when frames have been loaded
  1586    * @returns true if a framesadded notification should be expected.
  1587    */
  1588   fillFrames: function (aTotal, aCallback=noop) {
  1589     this._assertPaused("fillFrames");
  1590     if (this._frameCache.length >= aTotal) {
  1591       return false;
  1594     let numFrames = this._frameCache.length;
  1596     this.getFrames(numFrames, aTotal - numFrames, (aResponse) => {
  1597       if (aResponse.error) {
  1598         aCallback(aResponse);
  1599         return;
  1602       for each (let frame in aResponse.frames) {
  1603         this._frameCache[frame.depth] = frame;
  1606       // If we got as many frames as we asked for, there might be more
  1607       // frames available.
  1608       this.notify("framesadded");
  1610       aCallback(aResponse);
  1611     });
  1613     return true;
  1614   },
  1616   /**
  1617    * Clear the thread's stack frame cache. A framescleared event
  1618    * will be sent.
  1619    */
  1620   _clearFrames: function () {
  1621     if (this._frameCache.length > 0) {
  1622       this._frameCache = [];
  1623       this.notify("framescleared");
  1625   },
  1627   /**
  1628    * Return a ObjectClient object for the given object grip.
  1630    * @param aGrip object
  1631    *        A pause-lifetime object grip returned by the protocol.
  1632    */
  1633   pauseGrip: function (aGrip) {
  1634     if (aGrip.actor in this._pauseGrips) {
  1635       return this._pauseGrips[aGrip.actor];
  1638     let client = new ObjectClient(this.client, aGrip);
  1639     this._pauseGrips[aGrip.actor] = client;
  1640     return client;
  1641   },
  1643   /**
  1644    * Get or create a long string client, checking the grip client cache if it
  1645    * already exists.
  1647    * @param aGrip Object
  1648    *        The long string grip returned by the protocol.
  1649    * @param aGripCacheName String
  1650    *        The property name of the grip client cache to check for existing
  1651    *        clients in.
  1652    */
  1653   _longString: function (aGrip, aGripCacheName) {
  1654     if (aGrip.actor in this[aGripCacheName]) {
  1655       return this[aGripCacheName][aGrip.actor];
  1658     let client = new LongStringClient(this.client, aGrip);
  1659     this[aGripCacheName][aGrip.actor] = client;
  1660     return client;
  1661   },
  1663   /**
  1664    * Return an instance of LongStringClient for the given long string grip that
  1665    * is scoped to the current pause.
  1667    * @param aGrip Object
  1668    *        The long string grip returned by the protocol.
  1669    */
  1670   pauseLongString: function (aGrip) {
  1671     return this._longString(aGrip, "_pauseGrips");
  1672   },
  1674   /**
  1675    * Return an instance of LongStringClient for the given long string grip that
  1676    * is scoped to the thread lifetime.
  1678    * @param aGrip Object
  1679    *        The long string grip returned by the protocol.
  1680    */
  1681   threadLongString: function (aGrip) {
  1682     return this._longString(aGrip, "_threadGrips");
  1683   },
  1685   /**
  1686    * Clear and invalidate all the grip clients from the given cache.
  1688    * @param aGripCacheName
  1689    *        The property name of the grip cache we want to clear.
  1690    */
  1691   _clearObjectClients: function (aGripCacheName) {
  1692     for each (let grip in this[aGripCacheName]) {
  1693       grip.valid = false;
  1695     this[aGripCacheName] = {};
  1696   },
  1698   /**
  1699    * Invalidate pause-lifetime grip clients and clear the list of current grip
  1700    * clients.
  1701    */
  1702   _clearPauseGrips: function () {
  1703     this._clearObjectClients("_pauseGrips");
  1704   },
  1706   /**
  1707    * Invalidate thread-lifetime grip clients and clear the list of current grip
  1708    * clients.
  1709    */
  1710   _clearThreadGrips: function () {
  1711     this._clearObjectClients("_threadGrips");
  1712   },
  1714   /**
  1715    * Handle thread state change by doing necessary cleanup and notifying all
  1716    * registered listeners.
  1717    */
  1718   _onThreadState: function (aPacket) {
  1719     this._state = ThreadStateTypes[aPacket.type];
  1720     this._clearFrames();
  1721     this._clearPauseGrips();
  1722     aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
  1723     this.client._eventsEnabled && this.notify(aPacket.type, aPacket);
  1724   },
  1726   /**
  1727    * Return an EnvironmentClient instance for the given environment actor form.
  1728    */
  1729   environment: function (aForm) {
  1730     return new EnvironmentClient(this.client, aForm);
  1731   },
  1733   /**
  1734    * Return an instance of SourceClient for the given source actor form.
  1735    */
  1736   source: function (aForm) {
  1737     if (aForm.actor in this._threadGrips) {
  1738       return this._threadGrips[aForm.actor];
  1741     return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
  1742   },
  1744   /**
  1745    * Request the prototype and own properties of mutlipleObjects.
  1747    * @param aOnResponse function
  1748    *        Called with the request's response.
  1749    * @param actors [string]
  1750    *        List of actor ID of the queried objects.
  1751    */
  1752   getPrototypesAndProperties: DebuggerClient.requester({
  1753     type: "prototypesAndProperties",
  1754     actors: args(0)
  1755   }, {
  1756     telemetry: "PROTOTYPESANDPROPERTIES"
  1757   })
  1758 };
  1760 eventSource(ThreadClient.prototype);
  1762 /**
  1763  * Creates a tracing profiler client for the remote debugging protocol
  1764  * server. This client is a front to the trace actor created on the
  1765  * server side, hiding the protocol details in a traditional
  1766  * JavaScript API.
  1768  * @param aClient DebuggerClient
  1769  *        The debugger client parent.
  1770  * @param aActor string
  1771  *        The actor ID for this thread.
  1772  */
  1773 function TraceClient(aClient, aActor) {
  1774   this._client = aClient;
  1775   this._actor = aActor;
  1776   this._activeTraces = new Set();
  1777   this._waitingPackets = new Map();
  1778   this._expectedPacket = 0;
  1779   this.request = this._client.request;
  1782 TraceClient.prototype = {
  1783   get actor()   { return this._actor; },
  1784   get tracing() { return this._activeTraces.size > 0; },
  1786   get _transport() { return this._client._transport; },
  1788   /**
  1789    * Detach from the trace actor.
  1790    */
  1791   detach: DebuggerClient.requester({
  1792     type: "detach"
  1793   }, {
  1794     after: function (aResponse) {
  1795       this._client._tracerClients.delete(this.actor);
  1796       return aResponse;
  1797     },
  1798     telemetry: "TRACERDETACH"
  1799   }),
  1801   /**
  1802    * Start a new trace.
  1804    * @param aTrace [string]
  1805    *        An array of trace types to be recorded by the new trace.
  1807    * @param aName string
  1808    *        The name of the new trace.
  1810    * @param aOnResponse function
  1811    *        Called with the request's response.
  1812    */
  1813   startTrace: DebuggerClient.requester({
  1814     type: "startTrace",
  1815     name: args(1),
  1816     trace: args(0)
  1817   }, {
  1818     after: function (aResponse) {
  1819       if (aResponse.error) {
  1820         return aResponse;
  1823       if (!this.tracing) {
  1824         this._waitingPackets.clear();
  1825         this._expectedPacket = 0;
  1827       this._activeTraces.add(aResponse.name);
  1829       return aResponse;
  1830     },
  1831     telemetry: "STARTTRACE"
  1832   }),
  1834   /**
  1835    * End a trace. If a name is provided, stop the named
  1836    * trace. Otherwise, stop the most recently started trace.
  1838    * @param aName string
  1839    *        The name of the trace to stop.
  1841    * @param aOnResponse function
  1842    *        Called with the request's response.
  1843    */
  1844   stopTrace: DebuggerClient.requester({
  1845     type: "stopTrace",
  1846     name: args(0)
  1847   }, {
  1848     after: function (aResponse) {
  1849       if (aResponse.error) {
  1850         return aResponse;
  1853       this._activeTraces.delete(aResponse.name);
  1855       return aResponse;
  1856     },
  1857     telemetry: "STOPTRACE"
  1858   })
  1859 };
  1861 /**
  1862  * Grip clients are used to retrieve information about the relevant object.
  1864  * @param aClient DebuggerClient
  1865  *        The debugger client parent.
  1866  * @param aGrip object
  1867  *        A pause-lifetime object grip returned by the protocol.
  1868  */
  1869 function ObjectClient(aClient, aGrip)
  1871   this._grip = aGrip;
  1872   this._client = aClient;
  1873   this.request = this._client.request;
  1876 ObjectClient.prototype = {
  1877   get actor() { return this._grip.actor },
  1878   get _transport() { return this._client._transport; },
  1880   valid: true,
  1882   get isFrozen() this._grip.frozen,
  1883   get isSealed() this._grip.sealed,
  1884   get isExtensible() this._grip.extensible,
  1886   getDefinitionSite: DebuggerClient.requester({
  1887     type: "definitionSite"
  1888   }, {
  1889     before: function (aPacket) {
  1890       if (this._grip.class != "Function") {
  1891         throw new Error("getDefinitionSite is only valid for function grips.");
  1893       return aPacket;
  1895   }),
  1897   /**
  1898    * Request the names of a function's formal parameters.
  1900    * @param aOnResponse function
  1901    *        Called with an object of the form:
  1902    *        { parameterNames:[<parameterName>, ...] }
  1903    *        where each <parameterName> is the name of a parameter.
  1904    */
  1905   getParameterNames: DebuggerClient.requester({
  1906     type: "parameterNames"
  1907   }, {
  1908     before: function (aPacket) {
  1909       if (this._grip["class"] !== "Function") {
  1910         throw new Error("getParameterNames is only valid for function grips.");
  1912       return aPacket;
  1913     },
  1914     telemetry: "PARAMETERNAMES"
  1915   }),
  1917   /**
  1918    * Request the names of the properties defined on the object and not its
  1919    * prototype.
  1921    * @param aOnResponse function Called with the request's response.
  1922    */
  1923   getOwnPropertyNames: DebuggerClient.requester({
  1924     type: "ownPropertyNames"
  1925   }, {
  1926     telemetry: "OWNPROPERTYNAMES"
  1927   }),
  1929   /**
  1930    * Request the prototype and own properties of the object.
  1932    * @param aOnResponse function Called with the request's response.
  1933    */
  1934   getPrototypeAndProperties: DebuggerClient.requester({
  1935     type: "prototypeAndProperties"
  1936   }, {
  1937     telemetry: "PROTOTYPEANDPROPERTIES"
  1938   }),
  1940   /**
  1941    * Request the property descriptor of the object's specified property.
  1943    * @param aName string The name of the requested property.
  1944    * @param aOnResponse function Called with the request's response.
  1945    */
  1946   getProperty: DebuggerClient.requester({
  1947     type: "property",
  1948     name: args(0)
  1949   }, {
  1950     telemetry: "PROPERTY"
  1951   }),
  1953   /**
  1954    * Request the prototype of the object.
  1956    * @param aOnResponse function Called with the request's response.
  1957    */
  1958   getPrototype: DebuggerClient.requester({
  1959     type: "prototype"
  1960   }, {
  1961     telemetry: "PROTOTYPE"
  1962   }),
  1964   /**
  1965    * Request the display string of the object.
  1967    * @param aOnResponse function Called with the request's response.
  1968    */
  1969   getDisplayString: DebuggerClient.requester({
  1970     type: "displayString"
  1971   }, {
  1972     telemetry: "DISPLAYSTRING"
  1973   }),
  1975   /**
  1976    * Request the scope of the object.
  1978    * @param aOnResponse function Called with the request's response.
  1979    */
  1980   getScope: DebuggerClient.requester({
  1981     type: "scope"
  1982   }, {
  1983     before: function (aPacket) {
  1984       if (this._grip.class !== "Function") {
  1985         throw new Error("scope is only valid for function grips.");
  1987       return aPacket;
  1988     },
  1989     telemetry: "SCOPE"
  1990   })
  1991 };
  1993 /**
  1994  * A LongStringClient provides a way to access "very long" strings from the
  1995  * debugger server.
  1997  * @param aClient DebuggerClient
  1998  *        The debugger client parent.
  1999  * @param aGrip Object
  2000  *        A pause-lifetime long string grip returned by the protocol.
  2001  */
  2002 function LongStringClient(aClient, aGrip) {
  2003   this._grip = aGrip;
  2004   this._client = aClient;
  2005   this.request = this._client.request;
  2008 LongStringClient.prototype = {
  2009   get actor() { return this._grip.actor; },
  2010   get length() { return this._grip.length; },
  2011   get initial() { return this._grip.initial; },
  2012   get _transport() { return this._client._transport; },
  2014   valid: true,
  2016   /**
  2017    * Get the substring of this LongString from aStart to aEnd.
  2019    * @param aStart Number
  2020    *        The starting index.
  2021    * @param aEnd Number
  2022    *        The ending index.
  2023    * @param aCallback Function
  2024    *        The function called when we receive the substring.
  2025    */
  2026   substring: DebuggerClient.requester({
  2027     type: "substring",
  2028     start: args(0),
  2029     end: args(1)
  2030   }, {
  2031     telemetry: "SUBSTRING"
  2032   }),
  2033 };
  2035 /**
  2036  * A SourceClient provides a way to access the source text of a script.
  2038  * @param aClient ThreadClient
  2039  *        The thread client parent.
  2040  * @param aForm Object
  2041  *        The form sent across the remote debugging protocol.
  2042  */
  2043 function SourceClient(aClient, aForm) {
  2044   this._form = aForm;
  2045   this._isBlackBoxed = aForm.isBlackBoxed;
  2046   this._isPrettyPrinted = aForm.isPrettyPrinted;
  2047   this._activeThread = aClient;
  2048   this._client = aClient.client;
  2051 SourceClient.prototype = {
  2052   get _transport() this._client._transport,
  2053   get isBlackBoxed() this._isBlackBoxed,
  2054   get isPrettyPrinted() this._isPrettyPrinted,
  2055   get actor() this._form.actor,
  2056   get request() this._client.request,
  2057   get url() this._form.url,
  2059   /**
  2060    * Black box this SourceClient's source.
  2062    * @param aCallback Function
  2063    *        The callback function called when we receive the response from the server.
  2064    */
  2065   blackBox: DebuggerClient.requester({
  2066     type: "blackbox"
  2067   }, {
  2068     telemetry: "BLACKBOX",
  2069     after: function (aResponse) {
  2070       if (!aResponse.error) {
  2071         this._isBlackBoxed = true;
  2072         if (this._activeThread) {
  2073           this._activeThread.notify("blackboxchange", this);
  2076       return aResponse;
  2078   }),
  2080   /**
  2081    * Un-black box this SourceClient's source.
  2083    * @param aCallback Function
  2084    *        The callback function called when we receive the response from the server.
  2085    */
  2086   unblackBox: DebuggerClient.requester({
  2087     type: "unblackbox"
  2088   }, {
  2089     telemetry: "UNBLACKBOX",
  2090     after: function (aResponse) {
  2091       if (!aResponse.error) {
  2092         this._isBlackBoxed = false;
  2093         if (this._activeThread) {
  2094           this._activeThread.notify("blackboxchange", this);
  2097       return aResponse;
  2099   }),
  2101   /**
  2102    * Get a long string grip for this SourceClient's source.
  2103    */
  2104   source: function (aCallback) {
  2105     let packet = {
  2106       to: this._form.actor,
  2107       type: "source"
  2108     };
  2109     this._client.request(packet, aResponse => {
  2110       this._onSourceResponse(aResponse, aCallback)
  2111     });
  2112   },
  2114   /**
  2115    * Pretty print this source's text.
  2116    */
  2117   prettyPrint: function (aIndent, aCallback) {
  2118     const packet = {
  2119       to: this._form.actor,
  2120       type: "prettyPrint",
  2121       indent: aIndent
  2122     };
  2123     this._client.request(packet, aResponse => {
  2124       if (!aResponse.error) {
  2125         this._isPrettyPrinted = true;
  2126         this._activeThread._clearFrames();
  2127         this._activeThread.notify("prettyprintchange", this);
  2129       this._onSourceResponse(aResponse, aCallback);
  2130     });
  2131   },
  2133   /**
  2134    * Stop pretty printing this source's text.
  2135    */
  2136   disablePrettyPrint: function (aCallback) {
  2137     const packet = {
  2138       to: this._form.actor,
  2139       type: "disablePrettyPrint"
  2140     };
  2141     this._client.request(packet, aResponse => {
  2142       if (!aResponse.error) {
  2143         this._isPrettyPrinted = false;
  2144         this._activeThread._clearFrames();
  2145         this._activeThread.notify("prettyprintchange", this);
  2147       this._onSourceResponse(aResponse, aCallback);
  2148     });
  2149   },
  2151   _onSourceResponse: function (aResponse, aCallback) {
  2152     if (aResponse.error) {
  2153       aCallback(aResponse);
  2154       return;
  2157     if (typeof aResponse.source === "string") {
  2158       aCallback(aResponse);
  2159       return;
  2162     let { contentType, source } = aResponse;
  2163     let longString = this._activeThread.threadLongString(source);
  2164     longString.substring(0, longString.length, function (aResponse) {
  2165       if (aResponse.error) {
  2166         aCallback(aResponse);
  2167         return;
  2170       aCallback({
  2171         source: aResponse.substring,
  2172         contentType: contentType
  2173       });
  2174     });
  2176 };
  2178 /**
  2179  * Breakpoint clients are used to remove breakpoints that are no longer used.
  2181  * @param aClient DebuggerClient
  2182  *        The debugger client parent.
  2183  * @param aActor string
  2184  *        The actor ID for this breakpoint.
  2185  * @param aLocation object
  2186  *        The location of the breakpoint. This is an object with two properties:
  2187  *        url and line.
  2188  * @param aCondition string
  2189  *        The conditional expression of the breakpoint
  2190  */
  2191 function BreakpointClient(aClient, aActor, aLocation, aCondition) {
  2192   this._client = aClient;
  2193   this._actor = aActor;
  2194   this.location = aLocation;
  2195   this.request = this._client.request;
  2197   // The condition property should only exist if it's a truthy value
  2198   if (aCondition) {
  2199     this.condition = aCondition;
  2203 BreakpointClient.prototype = {
  2205   _actor: null,
  2206   get actor() { return this._actor; },
  2207   get _transport() { return this._client._transport; },
  2209   /**
  2210    * Remove the breakpoint from the server.
  2211    */
  2212   remove: DebuggerClient.requester({
  2213     type: "delete"
  2214   }, {
  2215     telemetry: "DELETE"
  2216   }),
  2218   /**
  2219    * Determines if this breakpoint has a condition
  2220    */
  2221   hasCondition: function() {
  2222     let root = this._client.mainRoot;
  2223     // XXX bug 990137: We will remove support for client-side handling of
  2224     // conditional breakpoints
  2225     if (root.traits.conditionalBreakpoints) {
  2226       return "condition" in this;
  2227     } else {
  2228       return "conditionalExpression" in this;
  2230   },
  2232   /**
  2233    * Get the condition of this breakpoint. Currently we have to
  2234    * support locally emulated conditional breakpoints until the
  2235    * debugger servers are updated (see bug 990137). We used a
  2236    * different property when moving it server-side to ensure that we
  2237    * are testing the right code.
  2238    */
  2239   getCondition: function() {
  2240     let root = this._client.mainRoot;
  2241     if (root.traits.conditionalBreakpoints) {
  2242       return this.condition;
  2243     } else {
  2244       return this.conditionalExpression;
  2246   },
  2248   /**
  2249    * Set the condition of this breakpoint
  2250    */
  2251   setCondition: function(gThreadClient, aCondition) {
  2252     let root = this._client.mainRoot;
  2253     let deferred = promise.defer();
  2255     if (root.traits.conditionalBreakpoints) {
  2256       let info = {
  2257         url: this.location.url,
  2258         line: this.location.line,
  2259         column: this.location.column,
  2260         condition: aCondition
  2261       };
  2263       // Remove the current breakpoint and add a new one with the
  2264       // condition.
  2265       this.remove(aResponse => {
  2266         if (aResponse && aResponse.error) {
  2267           deferred.reject(aResponse);
  2268           return;
  2271         gThreadClient.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
  2272           if (aResponse && aResponse.error) {
  2273             deferred.reject(aResponse);
  2274           } else {
  2275             deferred.resolve(aNewBreakpoint);
  2277         });
  2278       });
  2279     } else {
  2280       // The property shouldn't even exist if the condition is blank
  2281       if(aCondition === "") {
  2282         delete this.conditionalExpression;
  2284       else {
  2285         this.conditionalExpression = aCondition;
  2287       deferred.resolve(this);
  2290     return deferred.promise;
  2292 };
  2294 eventSource(BreakpointClient.prototype);
  2296 /**
  2297  * Environment clients are used to manipulate the lexical environment actors.
  2299  * @param aClient DebuggerClient
  2300  *        The debugger client parent.
  2301  * @param aForm Object
  2302  *        The form sent across the remote debugging protocol.
  2303  */
  2304 function EnvironmentClient(aClient, aForm) {
  2305   this._client = aClient;
  2306   this._form = aForm;
  2307   this.request = this._client.request;
  2310 EnvironmentClient.prototype = {
  2312   get actor() this._form.actor,
  2313   get _transport() { return this._client._transport; },
  2315   /**
  2316    * Fetches the bindings introduced by this lexical environment.
  2317    */
  2318   getBindings: DebuggerClient.requester({
  2319     type: "bindings"
  2320   }, {
  2321     telemetry: "BINDINGS"
  2322   }),
  2324   /**
  2325    * Changes the value of the identifier whose name is name (a string) to that
  2326    * represented by value (a grip).
  2327    */
  2328   assign: DebuggerClient.requester({
  2329     type: "assign",
  2330     name: args(0),
  2331     value: args(1)
  2332   }, {
  2333     telemetry: "ASSIGN"
  2334   })
  2335 };
  2337 eventSource(EnvironmentClient.prototype);
  2339 /**
  2340  * Connects to a debugger server socket and returns a DebuggerTransport.
  2342  * @param aHost string
  2343  *        The host name or IP address of the debugger server.
  2344  * @param aPort number
  2345  *        The port number of the debugger server.
  2346  */
  2347 this.debuggerSocketConnect = function (aHost, aPort)
  2349   let s = socketTransportService.createTransport(null, 0, aHost, aPort, null);
  2350   // By default the CONNECT socket timeout is very long, 65535 seconds,
  2351   // so that if we race to be in CONNECT state while the server socket is still
  2352   // initializing, the connection is stuck in connecting state for 18.20 hours!
  2353   s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
  2355   // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
  2356   // where the nsISocketTransport gets shutdown in between its instantiation and
  2357   // the call to this method.
  2358   let transport;
  2359   try {
  2360     transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
  2361                                       s.openOutputStream(0, 0, 0));
  2362   } catch(e) {
  2363     DevToolsUtils.reportException("debuggerSocketConnect", e);
  2364     throw e;
  2366   return transport;
  2369 /**
  2370  * Takes a pair of items and returns them as an array.
  2371  */
  2372 function pair(aItemOne, aItemTwo) {
  2373   return [aItemOne, aItemTwo];
  2375 function noop() {}

mercurial