toolkit/devtools/server/actors/script.js

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; js-indent-level: 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";
     9 let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
    11 let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
    12       "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
    13       "Float64Array"];
    15 // Number of items to preview in objects, arrays, maps, sets, lists,
    16 // collections, etc.
    17 let OBJECT_PREVIEW_MAX_ITEMS = 10;
    19 let addonManager = null;
    21 /**
    22  * This is a wrapper around amIAddonManager.mapURIToAddonID which always returns
    23  * false on B2G to avoid loading the add-on manager there and reports any
    24  * exceptions rather than throwing so that the caller doesn't have to worry
    25  * about them.
    26  */
    27 function mapURIToAddonID(uri, id) {
    28   if (Services.appinfo.ID == B2G_ID) {
    29     return false;
    30   }
    32   if (!addonManager) {
    33     addonManager = Cc["@mozilla.org/addons/integration;1"].
    34                    getService(Ci.amIAddonManager);
    35   }
    37   try {
    38     return addonManager.mapURIToAddonID(uri, id);
    39   }
    40   catch (e) {
    41     DevtoolsUtils.reportException("mapURIToAddonID", e);
    42     return false;
    43   }
    44 }
    46 /**
    47  * BreakpointStore objects keep track of all breakpoints that get set so that we
    48  * can reset them when the same script is introduced to the thread again (such
    49  * as after a refresh).
    50  */
    51 function BreakpointStore() {
    52   this._size = 0;
    54   // If we have a whole-line breakpoint set at LINE in URL, then
    55   //
    56   //   this._wholeLineBreakpoints[URL][LINE]
    57   //
    58   // is an object
    59   //
    60   //   { url, line[, actor] }
    61   //
    62   // where the `actor` property is optional.
    63   this._wholeLineBreakpoints = Object.create(null);
    65   // If we have a breakpoint set at LINE, COLUMN in URL, then
    66   //
    67   //   this._breakpoints[URL][LINE][COLUMN]
    68   //
    69   // is an object
    70   //
    71   //   { url, line, column[, actor] }
    72   //
    73   // where the `actor` property is optional.
    74   this._breakpoints = Object.create(null);
    75 }
    77 BreakpointStore.prototype = {
    78   _size: null,
    79   get size() { return this._size; },
    81   /**
    82    * Add a breakpoint to the breakpoint store.
    83    *
    84    * @param Object aBreakpoint
    85    *        The breakpoint to be added (not copied). It is an object with the
    86    *        following properties:
    87    *          - url
    88    *          - line
    89    *          - column (optional; omission implies that the breakpoint is for
    90    *            the whole line)
    91    *          - condition (optional)
    92    *          - actor (optional)
    93    */
    94   addBreakpoint: function (aBreakpoint) {
    95     let { url, line, column } = aBreakpoint;
    96     let updating = false;
    98     if (column != null) {
    99       if (!this._breakpoints[url]) {
   100         this._breakpoints[url] = [];
   101       }
   102       if (!this._breakpoints[url][line]) {
   103         this._breakpoints[url][line] = [];
   104       }
   105       this._breakpoints[url][line][column] = aBreakpoint;
   106     } else {
   107       // Add a breakpoint that breaks on the whole line.
   108       if (!this._wholeLineBreakpoints[url]) {
   109         this._wholeLineBreakpoints[url] = [];
   110       }
   111       this._wholeLineBreakpoints[url][line] = aBreakpoint;
   112     }
   114     this._size++;
   115   },
   117   /**
   118    * Remove a breakpoint from the breakpoint store.
   119    *
   120    * @param Object aBreakpoint
   121    *        The breakpoint to be removed. It is an object with the following
   122    *        properties:
   123    *          - url
   124    *          - line
   125    *          - column (optional)
   126    */
   127   removeBreakpoint: function ({ url, line, column }) {
   128     if (column != null) {
   129       if (this._breakpoints[url]) {
   130         if (this._breakpoints[url][line]) {
   131           if (this._breakpoints[url][line][column]) {
   132             delete this._breakpoints[url][line][column];
   133             this._size--;
   135             // If this was the last breakpoint on this line, delete the line from
   136             // `this._breakpoints[url]` as well. Otherwise `_iterLines` will yield
   137             // this line even though we no longer have breakpoints on
   138             // it. Furthermore, we use Object.keys() instead of just checking
   139             // `this._breakpoints[url].length` directly, because deleting
   140             // properties from sparse arrays doesn't update the `length` property
   141             // like adding them does.
   142             if (Object.keys(this._breakpoints[url][line]).length === 0) {
   143               delete this._breakpoints[url][line];
   144             }
   145           }
   146         }
   147       }
   148     } else {
   149       if (this._wholeLineBreakpoints[url]) {
   150         if (this._wholeLineBreakpoints[url][line]) {
   151           delete this._wholeLineBreakpoints[url][line];
   152           this._size--;
   153         }
   154       }
   155     }
   156   },
   158   /**
   159    * Get a breakpoint from the breakpoint store. Will throw an error if the
   160    * breakpoint is not found.
   161    *
   162    * @param Object aLocation
   163    *        The location of the breakpoint you are retrieving. It is an object
   164    *        with the following properties:
   165    *          - url
   166    *          - line
   167    *          - column (optional)
   168    */
   169   getBreakpoint: function (aLocation) {
   170     let { url, line, column } = aLocation;
   171     dbg_assert(url != null);
   172     dbg_assert(line != null);
   174     var foundBreakpoint = this.hasBreakpoint(aLocation);
   175     if (foundBreakpoint == null) {
   176       throw new Error("No breakpoint at url = " + url
   177           + ", line = " + line
   178           + ", column = " + column);
   179     }
   181     return foundBreakpoint;
   182   },
   184   /**
   185    * Checks if the breakpoint store has a requested breakpoint.
   186    *
   187    * @param Object aLocation
   188    *        The location of the breakpoint you are retrieving. It is an object
   189    *        with the following properties:
   190    *          - url
   191    *          - line
   192    *          - column (optional)
   193    * @returns The stored breakpoint if it exists, null otherwise.
   194    */
   195   hasBreakpoint: function (aLocation) {
   196     let { url, line, column } = aLocation;
   197     dbg_assert(url != null);
   198     dbg_assert(line != null);
   199     for (let bp of this.findBreakpoints(aLocation)) {
   200       // We will get whole line breakpoints before individual columns, so just
   201       // return the first one and if they didn't specify a column then they will
   202       // get the whole line breakpoint, and otherwise we will find the correct
   203       // one.
   204       return bp;
   205     }
   207     return null;
   208   },
   210   /**
   211    * Iterate over the breakpoints in this breakpoint store. You can optionally
   212    * provide search parameters to filter the set of breakpoints down to those
   213    * that match your parameters.
   214    *
   215    * @param Object aSearchParams
   216    *        Optional. An object with the following properties:
   217    *          - url
   218    *          - line (optional; requires the url property)
   219    *          - column (optional; requires the line property)
   220    */
   221   findBreakpoints: function* (aSearchParams={}) {
   222     if (aSearchParams.column != null) {
   223       dbg_assert(aSearchParams.line != null);
   224     }
   225     if (aSearchParams.line != null) {
   226       dbg_assert(aSearchParams.url != null);
   227     }
   229     for (let url of this._iterUrls(aSearchParams.url)) {
   230       for (let line of this._iterLines(url, aSearchParams.line)) {
   231         // Always yield whole line breakpoints first. See comment in
   232         // |BreakpointStore.prototype.hasBreakpoint|.
   233         if (aSearchParams.column == null
   234             && this._wholeLineBreakpoints[url]
   235             && this._wholeLineBreakpoints[url][line]) {
   236           yield this._wholeLineBreakpoints[url][line];
   237         }
   238         for (let column of this._iterColumns(url, line, aSearchParams.column)) {
   239           yield this._breakpoints[url][line][column];
   240         }
   241       }
   242     }
   243   },
   245   _iterUrls: function* (aUrl) {
   246     if (aUrl) {
   247       if (this._breakpoints[aUrl] || this._wholeLineBreakpoints[aUrl]) {
   248         yield aUrl;
   249       }
   250     } else {
   251       for (let url of Object.keys(this._wholeLineBreakpoints)) {
   252         yield url;
   253       }
   254       for (let url of Object.keys(this._breakpoints)) {
   255         if (url in this._wholeLineBreakpoints) {
   256           continue;
   257         }
   258         yield url;
   259       }
   260     }
   261   },
   263   _iterLines: function* (aUrl, aLine) {
   264     if (aLine != null) {
   265       if ((this._wholeLineBreakpoints[aUrl]
   266            && this._wholeLineBreakpoints[aUrl][aLine])
   267           || (this._breakpoints[aUrl] && this._breakpoints[aUrl][aLine])) {
   268         yield aLine;
   269       }
   270     } else {
   271       const wholeLines = this._wholeLineBreakpoints[aUrl]
   272         ? Object.keys(this._wholeLineBreakpoints[aUrl])
   273         : [];
   274       const columnLines = this._breakpoints[aUrl]
   275         ? Object.keys(this._breakpoints[aUrl])
   276         : [];
   278       const lines = wholeLines.concat(columnLines).sort();
   280       let lastLine;
   281       for (let line of lines) {
   282         if (line === lastLine) {
   283           continue;
   284         }
   285         yield line;
   286         lastLine = line;
   287       }
   288     }
   289   },
   291   _iterColumns: function* (aUrl, aLine, aColumn) {
   292     if (!this._breakpoints[aUrl] || !this._breakpoints[aUrl][aLine]) {
   293       return;
   294     }
   296     if (aColumn != null) {
   297       if (this._breakpoints[aUrl][aLine][aColumn]) {
   298         yield aColumn;
   299       }
   300     } else {
   301       for (let column in this._breakpoints[aUrl][aLine]) {
   302         yield column;
   303       }
   304     }
   305   },
   306 };
   308 /**
   309  * Manages pushing event loops and automatically pops and exits them in the
   310  * correct order as they are resolved.
   311  *
   312  * @param nsIJSInspector inspector
   313  *        The underlying JS inspector we use to enter and exit nested event
   314  *        loops.
   315  * @param ThreadActor thread
   316  *        The thread actor instance that owns this EventLoopStack.
   317  * @param DebuggerServerConnection connection
   318  *        The remote protocol connection associated with this event loop stack.
   319  * @param Object hooks
   320  *        An object with the following properties:
   321  *          - url: The URL string of the debuggee we are spinning an event loop
   322  *                 for.
   323  *          - preNest: function called before entering a nested event loop
   324  *          - postNest: function called after exiting a nested event loop
   325  */
   326 function EventLoopStack({ inspector, thread, connection, hooks }) {
   327   this._inspector = inspector;
   328   this._hooks = hooks;
   329   this._thread = thread;
   330   this._connection = connection;
   331 }
   333 EventLoopStack.prototype = {
   334   /**
   335    * The number of nested event loops on the stack.
   336    */
   337   get size() {
   338     return this._inspector.eventLoopNestLevel;
   339   },
   341   /**
   342    * The URL of the debuggee who pushed the event loop on top of the stack.
   343    */
   344   get lastPausedUrl() {
   345     let url = null;
   346     if (this.size > 0) {
   347       try {
   348         url = this._inspector.lastNestRequestor.url
   349       } catch (e) {
   350         // The tab's URL getter may throw if the tab is destroyed by the time
   351         // this code runs, but we don't really care at this point.
   352         dumpn(e);
   353       }
   354     }
   355     return url;
   356   },
   358   /**
   359    * The DebuggerServerConnection of the debugger who pushed the event loop on
   360    * top of the stack
   361    */
   362   get lastConnection() {
   363     return this._inspector.lastNestRequestor._connection;
   364   },
   366   /**
   367    * Push a new nested event loop onto the stack.
   368    *
   369    * @returns EventLoop
   370    */
   371   push: function () {
   372     return new EventLoop({
   373       inspector: this._inspector,
   374       thread: this._thread,
   375       connection: this._connection,
   376       hooks: this._hooks
   377     });
   378   }
   379 };
   381 /**
   382  * An object that represents a nested event loop. It is used as the nest
   383  * requestor with nsIJSInspector instances.
   384  *
   385  * @param nsIJSInspector inspector
   386  *        The JS Inspector that runs nested event loops.
   387  * @param ThreadActor thread
   388  *        The thread actor that is creating this nested event loop.
   389  * @param DebuggerServerConnection connection
   390  *        The remote protocol connection associated with this event loop.
   391  * @param Object hooks
   392  *        The same hooks object passed into EventLoopStack during its
   393  *        initialization.
   394  */
   395 function EventLoop({ inspector, thread, connection, hooks }) {
   396   this._inspector = inspector;
   397   this._thread = thread;
   398   this._hooks = hooks;
   399   this._connection = connection;
   401   this.enter = this.enter.bind(this);
   402   this.resolve = this.resolve.bind(this);
   403 }
   405 EventLoop.prototype = {
   406   entered: false,
   407   resolved: false,
   408   get url() { return this._hooks.url; },
   410   /**
   411    * Enter this nested event loop.
   412    */
   413   enter: function () {
   414     let nestData = this._hooks.preNest
   415       ? this._hooks.preNest()
   416       : null;
   418     this.entered = true;
   419     this._inspector.enterNestedEventLoop(this);
   421     // Keep exiting nested event loops while the last requestor is resolved.
   422     if (this._inspector.eventLoopNestLevel > 0) {
   423       const { resolved } = this._inspector.lastNestRequestor;
   424       if (resolved) {
   425         this._inspector.exitNestedEventLoop();
   426       }
   427     }
   429     dbg_assert(this._thread.state === "running",
   430                "Should be in the running state");
   432     if (this._hooks.postNest) {
   433       this._hooks.postNest(nestData);
   434     }
   435   },
   437   /**
   438    * Resolve this nested event loop.
   439    *
   440    * @returns boolean
   441    *          True if we exited this nested event loop because it was on top of
   442    *          the stack, false if there is another nested event loop above this
   443    *          one that hasn't resolved yet.
   444    */
   445   resolve: function () {
   446     if (!this.entered) {
   447       throw new Error("Can't resolve an event loop before it has been entered!");
   448     }
   449     if (this.resolved) {
   450       throw new Error("Already resolved this nested event loop!");
   451     }
   452     this.resolved = true;
   453     if (this === this._inspector.lastNestRequestor) {
   454       this._inspector.exitNestedEventLoop();
   455       return true;
   456     }
   457     return false;
   458   },
   459 };
   461 /**
   462  * JSD2 actors.
   463  */
   464 /**
   465  * Creates a ThreadActor.
   466  *
   467  * ThreadActors manage a JSInspector object and manage execution/inspection
   468  * of debuggees.
   469  *
   470  * @param aHooks object
   471  *        An object with preNest and postNest methods for calling when entering
   472  *        and exiting a nested event loop.
   473  * @param aGlobal object [optional]
   474  *        An optional (for content debugging only) reference to the content
   475  *        window.
   476  */
   477 function ThreadActor(aHooks, aGlobal)
   478 {
   479   this._state = "detached";
   480   this._frameActors = [];
   481   this._hooks = aHooks;
   482   this.global = aGlobal;
   483   // A map of actorID -> actor for breakpoints created and managed by the server.
   484   this._hiddenBreakpoints = new Map();
   486   this.findGlobals = this.globalManager.findGlobals.bind(this);
   487   this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
   488   this.onNewSource = this.onNewSource.bind(this);
   489   this._allEventsListener = this._allEventsListener.bind(this);
   491   this._options = {
   492     useSourceMaps: false
   493   };
   495   this._gripDepth = 0;
   496 }
   498 /**
   499  * The breakpoint store must be shared across instances of ThreadActor so that
   500  * page reloads don't blow away all of our breakpoints.
   501  */
   502 ThreadActor.breakpointStore = new BreakpointStore();
   504 ThreadActor.prototype = {
   505   // Used by the ObjectActor to keep track of the depth of grip() calls.
   506   _gripDepth: null,
   508   actorPrefix: "context",
   510   get state() { return this._state; },
   511   get attached() this.state == "attached" ||
   512                  this.state == "running" ||
   513                  this.state == "paused",
   515   get breakpointStore() { return ThreadActor.breakpointStore; },
   517   get threadLifetimePool() {
   518     if (!this._threadLifetimePool) {
   519       this._threadLifetimePool = new ActorPool(this.conn);
   520       this.conn.addActorPool(this._threadLifetimePool);
   521       this._threadLifetimePool.objectActors = new WeakMap();
   522     }
   523     return this._threadLifetimePool;
   524   },
   526   get sources() {
   527     if (!this._sources) {
   528       this._sources = new ThreadSources(this, this._options.useSourceMaps,
   529                                         this._allowSource, this.onNewSource);
   530     }
   531     return this._sources;
   532   },
   534   get youngestFrame() {
   535     if (this.state != "paused") {
   536       return null;
   537     }
   538     return this.dbg.getNewestFrame();
   539   },
   541   _prettyPrintWorker: null,
   542   get prettyPrintWorker() {
   543     if (!this._prettyPrintWorker) {
   544       this._prettyPrintWorker = new ChromeWorker(
   545         "resource://gre/modules/devtools/server/actors/pretty-print-worker.js");
   547       this._prettyPrintWorker.addEventListener(
   548         "error", this._onPrettyPrintError, false);
   550       if (dumpn.wantLogging) {
   551         this._prettyPrintWorker.addEventListener("message", this._onPrettyPrintMsg, false);
   553         const postMsg = this._prettyPrintWorker.postMessage;
   554         this._prettyPrintWorker.postMessage = data => {
   555           dumpn("Sending message to prettyPrintWorker: "
   556                 + JSON.stringify(data, null, 2) + "\n");
   557           return postMsg.call(this._prettyPrintWorker, data);
   558         };
   559       }
   560     }
   561     return this._prettyPrintWorker;
   562   },
   564   _onPrettyPrintError: function ({ message, filename, lineno }) {
   565     reportError(new Error(message + " @ " + filename + ":" + lineno));
   566   },
   568   _onPrettyPrintMsg: function ({ data }) {
   569     dumpn("Received message from prettyPrintWorker: "
   570           + JSON.stringify(data, null, 2) + "\n");
   571   },
   573   /**
   574    * Keep track of all of the nested event loops we use to pause the debuggee
   575    * when we hit a breakpoint/debugger statement/etc in one place so we can
   576    * resolve them when we get resume packets. We have more than one (and keep
   577    * them in a stack) because we can pause within client evals.
   578    */
   579   _threadPauseEventLoops: null,
   580   _pushThreadPause: function () {
   581     if (!this._threadPauseEventLoops) {
   582       this._threadPauseEventLoops = [];
   583     }
   584     const eventLoop = this._nestedEventLoops.push();
   585     this._threadPauseEventLoops.push(eventLoop);
   586     eventLoop.enter();
   587   },
   588   _popThreadPause: function () {
   589     const eventLoop = this._threadPauseEventLoops.pop();
   590     dbg_assert(eventLoop, "Should have an event loop.");
   591     eventLoop.resolve();
   592   },
   594   /**
   595    * Remove all debuggees and clear out the thread's sources.
   596    */
   597   clearDebuggees: function () {
   598     if (this.dbg) {
   599       this.dbg.removeAllDebuggees();
   600     }
   601     this._sources = null;
   602   },
   604   /**
   605    * Add a debuggee global to the Debugger object.
   606    *
   607    * @returns the Debugger.Object that corresponds to the global.
   608    */
   609   addDebuggee: function (aGlobal) {
   610     let globalDebugObject;
   611     try {
   612       globalDebugObject = this.dbg.addDebuggee(aGlobal);
   613     } catch (e) {
   614       // Ignore attempts to add the debugger's compartment as a debuggee.
   615       dumpn("Ignoring request to add the debugger's compartment as a debuggee");
   616     }
   617     return globalDebugObject;
   618   },
   620   /**
   621    * Initialize the Debugger.
   622    */
   623   _initDebugger: function () {
   624     this.dbg = new Debugger();
   625     this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
   626     this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
   627     this.dbg.onNewScript = this.onNewScript.bind(this);
   628     this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
   629     // Keep the debugger disabled until a client attaches.
   630     this.dbg.enabled = this._state != "detached";
   631   },
   633   /**
   634    * Remove a debuggee global from the JSInspector.
   635    */
   636   removeDebugee: function (aGlobal) {
   637     try {
   638       this.dbg.removeDebuggee(aGlobal);
   639     } catch(ex) {
   640       // XXX: This debuggee has code currently executing on the stack,
   641       // we need to save this for later.
   642     }
   643   },
   645   /**
   646    * Add the provided window and all windows in its frame tree as debuggees.
   647    *
   648    * @returns the Debugger.Object that corresponds to the window.
   649    */
   650   _addDebuggees: function (aWindow) {
   651     let globalDebugObject = this.addDebuggee(aWindow);
   652     let frames = aWindow.frames;
   653     if (frames) {
   654       for (let i = 0; i < frames.length; i++) {
   655         this._addDebuggees(frames[i]);
   656       }
   657     }
   658     return globalDebugObject;
   659   },
   661   /**
   662    * An object that will be used by ThreadActors to tailor their behavior
   663    * depending on the debugging context being required (chrome or content).
   664    */
   665   globalManager: {
   666     findGlobals: function () {
   667       const { gDevToolsExtensions: {
   668         getContentGlobals
   669       } } = Cu.import("resource://gre/modules/devtools/DevToolsExtensions.jsm", {});
   671       this.globalDebugObject = this._addDebuggees(this.global);
   673       // global may not be a window
   674       try {
   675         getContentGlobals({
   676           'inner-window-id': getInnerId(this.global)
   677         }).forEach(this.addDebuggee.bind(this));
   678       }
   679       catch(e) {}
   680     },
   682     /**
   683      * A function that the engine calls when a new global object
   684      * (for example a sandbox) has been created.
   685      *
   686      * @param aGlobal Debugger.Object
   687      *        The new global object that was created.
   688      */
   689     onNewGlobal: function (aGlobal) {
   690       let useGlobal = (aGlobal.hostAnnotations &&
   691                        aGlobal.hostAnnotations.type == "document" &&
   692                        aGlobal.hostAnnotations.element === this.global);
   694       // check if the global is a sdk page-mod sandbox
   695       if (!useGlobal) {
   696         let metadata = {};
   697         let id = "";
   698         try {
   699           id = getInnerId(this.global);
   700           metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
   701         }
   702         catch (e) {}
   704         useGlobal = (metadata['inner-window-id'] && metadata['inner-window-id'] == id);
   705       }
   707       // Content debugging only cares about new globals in the contant window,
   708       // like iframe children.
   709       if (useGlobal) {
   710         this.addDebuggee(aGlobal);
   711         // Notify the client.
   712         this.conn.send({
   713           from: this.actorID,
   714           type: "newGlobal",
   715           // TODO: after bug 801084 lands see if we need to JSONify this.
   716           hostAnnotations: aGlobal.hostAnnotations
   717         });
   718       }
   719     }
   720   },
   722   disconnect: function () {
   723     dumpn("in ThreadActor.prototype.disconnect");
   724     if (this._state == "paused") {
   725       this.onResume();
   726     }
   728     this.clearDebuggees();
   729     this.conn.removeActorPool(this._threadLifetimePool);
   730     this._threadLifetimePool = null;
   732     if (this._prettyPrintWorker) {
   733       this._prettyPrintWorker.removeEventListener(
   734         "error", this._onPrettyPrintError, false);
   735       this._prettyPrintWorker.removeEventListener(
   736         "message", this._onPrettyPrintMsg, false);
   737       this._prettyPrintWorker.terminate();
   738       this._prettyPrintWorker = null;
   739     }
   741     if (!this.dbg) {
   742       return;
   743     }
   744     this.dbg.enabled = false;
   745     this.dbg = null;
   746   },
   748   /**
   749    * Disconnect the debugger and put the actor in the exited state.
   750    */
   751   exit: function () {
   752     this.disconnect();
   753     this._state = "exited";
   754   },
   756   // Request handlers
   757   onAttach: function (aRequest) {
   758     if (this.state === "exited") {
   759       return { type: "exited" };
   760     }
   762     if (this.state !== "detached") {
   763       return { error: "wrongState",
   764                message: "Current state is " + this.state };
   765     }
   767     this._state = "attached";
   769     update(this._options, aRequest.options || {});
   771     // Initialize an event loop stack. This can't be done in the constructor,
   772     // because this.conn is not yet initialized by the actor pool at that time.
   773     this._nestedEventLoops = new EventLoopStack({
   774       inspector: DebuggerServer.xpcInspector,
   775       hooks: this._hooks,
   776       connection: this.conn,
   777       thread: this
   778     });
   780     if (!this.dbg) {
   781       this._initDebugger();
   782     }
   783     this.findGlobals();
   784     this.dbg.enabled = true;
   785     try {
   786       // Put ourselves in the paused state.
   787       let packet = this._paused();
   788       if (!packet) {
   789         return { error: "notAttached" };
   790       }
   791       packet.why = { type: "attached" };
   793       this._restoreBreakpoints();
   795       // Send the response to the attach request now (rather than
   796       // returning it), because we're going to start a nested event loop
   797       // here.
   798       this.conn.send(packet);
   800       // Start a nested event loop.
   801       this._pushThreadPause();
   803       // We already sent a response to this request, don't send one
   804       // now.
   805       return null;
   806     } catch (e) {
   807       reportError(e);
   808       return { error: "notAttached", message: e.toString() };
   809     }
   810   },
   812   onDetach: function (aRequest) {
   813     this.disconnect();
   814     this._state = "detached";
   816     dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet");
   817     return {
   818       type: "detached"
   819     };
   820   },
   822   onReconfigure: function (aRequest) {
   823     if (this.state == "exited") {
   824       return { error: "wrongState" };
   825     }
   827     update(this._options, aRequest.options || {});
   828     // Clear existing sources, so they can be recreated on next access.
   829     this._sources = null;
   831     return {};
   832   },
   834   /**
   835    * Pause the debuggee, by entering a nested event loop, and return a 'paused'
   836    * packet to the client.
   837    *
   838    * @param Debugger.Frame aFrame
   839    *        The newest debuggee frame in the stack.
   840    * @param object aReason
   841    *        An object with a 'type' property containing the reason for the pause.
   842    * @param function onPacket
   843    *        Hook to modify the packet before it is sent. Feel free to return a
   844    *        promise.
   845    */
   846   _pauseAndRespond: function (aFrame, aReason, onPacket=function (k) { return k; }) {
   847     try {
   848       let packet = this._paused(aFrame);
   849       if (!packet) {
   850         return undefined;
   851       }
   852       packet.why = aReason;
   854       this.sources.getOriginalLocation(packet.frame.where).then(aOrigPosition => {
   855         packet.frame.where = aOrigPosition;
   856         resolve(onPacket(packet))
   857           .then(null, error => {
   858             reportError(error);
   859             return {
   860               error: "unknownError",
   861               message: error.message + "\n" + error.stack
   862             };
   863           })
   864           .then(packet => {
   865             this.conn.send(packet);
   866           });
   867       });
   869       this._pushThreadPause();
   870     } catch(e) {
   871       reportError(e, "Got an exception during TA__pauseAndRespond: ");
   872     }
   874     return undefined;
   875   },
   877   /**
   878    * Handle resume requests that include a forceCompletion request.
   879    *
   880    * @param Object aRequest
   881    *        The request packet received over the RDP.
   882    * @returns A response packet.
   883    */
   884   _forceCompletion: function (aRequest) {
   885     // TODO: remove this when Debugger.Frame.prototype.pop is implemented in
   886     // bug 736733.
   887     return {
   888       error: "notImplemented",
   889       message: "forced completion is not yet implemented."
   890     };
   891   },
   893   _makeOnEnterFrame: function ({ pauseAndRespond }) {
   894     return aFrame => {
   895       const generatedLocation = getFrameLocation(aFrame);
   896       let { url } = this.synchronize(this.sources.getOriginalLocation(
   897         generatedLocation));
   899       return this.sources.isBlackBoxed(url)
   900         ? undefined
   901         : pauseAndRespond(aFrame);
   902     };
   903   },
   905   _makeOnPop: function ({ thread, pauseAndRespond, createValueGrip }) {
   906     return function (aCompletion) {
   907       // onPop is called with 'this' set to the current frame.
   909       const generatedLocation = getFrameLocation(this);
   910       const { url } = thread.synchronize(thread.sources.getOriginalLocation(
   911         generatedLocation));
   913       if (thread.sources.isBlackBoxed(url)) {
   914         return undefined;
   915       }
   917       // Note that we're popping this frame; we need to watch for
   918       // subsequent step events on its caller.
   919       this.reportedPop = true;
   921       return pauseAndRespond(this, aPacket => {
   922         aPacket.why.frameFinished = {};
   923         if (!aCompletion) {
   924           aPacket.why.frameFinished.terminated = true;
   925         } else if (aCompletion.hasOwnProperty("return")) {
   926           aPacket.why.frameFinished.return = createValueGrip(aCompletion.return);
   927         } else if (aCompletion.hasOwnProperty("yield")) {
   928           aPacket.why.frameFinished.return = createValueGrip(aCompletion.yield);
   929         } else {
   930           aPacket.why.frameFinished.throw = createValueGrip(aCompletion.throw);
   931         }
   932         return aPacket;
   933       });
   934     };
   935   },
   937   _makeOnStep: function ({ thread, pauseAndRespond, startFrame,
   938                            startLocation }) {
   939     return function () {
   940       // onStep is called with 'this' set to the current frame.
   942       const generatedLocation = getFrameLocation(this);
   943       const newLocation = thread.synchronize(thread.sources.getOriginalLocation(
   944         generatedLocation));
   946       // Cases when we should pause because we have executed enough to consider
   947       // a "step" to have occured:
   948       //
   949       // 1.1. We change frames.
   950       // 1.2. We change URLs (can happen without changing frames thanks to
   951       //      source mapping).
   952       // 1.3. We change lines.
   953       //
   954       // Cases when we should always continue execution, even if one of the
   955       // above cases is true:
   956       //
   957       // 2.1. We are in a source mapped region, but inside a null mapping
   958       //      (doesn't correlate to any region of original source)
   959       // 2.2. The source we are in is black boxed.
   961       // Cases 2.1 and 2.2
   962       if (newLocation.url == null
   963           || thread.sources.isBlackBoxed(newLocation.url)) {
   964         return undefined;
   965       }
   967       // Cases 1.1, 1.2 and 1.3
   968       if (this !== startFrame
   969           || startLocation.url !== newLocation.url
   970           || startLocation.line !== newLocation.line) {
   971         return pauseAndRespond(this);
   972       }
   974       // Otherwise, let execution continue (we haven't executed enough code to
   975       // consider this a "step" yet).
   976       return undefined;
   977     };
   978   },
   980   /**
   981    * Define the JS hook functions for stepping.
   982    */
   983   _makeSteppingHooks: function (aStartLocation) {
   984     // Bind these methods and state because some of the hooks are called
   985     // with 'this' set to the current frame. Rather than repeating the
   986     // binding in each _makeOnX method, just do it once here and pass it
   987     // in to each function.
   988     const steppingHookState = {
   989       pauseAndRespond: (aFrame, onPacket=(k)=>k) => {
   990         this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket);
   991       },
   992       createValueGrip: this.createValueGrip.bind(this),
   993       thread: this,
   994       startFrame: this.youngestFrame,
   995       startLocation: aStartLocation
   996     };
   998     return {
   999       onEnterFrame: this._makeOnEnterFrame(steppingHookState),
  1000       onPop: this._makeOnPop(steppingHookState),
  1001       onStep: this._makeOnStep(steppingHookState)
  1002     };
  1003   },
  1005   /**
  1006    * Handle attaching the various stepping hooks we need to attach when we
  1007    * receive a resume request with a resumeLimit property.
  1009    * @param Object aRequest
  1010    *        The request packet received over the RDP.
  1011    * @returns A promise that resolves to true once the hooks are attached, or is
  1012    *          rejected with an error packet.
  1013    */
  1014   _handleResumeLimit: function (aRequest) {
  1015     let steppingType = aRequest.resumeLimit.type;
  1016     if (["step", "next", "finish"].indexOf(steppingType) == -1) {
  1017       return reject({ error: "badParameterType",
  1018                       message: "Unknown resumeLimit type" });
  1021     const generatedLocation = getFrameLocation(this.youngestFrame);
  1022     return this.sources.getOriginalLocation(generatedLocation)
  1023       .then(originalLocation => {
  1024         const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation);
  1026         // Make sure there is still a frame on the stack if we are to continue
  1027         // stepping.
  1028         let stepFrame = this._getNextStepFrame(this.youngestFrame);
  1029         if (stepFrame) {
  1030           switch (steppingType) {
  1031             case "step":
  1032               this.dbg.onEnterFrame = onEnterFrame;
  1033               // Fall through.
  1034             case "next":
  1035               if (stepFrame.script) {
  1036                   stepFrame.onStep = onStep;
  1038               stepFrame.onPop = onPop;
  1039               break;
  1040             case "finish":
  1041               stepFrame.onPop = onPop;
  1045         return true;
  1046       });
  1047   },
  1049   /**
  1050    * Clear the onStep and onPop hooks from the given frame and all of the frames
  1051    * below it.
  1053    * @param Debugger.Frame aFrame
  1054    *        The frame we want to clear the stepping hooks from.
  1055    */
  1056   _clearSteppingHooks: function (aFrame) {
  1057     while (aFrame) {
  1058       aFrame.onStep = undefined;
  1059       aFrame.onPop = undefined;
  1060       aFrame = aFrame.older;
  1062   },
  1064   /**
  1065    * Listen to the debuggee's DOM events if we received a request to do so.
  1067    * @param Object aRequest
  1068    *        The resume request packet received over the RDP.
  1069    */
  1070   _maybeListenToEvents: function (aRequest) {
  1071     // Break-on-DOMEvents is only supported in content debugging.
  1072     let events = aRequest.pauseOnDOMEvents;
  1073     if (this.global && events &&
  1074         (events == "*" ||
  1075         (Array.isArray(events) && events.length))) {
  1076       this._pauseOnDOMEvents = events;
  1077       let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1078                 .getService(Ci.nsIEventListenerService);
  1079       els.addListenerForAllEvents(this.global, this._allEventsListener, true);
  1081   },
  1083   /**
  1084    * Handle a protocol request to resume execution of the debuggee.
  1085    */
  1086   onResume: function (aRequest) {
  1087     if (this._state !== "paused") {
  1088       return {
  1089         error: "wrongState",
  1090         message: "Can't resume when debuggee isn't paused. Current state is '"
  1091           + this._state + "'"
  1092       };
  1095     // In case of multiple nested event loops (due to multiple debuggers open in
  1096     // different tabs or multiple debugger clients connected to the same tab)
  1097     // only allow resumption in a LIFO order.
  1098     if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl
  1099         && (this._nestedEventLoops.lastPausedUrl !== this._hooks.url
  1100         || this._nestedEventLoops.lastConnection !== this.conn)) {
  1101       return {
  1102         error: "wrongOrder",
  1103         message: "trying to resume in the wrong order.",
  1104         lastPausedUrl: this._nestedEventLoops.lastPausedUrl
  1105       };
  1108     if (aRequest && aRequest.forceCompletion) {
  1109       return this._forceCompletion(aRequest);
  1112     let resumeLimitHandled;
  1113     if (aRequest && aRequest.resumeLimit) {
  1114       resumeLimitHandled = this._handleResumeLimit(aRequest)
  1115     } else {
  1116       this._clearSteppingHooks(this.youngestFrame);
  1117       resumeLimitHandled = resolve(true);
  1120     return resumeLimitHandled.then(() => {
  1121       if (aRequest) {
  1122         this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
  1123         this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
  1124         this.maybePauseOnExceptions();
  1125         this._maybeListenToEvents(aRequest);
  1128       let packet = this._resumed();
  1129       this._popThreadPause();
  1130       return packet;
  1131     }, error => {
  1132       return error instanceof Error
  1133         ? { error: "unknownError",
  1134             message: DevToolsUtils.safeErrorString(error) }
  1135         // It is a known error, and the promise was rejected with an error
  1136         // packet.
  1137         : error;
  1138     });
  1139   },
  1141   /**
  1142    * Spin up a nested event loop so we can synchronously resolve a promise.
  1144    * @param aPromise
  1145    *        The promise we want to resolve.
  1146    * @returns The promise's resolution.
  1147    */
  1148   synchronize: function(aPromise) {
  1149     let needNest = true;
  1150     let eventLoop;
  1151     let returnVal;
  1153     aPromise
  1154       .then((aResolvedVal) => {
  1155         needNest = false;
  1156         returnVal = aResolvedVal;
  1157       })
  1158       .then(null, (aError) => {
  1159         reportError(aError, "Error inside synchronize:");
  1160       })
  1161       .then(() => {
  1162         if (eventLoop) {
  1163           eventLoop.resolve();
  1165       });
  1167     if (needNest) {
  1168       eventLoop = this._nestedEventLoops.push();
  1169       eventLoop.enter();
  1172     return returnVal;
  1173   },
  1175   /**
  1176    * Set the debugging hook to pause on exceptions if configured to do so.
  1177    */
  1178   maybePauseOnExceptions: function() {
  1179     if (this._options.pauseOnExceptions) {
  1180       this.dbg.onExceptionUnwind = this.onExceptionUnwind.bind(this);
  1182   },
  1184   /**
  1185    * A listener that gets called for every event fired on the page, when a list
  1186    * of interesting events was provided with the pauseOnDOMEvents property. It
  1187    * is used to set server-managed breakpoints on any existing event listeners
  1188    * for those events.
  1190    * @param Event event
  1191    *        The event that was fired.
  1192    */
  1193   _allEventsListener: function(event) {
  1194     if (this._pauseOnDOMEvents == "*" ||
  1195         this._pauseOnDOMEvents.indexOf(event.type) != -1) {
  1196       for (let listener of this._getAllEventListeners(event.target)) {
  1197         if (event.type == listener.type || this._pauseOnDOMEvents == "*") {
  1198           this._breakOnEnter(listener.script);
  1202   },
  1204   /**
  1205    * Return an array containing all the event listeners attached to the
  1206    * specified event target and its ancestors in the event target chain.
  1208    * @param EventTarget eventTarget
  1209    *        The target the event was dispatched on.
  1210    * @returns Array
  1211    */
  1212   _getAllEventListeners: function(eventTarget) {
  1213     let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1214                 .getService(Ci.nsIEventListenerService);
  1216     let targets = els.getEventTargetChainFor(eventTarget);
  1217     let listeners = [];
  1219     for (let target of targets) {
  1220       let handlers = els.getListenerInfoFor(target);
  1221       for (let handler of handlers) {
  1222         // Null is returned for all-events handlers, and native event listeners
  1223         // don't provide any listenerObject, which makes them not that useful to
  1224         // a JS debugger.
  1225         if (!handler || !handler.listenerObject || !handler.type)
  1226           continue;
  1227         // Create a listener-like object suitable for our purposes.
  1228         let l = Object.create(null);
  1229         l.type = handler.type;
  1230         let listener = handler.listenerObject;
  1231         l.script = this.globalDebugObject.makeDebuggeeValue(listener).script;
  1232         // Chrome listeners won't be converted to debuggee values, since their
  1233         // compartment is not added as a debuggee.
  1234         if (!l.script)
  1235           continue;
  1236         listeners.push(l);
  1239     return listeners;
  1240   },
  1242   /**
  1243    * Set a breakpoint on the first bytecode offset in the provided script.
  1244    */
  1245   _breakOnEnter: function(script) {
  1246     let offsets = script.getAllOffsets();
  1247     for (let line = 0, n = offsets.length; line < n; line++) {
  1248       if (offsets[line]) {
  1249         let location = { url: script.url, line: line };
  1250         let resp = this._createAndStoreBreakpoint(location);
  1251         dbg_assert(!resp.actualLocation, "No actualLocation should be returned");
  1252         if (resp.error) {
  1253           reportError(new Error("Unable to set breakpoint on event listener"));
  1254           return;
  1256         let bp = this.breakpointStore.getBreakpoint(location);
  1257         let bpActor = bp.actor;
  1258         dbg_assert(bp, "Breakpoint must exist");
  1259         dbg_assert(bpActor, "Breakpoint actor must be created");
  1260         this._hiddenBreakpoints.set(bpActor.actorID, bpActor);
  1261         break;
  1264   },
  1266   /**
  1267    * Helper method that returns the next frame when stepping.
  1268    */
  1269   _getNextStepFrame: function (aFrame) {
  1270     let stepFrame = aFrame.reportedPop ? aFrame.older : aFrame;
  1271     if (!stepFrame || !stepFrame.script) {
  1272       stepFrame = null;
  1274     return stepFrame;
  1275   },
  1277   onClientEvaluate: function (aRequest) {
  1278     if (this.state !== "paused") {
  1279       return { error: "wrongState",
  1280                message: "Debuggee must be paused to evaluate code." };
  1283     let frame = this._requestFrame(aRequest.frame);
  1284     if (!frame) {
  1285       return { error: "unknownFrame",
  1286                message: "Evaluation frame not found" };
  1289     if (!frame.environment) {
  1290       return { error: "notDebuggee",
  1291                message: "cannot access the environment of this frame." };
  1294     let youngest = this.youngestFrame;
  1296     // Put ourselves back in the running state and inform the client.
  1297     let resumedPacket = this._resumed();
  1298     this.conn.send(resumedPacket);
  1300     // Run the expression.
  1301     // XXX: test syntax errors
  1302     let completion = frame.eval(aRequest.expression);
  1304     // Put ourselves back in the pause state.
  1305     let packet = this._paused(youngest);
  1306     packet.why = { type: "clientEvaluated",
  1307                    frameFinished: this.createProtocolCompletionValue(completion) };
  1309     // Return back to our previous pause's event loop.
  1310     return packet;
  1311   },
  1313   onFrames: function (aRequest) {
  1314     if (this.state !== "paused") {
  1315       return { error: "wrongState",
  1316                message: "Stack frames are only available while the debuggee is paused."};
  1319     let start = aRequest.start ? aRequest.start : 0;
  1320     let count = aRequest.count;
  1322     // Find the starting frame...
  1323     let frame = this.youngestFrame;
  1324     let i = 0;
  1325     while (frame && (i < start)) {
  1326       frame = frame.older;
  1327       i++;
  1330     // Return request.count frames, or all remaining
  1331     // frames if count is not defined.
  1332     let frames = [];
  1333     let promises = [];
  1334     for (; frame && (!count || i < (start + count)); i++, frame=frame.older) {
  1335       let form = this._createFrameActor(frame).form();
  1336       form.depth = i;
  1337       frames.push(form);
  1339       let promise = this.sources.getOriginalLocation(form.where)
  1340         .then((aOrigLocation) => {
  1341           form.where = aOrigLocation;
  1342           let source = this.sources.source({ url: form.where.url });
  1343           if (source) {
  1344             form.source = source.form();
  1346         });
  1347       promises.push(promise);
  1350     return all(promises).then(function () {
  1351       return { frames: frames };
  1352     });
  1353   },
  1355   onReleaseMany: function (aRequest) {
  1356     if (!aRequest.actors) {
  1357       return { error: "missingParameter",
  1358                message: "no actors were specified" };
  1361     let res;
  1362     for each (let actorID in aRequest.actors) {
  1363       let actor = this.threadLifetimePool.get(actorID);
  1364       if (!actor) {
  1365         if (!res) {
  1366           res = { error: "notReleasable",
  1367                   message: "Only thread-lifetime actors can be released." };
  1369         continue;
  1371       actor.onRelease();
  1373     return res ? res : {};
  1374   },
  1376   /**
  1377    * Handle a protocol request to set a breakpoint.
  1378    */
  1379   onSetBreakpoint: function (aRequest) {
  1380     if (this.state !== "paused") {
  1381       return { error: "wrongState",
  1382                message: "Breakpoints can only be set while the debuggee is paused."};
  1385     let { url: originalSource,
  1386           line: originalLine,
  1387           column: originalColumn } = aRequest.location;
  1389     let locationPromise = this.sources.getGeneratedLocation(aRequest.location);
  1390     return locationPromise.then(({url, line, column}) => {
  1391       if (line == null ||
  1392           line < 0 ||
  1393           this.dbg.findScripts({ url: url }).length == 0) {
  1394         return {
  1395           error: "noScript",
  1396           message: "Requested setting a breakpoint on "
  1397             + url + ":" + line
  1398             + (column != null ? ":" + column : "")
  1399             + " but there is no Debugger.Script at that location"
  1400         };
  1403       let response = this._createAndStoreBreakpoint({
  1404         url: url,
  1405         line: line,
  1406         column: column,
  1407         condition: aRequest.condition
  1408       });
  1409       // If the original location of our generated location is different from
  1410       // the original location we attempted to set the breakpoint on, we will
  1411       // need to know so that we can set actualLocation on the response.
  1412       let originalLocation = this.sources.getOriginalLocation({
  1413         url: url,
  1414         line: line,
  1415         column: column
  1416       });
  1418       return all([response, originalLocation])
  1419         .then(([aResponse, {url, line}]) => {
  1420           if (aResponse.actualLocation) {
  1421             let actualOrigLocation = this.sources.getOriginalLocation(aResponse.actualLocation);
  1422             return actualOrigLocation.then(({ url, line, column }) => {
  1423               if (url !== originalSource
  1424                   || line !== originalLine
  1425                   || column !== originalColumn) {
  1426                 aResponse.actualLocation = {
  1427                   url: url,
  1428                   line: line,
  1429                   column: column
  1430                 };
  1432               return aResponse;
  1433             });
  1436           if (url !== originalSource || line !== originalLine) {
  1437             aResponse.actualLocation = { url: url, line: line };
  1440           return aResponse;
  1441         });
  1442     });
  1443   },
  1445   /**
  1446    * Create a breakpoint at the specified location and store it in the
  1447    * cache. Takes ownership of `aLocation`.
  1449    * @param Object aLocation
  1450    *        An object of the form { url, line[, column] }
  1451    */
  1452   _createAndStoreBreakpoint: function (aLocation) {
  1453     // Add the breakpoint to the store for later reuse, in case it belongs to a
  1454     // script that hasn't appeared yet.
  1455     this.breakpointStore.addBreakpoint(aLocation);
  1456     return this._setBreakpoint(aLocation);
  1457   },
  1459   /**
  1460    * Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint
  1461    * is being set contains no code, then the breakpoint will slide down to the
  1462    * next line that has runnable code. In this case the server breakpoint cache
  1463    * will be updated, so callers that iterate over the breakpoint cache should
  1464    * take that into account.
  1466    * @param object aLocation
  1467    *        The location of the breakpoint (in the generated source, if source
  1468    *        mapping).
  1469    */
  1470   _setBreakpoint: function (aLocation) {
  1471     let actor;
  1472     let storedBp = this.breakpointStore.getBreakpoint(aLocation);
  1473     if (storedBp.actor) {
  1474       actor = storedBp.actor;
  1475       actor.condition = aLocation.condition;
  1476     } else {
  1477       storedBp.actor = actor = new BreakpointActor(this, {
  1478         url: aLocation.url,
  1479         line: aLocation.line,
  1480         column: aLocation.column,
  1481         condition: aLocation.condition
  1482       });
  1483       this.threadLifetimePool.addActor(actor);
  1486     // Find all scripts matching the given location
  1487     let scripts = this.dbg.findScripts(aLocation);
  1488     if (scripts.length == 0) {
  1489       return {
  1490         error: "noScript",
  1491         message: "Requested setting a breakpoint on "
  1492           + aLocation.url + ":" + aLocation.line
  1493           + (aLocation.column != null ? ":" + aLocation.column : "")
  1494           + " but there is no Debugger.Script at that location",
  1495         actor: actor.actorID
  1496       };
  1499    /**
  1500     * For each script, if the given line has at least one entry point, set a
  1501     * breakpoint on the bytecode offets for each of them.
  1502     */
  1504     // Debugger.Script -> array of offset mappings
  1505     let scriptsAndOffsetMappings = new Map();
  1507     for (let script of scripts) {
  1508       this._findClosestOffsetMappings(aLocation,
  1509                                       script,
  1510                                       scriptsAndOffsetMappings);
  1513     if (scriptsAndOffsetMappings.size > 0) {
  1514       for (let [script, mappings] of scriptsAndOffsetMappings) {
  1515         for (let offsetMapping of mappings) {
  1516           script.setBreakpoint(offsetMapping.offset, actor);
  1518         actor.addScript(script, this);
  1521       return {
  1522         actor: actor.actorID
  1523       };
  1526    /**
  1527     * If we get here, no breakpoint was set. This is because the given line
  1528     * has no entry points, for example because it is empty. As a fallback
  1529     * strategy, we try to set the breakpoint on the smallest line greater
  1530     * than or equal to the given line that as at least one entry point.
  1531     */
  1533     // Find all innermost scripts matching the given location
  1534     let scripts = this.dbg.findScripts({
  1535       url: aLocation.url,
  1536       line: aLocation.line,
  1537       innermost: true
  1538     });
  1540     /**
  1541      * For each innermost script, look for the smallest line greater than or
  1542      * equal to the given line that has one or more entry points. If found, set
  1543      * a breakpoint on the bytecode offset for each of its entry points.
  1544      */
  1545     let actualLocation;
  1546     let found = false;
  1547     for (let script of scripts) {
  1548       let offsets = script.getAllOffsets();
  1549       for (let line = aLocation.line; line < offsets.length; ++line) {
  1550         if (offsets[line]) {
  1551           for (let offset of offsets[line]) {
  1552             script.setBreakpoint(offset, actor);
  1554           actor.addScript(script, this);
  1555           if (!actualLocation) {
  1556             actualLocation = {
  1557               url: aLocation.url,
  1558               line: line
  1559             };
  1561           found = true;
  1562           break;
  1566     if (found) {
  1567       let existingBp = this.breakpointStore.hasBreakpoint(actualLocation);
  1569       if (existingBp && existingBp.actor) {
  1570         /**
  1571          * We already have a breakpoint actor for the actual location, so
  1572          * actor we created earlier is now redundant. Delete it, update the
  1573          * breakpoint store, and return the actor for the actual location.
  1574          */
  1575         actor.onDelete();
  1576         this.breakpointStore.removeBreakpoint(aLocation);
  1577         return {
  1578           actor: existingBp.actor.actorID,
  1579           actualLocation: actualLocation
  1580         };
  1581       } else {
  1582         /**
  1583          * We don't have a breakpoint actor for the actual location yet.
  1584          * Instead or creating a new actor, reuse the actor we created earlier,
  1585          * and update the breakpoint store.
  1586          */
  1587         actor.location = actualLocation;
  1588         this.breakpointStore.addBreakpoint({
  1589           actor: actor,
  1590           url: actualLocation.url,
  1591           line: actualLocation.line,
  1592           column: actualLocation.column
  1593         });
  1594         this.breakpointStore.removeBreakpoint(aLocation);
  1595         return {
  1596           actor: actor.actorID,
  1597           actualLocation: actualLocation
  1598         };
  1602     /**
  1603      * If we get here, no line matching the given line was found, so just
  1604      * fail epically.
  1605      */
  1606     return {
  1607       error: "noCodeAtLineColumn",
  1608       actor: actor.actorID
  1609     };
  1610   },
  1612   /**
  1613    * Find all of the offset mappings associated with `aScript` that are closest
  1614    * to `aTargetLocation`. If new offset mappings are found that are closer to
  1615    * `aTargetOffset` than the existing offset mappings inside
  1616    * `aScriptsAndOffsetMappings`, we empty that map and only consider the
  1617    * closest offset mappings. If there is no column in `aTargetLocation`, we add
  1618    * all offset mappings that are on the given line.
  1620    * @param Object aTargetLocation
  1621    *        An object of the form { url, line[, column] }.
  1622    * @param Debugger.Script aScript
  1623    *        The script in which we are searching for offsets.
  1624    * @param Map aScriptsAndOffsetMappings
  1625    *        A Map object which maps Debugger.Script instances to arrays of
  1626    *        offset mappings. This is an out param.
  1627    */
  1628   _findClosestOffsetMappings: function (aTargetLocation,
  1629                                         aScript,
  1630                                         aScriptsAndOffsetMappings) {
  1631     // If we are given a column, we will try and break only at that location,
  1632     // otherwise we will break anytime we get on that line.
  1634     if (aTargetLocation.column == null) {
  1635       let offsetMappings = aScript.getLineOffsets(aTargetLocation.line)
  1636         .map(o => ({
  1637           line: aTargetLocation.line,
  1638           offset: o
  1639         }));
  1640       if (offsetMappings.length) {
  1641         aScriptsAndOffsetMappings.set(aScript, offsetMappings);
  1643       return;
  1646     let offsetMappings = aScript.getAllColumnOffsets()
  1647       .filter(({ lineNumber }) => lineNumber === aTargetLocation.line);
  1649     // Attempt to find the current closest offset distance from the target
  1650     // location by grabbing any offset mapping in the map by doing one iteration
  1651     // and then breaking (they all have the same distance from the target
  1652     // location).
  1653     let closestDistance = Infinity;
  1654     if (aScriptsAndOffsetMappings.size) {
  1655       for (let mappings of aScriptsAndOffsetMappings.values()) {
  1656         closestDistance = Math.abs(aTargetLocation.column - mappings[0].columnNumber);
  1657         break;
  1661     for (let mapping of offsetMappings) {
  1662       let currentDistance = Math.abs(aTargetLocation.column - mapping.columnNumber);
  1664       if (currentDistance > closestDistance) {
  1665         continue;
  1666       } else if (currentDistance < closestDistance) {
  1667         closestDistance = currentDistance;
  1668         aScriptsAndOffsetMappings.clear();
  1669         aScriptsAndOffsetMappings.set(aScript, [mapping]);
  1670       } else {
  1671         if (!aScriptsAndOffsetMappings.has(aScript)) {
  1672           aScriptsAndOffsetMappings.set(aScript, []);
  1674         aScriptsAndOffsetMappings.get(aScript).push(mapping);
  1677   },
  1679   /**
  1680    * Get the script and source lists from the debugger.
  1681    */
  1682   _discoverSources: function () {
  1683     // Only get one script per url.
  1684     const sourcesToScripts = new Map();
  1685     for (let s of this.dbg.findScripts()) {
  1686       if (s.source) {
  1687         sourcesToScripts.set(s.source, s);
  1691     return all([this.sources.sourcesForScript(script)
  1692                 for (script of sourcesToScripts.values())]);
  1693   },
  1695   onSources: function (aRequest) {
  1696     return this._discoverSources().then(() => {
  1697       return {
  1698         sources: [s.form() for (s of this.sources.iter())]
  1699       };
  1700     });
  1701   },
  1703   /**
  1704    * Disassociate all breakpoint actors from their scripts and clear the
  1705    * breakpoint handlers. This method can be used when the thread actor intends
  1706    * to keep the breakpoint store, but needs to clear any actual breakpoints,
  1707    * e.g. due to a page navigation. This way the breakpoint actors' script
  1708    * caches won't hold on to the Debugger.Script objects leaking memory.
  1709    */
  1710   disableAllBreakpoints: function () {
  1711     for (let bp of this.breakpointStore.findBreakpoints()) {
  1712       if (bp.actor) {
  1713         bp.actor.removeScripts();
  1716   },
  1718   /**
  1719    * Handle a protocol request to pause the debuggee.
  1720    */
  1721   onInterrupt: function (aRequest) {
  1722     if (this.state == "exited") {
  1723       return { type: "exited" };
  1724     } else if (this.state == "paused") {
  1725       // TODO: return the actual reason for the existing pause.
  1726       return { type: "paused", why: { type: "alreadyPaused" } };
  1727     } else if (this.state != "running") {
  1728       return { error: "wrongState",
  1729                message: "Received interrupt request in " + this.state +
  1730                         " state." };
  1733     try {
  1734       // Put ourselves in the paused state.
  1735       let packet = this._paused();
  1736       if (!packet) {
  1737         return { error: "notInterrupted" };
  1739       packet.why = { type: "interrupted" };
  1741       // Send the response to the interrupt request now (rather than
  1742       // returning it), because we're going to start a nested event loop
  1743       // here.
  1744       this.conn.send(packet);
  1746       // Start a nested event loop.
  1747       this._pushThreadPause();
  1749       // We already sent a response to this request, don't send one
  1750       // now.
  1751       return null;
  1752     } catch (e) {
  1753       reportError(e);
  1754       return { error: "notInterrupted", message: e.toString() };
  1756   },
  1758   /**
  1759    * Handle a protocol request to retrieve all the event listeners on the page.
  1760    */
  1761   onEventListeners: function (aRequest) {
  1762     // This request is only supported in content debugging.
  1763     if (!this.global) {
  1764       return {
  1765         error: "notImplemented",
  1766         message: "eventListeners request is only supported in content debugging"
  1767       };
  1770     let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1771                 .getService(Ci.nsIEventListenerService);
  1773     let nodes = this.global.document.getElementsByTagName("*");
  1774     nodes = [this.global].concat([].slice.call(nodes));
  1775     let listeners = [];
  1777     for (let node of nodes) {
  1778       let handlers = els.getListenerInfoFor(node);
  1780       for (let handler of handlers) {
  1781         // Create a form object for serializing the listener via the protocol.
  1782         let listenerForm = Object.create(null);
  1783         let listener = handler.listenerObject;
  1784         // Native event listeners don't provide any listenerObject or type and
  1785         // are not that useful to a JS debugger.
  1786         if (!listener || !handler.type) {
  1787           continue;
  1790         // There will be no tagName if the event listener is set on the window.
  1791         let selector = node.tagName ? findCssSelector(node) : "window";
  1792         let nodeDO = this.globalDebugObject.makeDebuggeeValue(node);
  1793         listenerForm.node = {
  1794           selector: selector,
  1795           object: this.createValueGrip(nodeDO)
  1796         };
  1797         listenerForm.type = handler.type;
  1798         listenerForm.capturing = handler.capturing;
  1799         listenerForm.allowsUntrusted = handler.allowsUntrusted;
  1800         listenerForm.inSystemEventGroup = handler.inSystemEventGroup;
  1801         listenerForm.isEventHandler = !!node["on" + listenerForm.type];
  1802         // Get the Debugger.Object for the listener object.
  1803         let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
  1804         listenerForm.function = this.createValueGrip(listenerDO);
  1805         listeners.push(listenerForm);
  1808     return { listeners: listeners };
  1809   },
  1811   /**
  1812    * Return the Debug.Frame for a frame mentioned by the protocol.
  1813    */
  1814   _requestFrame: function (aFrameID) {
  1815     if (!aFrameID) {
  1816       return this.youngestFrame;
  1819     if (this._framePool.has(aFrameID)) {
  1820       return this._framePool.get(aFrameID).frame;
  1823     return undefined;
  1824   },
  1826   _paused: function (aFrame) {
  1827     // We don't handle nested pauses correctly.  Don't try - if we're
  1828     // paused, just continue running whatever code triggered the pause.
  1829     // We don't want to actually have nested pauses (although we
  1830     // have nested event loops).  If code runs in the debuggee during
  1831     // a pause, it should cause the actor to resume (dropping
  1832     // pause-lifetime actors etc) and then repause when complete.
  1834     if (this.state === "paused") {
  1835       return undefined;
  1838     // Clear stepping hooks.
  1839     this.dbg.onEnterFrame = undefined;
  1840     this.dbg.onExceptionUnwind = undefined;
  1841     if (aFrame) {
  1842       aFrame.onStep = undefined;
  1843       aFrame.onPop = undefined;
  1845     // Clear DOM event breakpoints.
  1846     // XPCShell tests don't use actual DOM windows for globals and cause
  1847     // removeListenerForAllEvents to throw.
  1848     if (this.global && !this.global.toString().contains("Sandbox")) {
  1849       let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1850                 .getService(Ci.nsIEventListenerService);
  1851       els.removeListenerForAllEvents(this.global, this._allEventsListener, true);
  1852       for (let [,bp] of this._hiddenBreakpoints) {
  1853         bp.onDelete();
  1855       this._hiddenBreakpoints.clear();
  1858     this._state = "paused";
  1860     // Create the actor pool that will hold the pause actor and its
  1861     // children.
  1862     dbg_assert(!this._pausePool, "No pause pool should exist yet");
  1863     this._pausePool = new ActorPool(this.conn);
  1864     this.conn.addActorPool(this._pausePool);
  1866     // Give children of the pause pool a quick link back to the
  1867     // thread...
  1868     this._pausePool.threadActor = this;
  1870     // Create the pause actor itself...
  1871     dbg_assert(!this._pauseActor, "No pause actor should exist yet");
  1872     this._pauseActor = new PauseActor(this._pausePool);
  1873     this._pausePool.addActor(this._pauseActor);
  1875     // Update the list of frames.
  1876     let poppedFrames = this._updateFrames();
  1878     // Send off the paused packet and spin an event loop.
  1879     let packet = { from: this.actorID,
  1880                    type: "paused",
  1881                    actor: this._pauseActor.actorID };
  1882     if (aFrame) {
  1883       packet.frame = this._createFrameActor(aFrame).form();
  1886     if (poppedFrames) {
  1887       packet.poppedFrames = poppedFrames;
  1890     return packet;
  1891   },
  1893   _resumed: function () {
  1894     this._state = "running";
  1896     // Drop the actors in the pause actor pool.
  1897     this.conn.removeActorPool(this._pausePool);
  1899     this._pausePool = null;
  1900     this._pauseActor = null;
  1902     return { from: this.actorID, type: "resumed" };
  1903   },
  1905   /**
  1906    * Expire frame actors for frames that have been popped.
  1908    * @returns A list of actor IDs whose frames have been popped.
  1909    */
  1910   _updateFrames: function () {
  1911     let popped = [];
  1913     // Create the actor pool that will hold the still-living frames.
  1914     let framePool = new ActorPool(this.conn);
  1915     let frameList = [];
  1917     for each (let frameActor in this._frameActors) {
  1918       if (frameActor.frame.live) {
  1919         framePool.addActor(frameActor);
  1920         frameList.push(frameActor);
  1921       } else {
  1922         popped.push(frameActor.actorID);
  1926     // Remove the old frame actor pool, this will expire
  1927     // any actors that weren't added to the new pool.
  1928     if (this._framePool) {
  1929       this.conn.removeActorPool(this._framePool);
  1932     this._frameActors = frameList;
  1933     this._framePool = framePool;
  1934     this.conn.addActorPool(framePool);
  1936     return popped;
  1937   },
  1939   _createFrameActor: function (aFrame) {
  1940     if (aFrame.actor) {
  1941       return aFrame.actor;
  1944     let actor = new FrameActor(aFrame, this);
  1945     this._frameActors.push(actor);
  1946     this._framePool.addActor(actor);
  1947     aFrame.actor = actor;
  1949     return actor;
  1950   },
  1952   /**
  1953    * Create and return an environment actor that corresponds to the provided
  1954    * Debugger.Environment.
  1955    * @param Debugger.Environment aEnvironment
  1956    *        The lexical environment we want to extract.
  1957    * @param object aPool
  1958    *        The pool where the newly-created actor will be placed.
  1959    * @return The EnvironmentActor for aEnvironment or undefined for host
  1960    *         functions or functions scoped to a non-debuggee global.
  1961    */
  1962   createEnvironmentActor: function (aEnvironment, aPool) {
  1963     if (!aEnvironment) {
  1964       return undefined;
  1967     if (aEnvironment.actor) {
  1968       return aEnvironment.actor;
  1971     let actor = new EnvironmentActor(aEnvironment, this);
  1972     aPool.addActor(actor);
  1973     aEnvironment.actor = actor;
  1975     return actor;
  1976   },
  1978   /**
  1979    * Create a grip for the given debuggee value.  If the value is an
  1980    * object, will create an actor with the given lifetime.
  1981    */
  1982   createValueGrip: function (aValue, aPool=false) {
  1983     if (!aPool) {
  1984       aPool = this._pausePool;
  1987     switch (typeof aValue) {
  1988       case "boolean":
  1989         return aValue;
  1990       case "string":
  1991         if (this._stringIsLong(aValue)) {
  1992           return this.longStringGrip(aValue, aPool);
  1994         return aValue;
  1995       case "number":
  1996         if (aValue === Infinity) {
  1997           return { type: "Infinity" };
  1998         } else if (aValue === -Infinity) {
  1999           return { type: "-Infinity" };
  2000         } else if (Number.isNaN(aValue)) {
  2001           return { type: "NaN" };
  2002         } else if (!aValue && 1 / aValue === -Infinity) {
  2003           return { type: "-0" };
  2005         return aValue;
  2006       case "undefined":
  2007         return { type: "undefined" };
  2008       case "object":
  2009         if (aValue === null) {
  2010           return { type: "null" };
  2012         return this.objectGrip(aValue, aPool);
  2013       default:
  2014         dbg_assert(false, "Failed to provide a grip for: " + aValue);
  2015         return null;
  2017   },
  2019   /**
  2020    * Return a protocol completion value representing the given
  2021    * Debugger-provided completion value.
  2022    */
  2023   createProtocolCompletionValue: function (aCompletion) {
  2024     let protoValue = {};
  2025     if ("return" in aCompletion) {
  2026       protoValue.return = this.createValueGrip(aCompletion.return);
  2027     } else if ("yield" in aCompletion) {
  2028       protoValue.return = this.createValueGrip(aCompletion.yield);
  2029     } else if ("throw" in aCompletion) {
  2030       protoValue.throw = this.createValueGrip(aCompletion.throw);
  2031     } else {
  2032       protoValue.terminated = true;
  2034     return protoValue;
  2035   },
  2037   /**
  2038    * Create a grip for the given debuggee object.
  2040    * @param aValue Debugger.Object
  2041    *        The debuggee object value.
  2042    * @param aPool ActorPool
  2043    *        The actor pool where the new object actor will be added.
  2044    */
  2045   objectGrip: function (aValue, aPool) {
  2046     if (!aPool.objectActors) {
  2047       aPool.objectActors = new WeakMap();
  2050     if (aPool.objectActors.has(aValue)) {
  2051       return aPool.objectActors.get(aValue).grip();
  2052     } else if (this.threadLifetimePool.objectActors.has(aValue)) {
  2053       return this.threadLifetimePool.objectActors.get(aValue).grip();
  2056     let actor = new PauseScopedObjectActor(aValue, this);
  2057     aPool.addActor(actor);
  2058     aPool.objectActors.set(aValue, actor);
  2059     return actor.grip();
  2060   },
  2062   /**
  2063    * Create a grip for the given debuggee object with a pause lifetime.
  2065    * @param aValue Debugger.Object
  2066    *        The debuggee object value.
  2067    */
  2068   pauseObjectGrip: function (aValue) {
  2069     if (!this._pausePool) {
  2070       throw "Object grip requested while not paused.";
  2073     return this.objectGrip(aValue, this._pausePool);
  2074   },
  2076   /**
  2077    * Extend the lifetime of the provided object actor to thread lifetime.
  2079    * @param aActor object
  2080    *        The object actor.
  2081    */
  2082   threadObjectGrip: function (aActor) {
  2083     // We want to reuse the existing actor ID, so we just remove it from the
  2084     // current pool's weak map and then let pool.addActor do the rest.
  2085     aActor.registeredPool.objectActors.delete(aActor.obj);
  2086     this.threadLifetimePool.addActor(aActor);
  2087     this.threadLifetimePool.objectActors.set(aActor.obj, aActor);
  2088   },
  2090   /**
  2091    * Handle a protocol request to promote multiple pause-lifetime grips to
  2092    * thread-lifetime grips.
  2094    * @param aRequest object
  2095    *        The protocol request object.
  2096    */
  2097   onThreadGrips: function (aRequest) {
  2098     if (this.state != "paused") {
  2099       return { error: "wrongState" };
  2102     if (!aRequest.actors) {
  2103       return { error: "missingParameter",
  2104                message: "no actors were specified" };
  2107     for (let actorID of aRequest.actors) {
  2108       let actor = this._pausePool.get(actorID);
  2109       if (actor) {
  2110         this.threadObjectGrip(actor);
  2113     return {};
  2114   },
  2116   /**
  2117    * Create a grip for the given string.
  2119    * @param aString String
  2120    *        The string we are creating a grip for.
  2121    * @param aPool ActorPool
  2122    *        The actor pool where the new actor will be added.
  2123    */
  2124   longStringGrip: function (aString, aPool) {
  2125     if (!aPool.longStringActors) {
  2126       aPool.longStringActors = {};
  2129     if (aPool.longStringActors.hasOwnProperty(aString)) {
  2130       return aPool.longStringActors[aString].grip();
  2133     let actor = new LongStringActor(aString, this);
  2134     aPool.addActor(actor);
  2135     aPool.longStringActors[aString] = actor;
  2136     return actor.grip();
  2137   },
  2139   /**
  2140    * Create a long string grip that is scoped to a pause.
  2142    * @param aString String
  2143    *        The string we are creating a grip for.
  2144    */
  2145   pauseLongStringGrip: function (aString) {
  2146     return this.longStringGrip(aString, this._pausePool);
  2147   },
  2149   /**
  2150    * Create a long string grip that is scoped to a thread.
  2152    * @param aString String
  2153    *        The string we are creating a grip for.
  2154    */
  2155   threadLongStringGrip: function (aString) {
  2156     return this.longStringGrip(aString, this._threadLifetimePool);
  2157   },
  2159   /**
  2160    * Returns true if the string is long enough to use a LongStringActor instead
  2161    * of passing the value directly over the protocol.
  2163    * @param aString String
  2164    *        The string we are checking the length of.
  2165    */
  2166   _stringIsLong: function (aString) {
  2167     return aString.length >= DebuggerServer.LONG_STRING_LENGTH;
  2168   },
  2170   // JS Debugger API hooks.
  2172   /**
  2173    * A function that the engine calls when a call to a debug event hook,
  2174    * breakpoint handler, watchpoint handler, or similar function throws some
  2175    * exception.
  2177    * @param aException exception
  2178    *        The exception that was thrown in the debugger code.
  2179    */
  2180   uncaughtExceptionHook: function (aException) {
  2181     dumpn("Got an exception: " + aException.message + "\n" + aException.stack);
  2182   },
  2184   /**
  2185    * A function that the engine calls when a debugger statement has been
  2186    * executed in the specified frame.
  2188    * @param aFrame Debugger.Frame
  2189    *        The stack frame that contained the debugger statement.
  2190    */
  2191   onDebuggerStatement: function (aFrame) {
  2192     // Don't pause if we are currently stepping (in or over) or the frame is
  2193     // black-boxed.
  2194     const generatedLocation = getFrameLocation(aFrame);
  2195     const { url } = this.synchronize(this.sources.getOriginalLocation(
  2196       generatedLocation));
  2198     return this.sources.isBlackBoxed(url) || aFrame.onStep
  2199       ? undefined
  2200       : this._pauseAndRespond(aFrame, { type: "debuggerStatement" });
  2201   },
  2203   /**
  2204    * A function that the engine calls when an exception has been thrown and has
  2205    * propagated to the specified frame.
  2207    * @param aFrame Debugger.Frame
  2208    *        The youngest remaining stack frame.
  2209    * @param aValue object
  2210    *        The exception that was thrown.
  2211    */
  2212   onExceptionUnwind: function (aFrame, aValue) {
  2213     let willBeCaught = false;
  2214     for (let frame = aFrame; frame != null; frame = frame.older) {
  2215       if (frame.script.isInCatchScope(frame.offset)) {
  2216         willBeCaught = true;
  2217         break;
  2221     if (willBeCaught && this._options.ignoreCaughtExceptions) {
  2222       return undefined;
  2225     const generatedLocation = getFrameLocation(aFrame);
  2226     const { url } = this.synchronize(this.sources.getOriginalLocation(
  2227       generatedLocation));
  2229     if (this.sources.isBlackBoxed(url)) {
  2230       return undefined;
  2233     try {
  2234       let packet = this._paused(aFrame);
  2235       if (!packet) {
  2236         return undefined;
  2239       packet.why = { type: "exception",
  2240                      exception: this.createValueGrip(aValue) };
  2241       this.conn.send(packet);
  2243       this._pushThreadPause();
  2244     } catch(e) {
  2245       reportError(e, "Got an exception during TA_onExceptionUnwind: ");
  2248     return undefined;
  2249   },
  2251   /**
  2252    * A function that the engine calls when a new script has been loaded into the
  2253    * scope of the specified debuggee global.
  2255    * @param aScript Debugger.Script
  2256    *        The source script that has been loaded into a debuggee compartment.
  2257    * @param aGlobal Debugger.Object
  2258    *        A Debugger.Object instance whose referent is the global object.
  2259    */
  2260   onNewScript: function (aScript, aGlobal) {
  2261     this._addScript(aScript);
  2263     // |onNewScript| is only fired for top level scripts (AKA staticLevel == 0),
  2264     // so we have to make sure to call |_addScript| on every child script as
  2265     // well to restore breakpoints in those scripts.
  2266     for (let s of aScript.getChildScripts()) {
  2267       this._addScript(s);
  2270     this.sources.sourcesForScript(aScript);
  2271   },
  2273   onNewSource: function (aSource) {
  2274     this.conn.send({
  2275       from: this.actorID,
  2276       type: "newSource",
  2277       source: aSource.form()
  2278     });
  2279   },
  2281   /**
  2282    * Check if scripts from the provided source URL are allowed to be stored in
  2283    * the cache.
  2285    * @param aSourceUrl String
  2286    *        The url of the script's source that will be stored.
  2287    * @returns true, if the script can be added, false otherwise.
  2288    */
  2289   _allowSource: function (aSourceUrl) {
  2290     // Ignore anything we don't have a URL for (eval scripts, for example).
  2291     if (!aSourceUrl)
  2292       return false;
  2293     // Ignore XBL bindings for content debugging.
  2294     if (aSourceUrl.indexOf("chrome://") == 0) {
  2295       return false;
  2297     // Ignore about:* pages for content debugging.
  2298     if (aSourceUrl.indexOf("about:") == 0) {
  2299       return false;
  2301     return true;
  2302   },
  2304   /**
  2305    * Restore any pre-existing breakpoints to the scripts that we have access to.
  2306    */
  2307   _restoreBreakpoints: function () {
  2308     if (this.breakpointStore.size === 0) {
  2309       return;
  2312     for (let s of this.dbg.findScripts()) {
  2313       this._addScript(s);
  2315   },
  2317   /**
  2318    * Add the provided script to the server cache.
  2320    * @param aScript Debugger.Script
  2321    *        The source script that will be stored.
  2322    * @returns true, if the script was added; false otherwise.
  2323    */
  2324   _addScript: function (aScript) {
  2325     if (!this._allowSource(aScript.url)) {
  2326       return false;
  2329     // Set any stored breakpoints.
  2331     let endLine = aScript.startLine + aScript.lineCount - 1;
  2332     for (let bp of this.breakpointStore.findBreakpoints({ url: aScript.url })) {
  2333       // Only consider breakpoints that are not already associated with
  2334       // scripts, and limit search to the line numbers contained in the new
  2335       // script.
  2336       if (!bp.actor.scripts.length
  2337           && bp.line >= aScript.startLine
  2338           && bp.line <= endLine) {
  2339         this._setBreakpoint(bp);
  2343     return true;
  2344   },
  2347   /**
  2348    * Get prototypes and properties of multiple objects.
  2349    */
  2350   onPrototypesAndProperties: function (aRequest) {
  2351     let result = {};
  2352     for (let actorID of aRequest.actors) {
  2353       // This code assumes that there are no lazily loaded actors returned
  2354       // by this call.
  2355       let actor = this.conn.getActor(actorID);
  2356       if (!actor) {
  2357         return { from: this.actorID,
  2358                  error: "noSuchActor" };
  2360       let handler = actor.onPrototypeAndProperties;
  2361       if (!handler) {
  2362         return { from: this.actorID,
  2363                  error: "unrecognizedPacketType",
  2364                  message: ('Actor "' + actorID +
  2365                            '" does not recognize the packet type ' +
  2366                            '"prototypeAndProperties"') };
  2368       result[actorID] = handler.call(actor, {});
  2370     return { from: this.actorID,
  2371              actors: result };
  2374 };
  2376 ThreadActor.prototype.requestTypes = {
  2377   "attach": ThreadActor.prototype.onAttach,
  2378   "detach": ThreadActor.prototype.onDetach,
  2379   "reconfigure": ThreadActor.prototype.onReconfigure,
  2380   "resume": ThreadActor.prototype.onResume,
  2381   "clientEvaluate": ThreadActor.prototype.onClientEvaluate,
  2382   "frames": ThreadActor.prototype.onFrames,
  2383   "interrupt": ThreadActor.prototype.onInterrupt,
  2384   "eventListeners": ThreadActor.prototype.onEventListeners,
  2385   "releaseMany": ThreadActor.prototype.onReleaseMany,
  2386   "setBreakpoint": ThreadActor.prototype.onSetBreakpoint,
  2387   "sources": ThreadActor.prototype.onSources,
  2388   "threadGrips": ThreadActor.prototype.onThreadGrips,
  2389   "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
  2390 };
  2393 /**
  2394  * Creates a PauseActor.
  2396  * PauseActors exist for the lifetime of a given debuggee pause.  Used to
  2397  * scope pause-lifetime grips.
  2399  * @param ActorPool aPool
  2400  *        The actor pool created for this pause.
  2401  */
  2402 function PauseActor(aPool)
  2404   this.pool = aPool;
  2407 PauseActor.prototype = {
  2408   actorPrefix: "pause"
  2409 };
  2412 /**
  2413  * A base actor for any actors that should only respond receive messages in the
  2414  * paused state. Subclasses may expose a `threadActor` which is used to help
  2415  * determine when we are in a paused state. Subclasses should set their own
  2416  * "constructor" property if they want better error messages. You should never
  2417  * instantiate a PauseScopedActor directly, only through subclasses.
  2418  */
  2419 function PauseScopedActor()
  2423 /**
  2424  * A function decorator for creating methods to handle protocol messages that
  2425  * should only be received while in the paused state.
  2427  * @param aMethod Function
  2428  *        The function we are decorating.
  2429  */
  2430 PauseScopedActor.withPaused = function (aMethod) {
  2431   return function () {
  2432     if (this.isPaused()) {
  2433       return aMethod.apply(this, arguments);
  2434     } else {
  2435       return this._wrongState();
  2437   };
  2438 };
  2440 PauseScopedActor.prototype = {
  2442   /**
  2443    * Returns true if we are in the paused state.
  2444    */
  2445   isPaused: function () {
  2446     // When there is not a ThreadActor available (like in the webconsole) we
  2447     // have to be optimistic and assume that we are paused so that we can
  2448     // respond to requests.
  2449     return this.threadActor ? this.threadActor.state === "paused" : true;
  2450   },
  2452   /**
  2453    * Returns the wrongState response packet for this actor.
  2454    */
  2455   _wrongState: function () {
  2456     return {
  2457       error: "wrongState",
  2458       message: this.constructor.name +
  2459         " actors can only be accessed while the thread is paused."
  2460     };
  2462 };
  2464 /**
  2465  * Resolve a URI back to physical file.
  2467  * Of course, this works only for URIs pointing to local resources.
  2469  * @param  aURI
  2470  *         URI to resolve
  2471  * @return
  2472  *         resolved nsIURI
  2473  */
  2474 function resolveURIToLocalPath(aURI) {
  2475   switch (aURI.scheme) {
  2476     case "jar":
  2477     case "file":
  2478       return aURI;
  2480     case "chrome":
  2481       let resolved = Cc["@mozilla.org/chrome/chrome-registry;1"].
  2482                      getService(Ci.nsIChromeRegistry).convertChromeURL(aURI);
  2483       return resolveURIToLocalPath(resolved);
  2485     case "resource":
  2486       resolved = Cc["@mozilla.org/network/protocol;1?name=resource"].
  2487                  getService(Ci.nsIResProtocolHandler).resolveURI(aURI);
  2488       aURI = Services.io.newURI(resolved, null, null);
  2489       return resolveURIToLocalPath(aURI);
  2491     default:
  2492       return null;
  2496 /**
  2497  * A SourceActor provides information about the source of a script.
  2499  * @param String url
  2500  *        The url of the source we are representing.
  2501  * @param ThreadActor thread
  2502  *        The current thread actor.
  2503  * @param SourceMapConsumer sourceMap
  2504  *        Optional. The source map that introduced this source, if available.
  2505  * @param String generatedSource
  2506  *        Optional, passed in when aSourceMap is also passed in. The generated
  2507  *        source url that introduced this source.
  2508  * @param String text
  2509  *        Optional. The content text of this source, if immediately available.
  2510  * @param String contentType
  2511  *        Optional. The content type of this source, if immediately available.
  2512  */
  2513 function SourceActor({ url, thread, sourceMap, generatedSource, text,
  2514                        contentType }) {
  2515   this._threadActor = thread;
  2516   this._url = url;
  2517   this._sourceMap = sourceMap;
  2518   this._generatedSource = generatedSource;
  2519   this._text = text;
  2520   this._contentType = contentType;
  2522   this.onSource = this.onSource.bind(this);
  2523   this._invertSourceMap = this._invertSourceMap.bind(this);
  2524   this._saveMap = this._saveMap.bind(this);
  2525   this._getSourceText = this._getSourceText.bind(this);
  2527   this._mapSourceToAddon();
  2529   if (this.threadActor.sources.isPrettyPrinted(this.url)) {
  2530     this._init = this.onPrettyPrint({
  2531       indent: this.threadActor.sources.prettyPrintIndent(this.url)
  2532     }).then(null, error => {
  2533       DevToolsUtils.reportException("SourceActor", error);
  2534     });
  2535   } else {
  2536     this._init = null;
  2540 SourceActor.prototype = {
  2541   constructor: SourceActor,
  2542   actorPrefix: "source",
  2544   _oldSourceMap: null,
  2545   _init: null,
  2546   _addonID: null,
  2547   _addonPath: null,
  2549   get threadActor() this._threadActor,
  2550   get url() this._url,
  2551   get addonID() this._addonID,
  2552   get addonPath() this._addonPath,
  2554   get prettyPrintWorker() {
  2555     return this.threadActor.prettyPrintWorker;
  2556   },
  2558   form: function () {
  2559     return {
  2560       actor: this.actorID,
  2561       url: this._url,
  2562       addonID: this._addonID,
  2563       addonPath: this._addonPath,
  2564       isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url),
  2565       isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url)
  2566       // TODO bug 637572: introductionScript
  2567     };
  2568   },
  2570   disconnect: function () {
  2571     if (this.registeredPool && this.registeredPool.sourceActors) {
  2572       delete this.registeredPool.sourceActors[this.actorID];
  2574   },
  2576   _mapSourceToAddon: function() {
  2577     try {
  2578       var nsuri = Services.io.newURI(this._url.split(" -> ").pop(), null, null);
  2580     catch (e) {
  2581       // We can't do anything with an invalid URI
  2582       return;
  2585     let localURI = resolveURIToLocalPath(nsuri);
  2587     let id = {};
  2588     if (localURI && mapURIToAddonID(localURI, id)) {
  2589       this._addonID = id.value;
  2591       if (localURI instanceof Ci.nsIJARURI) {
  2592         // The path in the add-on is easy for jar: uris
  2593         this._addonPath = localURI.JAREntry;
  2595       else if (localURI instanceof Ci.nsIFileURL) {
  2596         // For file: uris walk up to find the last directory that is part of the
  2597         // add-on
  2598         let target = localURI.file;
  2599         let path = target.leafName;
  2601         // We can assume that the directory containing the source file is part
  2602         // of the add-on
  2603         let root = target.parent;
  2604         let file = root.parent;
  2605         while (file && mapURIToAddonID(Services.io.newFileURI(file), {})) {
  2606           path = root.leafName + "/" + path;
  2607           root = file;
  2608           file = file.parent;
  2611         if (!file) {
  2612           const error = new Error("Could not find the root of the add-on for " + this._url);
  2613           DevToolsUtils.reportException("SourceActor.prototype._mapSourceToAddon", error)
  2614           return;
  2617         this._addonPath = path;
  2620   },
  2622   _getSourceText: function () {
  2623     const toResolvedContent = t => resolve({
  2624       content: t,
  2625       contentType: this._contentType
  2626     });
  2628     let sc;
  2629     if (this._sourceMap && (sc = this._sourceMap.sourceContentFor(this._url))) {
  2630       return toResolvedContent(sc);
  2633     if (this._text) {
  2634       return toResolvedContent(this._text);
  2637     // XXX bug 865252: Don't load from the cache if this is a source mapped
  2638     // source because we can't guarantee that the cache has the most up to date
  2639     // content for this source like we can if it isn't source mapped.
  2640     let sourceFetched = fetch(this._url, { loadFromCache: !this._sourceMap });
  2642     // Record the contentType we just learned during fetching
  2643     sourceFetched.then(({ contentType }) => {
  2644       this._contentType = contentType;
  2645     });
  2647     return sourceFetched;
  2648   },
  2650   /**
  2651    * Handler for the "source" packet.
  2652    */
  2653   onSource: function () {
  2654     return resolve(this._init)
  2655       .then(this._getSourceText)
  2656       .then(({ content, contentType }) => {
  2657         return {
  2658           from: this.actorID,
  2659           source: this.threadActor.createValueGrip(
  2660             content, this.threadActor.threadLifetimePool),
  2661           contentType: contentType
  2662         };
  2663       })
  2664       .then(null, aError => {
  2665         reportError(aError, "Got an exception during SA_onSource: ");
  2666         return {
  2667           "from": this.actorID,
  2668           "error": "loadSourceError",
  2669           "message": "Could not load the source for " + this._url + ".\n"
  2670             + DevToolsUtils.safeErrorString(aError)
  2671         };
  2672       });
  2673   },
  2675   /**
  2676    * Handler for the "prettyPrint" packet.
  2677    */
  2678   onPrettyPrint: function ({ indent }) {
  2679     this.threadActor.sources.prettyPrint(this._url, indent);
  2680     return this._getSourceText()
  2681       .then(this._sendToPrettyPrintWorker(indent))
  2682       .then(this._invertSourceMap)
  2683       .then(this._saveMap)
  2684       .then(() => {
  2685         // We need to reset `_init` now because we have already done the work of
  2686         // pretty printing, and don't want onSource to wait forever for
  2687         // initialization to complete.
  2688         this._init = null;
  2689       })
  2690       .then(this.onSource)
  2691       .then(null, error => {
  2692         this.onDisablePrettyPrint();
  2693         return {
  2694           from: this.actorID,
  2695           error: "prettyPrintError",
  2696           message: DevToolsUtils.safeErrorString(error)
  2697         };
  2698       });
  2699   },
  2701   /**
  2702    * Return a function that sends a request to the pretty print worker, waits on
  2703    * the worker's response, and then returns the pretty printed code.
  2705    * @param Number aIndent
  2706    *        The number of spaces to indent by the code by, when we send the
  2707    *        request to the pretty print worker.
  2708    * @returns Function
  2709    *          Returns a function which takes an AST, and returns a promise that
  2710    *          is resolved with `{ code, mappings }` where `code` is the pretty
  2711    *          printed code, and `mappings` is an array of source mappings.
  2712    */
  2713   _sendToPrettyPrintWorker: function (aIndent) {
  2714     return ({ content }) => {
  2715       const deferred = promise.defer();
  2716       const id = Math.random();
  2718       const onReply = ({ data }) => {
  2719         if (data.id !== id) {
  2720           return;
  2722         this.prettyPrintWorker.removeEventListener("message", onReply, false);
  2724         if (data.error) {
  2725           deferred.reject(new Error(data.error));
  2726         } else {
  2727           deferred.resolve(data);
  2729       };
  2731       this.prettyPrintWorker.addEventListener("message", onReply, false);
  2732       this.prettyPrintWorker.postMessage({
  2733         id: id,
  2734         url: this._url,
  2735         indent: aIndent,
  2736         source: content
  2737       });
  2739       return deferred.promise;
  2740     };
  2741   },
  2743   /**
  2744    * Invert a source map. So if a source map maps from a to b, return a new
  2745    * source map from b to a. We need to do this because the source map we get
  2746    * from _generatePrettyCodeAndMap goes the opposite way we want it to for
  2747    * debugging.
  2749    * Note that the source map is modified in place.
  2750    */
  2751   _invertSourceMap: function ({ code, mappings }) {
  2752     const generator = new SourceMapGenerator({ file: this._url });
  2753     return DevToolsUtils.yieldingEach(mappings, m => {
  2754       let mapping = {
  2755         generated: {
  2756           line: m.generatedLine,
  2757           column: m.generatedColumn
  2759       };
  2760       if (m.source) {
  2761         mapping.source = m.source;
  2762         mapping.original = {
  2763           line: m.originalLine,
  2764           column: m.originalColumn
  2765         };
  2766         mapping.name = m.name;
  2768       generator.addMapping(mapping);
  2769     }).then(() => {
  2770       generator.setSourceContent(this._url, code);
  2771       const consumer = SourceMapConsumer.fromSourceMap(generator);
  2773       // XXX bug 918802: Monkey punch the source map consumer, because iterating
  2774       // over all mappings and inverting each of them, and then creating a new
  2775       // SourceMapConsumer is slow.
  2777       const getOrigPos = consumer.originalPositionFor.bind(consumer);
  2778       const getGenPos = consumer.generatedPositionFor.bind(consumer);
  2780       consumer.originalPositionFor = ({ line, column }) => {
  2781         const location = getGenPos({
  2782           line: line,
  2783           column: column,
  2784           source: this._url
  2785         });
  2786         location.source = this._url;
  2787         return location;
  2788       };
  2790       consumer.generatedPositionFor = ({ line, column }) => getOrigPos({
  2791         line: line,
  2792         column: column
  2793       });
  2795       return {
  2796         code: code,
  2797         map: consumer
  2798       };
  2799     });
  2800   },
  2802   /**
  2803    * Save the source map back to our thread's ThreadSources object so that
  2804    * stepping, breakpoints, debugger statements, etc can use it. If we are
  2805    * pretty printing a source mapped source, we need to compose the existing
  2806    * source map with our new one.
  2807    */
  2808   _saveMap: function ({ map }) {
  2809     if (this._sourceMap) {
  2810       // Compose the source maps
  2811       this._oldSourceMap = this._sourceMap;
  2812       this._sourceMap = SourceMapGenerator.fromSourceMap(this._sourceMap);
  2813       this._sourceMap.applySourceMap(map, this._url);
  2814       this._sourceMap = SourceMapConsumer.fromSourceMap(this._sourceMap);
  2815       this._threadActor.sources.saveSourceMap(this._sourceMap,
  2816                                               this._generatedSource);
  2817     } else {
  2818       this._sourceMap = map;
  2819       this._threadActor.sources.saveSourceMap(this._sourceMap, this._url);
  2821   },
  2823   /**
  2824    * Handler for the "disablePrettyPrint" packet.
  2825    */
  2826   onDisablePrettyPrint: function () {
  2827     this._sourceMap = this._oldSourceMap;
  2828     this.threadActor.sources.saveSourceMap(this._sourceMap,
  2829                                            this._generatedSource || this._url);
  2830     this.threadActor.sources.disablePrettyPrint(this._url);
  2831     return this.onSource();
  2832   },
  2834   /**
  2835    * Handler for the "blackbox" packet.
  2836    */
  2837   onBlackBox: function (aRequest) {
  2838     this.threadActor.sources.blackBox(this.url);
  2839     let packet = {
  2840       from: this.actorID
  2841     };
  2842     if (this.threadActor.state == "paused"
  2843         && this.threadActor.youngestFrame
  2844         && this.threadActor.youngestFrame.script.url == this.url) {
  2845       packet.pausedInSource = true;
  2847     return packet;
  2848   },
  2850   /**
  2851    * Handler for the "unblackbox" packet.
  2852    */
  2853   onUnblackBox: function (aRequest) {
  2854     this.threadActor.sources.unblackBox(this.url);
  2855     return {
  2856       from: this.actorID
  2857     };
  2859 };
  2861 SourceActor.prototype.requestTypes = {
  2862   "source": SourceActor.prototype.onSource,
  2863   "blackbox": SourceActor.prototype.onBlackBox,
  2864   "unblackbox": SourceActor.prototype.onUnblackBox,
  2865   "prettyPrint": SourceActor.prototype.onPrettyPrint,
  2866   "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint
  2867 };
  2870 /**
  2871  * Determine if a given value is non-primitive.
  2873  * @param Any aValue
  2874  *        The value to test.
  2875  * @return Boolean
  2876  *         Whether the value is non-primitive.
  2877  */
  2878 function isObject(aValue) {
  2879   const type = typeof aValue;
  2880   return type == "object" ? aValue !== null : type == "function";
  2883 /**
  2884  * Create a function that can safely stringify Debugger.Objects of a given
  2885  * builtin type.
  2887  * @param Function aCtor
  2888  *        The builtin class constructor.
  2889  * @return Function
  2890  *         The stringifier for the class.
  2891  */
  2892 function createBuiltinStringifier(aCtor) {
  2893   return aObj => aCtor.prototype.toString.call(aObj.unsafeDereference());
  2896 /**
  2897  * Stringify a Debugger.Object-wrapped Error instance.
  2899  * @param Debugger.Object aObj
  2900  *        The object to stringify.
  2901  * @return String
  2902  *         The stringification of the object.
  2903  */
  2904 function errorStringify(aObj) {
  2905   let name = DevToolsUtils.getProperty(aObj, "name");
  2906   if (name === "" || name === undefined) {
  2907     name = aObj.class;
  2908   } else if (isObject(name)) {
  2909     name = stringify(name);
  2912   let message = DevToolsUtils.getProperty(aObj, "message");
  2913   if (isObject(message)) {
  2914     message = stringify(message);
  2917   if (message === "" || message === undefined) {
  2918     return name;
  2920   return name + ": " + message;
  2923 /**
  2924  * Stringify a Debugger.Object based on its class.
  2926  * @param Debugger.Object aObj
  2927  *        The object to stringify.
  2928  * @return String
  2929  *         The stringification for the object.
  2930  */
  2931 function stringify(aObj) {
  2932   if (aObj.class == "DeadObject") {
  2933     const error = new Error("Dead object encountered.");
  2934     DevToolsUtils.reportException("stringify", error);
  2935     return "<dead object>";
  2937   const stringifier = stringifiers[aObj.class] || stringifiers.Object;
  2938   return stringifier(aObj);
  2941 // Used to prevent infinite recursion when an array is found inside itself.
  2942 let seen = null;
  2944 let stringifiers = {
  2945   Error: errorStringify,
  2946   EvalError: errorStringify,
  2947   RangeError: errorStringify,
  2948   ReferenceError: errorStringify,
  2949   SyntaxError: errorStringify,
  2950   TypeError: errorStringify,
  2951   URIError: errorStringify,
  2952   Boolean: createBuiltinStringifier(Boolean),
  2953   Function: createBuiltinStringifier(Function),
  2954   Number: createBuiltinStringifier(Number),
  2955   RegExp: createBuiltinStringifier(RegExp),
  2956   String: createBuiltinStringifier(String),
  2957   Object: obj => "[object " + obj.class + "]",
  2958   Array: obj => {
  2959     // If we're at the top level then we need to create the Set for tracking
  2960     // previously stringified arrays.
  2961     const topLevel = !seen;
  2962     if (topLevel) {
  2963       seen = new Set();
  2964     } else if (seen.has(obj)) {
  2965       return "";
  2968     seen.add(obj);
  2970     const len = DevToolsUtils.getProperty(obj, "length");
  2971     let string = "";
  2973     // The following check is only required because the debuggee could possibly
  2974     // be a Proxy and return any value. For normal objects, array.length is
  2975     // always a non-negative integer.
  2976     if (typeof len == "number" && len > 0) {
  2977       for (let i = 0; i < len; i++) {
  2978         const desc = obj.getOwnPropertyDescriptor(i);
  2979         if (desc) {
  2980           const { value } = desc;
  2981           if (value != null) {
  2982             string += isObject(value) ? stringify(value) : value;
  2986         if (i < len - 1) {
  2987           string += ",";
  2992     if (topLevel) {
  2993       seen = null;
  2996     return string;
  2997   },
  2998   DOMException: obj => {
  2999     const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
  3000     const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
  3001     const code = DevToolsUtils.getProperty(obj, "code");
  3002     const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
  3004     return '[Exception... "' + message + '" ' +
  3005            'code: "' + code +'" ' +
  3006            'nsresult: "0x' + result + ' (' + name + ')"]';
  3008 };
  3010 /**
  3011  * Creates an actor for the specified object.
  3013  * @param aObj Debugger.Object
  3014  *        The debuggee object.
  3015  * @param aThreadActor ThreadActor
  3016  *        The parent thread actor for this object.
  3017  */
  3018 function ObjectActor(aObj, aThreadActor)
  3020   dbg_assert(!aObj.optimizedOut, "Should not create object actors for optimized out values!");
  3021   this.obj = aObj;
  3022   this.threadActor = aThreadActor;
  3025 ObjectActor.prototype = {
  3026   actorPrefix: "obj",
  3028   /**
  3029    * Returns a grip for this actor for returning in a protocol message.
  3030    */
  3031   grip: function () {
  3032     this.threadActor._gripDepth++;
  3034     let g = {
  3035       "type": "object",
  3036       "class": this.obj.class,
  3037       "actor": this.actorID,
  3038       "extensible": this.obj.isExtensible(),
  3039       "frozen": this.obj.isFrozen(),
  3040       "sealed": this.obj.isSealed()
  3041     };
  3043     if (this.obj.class != "DeadObject") {
  3044       let raw = Cu.unwaiveXrays(this.obj.unsafeDereference());
  3045       if (!DevToolsUtils.isSafeJSObject(raw)) {
  3046         raw = null;
  3049       let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
  3050                        DebuggerServer.ObjectActorPreviewers.Object;
  3051       for (let fn of previewers) {
  3052         try {
  3053           if (fn(this, g, raw)) {
  3054             break;
  3056         } catch (e) {
  3057           DevToolsUtils.reportException("ObjectActor.prototype.grip previewer function", e);
  3062     this.threadActor._gripDepth--;
  3063     return g;
  3064   },
  3066   /**
  3067    * Releases this actor from the pool.
  3068    */
  3069   release: function () {
  3070     if (this.registeredPool.objectActors) {
  3071       this.registeredPool.objectActors.delete(this.obj);
  3073     this.registeredPool.removeActor(this);
  3074   },
  3076   /**
  3077    * Handle a protocol request to provide the definition site of this function
  3078    * object.
  3080    * @param aRequest object
  3081    *        The protocol request object.
  3082    */
  3083   onDefinitionSite: function OA_onDefinitionSite(aRequest) {
  3084     if (this.obj.class != "Function") {
  3085       return {
  3086         from: this.actorID,
  3087         error: "objectNotFunction",
  3088         message: this.actorID + " is not a function."
  3089       };
  3092     if (!this.obj.script) {
  3093       return {
  3094         from: this.actorID,
  3095         error: "noScript",
  3096         message: this.actorID + " has no Debugger.Script"
  3097       };
  3100     const generatedLocation = {
  3101       url: this.obj.script.url,
  3102       line: this.obj.script.startLine,
  3103       // TODO bug 901138: use Debugger.Script.prototype.startColumn.
  3104       column: 0
  3105     };
  3107     return this.threadActor.sources.getOriginalLocation(generatedLocation)
  3108       .then(({ url, line, column }) => {
  3109         return {
  3110           from: this.actorID,
  3111           url: url,
  3112           line: line,
  3113           column: column
  3114         };
  3115       });
  3116   },
  3118   /**
  3119    * Handle a protocol request to provide the names of the properties defined on
  3120    * the object and not its prototype.
  3122    * @param aRequest object
  3123    *        The protocol request object.
  3124    */
  3125   onOwnPropertyNames: function (aRequest) {
  3126     return { from: this.actorID,
  3127              ownPropertyNames: this.obj.getOwnPropertyNames() };
  3128   },
  3130   /**
  3131    * Handle a protocol request to provide the prototype and own properties of
  3132    * the object.
  3134    * @param aRequest object
  3135    *        The protocol request object.
  3136    */
  3137   onPrototypeAndProperties: function (aRequest) {
  3138     let ownProperties = Object.create(null);
  3139     let names;
  3140     try {
  3141       names = this.obj.getOwnPropertyNames();
  3142     } catch (ex) {
  3143       // The above can throw if this.obj points to a dead object.
  3144       // TODO: we should use Cu.isDeadWrapper() - see bug 885800.
  3145       return { from: this.actorID,
  3146                prototype: this.threadActor.createValueGrip(null),
  3147                ownProperties: ownProperties,
  3148                safeGetterValues: Object.create(null) };
  3150     for (let name of names) {
  3151       ownProperties[name] = this._propertyDescriptor(name);
  3153     return { from: this.actorID,
  3154              prototype: this.threadActor.createValueGrip(this.obj.proto),
  3155              ownProperties: ownProperties,
  3156              safeGetterValues: this._findSafeGetterValues(ownProperties) };
  3157   },
  3159   /**
  3160    * Find the safe getter values for the current Debugger.Object, |this.obj|.
  3162    * @private
  3163    * @param object aOwnProperties
  3164    *        The object that holds the list of known ownProperties for
  3165    *        |this.obj|.
  3166    * @param number [aLimit=0]
  3167    *        Optional limit of getter values to find.
  3168    * @return object
  3169    *         An object that maps property names to safe getter descriptors as
  3170    *         defined by the remote debugging protocol.
  3171    */
  3172   _findSafeGetterValues: function (aOwnProperties, aLimit = 0)
  3174     let safeGetterValues = Object.create(null);
  3175     let obj = this.obj;
  3176     let level = 0, i = 0;
  3178     while (obj) {
  3179       let getters = this._findSafeGetters(obj);
  3180       for (let name of getters) {
  3181         // Avoid overwriting properties from prototypes closer to this.obj. Also
  3182         // avoid providing safeGetterValues from prototypes if property |name|
  3183         // is already defined as an own property.
  3184         if (name in safeGetterValues ||
  3185             (obj != this.obj && name in aOwnProperties)) {
  3186           continue;
  3189         let desc = null, getter = null;
  3190         try {
  3191           desc = obj.getOwnPropertyDescriptor(name);
  3192           getter = desc.get;
  3193         } catch (ex) {
  3194           // The above can throw if the cache becomes stale.
  3196         if (!getter) {
  3197           obj._safeGetters = null;
  3198           continue;
  3201         let result = getter.call(this.obj);
  3202         if (result && !("throw" in result)) {
  3203           let getterValue = undefined;
  3204           if ("return" in result) {
  3205             getterValue = result.return;
  3206           } else if ("yield" in result) {
  3207             getterValue = result.yield;
  3209           // WebIDL attributes specified with the LenientThis extended attribute
  3210           // return undefined and should be ignored.
  3211           if (getterValue !== undefined) {
  3212             safeGetterValues[name] = {
  3213               getterValue: this.threadActor.createValueGrip(getterValue),
  3214               getterPrototypeLevel: level,
  3215               enumerable: desc.enumerable,
  3216               writable: level == 0 ? desc.writable : true,
  3217             };
  3218             if (aLimit && ++i == aLimit) {
  3219               break;
  3224       if (aLimit && i == aLimit) {
  3225         break;
  3228       obj = obj.proto;
  3229       level++;
  3232     return safeGetterValues;
  3233   },
  3235   /**
  3236    * Find the safe getters for a given Debugger.Object. Safe getters are native
  3237    * getters which are safe to execute.
  3239    * @private
  3240    * @param Debugger.Object aObject
  3241    *        The Debugger.Object where you want to find safe getters.
  3242    * @return Set
  3243    *         A Set of names of safe getters. This result is cached for each
  3244    *         Debugger.Object.
  3245    */
  3246   _findSafeGetters: function (aObject)
  3248     if (aObject._safeGetters) {
  3249       return aObject._safeGetters;
  3252     let getters = new Set();
  3253     let names = [];
  3254     try {
  3255       names = aObject.getOwnPropertyNames()
  3256     } catch (ex) {
  3257       // Calling getOwnPropertyNames() on some wrapped native prototypes is not
  3258       // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
  3261     for (let name of names) {
  3262       let desc = null;
  3263       try {
  3264         desc = aObject.getOwnPropertyDescriptor(name);
  3265       } catch (e) {
  3266         // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
  3267         // allowed (bug 560072).
  3269       if (!desc || desc.value !== undefined || !("get" in desc)) {
  3270         continue;
  3273       if (DevToolsUtils.hasSafeGetter(desc)) {
  3274         getters.add(name);
  3278     aObject._safeGetters = getters;
  3279     return getters;
  3280   },
  3282   /**
  3283    * Handle a protocol request to provide the prototype of the object.
  3285    * @param aRequest object
  3286    *        The protocol request object.
  3287    */
  3288   onPrototype: function (aRequest) {
  3289     return { from: this.actorID,
  3290              prototype: this.threadActor.createValueGrip(this.obj.proto) };
  3291   },
  3293   /**
  3294    * Handle a protocol request to provide the property descriptor of the
  3295    * object's specified property.
  3297    * @param aRequest object
  3298    *        The protocol request object.
  3299    */
  3300   onProperty: function (aRequest) {
  3301     if (!aRequest.name) {
  3302       return { error: "missingParameter",
  3303                message: "no property name was specified" };
  3306     return { from: this.actorID,
  3307              descriptor: this._propertyDescriptor(aRequest.name) };
  3308   },
  3310   /**
  3311    * Handle a protocol request to provide the display string for the object.
  3313    * @param aRequest object
  3314    *        The protocol request object.
  3315    */
  3316   onDisplayString: function (aRequest) {
  3317     const string = stringify(this.obj);
  3318     return { from: this.actorID,
  3319              displayString: this.threadActor.createValueGrip(string) };
  3320   },
  3322   /**
  3323    * A helper method that creates a property descriptor for the provided object,
  3324    * properly formatted for sending in a protocol response.
  3326    * @private
  3327    * @param string aName
  3328    *        The property that the descriptor is generated for.
  3329    * @param boolean [aOnlyEnumerable]
  3330    *        Optional: true if you want a descriptor only for an enumerable
  3331    *        property, false otherwise.
  3332    * @return object|undefined
  3333    *         The property descriptor, or undefined if this is not an enumerable
  3334    *         property and aOnlyEnumerable=true.
  3335    */
  3336   _propertyDescriptor: function (aName, aOnlyEnumerable) {
  3337     let desc;
  3338     try {
  3339       desc = this.obj.getOwnPropertyDescriptor(aName);
  3340     } catch (e) {
  3341       // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
  3342       // allowed (bug 560072). Inform the user with a bogus, but hopefully
  3343       // explanatory, descriptor.
  3344       return {
  3345         configurable: false,
  3346         writable: false,
  3347         enumerable: false,
  3348         value: e.name
  3349       };
  3352     if (!desc || aOnlyEnumerable && !desc.enumerable) {
  3353       return undefined;
  3356     let retval = {
  3357       configurable: desc.configurable,
  3358       enumerable: desc.enumerable
  3359     };
  3361     if ("value" in desc) {
  3362       retval.writable = desc.writable;
  3363       retval.value = this.threadActor.createValueGrip(desc.value);
  3364     } else {
  3365       if ("get" in desc) {
  3366         retval.get = this.threadActor.createValueGrip(desc.get);
  3368       if ("set" in desc) {
  3369         retval.set = this.threadActor.createValueGrip(desc.set);
  3372     return retval;
  3373   },
  3375   /**
  3376    * Handle a protocol request to provide the source code of a function.
  3378    * @param aRequest object
  3379    *        The protocol request object.
  3380    */
  3381   onDecompile: function (aRequest) {
  3382     if (this.obj.class !== "Function") {
  3383       return { error: "objectNotFunction",
  3384                message: "decompile request is only valid for object grips " +
  3385                         "with a 'Function' class." };
  3388     return { from: this.actorID,
  3389              decompiledCode: this.obj.decompile(!!aRequest.pretty) };
  3390   },
  3392   /**
  3393    * Handle a protocol request to provide the parameters of a function.
  3395    * @param aRequest object
  3396    *        The protocol request object.
  3397    */
  3398   onParameterNames: function (aRequest) {
  3399     if (this.obj.class !== "Function") {
  3400       return { error: "objectNotFunction",
  3401                message: "'parameterNames' request is only valid for object " +
  3402                         "grips with a 'Function' class." };
  3405     return { parameterNames: this.obj.parameterNames };
  3406   },
  3408   /**
  3409    * Handle a protocol request to release a thread-lifetime grip.
  3411    * @param aRequest object
  3412    *        The protocol request object.
  3413    */
  3414   onRelease: function (aRequest) {
  3415     this.release();
  3416     return {};
  3417   },
  3419   /**
  3420    * Handle a protocol request to provide the lexical scope of a function.
  3422    * @param aRequest object
  3423    *        The protocol request object.
  3424    */
  3425   onScope: function (aRequest) {
  3426     if (this.obj.class !== "Function") {
  3427       return { error: "objectNotFunction",
  3428                message: "scope request is only valid for object grips with a" +
  3429                         " 'Function' class." };
  3432     let envActor = this.threadActor.createEnvironmentActor(this.obj.environment,
  3433                                                            this.registeredPool);
  3434     if (!envActor) {
  3435       return { error: "notDebuggee",
  3436                message: "cannot access the environment of this function." };
  3439     return { from: this.actorID, scope: envActor.form() };
  3441 };
  3443 ObjectActor.prototype.requestTypes = {
  3444   "definitionSite": ObjectActor.prototype.onDefinitionSite,
  3445   "parameterNames": ObjectActor.prototype.onParameterNames,
  3446   "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
  3447   "prototype": ObjectActor.prototype.onPrototype,
  3448   "property": ObjectActor.prototype.onProperty,
  3449   "displayString": ObjectActor.prototype.onDisplayString,
  3450   "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
  3451   "decompile": ObjectActor.prototype.onDecompile,
  3452   "release": ObjectActor.prototype.onRelease,
  3453   "scope": ObjectActor.prototype.onScope,
  3454 };
  3457 /**
  3458  * Functions for adding information to ObjectActor grips for the purpose of
  3459  * having customized output. This object holds arrays mapped by
  3460  * Debugger.Object.prototype.class.
  3462  * In each array you can add functions that take two
  3463  * arguments:
  3464  *   - the ObjectActor instance to make a preview for,
  3465  *   - the grip object being prepared for the client,
  3466  *   - the raw JS object after calling Debugger.Object.unsafeDereference(). This
  3467  *   argument is only provided if the object is safe for reading properties and
  3468  *   executing methods. See DevToolsUtils.isSafeJSObject().
  3470  * Functions must return false if they cannot provide preview
  3471  * information for the debugger object, or true otherwise.
  3472  */
  3473 DebuggerServer.ObjectActorPreviewers = {
  3474   String: [function({obj, threadActor}, aGrip) {
  3475     let result = genericObjectPreviewer("String", String, obj, threadActor);
  3476     if (result) {
  3477       let length = DevToolsUtils.getProperty(obj, "length");
  3478       if (typeof length != "number") {
  3479         return false;
  3482       aGrip.displayString = result.value;
  3483       return true;
  3486     return true;
  3487   }],
  3489   Boolean: [function({obj, threadActor}, aGrip) {
  3490     let result = genericObjectPreviewer("Boolean", Boolean, obj, threadActor);
  3491     if (result) {
  3492       aGrip.preview = result;
  3493       return true;
  3496     return false;
  3497   }],
  3499   Number: [function({obj, threadActor}, aGrip) {
  3500     let result = genericObjectPreviewer("Number", Number, obj, threadActor);
  3501     if (result) {
  3502       aGrip.preview = result;
  3503       return true;
  3506     return false;
  3507   }],
  3509   Function: [function({obj, threadActor}, aGrip) {
  3510     if (obj.name) {
  3511       aGrip.name = obj.name;
  3514     if (obj.displayName) {
  3515       aGrip.displayName = obj.displayName.substr(0, 500);
  3518     if (obj.parameterNames) {
  3519       aGrip.parameterNames = obj.parameterNames;
  3522     // Check if the developer has added a de-facto standard displayName
  3523     // property for us to use.
  3524     let userDisplayName;
  3525     try {
  3526       userDisplayName = obj.getOwnPropertyDescriptor("displayName");
  3527     } catch (e) {
  3528       // Calling getOwnPropertyDescriptor with displayName might throw
  3529       // with "permission denied" errors for some functions.
  3530       dumpn(e);
  3533     if (userDisplayName && typeof userDisplayName.value == "string" &&
  3534         userDisplayName.value) {
  3535       aGrip.userDisplayName = threadActor.createValueGrip(userDisplayName.value);
  3538     return true;
  3539   }],
  3541   RegExp: [function({obj, threadActor}, aGrip) {
  3542     // Avoid having any special preview for the RegExp.prototype itself.
  3543     if (!obj.proto || obj.proto.class != "RegExp") {
  3544       return false;
  3547     let str = RegExp.prototype.toString.call(obj.unsafeDereference());
  3548     aGrip.displayString = threadActor.createValueGrip(str);
  3549     return true;
  3550   }],
  3552   Date: [function({obj, threadActor}, aGrip) {
  3553     if (!obj.proto || obj.proto.class != "Date") {
  3554       return false;
  3557     let time = Date.prototype.getTime.call(obj.unsafeDereference());
  3559     aGrip.preview = {
  3560       timestamp: threadActor.createValueGrip(time),
  3561     };
  3562     return true;
  3563   }],
  3565   Array: [function({obj, threadActor}, aGrip) {
  3566     let length = DevToolsUtils.getProperty(obj, "length");
  3567     if (typeof length != "number") {
  3568       return false;
  3571     aGrip.preview = {
  3572       kind: "ArrayLike",
  3573       length: length,
  3574     };
  3576     if (threadActor._gripDepth > 1) {
  3577       return true;
  3580     let raw = obj.unsafeDereference();
  3581     let items = aGrip.preview.items = [];
  3583     for (let [i, value] of Array.prototype.entries.call(raw)) {
  3584       if (Object.hasOwnProperty.call(raw, i)) {
  3585         value = makeDebuggeeValueIfNeeded(obj, value);
  3586         items.push(threadActor.createValueGrip(value));
  3587       } else {
  3588         items.push(null);
  3591       if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
  3592         break;
  3596     return true;
  3597   }], // Array
  3599   Set: [function({obj, threadActor}, aGrip) {
  3600     let size = DevToolsUtils.getProperty(obj, "size");
  3601     if (typeof size != "number") {
  3602       return false;
  3605     aGrip.preview = {
  3606       kind: "ArrayLike",
  3607       length: size,
  3608     };
  3610     // Avoid recursive object grips.
  3611     if (threadActor._gripDepth > 1) {
  3612       return true;
  3615     let raw = obj.unsafeDereference();
  3616     let items = aGrip.preview.items = [];
  3617     for (let item of Set.prototype.values.call(raw)) {
  3618       item = makeDebuggeeValueIfNeeded(obj, item);
  3619       items.push(threadActor.createValueGrip(item));
  3620       if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
  3621         break;
  3625     return true;
  3626   }], // Set
  3628   Map: [function({obj, threadActor}, aGrip) {
  3629     let size = DevToolsUtils.getProperty(obj, "size");
  3630     if (typeof size != "number") {
  3631       return false;
  3634     aGrip.preview = {
  3635       kind: "MapLike",
  3636       size: size,
  3637     };
  3639     if (threadActor._gripDepth > 1) {
  3640       return true;
  3643     let raw = obj.unsafeDereference();
  3644     let entries = aGrip.preview.entries = [];
  3645     for (let [key, value] of Map.prototype.entries.call(raw)) {
  3646       key = makeDebuggeeValueIfNeeded(obj, key);
  3647       value = makeDebuggeeValueIfNeeded(obj, value);
  3648       entries.push([threadActor.createValueGrip(key),
  3649                     threadActor.createValueGrip(value)]);
  3650       if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
  3651         break;
  3655     return true;
  3656   }], // Map
  3658   DOMStringMap: [function({obj, threadActor}, aGrip, aRawObj) {
  3659     if (!aRawObj) {
  3660       return false;
  3663     let keys = obj.getOwnPropertyNames();
  3664     aGrip.preview = {
  3665       kind: "MapLike",
  3666       size: keys.length,
  3667     };
  3669     if (threadActor._gripDepth > 1) {
  3670       return true;
  3673     let entries = aGrip.preview.entries = [];
  3674     for (let key of keys) {
  3675       let value = makeDebuggeeValueIfNeeded(obj, aRawObj[key]);
  3676       entries.push([key, threadActor.createValueGrip(value)]);
  3677       if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
  3678         break;
  3682     return true;
  3683   }], // DOMStringMap
  3684 }; // DebuggerServer.ObjectActorPreviewers
  3686 /**
  3687  * Generic previewer for "simple" classes like String, Number and Boolean.
  3689  * @param string aClassName
  3690  *        Class name to expect.
  3691  * @param object aClass
  3692  *        The class to expect, eg. String. The valueOf() method of the class is
  3693  *        invoked on the given object.
  3694  * @param Debugger.Object aObj
  3695  *        The debugger object we need to preview.
  3696  * @param object aThreadActor
  3697  *        The thread actor to use to create a value grip.
  3698  * @return object|null
  3699  *         An object with one property, "value", which holds the value grip that
  3700  *         represents the given object. Null is returned if we cant preview the
  3701  *         object.
  3702  */
  3703 function genericObjectPreviewer(aClassName, aClass, aObj, aThreadActor) {
  3704   if (!aObj.proto || aObj.proto.class != aClassName) {
  3705     return null;
  3708   let raw = aObj.unsafeDereference();
  3709   let v = null;
  3710   try {
  3711     v = aClass.prototype.valueOf.call(raw);
  3712   } catch (ex) {
  3713     // valueOf() can throw if the raw JS object is "misbehaved".
  3714     return null;
  3717   if (v !== null) {
  3718     v = aThreadActor.createValueGrip(makeDebuggeeValueIfNeeded(aObj, v));
  3719     return { value: v };
  3722   return null;
  3725 // Preview functions that do not rely on the object class.
  3726 DebuggerServer.ObjectActorPreviewers.Object = [
  3727   function TypedArray({obj, threadActor}, aGrip) {
  3728     if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
  3729       return false;
  3732     let length = DevToolsUtils.getProperty(obj, "length");
  3733     if (typeof length != "number") {
  3734       return false;
  3737     aGrip.preview = {
  3738       kind: "ArrayLike",
  3739       length: length,
  3740     };
  3742     if (threadActor._gripDepth > 1) {
  3743       return true;
  3746     let raw = obj.unsafeDereference();
  3747     let global = Cu.getGlobalForObject(DebuggerServer);
  3748     let classProto = global[obj.class].prototype;
  3749     let safeView = classProto.subarray.call(raw, 0, OBJECT_PREVIEW_MAX_ITEMS);
  3750     let items = aGrip.preview.items = [];
  3751     for (let i = 0; i < safeView.length; i++) {
  3752       items.push(safeView[i]);
  3755     return true;
  3756   },
  3758   function Error({obj, threadActor}, aGrip) {
  3759     switch (obj.class) {
  3760       case "Error":
  3761       case "EvalError":
  3762       case "RangeError":
  3763       case "ReferenceError":
  3764       case "SyntaxError":
  3765       case "TypeError":
  3766       case "URIError":
  3767         let name = DevToolsUtils.getProperty(obj, "name");
  3768         let msg = DevToolsUtils.getProperty(obj, "message");
  3769         let stack = DevToolsUtils.getProperty(obj, "stack");
  3770         let fileName = DevToolsUtils.getProperty(obj, "fileName");
  3771         let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
  3772         let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
  3773         aGrip.preview = {
  3774           kind: "Error",
  3775           name: threadActor.createValueGrip(name),
  3776           message: threadActor.createValueGrip(msg),
  3777           stack: threadActor.createValueGrip(stack),
  3778           fileName: threadActor.createValueGrip(fileName),
  3779           lineNumber: threadActor.createValueGrip(lineNumber),
  3780           columnNumber: threadActor.createValueGrip(columnNumber),
  3781         };
  3782         return true;
  3783       default:
  3784         return false;
  3786   },
  3788   function CSSMediaRule({obj, threadActor}, aGrip, aRawObj) {
  3789     if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSMediaRule)) {
  3790       return false;
  3792     aGrip.preview = {
  3793       kind: "ObjectWithText",
  3794       text: threadActor.createValueGrip(aRawObj.conditionText),
  3795     };
  3796     return true;
  3797   },
  3799   function CSSStyleRule({obj, threadActor}, aGrip, aRawObj) {
  3800     if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleRule)) {
  3801       return false;
  3803     aGrip.preview = {
  3804       kind: "ObjectWithText",
  3805       text: threadActor.createValueGrip(aRawObj.selectorText),
  3806     };
  3807     return true;
  3808   },
  3810   function ObjectWithURL({obj, threadActor}, aGrip, aRawObj) {
  3811     if (!aRawObj ||
  3812         !(aRawObj instanceof Ci.nsIDOMCSSImportRule ||
  3813           aRawObj instanceof Ci.nsIDOMCSSStyleSheet ||
  3814           aRawObj instanceof Ci.nsIDOMLocation ||
  3815           aRawObj instanceof Ci.nsIDOMWindow)) {
  3816       return false;
  3819     let url;
  3820     if (aRawObj instanceof Ci.nsIDOMWindow && aRawObj.location) {
  3821       url = aRawObj.location.href;
  3822     } else if (aRawObj.href) {
  3823       url = aRawObj.href;
  3824     } else {
  3825       return false;
  3828     aGrip.preview = {
  3829       kind: "ObjectWithURL",
  3830       url: threadActor.createValueGrip(url),
  3831     };
  3833     return true;
  3834   },
  3836   function ArrayLike({obj, threadActor}, aGrip, aRawObj) {
  3837     if (!aRawObj ||
  3838         obj.class != "DOMStringList" &&
  3839         obj.class != "DOMTokenList" &&
  3840         !(aRawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
  3841           aRawObj instanceof Ci.nsIDOMCSSRuleList ||
  3842           aRawObj instanceof Ci.nsIDOMCSSValueList ||
  3843           aRawObj instanceof Ci.nsIDOMFileList ||
  3844           aRawObj instanceof Ci.nsIDOMFontFaceList ||
  3845           aRawObj instanceof Ci.nsIDOMMediaList ||
  3846           aRawObj instanceof Ci.nsIDOMNodeList ||
  3847           aRawObj instanceof Ci.nsIDOMStyleSheetList)) {
  3848       return false;
  3851     if (typeof aRawObj.length != "number") {
  3852       return false;
  3855     aGrip.preview = {
  3856       kind: "ArrayLike",
  3857       length: aRawObj.length,
  3858     };
  3860     if (threadActor._gripDepth > 1) {
  3861       return true;
  3864     let items = aGrip.preview.items = [];
  3866     for (let i = 0; i < aRawObj.length &&
  3867                     items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
  3868       let value = makeDebuggeeValueIfNeeded(obj, aRawObj[i]);
  3869       items.push(threadActor.createValueGrip(value));
  3872     return true;
  3873   }, // ArrayLike
  3875   function CSSStyleDeclaration({obj, threadActor}, aGrip, aRawObj) {
  3876     if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
  3877       return false;
  3880     aGrip.preview = {
  3881       kind: "MapLike",
  3882       size: aRawObj.length,
  3883     };
  3885     let entries = aGrip.preview.entries = [];
  3887     for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
  3888                     i < aRawObj.length; i++) {
  3889       let prop = aRawObj[i];
  3890       let value = aRawObj.getPropertyValue(prop);
  3891       entries.push([prop, threadActor.createValueGrip(value)]);
  3894     return true;
  3895   },
  3897   function DOMNode({obj, threadActor}, aGrip, aRawObj) {
  3898     if (obj.class == "Object" || !aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) {
  3899       return false;
  3902     let preview = aGrip.preview = {
  3903       kind: "DOMNode",
  3904       nodeType: aRawObj.nodeType,
  3905       nodeName: aRawObj.nodeName,
  3906     };
  3908     if (aRawObj instanceof Ci.nsIDOMDocument && aRawObj.location) {
  3909       preview.location = threadActor.createValueGrip(aRawObj.location.href);
  3910     } else if (aRawObj instanceof Ci.nsIDOMDocumentFragment) {
  3911       preview.childNodesLength = aRawObj.childNodes.length;
  3913       if (threadActor._gripDepth < 2) {
  3914         preview.childNodes = [];
  3915         for (let node of aRawObj.childNodes) {
  3916           let actor = threadActor.createValueGrip(obj.makeDebuggeeValue(node));
  3917           preview.childNodes.push(actor);
  3918           if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
  3919             break;
  3923     } else if (aRawObj instanceof Ci.nsIDOMElement) {
  3924       // Add preview for DOM element attributes.
  3925       if (aRawObj instanceof Ci.nsIDOMHTMLElement) {
  3926         preview.nodeName = preview.nodeName.toLowerCase();
  3929       let i = 0;
  3930       preview.attributes = {};
  3931       preview.attributesLength = aRawObj.attributes.length;
  3932       for (let attr of aRawObj.attributes) {
  3933         preview.attributes[attr.nodeName] = threadActor.createValueGrip(attr.value);
  3934         if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
  3935           break;
  3938     } else if (aRawObj instanceof Ci.nsIDOMAttr) {
  3939       preview.value = threadActor.createValueGrip(aRawObj.value);
  3940     } else if (aRawObj instanceof Ci.nsIDOMText ||
  3941                aRawObj instanceof Ci.nsIDOMComment) {
  3942       preview.textContent = threadActor.createValueGrip(aRawObj.textContent);
  3945     return true;
  3946   }, // DOMNode
  3948   function DOMEvent({obj, threadActor}, aGrip, aRawObj) {
  3949     if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMEvent)) {
  3950       return false;
  3953     let preview = aGrip.preview = {
  3954       kind: "DOMEvent",
  3955       type: aRawObj.type,
  3956       properties: Object.create(null),
  3957     };
  3959     if (threadActor._gripDepth < 2) {
  3960       let target = obj.makeDebuggeeValue(aRawObj.target);
  3961       preview.target = threadActor.createValueGrip(target);
  3964     let props = [];
  3965     if (aRawObj instanceof Ci.nsIDOMMouseEvent) {
  3966       props.push("buttons", "clientX", "clientY", "layerX", "layerY");
  3967     } else if (aRawObj instanceof Ci.nsIDOMKeyEvent) {
  3968       let modifiers = [];
  3969       if (aRawObj.altKey) {
  3970         modifiers.push("Alt");
  3972       if (aRawObj.ctrlKey) {
  3973         modifiers.push("Control");
  3975       if (aRawObj.metaKey) {
  3976         modifiers.push("Meta");
  3978       if (aRawObj.shiftKey) {
  3979         modifiers.push("Shift");
  3981       preview.eventKind = "key";
  3982       preview.modifiers = modifiers;
  3984       props.push("key", "charCode", "keyCode");
  3985     } else if (aRawObj instanceof Ci.nsIDOMTransitionEvent ||
  3986                aRawObj instanceof Ci.nsIDOMAnimationEvent) {
  3987       props.push("animationName", "pseudoElement");
  3988     } else if (aRawObj instanceof Ci.nsIDOMClipboardEvent) {
  3989       props.push("clipboardData");
  3992     // Add event-specific properties.
  3993     for (let prop of props) {
  3994       let value = aRawObj[prop];
  3995       if (value && (typeof value == "object" || typeof value == "function")) {
  3996         // Skip properties pointing to objects.
  3997         if (threadActor._gripDepth > 1) {
  3998           continue;
  4000         value = obj.makeDebuggeeValue(value);
  4002       preview.properties[prop] = threadActor.createValueGrip(value);
  4005     // Add any properties we find on the event object.
  4006     if (!props.length) {
  4007       let i = 0;
  4008       for (let prop in aRawObj) {
  4009         let value = aRawObj[prop];
  4010         if (prop == "target" || prop == "type" || value === null ||
  4011             typeof value == "function") {
  4012           continue;
  4014         if (value && typeof value == "object") {
  4015           if (threadActor._gripDepth > 1) {
  4016             continue;
  4018           value = obj.makeDebuggeeValue(value);
  4020         preview.properties[prop] = threadActor.createValueGrip(value);
  4021         if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
  4022           break;
  4027     return true;
  4028   }, // DOMEvent
  4030   function DOMException({obj, threadActor}, aGrip, aRawObj) {
  4031     if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMDOMException)) {
  4032       return false;
  4035     aGrip.preview = {
  4036       kind: "DOMException",
  4037       name: threadActor.createValueGrip(aRawObj.name),
  4038       message: threadActor.createValueGrip(aRawObj.message),
  4039       code: threadActor.createValueGrip(aRawObj.code),
  4040       result: threadActor.createValueGrip(aRawObj.result),
  4041       filename: threadActor.createValueGrip(aRawObj.filename),
  4042       lineNumber: threadActor.createValueGrip(aRawObj.lineNumber),
  4043       columnNumber: threadActor.createValueGrip(aRawObj.columnNumber),
  4044     };
  4046     return true;
  4047   },
  4049   function GenericObject(aObjectActor, aGrip) {
  4050     let {obj, threadActor} = aObjectActor;
  4051     if (aGrip.preview || aGrip.displayString || threadActor._gripDepth > 1) {
  4052       return false;
  4055     let i = 0, names = [];
  4056     let preview = aGrip.preview = {
  4057       kind: "Object",
  4058       ownProperties: Object.create(null),
  4059     };
  4061     try {
  4062       names = obj.getOwnPropertyNames();
  4063     } catch (ex) {
  4064       // Calling getOwnPropertyNames() on some wrapped native prototypes is not
  4065       // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
  4068     preview.ownPropertiesLength = names.length;
  4070     for (let name of names) {
  4071       let desc = aObjectActor._propertyDescriptor(name, true);
  4072       if (!desc) {
  4073         continue;
  4076       preview.ownProperties[name] = desc;
  4077       if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
  4078         break;
  4082     if (i < OBJECT_PREVIEW_MAX_ITEMS) {
  4083       preview.safeGetterValues = aObjectActor.
  4084                                  _findSafeGetterValues(preview.ownProperties,
  4085                                                        OBJECT_PREVIEW_MAX_ITEMS - i);
  4088     return true;
  4089   }, // GenericObject
  4090 ]; // DebuggerServer.ObjectActorPreviewers.Object
  4092 /**
  4093  * Creates a pause-scoped actor for the specified object.
  4094  * @see ObjectActor
  4095  */
  4096 function PauseScopedObjectActor()
  4098   ObjectActor.apply(this, arguments);
  4101 PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
  4103 update(PauseScopedObjectActor.prototype, ObjectActor.prototype);
  4105 update(PauseScopedObjectActor.prototype, {
  4106   constructor: PauseScopedObjectActor,
  4107   actorPrefix: "pausedobj",
  4109   onOwnPropertyNames:
  4110     PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
  4112   onPrototypeAndProperties:
  4113     PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
  4115   onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
  4116   onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
  4117   onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
  4119   onDisplayString:
  4120     PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
  4122   onParameterNames:
  4123     PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
  4125   /**
  4126    * Handle a protocol request to promote a pause-lifetime grip to a
  4127    * thread-lifetime grip.
  4129    * @param aRequest object
  4130    *        The protocol request object.
  4131    */
  4132   onThreadGrip: PauseScopedActor.withPaused(function (aRequest) {
  4133     this.threadActor.threadObjectGrip(this);
  4134     return {};
  4135   }),
  4137   /**
  4138    * Handle a protocol request to release a thread-lifetime grip.
  4140    * @param aRequest object
  4141    *        The protocol request object.
  4142    */
  4143   onRelease: PauseScopedActor.withPaused(function (aRequest) {
  4144     if (this.registeredPool !== this.threadActor.threadLifetimePool) {
  4145       return { error: "notReleasable",
  4146                message: "Only thread-lifetime actors can be released." };
  4149     this.release();
  4150     return {};
  4151   }),
  4152 });
  4154 update(PauseScopedObjectActor.prototype.requestTypes, {
  4155   "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
  4156 });
  4159 /**
  4160  * Creates an actor for the specied "very long" string. "Very long" is specified
  4161  * at the server's discretion.
  4163  * @param aString String
  4164  *        The string.
  4165  */
  4166 function LongStringActor(aString)
  4168   this.string = aString;
  4169   this.stringLength = aString.length;
  4172 LongStringActor.prototype = {
  4174   actorPrefix: "longString",
  4176   disconnect: function () {
  4177     // Because longStringActors is not a weak map, we won't automatically leave
  4178     // it so we need to manually leave on disconnect so that we don't leak
  4179     // memory.
  4180     if (this.registeredPool && this.registeredPool.longStringActors) {
  4181       delete this.registeredPool.longStringActors[this.actorID];
  4183   },
  4185   /**
  4186    * Returns a grip for this actor for returning in a protocol message.
  4187    */
  4188   grip: function () {
  4189     return {
  4190       "type": "longString",
  4191       "initial": this.string.substring(
  4192         0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
  4193       "length": this.stringLength,
  4194       "actor": this.actorID
  4195     };
  4196   },
  4198   /**
  4199    * Handle a request to extract part of this actor's string.
  4201    * @param aRequest object
  4202    *        The protocol request object.
  4203    */
  4204   onSubstring: function (aRequest) {
  4205     return {
  4206       "from": this.actorID,
  4207       "substring": this.string.substring(aRequest.start, aRequest.end)
  4208     };
  4209   },
  4211   /**
  4212    * Handle a request to release this LongStringActor instance.
  4213    */
  4214   onRelease: function () {
  4215     // TODO: also check if registeredPool === threadActor.threadLifetimePool
  4216     // when the web console moves aray from manually releasing pause-scoped
  4217     // actors.
  4218     if (this.registeredPool.longStringActors) {
  4219       delete this.registeredPool.longStringActors[this.actorID];
  4221     this.registeredPool.removeActor(this);
  4222     return {};
  4223   },
  4224 };
  4226 LongStringActor.prototype.requestTypes = {
  4227   "substring": LongStringActor.prototype.onSubstring,
  4228   "release": LongStringActor.prototype.onRelease
  4229 };
  4232 /**
  4233  * Creates an actor for the specified stack frame.
  4235  * @param aFrame Debugger.Frame
  4236  *        The debuggee frame.
  4237  * @param aThreadActor ThreadActor
  4238  *        The parent thread actor for this frame.
  4239  */
  4240 function FrameActor(aFrame, aThreadActor)
  4242   this.frame = aFrame;
  4243   this.threadActor = aThreadActor;
  4246 FrameActor.prototype = {
  4247   actorPrefix: "frame",
  4249   /**
  4250    * A pool that contains frame-lifetime objects, like the environment.
  4251    */
  4252   _frameLifetimePool: null,
  4253   get frameLifetimePool() {
  4254     if (!this._frameLifetimePool) {
  4255       this._frameLifetimePool = new ActorPool(this.conn);
  4256       this.conn.addActorPool(this._frameLifetimePool);
  4258     return this._frameLifetimePool;
  4259   },
  4261   /**
  4262    * Finalization handler that is called when the actor is being evicted from
  4263    * the pool.
  4264    */
  4265   disconnect: function () {
  4266     this.conn.removeActorPool(this._frameLifetimePool);
  4267     this._frameLifetimePool = null;
  4268   },
  4270   /**
  4271    * Returns a frame form for use in a protocol message.
  4272    */
  4273   form: function () {
  4274     let form = { actor: this.actorID,
  4275                  type: this.frame.type };
  4276     if (this.frame.type === "call") {
  4277       form.callee = this.threadActor.createValueGrip(this.frame.callee);
  4280     if (this.frame.environment) {
  4281       let envActor = this.threadActor
  4282         .createEnvironmentActor(this.frame.environment,
  4283                                 this.frameLifetimePool);
  4284       form.environment = envActor.form();
  4286     form.this = this.threadActor.createValueGrip(this.frame.this);
  4287     form.arguments = this._args();
  4288     if (this.frame.script) {
  4289       form.where = getFrameLocation(this.frame);
  4292     if (!this.frame.older) {
  4293       form.oldest = true;
  4296     return form;
  4297   },
  4299   _args: function () {
  4300     if (!this.frame.arguments) {
  4301       return [];
  4304     return [this.threadActor.createValueGrip(arg)
  4305             for each (arg in this.frame.arguments)];
  4306   },
  4308   /**
  4309    * Handle a protocol request to pop this frame from the stack.
  4311    * @param aRequest object
  4312    *        The protocol request object.
  4313    */
  4314   onPop: function (aRequest) {
  4315     // TODO: remove this when Debugger.Frame.prototype.pop is implemented
  4316     if (typeof this.frame.pop != "function") {
  4317       return { error: "notImplemented",
  4318                message: "Popping frames is not yet implemented." };
  4321     while (this.frame != this.threadActor.dbg.getNewestFrame()) {
  4322       this.threadActor.dbg.getNewestFrame().pop();
  4324     this.frame.pop(aRequest.completionValue);
  4326     // TODO: return the watches property when frame pop watch actors are
  4327     // implemented.
  4328     return { from: this.actorID };
  4330 };
  4332 FrameActor.prototype.requestTypes = {
  4333   "pop": FrameActor.prototype.onPop,
  4334 };
  4337 /**
  4338  * Creates a BreakpointActor. BreakpointActors exist for the lifetime of their
  4339  * containing thread and are responsible for deleting breakpoints, handling
  4340  * breakpoint hits and associating breakpoints with scripts.
  4342  * @param ThreadActor aThreadActor
  4343  *        The parent thread actor that contains this breakpoint.
  4344  * @param object aLocation
  4345  *        The location of the breakpoint as specified in the protocol.
  4346  */
  4347 function BreakpointActor(aThreadActor, { url, line, column, condition })
  4349   this.scripts = [];
  4350   this.threadActor = aThreadActor;
  4351   this.location = { url: url, line: line, column: column };
  4352   this.condition = condition;
  4355 BreakpointActor.prototype = {
  4356   actorPrefix: "breakpoint",
  4357   condition: null,
  4359   /**
  4360    * Called when this same breakpoint is added to another Debugger.Script
  4361    * instance, in the case of a page reload.
  4363    * @param aScript Debugger.Script
  4364    *        The new source script on which the breakpoint has been set.
  4365    * @param ThreadActor aThreadActor
  4366    *        The parent thread actor that contains this breakpoint.
  4367    */
  4368   addScript: function (aScript, aThreadActor) {
  4369     this.threadActor = aThreadActor;
  4370     this.scripts.push(aScript);
  4371   },
  4373   /**
  4374    * Remove the breakpoints from associated scripts and clear the script cache.
  4375    */
  4376   removeScripts: function () {
  4377     for (let script of this.scripts) {
  4378       script.clearBreakpoint(this);
  4380     this.scripts = [];
  4381   },
  4383   /**
  4384    * Check if this breakpoint has a condition that doesn't error and
  4385    * evaluates to true in aFrame
  4387    * @param aFrame Debugger.Frame
  4388    *        The frame to evaluate the condition in
  4389    */
  4390   isValidCondition: function(aFrame) {
  4391     if(!this.condition) {
  4392       return true;
  4394     var res = aFrame.eval(this.condition);
  4395     return res.return;
  4396   },
  4398   /**
  4399    * A function that the engine calls when a breakpoint has been hit.
  4401    * @param aFrame Debugger.Frame
  4402    *        The stack frame that contained the breakpoint.
  4403    */
  4404   hit: function (aFrame) {
  4405     // Don't pause if we are currently stepping (in or over) or the frame is
  4406     // black-boxed.
  4407     let { url } = this.threadActor.synchronize(
  4408       this.threadActor.sources.getOriginalLocation({
  4409         url: this.location.url,
  4410         line: this.location.line,
  4411         column: this.location.column
  4412       }));
  4414     if (this.threadActor.sources.isBlackBoxed(url)
  4415         || aFrame.onStep
  4416         || !this.isValidCondition(aFrame)) {
  4417       return undefined;
  4420     let reason = {};
  4421     if (this.threadActor._hiddenBreakpoints.has(this.actorID)) {
  4422       reason.type = "pauseOnDOMEvents";
  4423     } else {
  4424       reason.type = "breakpoint";
  4425       // TODO: add the rest of the breakpoints on that line (bug 676602).
  4426       reason.actors = [ this.actorID ];
  4428     return this.threadActor._pauseAndRespond(aFrame, reason);
  4429   },
  4431   /**
  4432    * Handle a protocol request to remove this breakpoint.
  4434    * @param aRequest object
  4435    *        The protocol request object.
  4436    */
  4437   onDelete: function (aRequest) {
  4438     // Remove from the breakpoint store.
  4439     this.threadActor.breakpointStore.removeBreakpoint(this.location);
  4440     this.threadActor.threadLifetimePool.removeActor(this);
  4441     // Remove the actual breakpoint from the associated scripts.
  4442     this.removeScripts();
  4443     return { from: this.actorID };
  4445 };
  4447 BreakpointActor.prototype.requestTypes = {
  4448   "delete": BreakpointActor.prototype.onDelete
  4449 };
  4452 /**
  4453  * Creates an EnvironmentActor. EnvironmentActors are responsible for listing
  4454  * the bindings introduced by a lexical environment and assigning new values to
  4455  * those identifier bindings.
  4457  * @param Debugger.Environment aEnvironment
  4458  *        The lexical environment that will be used to create the actor.
  4459  * @param ThreadActor aThreadActor
  4460  *        The parent thread actor that contains this environment.
  4461  */
  4462 function EnvironmentActor(aEnvironment, aThreadActor)
  4464   this.obj = aEnvironment;
  4465   this.threadActor = aThreadActor;
  4468 EnvironmentActor.prototype = {
  4469   actorPrefix: "environment",
  4471   /**
  4472    * Return an environment form for use in a protocol message.
  4473    */
  4474   form: function () {
  4475     let form = { actor: this.actorID };
  4477     // What is this environment's type?
  4478     if (this.obj.type == "declarative") {
  4479       form.type = this.obj.callee ? "function" : "block";
  4480     } else {
  4481       form.type = this.obj.type;
  4484     // Does this environment have a parent?
  4485     if (this.obj.parent) {
  4486       form.parent = (this.threadActor
  4487                      .createEnvironmentActor(this.obj.parent,
  4488                                              this.registeredPool)
  4489                      .form());
  4492     // Does this environment reflect the properties of an object as variables?
  4493     if (this.obj.type == "object" || this.obj.type == "with") {
  4494       form.object = this.threadActor.createValueGrip(this.obj.object);
  4497     // Is this the environment created for a function call?
  4498     if (this.obj.callee) {
  4499       form.function = this.threadActor.createValueGrip(this.obj.callee);
  4502     // Shall we list this environment's bindings?
  4503     if (this.obj.type == "declarative") {
  4504       form.bindings = this._bindings();
  4507     return form;
  4508   },
  4510   /**
  4511    * Return the identifier bindings object as required by the remote protocol
  4512    * specification.
  4513    */
  4514   _bindings: function () {
  4515     let bindings = { arguments: [], variables: {} };
  4517     // TODO: this part should be removed in favor of the commented-out part
  4518     // below when getVariableDescriptor lands (bug 725815).
  4519     if (typeof this.obj.getVariable != "function") {
  4520     //if (typeof this.obj.getVariableDescriptor != "function") {
  4521       return bindings;
  4524     let parameterNames;
  4525     if (this.obj.callee) {
  4526       parameterNames = this.obj.callee.parameterNames;
  4528     for each (let name in parameterNames) {
  4529       let arg = {};
  4531       let value = this.obj.getVariable(name);
  4532       // The slot is optimized out.
  4533       // FIXME: Need actual UI, bug 941287.
  4534       if (value && value.optimizedOut) {
  4535         continue;
  4538       // TODO: this part should be removed in favor of the commented-out part
  4539       // below when getVariableDescriptor lands (bug 725815).
  4540       let desc = {
  4541         value: value,
  4542         configurable: false,
  4543         writable: true,
  4544         enumerable: true
  4545       };
  4547       // let desc = this.obj.getVariableDescriptor(name);
  4548       let descForm = {
  4549         enumerable: true,
  4550         configurable: desc.configurable
  4551       };
  4552       if ("value" in desc) {
  4553         descForm.value = this.threadActor.createValueGrip(desc.value);
  4554         descForm.writable = desc.writable;
  4555       } else {
  4556         descForm.get = this.threadActor.createValueGrip(desc.get);
  4557         descForm.set = this.threadActor.createValueGrip(desc.set);
  4559       arg[name] = descForm;
  4560       bindings.arguments.push(arg);
  4563     for each (let name in this.obj.names()) {
  4564       if (bindings.arguments.some(function exists(element) {
  4565                                     return !!element[name];
  4566                                   })) {
  4567         continue;
  4570       let value = this.obj.getVariable(name);
  4571       // The slot is optimized out or arguments on a dead scope.
  4572       // FIXME: Need actual UI, bug 941287.
  4573       if (value && (value.optimizedOut || value.missingArguments)) {
  4574         continue;
  4577       // TODO: this part should be removed in favor of the commented-out part
  4578       // below when getVariableDescriptor lands.
  4579       let desc = {
  4580         value: value,
  4581         configurable: false,
  4582         writable: true,
  4583         enumerable: true
  4584       };
  4586       //let desc = this.obj.getVariableDescriptor(name);
  4587       let descForm = {
  4588         enumerable: true,
  4589         configurable: desc.configurable
  4590       };
  4591       if ("value" in desc) {
  4592         descForm.value = this.threadActor.createValueGrip(desc.value);
  4593         descForm.writable = desc.writable;
  4594       } else {
  4595         descForm.get = this.threadActor.createValueGrip(desc.get);
  4596         descForm.set = this.threadActor.createValueGrip(desc.set);
  4598       bindings.variables[name] = descForm;
  4601     return bindings;
  4602   },
  4604   /**
  4605    * Handle a protocol request to change the value of a variable bound in this
  4606    * lexical environment.
  4608    * @param aRequest object
  4609    *        The protocol request object.
  4610    */
  4611   onAssign: function (aRequest) {
  4612     // TODO: enable the commented-out part when getVariableDescriptor lands
  4613     // (bug 725815).
  4614     /*let desc = this.obj.getVariableDescriptor(aRequest.name);
  4616     if (!desc.writable) {
  4617       return { error: "immutableBinding",
  4618                message: "Changing the value of an immutable binding is not " +
  4619                         "allowed" };
  4620     }*/
  4622     try {
  4623       this.obj.setVariable(aRequest.name, aRequest.value);
  4624     } catch (e if e instanceof Debugger.DebuggeeWouldRun) {
  4625         return { error: "threadWouldRun",
  4626                  cause: e.cause ? e.cause : "setter",
  4627                  message: "Assigning a value would cause the debuggee to run" };
  4629     return { from: this.actorID };
  4630   },
  4632   /**
  4633    * Handle a protocol request to fully enumerate the bindings introduced by the
  4634    * lexical environment.
  4636    * @param aRequest object
  4637    *        The protocol request object.
  4638    */
  4639   onBindings: function (aRequest) {
  4640     return { from: this.actorID,
  4641              bindings: this._bindings() };
  4643 };
  4645 EnvironmentActor.prototype.requestTypes = {
  4646   "assign": EnvironmentActor.prototype.onAssign,
  4647   "bindings": EnvironmentActor.prototype.onBindings
  4648 };
  4650 /**
  4651  * Override the toString method in order to get more meaningful script output
  4652  * for debugging the debugger.
  4653  */
  4654 Debugger.Script.prototype.toString = function() {
  4655   let output = "";
  4656   if (this.url) {
  4657     output += this.url;
  4659   if (typeof this.startLine != "undefined") {
  4660     output += ":" + this.startLine;
  4661     if (this.lineCount && this.lineCount > 1) {
  4662       output += "-" + (this.startLine + this.lineCount - 1);
  4665   if (this.strictMode) {
  4666     output += ":strict";
  4668   return output;
  4669 };
  4671 /**
  4672  * Helper property for quickly getting to the line number a stack frame is
  4673  * currently paused at.
  4674  */
  4675 Object.defineProperty(Debugger.Frame.prototype, "line", {
  4676   configurable: true,
  4677   get: function() {
  4678     if (this.script) {
  4679       return this.script.getOffsetLine(this.offset);
  4680     } else {
  4681       return null;
  4684 });
  4687 /**
  4688  * Creates an actor for handling chrome debugging. ChromeDebuggerActor is a
  4689  * thin wrapper over ThreadActor, slightly changing some of its behavior.
  4691  * @param aConnection object
  4692  *        The DebuggerServerConnection with which this ChromeDebuggerActor
  4693  *        is associated. (Currently unused, but required to make this
  4694  *        constructor usable with addGlobalActor.)
  4696  * @param aHooks object
  4697  *        An object with preNest and postNest methods for calling when entering
  4698  *        and exiting a nested event loop.
  4699  */
  4700 function ChromeDebuggerActor(aConnection, aHooks)
  4702   ThreadActor.call(this, aHooks);
  4705 ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype);
  4707 update(ChromeDebuggerActor.prototype, {
  4708   constructor: ChromeDebuggerActor,
  4710   // A constant prefix that will be used to form the actor ID by the server.
  4711   actorPrefix: "chromeDebugger",
  4713   /**
  4714    * Override the eligibility check for scripts and sources to make sure every
  4715    * script and source with a URL is stored when debugging chrome.
  4716    */
  4717   _allowSource: function(aSourceURL) !!aSourceURL,
  4719    /**
  4720    * An object that will be used by ThreadActors to tailor their behavior
  4721    * depending on the debugging context being required (chrome or content).
  4722    * The methods that this object provides must be bound to the ThreadActor
  4723    * before use.
  4724    */
  4725   globalManager: {
  4726     findGlobals: function () {
  4727       // Add every global known to the debugger as debuggee.
  4728       this.dbg.addAllGlobalsAsDebuggees();
  4729     },
  4731     /**
  4732      * A function that the engine calls when a new global object has been
  4733      * created.
  4735      * @param aGlobal Debugger.Object
  4736      *        The new global object that was created.
  4737      */
  4738     onNewGlobal: function (aGlobal) {
  4739       this.addDebuggee(aGlobal);
  4740       // Notify the client.
  4741       this.conn.send({
  4742         from: this.actorID,
  4743         type: "newGlobal",
  4744         // TODO: after bug 801084 lands see if we need to JSONify this.
  4745         hostAnnotations: aGlobal.hostAnnotations
  4746       });
  4749 });
  4751 /**
  4752  * Creates an actor for handling add-on debugging. AddonThreadActor is
  4753  * a thin wrapper over ThreadActor.
  4755  * @param aConnection object
  4756  *        The DebuggerServerConnection with which this AddonThreadActor
  4757  *        is associated. (Currently unused, but required to make this
  4758  *        constructor usable with addGlobalActor.)
  4760  * @param aHooks object
  4761  *        An object with preNest and postNest methods for calling
  4762  *        when entering and exiting a nested event loops.
  4764  * @param aAddonID string
  4765  *        ID of the add-on this actor will debug. It will be used to
  4766  *        filter out globals marked for debugging.
  4767  */
  4769 function AddonThreadActor(aConnect, aHooks, aAddonID) {
  4770   this.addonID = aAddonID;
  4771   ThreadActor.call(this, aHooks);
  4774 AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
  4776 update(AddonThreadActor.prototype, {
  4777   constructor: AddonThreadActor,
  4779   // A constant prefix that will be used to form the actor ID by the server.
  4780   actorPrefix: "addonThread",
  4782   onAttach: function(aRequest) {
  4783     if (!this.attached) {
  4784       Services.obs.addObserver(this, "chrome-document-global-created", false);
  4785       Services.obs.addObserver(this, "content-document-global-created", false);
  4787     return ThreadActor.prototype.onAttach.call(this, aRequest);
  4788   },
  4790   disconnect: function() {
  4791     if (this.attached) {
  4792       Services.obs.removeObserver(this, "content-document-global-created");
  4793       Services.obs.removeObserver(this, "chrome-document-global-created");
  4795     return ThreadActor.prototype.disconnect.call(this);
  4796   },
  4798   /**
  4799    * Called when a new DOM document global is created. Check if the DOM was
  4800    * loaded from an add-on and if so make the window a debuggee.
  4801    */
  4802   observe: function(aSubject, aTopic, aData) {
  4803     let id = {};
  4804     if (mapURIToAddonID(aSubject.location, id) && id.value === this.addonID) {
  4805       this.dbg.addDebuggee(aSubject.defaultView);
  4807   },
  4809   /**
  4810    * Override the eligibility check for scripts and sources to make
  4811    * sure every script and source with a URL is stored when debugging
  4812    * add-ons.
  4813    */
  4814   _allowSource: function(aSourceURL) {
  4815     // Hide eval scripts
  4816     if (!aSourceURL) {
  4817       return false;
  4820     // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it
  4821     if (aSourceURL == "resource://gre/modules/addons/XPIProvider.jsm") {
  4822       return false;
  4825     return true;
  4826   },
  4828   /**
  4829    * An object that will be used by ThreadActors to tailor their
  4830    * behaviour depending on the debugging context being required (chrome,
  4831    * addon or content). The methods that this object provides must
  4832    * be bound to the ThreadActor before use.
  4833    */
  4834   globalManager: {
  4835     findGlobals: function ADA_findGlobals() {
  4836       for (let global of this.dbg.findAllGlobals()) {
  4837         if (this._checkGlobal(global)) {
  4838           this.dbg.addDebuggee(global);
  4841     },
  4843     /**
  4844      * A function that the engine calls when a new global object
  4845      * has been created.
  4847      * @param aGlobal Debugger.Object
  4848      *        The new global object that was created.
  4849      */
  4850     onNewGlobal: function ADA_onNewGlobal(aGlobal) {
  4851       if (this._checkGlobal(aGlobal)) {
  4852         this.addDebuggee(aGlobal);
  4853         // Notify the client.
  4854         this.conn.send({
  4855           from: this.actorID,
  4856           type: "newGlobal",
  4857           // TODO: after bug 801084 lands see if we need to JSONify this.
  4858           hostAnnotations: aGlobal.hostAnnotations
  4859         });
  4862   },
  4864   /**
  4865    * Checks if the provided global belongs to the debugged add-on.
  4867    * @param aGlobal Debugger.Object
  4868    */
  4869   _checkGlobal: function ADA_checkGlobal(aGlobal) {
  4870     let obj = null;
  4871     try {
  4872       obj = aGlobal.unsafeDereference();
  4874     catch (e) {
  4875       // Because of bug 991399 we sometimes get bad objects here. If we can't
  4876       // dereference them then they won't be useful to us
  4877       return false;
  4880     try {
  4881       // This will fail for non-Sandbox objects, hence the try-catch block.
  4882       let metadata = Cu.getSandboxMetadata(obj);
  4883       if (metadata) {
  4884         return metadata.addonID === this.addonID;
  4886     } catch (e) {
  4889     if (obj instanceof Ci.nsIDOMWindow) {
  4890       let id = {};
  4891       if (mapURIToAddonID(obj.document.documentURIObject, id)) {
  4892         return id.value === this.addonID;
  4894       return false;
  4897     // Check the global for a __URI__ property and then try to map that to an
  4898     // add-on
  4899     let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
  4900     if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
  4901       let uri;
  4902       try {
  4903         uri = Services.io.newURI(uridescriptor.value, null, null);
  4905       catch (e) {
  4906         DevToolsUtils.reportException("AddonThreadActor.prototype._checkGlobal",
  4907                                       new Error("Invalid URI: " + uridescriptor.value));
  4908         return false;
  4911       let id = {};
  4912       if (mapURIToAddonID(uri, id)) {
  4913         return id.value === this.addonID;
  4917     return false;
  4919 });
  4921 AddonThreadActor.prototype.requestTypes = Object.create(ThreadActor.prototype.requestTypes);
  4922 update(AddonThreadActor.prototype.requestTypes, {
  4923   "attach": AddonThreadActor.prototype.onAttach
  4924 });
  4926 /**
  4927  * Manages the sources for a thread. Handles source maps, locations in the
  4928  * sources, etc for ThreadActors.
  4929  */
  4930 function ThreadSources(aThreadActor, aUseSourceMaps, aAllowPredicate,
  4931                        aOnNewSource) {
  4932   this._thread = aThreadActor;
  4933   this._useSourceMaps = aUseSourceMaps;
  4934   this._allow = aAllowPredicate;
  4935   this._onNewSource = aOnNewSource;
  4937   // generated source url --> promise of SourceMapConsumer
  4938   this._sourceMapsByGeneratedSource = Object.create(null);
  4939   // original source url --> promise of SourceMapConsumer
  4940   this._sourceMapsByOriginalSource = Object.create(null);
  4941   // source url --> SourceActor
  4942   this._sourceActors = Object.create(null);
  4943   // original url --> generated url
  4944   this._generatedUrlsByOriginalUrl = Object.create(null);
  4947 /**
  4948  * Must be a class property because it needs to persist across reloads, same as
  4949  * the breakpoint store.
  4950  */
  4951 ThreadSources._blackBoxedSources = new Set(["self-hosted"]);
  4952 ThreadSources._prettyPrintedSources = new Map();
  4954 ThreadSources.prototype = {
  4955   /**
  4956    * Return the source actor representing |url|, creating one if none
  4957    * exists already. Returns null if |url| is not allowed by the 'allow'
  4958    * predicate.
  4960    * Right now this takes a URL, but in the future it should
  4961    * take a Debugger.Source. See bug 637572.
  4963    * @param String url
  4964    *        The source URL.
  4965    * @param optional SourceMapConsumer sourceMap
  4966    *        The source map that introduced this source, if any.
  4967    * @param optional String generatedSource
  4968    *        The generated source url that introduced this source via source map,
  4969    *        if any.
  4970    * @param optional String text
  4971    *        The text content of the source, if immediately available.
  4972    * @param optional String contentType
  4973    *        The content type of the source, if immediately available.
  4974    * @returns a SourceActor representing the source at aURL or null.
  4975    */
  4976   source: function ({ url, sourceMap, generatedSource, text, contentType }) {
  4977     if (!this._allow(url)) {
  4978       return null;
  4981     if (url in this._sourceActors) {
  4982       return this._sourceActors[url];
  4985     let actor = new SourceActor({
  4986       url: url,
  4987       thread: this._thread,
  4988       sourceMap: sourceMap,
  4989       generatedSource: generatedSource,
  4990       text: text,
  4991       contentType: contentType
  4992     });
  4993     this._thread.threadLifetimePool.addActor(actor);
  4994     this._sourceActors[url] = actor;
  4995     try {
  4996       this._onNewSource(actor);
  4997     } catch (e) {
  4998       reportError(e);
  5000     return actor;
  5001   },
  5003   /**
  5004    * Only to be used when we aren't source mapping.
  5005    */
  5006   _sourceForScript: function (aScript) {
  5007     const spec = {
  5008       url: aScript.url
  5009     };
  5011     // XXX bug 915433: We can't rely on Debugger.Source.prototype.text if the
  5012     // source is an HTML-embedded <script> tag. Since we don't have an API
  5013     // implemented to detect whether this is the case, we need to be
  5014     // conservative and only use Debugger.Source.prototype.text if we get a
  5015     // normal .js file.
  5016     if (aScript.url) {
  5017       try {
  5018         const url = Services.io.newURI(aScript.url, null, null)
  5019           .QueryInterface(Ci.nsIURL);
  5020         if (url.fileExtension === "js") {
  5021           spec.contentType = "text/javascript";
  5022           spec.text = aScript.source.text;
  5024       } catch(ex) {
  5025         // Not a valid URI.
  5029     return this.source(spec);
  5030   },
  5032   /**
  5033    * Return a promise of an array of source actors representing all the
  5034    * sources of |aScript|.
  5036    * If source map handling is enabled and |aScript| has a source map, then
  5037    * use it to find all of |aScript|'s *original* sources; return a promise
  5038    * of an array of source actors for those.
  5039    */
  5040   sourcesForScript: function (aScript) {
  5041     if (!this._useSourceMaps || !aScript.sourceMapURL) {
  5042       return resolve([this._sourceForScript(aScript)].filter(isNotNull));
  5045     return this.sourceMap(aScript)
  5046       .then((aSourceMap) => {
  5047         return [
  5048           this.source({ url: s,
  5049                         sourceMap: aSourceMap,
  5050                         generatedSource: aScript.url })
  5051           for (s of aSourceMap.sources)
  5052         ];
  5053       })
  5054       .then(null, (e) => {
  5055         reportError(e);
  5056         delete this._sourceMapsByGeneratedSource[aScript.url];
  5057         return [this._sourceForScript(aScript)];
  5058       })
  5059       .then(ss => ss.filter(isNotNull));
  5060   },
  5062   /**
  5063    * Return a promise of a SourceMapConsumer for the source map for
  5064    * |aScript|; if we already have such a promise extant, return that.
  5065    * |aScript| must have a non-null sourceMapURL.
  5066    */
  5067   sourceMap: function (aScript) {
  5068     dbg_assert(aScript.sourceMapURL, "Script should have a sourceMapURL");
  5069     let sourceMapURL = this._normalize(aScript.sourceMapURL, aScript.url);
  5070     let map = this._fetchSourceMap(sourceMapURL, aScript.url)
  5071       .then(aSourceMap => this.saveSourceMap(aSourceMap, aScript.url));
  5072     this._sourceMapsByGeneratedSource[aScript.url] = map;
  5073     return map;
  5074   },
  5076   /**
  5077    * Save the given source map so that we can use it to query source locations
  5078    * down the line.
  5079    */
  5080   saveSourceMap: function (aSourceMap, aGeneratedSource) {
  5081     if (!aSourceMap) {
  5082       delete this._sourceMapsByGeneratedSource[aGeneratedSource];
  5083       return null;
  5085     this._sourceMapsByGeneratedSource[aGeneratedSource] = resolve(aSourceMap);
  5086     for (let s of aSourceMap.sources) {
  5087       this._generatedUrlsByOriginalUrl[s] = aGeneratedSource;
  5088       this._sourceMapsByOriginalSource[s] = resolve(aSourceMap);
  5090     return aSourceMap;
  5091   },
  5093   /**
  5094    * Return a promise of a SourceMapConsumer for the source map located at
  5095    * |aAbsSourceMapURL|, which must be absolute. If there is already such a
  5096    * promise extant, return it.
  5098    * @param string aAbsSourceMapURL
  5099    *        The source map URL, in absolute form, not relative.
  5100    * @param string aScriptURL
  5101    *        When the source map URL is a data URI, there is no sourceRoot on the
  5102    *        source map, and the source map's sources are relative, we resolve
  5103    *        them from aScriptURL.
  5104    */
  5105   _fetchSourceMap: function (aAbsSourceMapURL, aScriptURL) {
  5106     return fetch(aAbsSourceMapURL, { loadFromCache: false })
  5107       .then(({ content }) => {
  5108         let map = new SourceMapConsumer(content);
  5109         this._setSourceMapRoot(map, aAbsSourceMapURL, aScriptURL);
  5110         return map;
  5111       });
  5112   },
  5114   /**
  5115    * Sets the source map's sourceRoot to be relative to the source map url.
  5116    */
  5117   _setSourceMapRoot: function (aSourceMap, aAbsSourceMapURL, aScriptURL) {
  5118     const base = this._dirname(
  5119       aAbsSourceMapURL.indexOf("data:") === 0
  5120         ? aScriptURL
  5121         : aAbsSourceMapURL);
  5122     aSourceMap.sourceRoot = aSourceMap.sourceRoot
  5123       ? this._normalize(aSourceMap.sourceRoot, base)
  5124       : base;
  5125   },
  5127   _dirname: function (aPath) {
  5128     return Services.io.newURI(
  5129       ".", null, Services.io.newURI(aPath, null, null)).spec;
  5130   },
  5132   /**
  5133    * Returns a promise of the location in the original source if the source is
  5134    * source mapped, otherwise a promise of the same location.
  5135    */
  5136   getOriginalLocation: function ({ url, line, column }) {
  5137     if (url in this._sourceMapsByGeneratedSource) {
  5138       column = column || 0;
  5140       return this._sourceMapsByGeneratedSource[url]
  5141         .then((aSourceMap) => {
  5142           let { source: aSourceURL, line: aLine, column: aColumn } = aSourceMap.originalPositionFor({
  5143             line: line,
  5144             column: column
  5145           });
  5146           return {
  5147             url: aSourceURL,
  5148             line: aLine,
  5149             column: aColumn
  5150           };
  5151         })
  5152         .then(null, error => {
  5153           if (!DevToolsUtils.reportingDisabled) {
  5154             DevToolsUtils.reportException("ThreadSources.prototype.getOriginalLocation", error);
  5156           return { url: null, line: null, column: null };
  5157         });
  5160     // No source map
  5161     return resolve({
  5162       url: url,
  5163       line: line,
  5164       column: column
  5165     });
  5166   },
  5168   /**
  5169    * Returns a promise of the location in the generated source corresponding to
  5170    * the original source and line given.
  5172    * When we pass a script S representing generated code to |sourceMap|,
  5173    * above, that returns a promise P. The process of resolving P populates
  5174    * the tables this function uses; thus, it won't know that S's original
  5175    * source URLs map to S until P is resolved.
  5176    */
  5177   getGeneratedLocation: function ({ url, line, column }) {
  5178     if (url in this._sourceMapsByOriginalSource) {
  5179       return this._sourceMapsByOriginalSource[url]
  5180         .then((aSourceMap) => {
  5181           let { line: aLine, column: aColumn } = aSourceMap.generatedPositionFor({
  5182             source: url,
  5183             line: line,
  5184             column: column == null ? Infinity : column
  5185           });
  5186           return {
  5187             url: this._generatedUrlsByOriginalUrl[url],
  5188             line: aLine,
  5189             column: aColumn
  5190           };
  5191         });
  5194     // No source map
  5195     return resolve({
  5196       url: url,
  5197       line: line,
  5198       column: column
  5199     });
  5200   },
  5202   /**
  5203    * Returns true if URL for the given source is black boxed.
  5205    * @param aURL String
  5206    *        The URL of the source which we are checking whether it is black
  5207    *        boxed or not.
  5208    */
  5209   isBlackBoxed: function (aURL) {
  5210     return ThreadSources._blackBoxedSources.has(aURL);
  5211   },
  5213   /**
  5214    * Add the given source URL to the set of sources that are black boxed.
  5216    * @param aURL String
  5217    *        The URL of the source which we are black boxing.
  5218    */
  5219   blackBox: function (aURL) {
  5220     ThreadSources._blackBoxedSources.add(aURL);
  5221   },
  5223   /**
  5224    * Remove the given source URL to the set of sources that are black boxed.
  5226    * @param aURL String
  5227    *        The URL of the source which we are no longer black boxing.
  5228    */
  5229   unblackBox: function (aURL) {
  5230     ThreadSources._blackBoxedSources.delete(aURL);
  5231   },
  5233   /**
  5234    * Returns true if the given URL is pretty printed.
  5236    * @param aURL String
  5237    *        The URL of the source that might be pretty printed.
  5238    */
  5239   isPrettyPrinted: function (aURL) {
  5240     return ThreadSources._prettyPrintedSources.has(aURL);
  5241   },
  5243   /**
  5244    * Add the given URL to the set of sources that are pretty printed.
  5246    * @param aURL String
  5247    *        The URL of the source to be pretty printed.
  5248    */
  5249   prettyPrint: function (aURL, aIndent) {
  5250     ThreadSources._prettyPrintedSources.set(aURL, aIndent);
  5251   },
  5253   /**
  5254    * Return the indent the given URL was pretty printed by.
  5255    */
  5256   prettyPrintIndent: function (aURL) {
  5257     return ThreadSources._prettyPrintedSources.get(aURL);
  5258   },
  5260   /**
  5261    * Remove the given URL from the set of sources that are pretty printed.
  5263    * @param aURL String
  5264    *        The URL of the source that is no longer pretty printed.
  5265    */
  5266   disablePrettyPrint: function (aURL) {
  5267     ThreadSources._prettyPrintedSources.delete(aURL);
  5268   },
  5270   /**
  5271    * Normalize multiple relative paths towards the base paths on the right.
  5272    */
  5273   _normalize: function (...aURLs) {
  5274     dbg_assert(aURLs.length > 1, "Should have more than 1 URL");
  5275     let base = Services.io.newURI(aURLs.pop(), null, null);
  5276     let url;
  5277     while ((url = aURLs.pop())) {
  5278       base = Services.io.newURI(url, null, base);
  5280     return base.spec;
  5281   },
  5283   iter: function* () {
  5284     for (let url in this._sourceActors) {
  5285       yield this._sourceActors[url];
  5288 };
  5290 // Utility functions.
  5292 // TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
  5293 // implemented.
  5294 function getOffsetColumn(aOffset, aScript) {
  5295   let bestOffsetMapping = null;
  5296   for (let offsetMapping of aScript.getAllColumnOffsets()) {
  5297     if (!bestOffsetMapping ||
  5298         (offsetMapping.offset <= aOffset &&
  5299          offsetMapping.offset > bestOffsetMapping.offset)) {
  5300       bestOffsetMapping = offsetMapping;
  5304   if (!bestOffsetMapping) {
  5305     // XXX: Try not to completely break the experience of using the debugger for
  5306     // the user by assuming column 0. Simultaneously, report the error so that
  5307     // there is a paper trail if the assumption is bad and the debugging
  5308     // experience becomes wonky.
  5309     reportError(new Error("Could not find a column for offset " + aOffset
  5310                           + " in the script " + aScript));
  5311     return 0;
  5314   return bestOffsetMapping.columnNumber;
  5317 /**
  5318  * Return the non-source-mapped location of the given Debugger.Frame. If the
  5319  * frame does not have a script, the location's properties are all null.
  5321  * @param Debugger.Frame aFrame
  5322  *        The frame whose location we are getting.
  5323  * @returns Object
  5324  *          Returns an object of the form { url, line, column }
  5325  */
  5326 function getFrameLocation(aFrame) {
  5327   if (!aFrame || !aFrame.script) {
  5328     return { url: null, line: null, column: null };
  5330   return {
  5331     url: aFrame.script.url,
  5332     line: aFrame.script.getOffsetLine(aFrame.offset),
  5333     column: getOffsetColumn(aFrame.offset, aFrame.script)
  5337 /**
  5338  * Utility function for updating an object with the properties of another
  5339  * object.
  5341  * @param aTarget Object
  5342  *        The object being updated.
  5343  * @param aNewAttrs Object
  5344  *        The new attributes being set on the target.
  5345  */
  5346 function update(aTarget, aNewAttrs) {
  5347   for (let key in aNewAttrs) {
  5348     let desc = Object.getOwnPropertyDescriptor(aNewAttrs, key);
  5350     if (desc) {
  5351       Object.defineProperty(aTarget, key, desc);
  5356 /**
  5357  * Returns true if its argument is not null.
  5358  */
  5359 function isNotNull(aThing) {
  5360   return aThing !== null;
  5363 /**
  5364  * Performs a request to load the desired URL and returns a promise.
  5366  * @param aURL String
  5367  *        The URL we will request.
  5368  * @returns Promise
  5369  *        A promise of the document at that URL, as a string.
  5371  * XXX: It may be better to use nsITraceableChannel to get to the sources
  5372  * without relying on caching when we can (not for eval, etc.):
  5373  * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
  5374  */
  5375 function fetch(aURL, aOptions={ loadFromCache: true }) {
  5376   let deferred = defer();
  5377   let scheme;
  5378   let url = aURL.split(" -> ").pop();
  5379   let charset;
  5380   let contentType;
  5382   try {
  5383     scheme = Services.io.extractScheme(url);
  5384   } catch (e) {
  5385     // In the xpcshell tests, the script url is the absolute path of the test
  5386     // file, which will make a malformed URI error be thrown. Add the file
  5387     // scheme prefix ourselves.
  5388     url = "file://" + url;
  5389     scheme = Services.io.extractScheme(url);
  5392   switch (scheme) {
  5393     case "file":
  5394     case "chrome":
  5395     case "resource":
  5396       try {
  5397         NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
  5398           if (!Components.isSuccessCode(aStatus)) {
  5399             deferred.reject(new Error("Request failed with status code = "
  5400                                       + aStatus
  5401                                       + " after NetUtil.asyncFetch for url = "
  5402                                       + url));
  5403             return;
  5406           let source = NetUtil.readInputStreamToString(aStream, aStream.available());
  5407           contentType = aRequest.contentType;
  5408           deferred.resolve(source);
  5409           aStream.close();
  5410         });
  5411       } catch (ex) {
  5412         deferred.reject(ex);
  5414       break;
  5416     default:
  5417       let channel;
  5418       try {
  5419         channel = Services.io.newChannel(url, null, null);
  5420       } catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
  5421         // On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
  5422         // newChannel won't be able to handle it.
  5423         url = "file:///" + url;
  5424         channel = Services.io.newChannel(url, null, null);
  5426       let chunks = [];
  5427       let streamListener = {
  5428         onStartRequest: function(aRequest, aContext, aStatusCode) {
  5429           if (!Components.isSuccessCode(aStatusCode)) {
  5430             deferred.reject(new Error("Request failed with status code = "
  5431                                       + aStatusCode
  5432                                       + " in onStartRequest handler for url = "
  5433                                       + url));
  5435         },
  5436         onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
  5437           chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
  5438         },
  5439         onStopRequest: function(aRequest, aContext, aStatusCode) {
  5440           if (!Components.isSuccessCode(aStatusCode)) {
  5441             deferred.reject(new Error("Request failed with status code = "
  5442                                       + aStatusCode
  5443                                       + " in onStopRequest handler for url = "
  5444                                       + url));
  5445             return;
  5448           charset = channel.contentCharset;
  5449           contentType = channel.contentType;
  5450           deferred.resolve(chunks.join(""));
  5452       };
  5454       channel.loadFlags = aOptions.loadFromCache
  5455         ? channel.LOAD_FROM_CACHE
  5456         : channel.LOAD_BYPASS_CACHE;
  5457       channel.asyncOpen(streamListener, null);
  5458       break;
  5461   return deferred.promise.then(source => {
  5462     return {
  5463       content: convertToUnicode(source, charset),
  5464       contentType: contentType
  5465     };
  5466   });
  5469 /**
  5470  * Convert a given string, encoded in a given character set, to unicode.
  5472  * @param string aString
  5473  *        A string.
  5474  * @param string aCharset
  5475  *        A character set.
  5476  */
  5477 function convertToUnicode(aString, aCharset=null) {
  5478   // Decoding primitives.
  5479   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  5480     .createInstance(Ci.nsIScriptableUnicodeConverter);
  5481   try {
  5482     converter.charset = aCharset || "UTF-8";
  5483     return converter.ConvertToUnicode(aString);
  5484   } catch(e) {
  5485     return aString;
  5489 /**
  5490  * Report the given error in the error console and to stdout.
  5492  * @param Error aError
  5493  *        The error object you wish to report.
  5494  * @param String aPrefix
  5495  *        An optional prefix for the reported error message.
  5496  */
  5497 function reportError(aError, aPrefix="") {
  5498   dbg_assert(aError instanceof Error, "Must pass Error objects to reportError");
  5499   let msg = aPrefix + aError.message + ":\n" + aError.stack;
  5500   Cu.reportError(msg);
  5501   dumpn(msg);
  5504 // The following are copied here verbatim from css-logic.js, until we create a
  5505 // server-friendly helper module.
  5507 /**
  5508  * Find a unique CSS selector for a given element
  5509  * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
  5510  * and ele.ownerDocument.querySelectorAll(reply).length === 1
  5511  */
  5512 function findCssSelector(ele) {
  5513   var document = ele.ownerDocument;
  5514   if (ele.id && document.getElementById(ele.id) === ele) {
  5515     return '#' + ele.id;
  5518   // Inherently unique by tag name
  5519   var tagName = ele.tagName.toLowerCase();
  5520   if (tagName === 'html') {
  5521     return 'html';
  5523   if (tagName === 'head') {
  5524     return 'head';
  5526   if (tagName === 'body') {
  5527     return 'body';
  5530   if (ele.parentNode == null) {
  5531     console.log('danger: ' + tagName);
  5534   // We might be able to find a unique class name
  5535   var selector, index, matches;
  5536   if (ele.classList.length > 0) {
  5537     for (var i = 0; i < ele.classList.length; i++) {
  5538       // Is this className unique by itself?
  5539       selector = '.' + ele.classList.item(i);
  5540       matches = document.querySelectorAll(selector);
  5541       if (matches.length === 1) {
  5542         return selector;
  5544       // Maybe it's unique with a tag name?
  5545       selector = tagName + selector;
  5546       matches = document.querySelectorAll(selector);
  5547       if (matches.length === 1) {
  5548         return selector;
  5550       // Maybe it's unique using a tag name and nth-child
  5551       index = positionInNodeList(ele, ele.parentNode.children) + 1;
  5552       selector = selector + ':nth-child(' + index + ')';
  5553       matches = document.querySelectorAll(selector);
  5554       if (matches.length === 1) {
  5555         return selector;
  5560   // So we can be unique w.r.t. our parent, and use recursion
  5561   index = positionInNodeList(ele, ele.parentNode.children) + 1;
  5562   selector = findCssSelector(ele.parentNode) + ' > ' +
  5563           tagName + ':nth-child(' + index + ')';
  5565   return selector;
  5566 };
  5568 /**
  5569  * Find the position of [element] in [nodeList].
  5570  * @returns an index of the match, or -1 if there is no match
  5571  */
  5572 function positionInNodeList(element, nodeList) {
  5573   for (var i = 0; i < nodeList.length; i++) {
  5574     if (element === nodeList[i]) {
  5575       return i;
  5578   return -1;
  5581 /**
  5582  * Make a debuggee value for the given object, if needed. Primitive values
  5583  * are left the same.
  5585  * Use case: you have a raw JS object (after unsafe dereference) and you want to
  5586  * send it to the client. In that case you need to use an ObjectActor which
  5587  * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
  5588  * method works only for JS objects and functions.
  5590  * @param Debugger.Object obj
  5591  * @param any value
  5592  * @return object
  5593  */
  5594 function makeDebuggeeValueIfNeeded(obj, value) {
  5595   if (value && (typeof value == "object" || typeof value == "function")) {
  5596     return obj.makeDebuggeeValue(value);
  5598   return value;
  5601 function getInnerId(window) {
  5602   return window.QueryInterface(Ci.nsIInterfaceRequestor).
  5603                 getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  5604 };

mercurial