toolkit/devtools/server/actors/script.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/server/actors/script.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,5604 @@
     1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */
     1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
    1.13 +
    1.14 +let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
    1.15 +      "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
    1.16 +      "Float64Array"];
    1.17 +
    1.18 +// Number of items to preview in objects, arrays, maps, sets, lists,
    1.19 +// collections, etc.
    1.20 +let OBJECT_PREVIEW_MAX_ITEMS = 10;
    1.21 +
    1.22 +let addonManager = null;
    1.23 +
    1.24 +/**
    1.25 + * This is a wrapper around amIAddonManager.mapURIToAddonID which always returns
    1.26 + * false on B2G to avoid loading the add-on manager there and reports any
    1.27 + * exceptions rather than throwing so that the caller doesn't have to worry
    1.28 + * about them.
    1.29 + */
    1.30 +function mapURIToAddonID(uri, id) {
    1.31 +  if (Services.appinfo.ID == B2G_ID) {
    1.32 +    return false;
    1.33 +  }
    1.34 +
    1.35 +  if (!addonManager) {
    1.36 +    addonManager = Cc["@mozilla.org/addons/integration;1"].
    1.37 +                   getService(Ci.amIAddonManager);
    1.38 +  }
    1.39 +
    1.40 +  try {
    1.41 +    return addonManager.mapURIToAddonID(uri, id);
    1.42 +  }
    1.43 +  catch (e) {
    1.44 +    DevtoolsUtils.reportException("mapURIToAddonID", e);
    1.45 +    return false;
    1.46 +  }
    1.47 +}
    1.48 +
    1.49 +/**
    1.50 + * BreakpointStore objects keep track of all breakpoints that get set so that we
    1.51 + * can reset them when the same script is introduced to the thread again (such
    1.52 + * as after a refresh).
    1.53 + */
    1.54 +function BreakpointStore() {
    1.55 +  this._size = 0;
    1.56 +
    1.57 +  // If we have a whole-line breakpoint set at LINE in URL, then
    1.58 +  //
    1.59 +  //   this._wholeLineBreakpoints[URL][LINE]
    1.60 +  //
    1.61 +  // is an object
    1.62 +  //
    1.63 +  //   { url, line[, actor] }
    1.64 +  //
    1.65 +  // where the `actor` property is optional.
    1.66 +  this._wholeLineBreakpoints = Object.create(null);
    1.67 +
    1.68 +  // If we have a breakpoint set at LINE, COLUMN in URL, then
    1.69 +  //
    1.70 +  //   this._breakpoints[URL][LINE][COLUMN]
    1.71 +  //
    1.72 +  // is an object
    1.73 +  //
    1.74 +  //   { url, line, column[, actor] }
    1.75 +  //
    1.76 +  // where the `actor` property is optional.
    1.77 +  this._breakpoints = Object.create(null);
    1.78 +}
    1.79 +
    1.80 +BreakpointStore.prototype = {
    1.81 +  _size: null,
    1.82 +  get size() { return this._size; },
    1.83 +
    1.84 +  /**
    1.85 +   * Add a breakpoint to the breakpoint store.
    1.86 +   *
    1.87 +   * @param Object aBreakpoint
    1.88 +   *        The breakpoint to be added (not copied). It is an object with the
    1.89 +   *        following properties:
    1.90 +   *          - url
    1.91 +   *          - line
    1.92 +   *          - column (optional; omission implies that the breakpoint is for
    1.93 +   *            the whole line)
    1.94 +   *          - condition (optional)
    1.95 +   *          - actor (optional)
    1.96 +   */
    1.97 +  addBreakpoint: function (aBreakpoint) {
    1.98 +    let { url, line, column } = aBreakpoint;
    1.99 +    let updating = false;
   1.100 +
   1.101 +    if (column != null) {
   1.102 +      if (!this._breakpoints[url]) {
   1.103 +        this._breakpoints[url] = [];
   1.104 +      }
   1.105 +      if (!this._breakpoints[url][line]) {
   1.106 +        this._breakpoints[url][line] = [];
   1.107 +      }
   1.108 +      this._breakpoints[url][line][column] = aBreakpoint;
   1.109 +    } else {
   1.110 +      // Add a breakpoint that breaks on the whole line.
   1.111 +      if (!this._wholeLineBreakpoints[url]) {
   1.112 +        this._wholeLineBreakpoints[url] = [];
   1.113 +      }
   1.114 +      this._wholeLineBreakpoints[url][line] = aBreakpoint;
   1.115 +    }
   1.116 +
   1.117 +    this._size++;
   1.118 +  },
   1.119 +
   1.120 +  /**
   1.121 +   * Remove a breakpoint from the breakpoint store.
   1.122 +   *
   1.123 +   * @param Object aBreakpoint
   1.124 +   *        The breakpoint to be removed. It is an object with the following
   1.125 +   *        properties:
   1.126 +   *          - url
   1.127 +   *          - line
   1.128 +   *          - column (optional)
   1.129 +   */
   1.130 +  removeBreakpoint: function ({ url, line, column }) {
   1.131 +    if (column != null) {
   1.132 +      if (this._breakpoints[url]) {
   1.133 +        if (this._breakpoints[url][line]) {
   1.134 +          if (this._breakpoints[url][line][column]) {
   1.135 +            delete this._breakpoints[url][line][column];
   1.136 +            this._size--;
   1.137 +
   1.138 +            // If this was the last breakpoint on this line, delete the line from
   1.139 +            // `this._breakpoints[url]` as well. Otherwise `_iterLines` will yield
   1.140 +            // this line even though we no longer have breakpoints on
   1.141 +            // it. Furthermore, we use Object.keys() instead of just checking
   1.142 +            // `this._breakpoints[url].length` directly, because deleting
   1.143 +            // properties from sparse arrays doesn't update the `length` property
   1.144 +            // like adding them does.
   1.145 +            if (Object.keys(this._breakpoints[url][line]).length === 0) {
   1.146 +              delete this._breakpoints[url][line];
   1.147 +            }
   1.148 +          }
   1.149 +        }
   1.150 +      }
   1.151 +    } else {
   1.152 +      if (this._wholeLineBreakpoints[url]) {
   1.153 +        if (this._wholeLineBreakpoints[url][line]) {
   1.154 +          delete this._wholeLineBreakpoints[url][line];
   1.155 +          this._size--;
   1.156 +        }
   1.157 +      }
   1.158 +    }
   1.159 +  },
   1.160 +
   1.161 +  /**
   1.162 +   * Get a breakpoint from the breakpoint store. Will throw an error if the
   1.163 +   * breakpoint is not found.
   1.164 +   *
   1.165 +   * @param Object aLocation
   1.166 +   *        The location of the breakpoint you are retrieving. It is an object
   1.167 +   *        with the following properties:
   1.168 +   *          - url
   1.169 +   *          - line
   1.170 +   *          - column (optional)
   1.171 +   */
   1.172 +  getBreakpoint: function (aLocation) {
   1.173 +    let { url, line, column } = aLocation;
   1.174 +    dbg_assert(url != null);
   1.175 +    dbg_assert(line != null);
   1.176 +
   1.177 +    var foundBreakpoint = this.hasBreakpoint(aLocation);
   1.178 +    if (foundBreakpoint == null) {
   1.179 +      throw new Error("No breakpoint at url = " + url
   1.180 +          + ", line = " + line
   1.181 +          + ", column = " + column);
   1.182 +    }
   1.183 +
   1.184 +    return foundBreakpoint;
   1.185 +  },
   1.186 +
   1.187 +  /**
   1.188 +   * Checks if the breakpoint store has a requested breakpoint.
   1.189 +   *
   1.190 +   * @param Object aLocation
   1.191 +   *        The location of the breakpoint you are retrieving. It is an object
   1.192 +   *        with the following properties:
   1.193 +   *          - url
   1.194 +   *          - line
   1.195 +   *          - column (optional)
   1.196 +   * @returns The stored breakpoint if it exists, null otherwise.
   1.197 +   */
   1.198 +  hasBreakpoint: function (aLocation) {
   1.199 +    let { url, line, column } = aLocation;
   1.200 +    dbg_assert(url != null);
   1.201 +    dbg_assert(line != null);
   1.202 +    for (let bp of this.findBreakpoints(aLocation)) {
   1.203 +      // We will get whole line breakpoints before individual columns, so just
   1.204 +      // return the first one and if they didn't specify a column then they will
   1.205 +      // get the whole line breakpoint, and otherwise we will find the correct
   1.206 +      // one.
   1.207 +      return bp;
   1.208 +    }
   1.209 +
   1.210 +    return null;
   1.211 +  },
   1.212 +
   1.213 +  /**
   1.214 +   * Iterate over the breakpoints in this breakpoint store. You can optionally
   1.215 +   * provide search parameters to filter the set of breakpoints down to those
   1.216 +   * that match your parameters.
   1.217 +   *
   1.218 +   * @param Object aSearchParams
   1.219 +   *        Optional. An object with the following properties:
   1.220 +   *          - url
   1.221 +   *          - line (optional; requires the url property)
   1.222 +   *          - column (optional; requires the line property)
   1.223 +   */
   1.224 +  findBreakpoints: function* (aSearchParams={}) {
   1.225 +    if (aSearchParams.column != null) {
   1.226 +      dbg_assert(aSearchParams.line != null);
   1.227 +    }
   1.228 +    if (aSearchParams.line != null) {
   1.229 +      dbg_assert(aSearchParams.url != null);
   1.230 +    }
   1.231 +
   1.232 +    for (let url of this._iterUrls(aSearchParams.url)) {
   1.233 +      for (let line of this._iterLines(url, aSearchParams.line)) {
   1.234 +        // Always yield whole line breakpoints first. See comment in
   1.235 +        // |BreakpointStore.prototype.hasBreakpoint|.
   1.236 +        if (aSearchParams.column == null
   1.237 +            && this._wholeLineBreakpoints[url]
   1.238 +            && this._wholeLineBreakpoints[url][line]) {
   1.239 +          yield this._wholeLineBreakpoints[url][line];
   1.240 +        }
   1.241 +        for (let column of this._iterColumns(url, line, aSearchParams.column)) {
   1.242 +          yield this._breakpoints[url][line][column];
   1.243 +        }
   1.244 +      }
   1.245 +    }
   1.246 +  },
   1.247 +
   1.248 +  _iterUrls: function* (aUrl) {
   1.249 +    if (aUrl) {
   1.250 +      if (this._breakpoints[aUrl] || this._wholeLineBreakpoints[aUrl]) {
   1.251 +        yield aUrl;
   1.252 +      }
   1.253 +    } else {
   1.254 +      for (let url of Object.keys(this._wholeLineBreakpoints)) {
   1.255 +        yield url;
   1.256 +      }
   1.257 +      for (let url of Object.keys(this._breakpoints)) {
   1.258 +        if (url in this._wholeLineBreakpoints) {
   1.259 +          continue;
   1.260 +        }
   1.261 +        yield url;
   1.262 +      }
   1.263 +    }
   1.264 +  },
   1.265 +
   1.266 +  _iterLines: function* (aUrl, aLine) {
   1.267 +    if (aLine != null) {
   1.268 +      if ((this._wholeLineBreakpoints[aUrl]
   1.269 +           && this._wholeLineBreakpoints[aUrl][aLine])
   1.270 +          || (this._breakpoints[aUrl] && this._breakpoints[aUrl][aLine])) {
   1.271 +        yield aLine;
   1.272 +      }
   1.273 +    } else {
   1.274 +      const wholeLines = this._wholeLineBreakpoints[aUrl]
   1.275 +        ? Object.keys(this._wholeLineBreakpoints[aUrl])
   1.276 +        : [];
   1.277 +      const columnLines = this._breakpoints[aUrl]
   1.278 +        ? Object.keys(this._breakpoints[aUrl])
   1.279 +        : [];
   1.280 +
   1.281 +      const lines = wholeLines.concat(columnLines).sort();
   1.282 +
   1.283 +      let lastLine;
   1.284 +      for (let line of lines) {
   1.285 +        if (line === lastLine) {
   1.286 +          continue;
   1.287 +        }
   1.288 +        yield line;
   1.289 +        lastLine = line;
   1.290 +      }
   1.291 +    }
   1.292 +  },
   1.293 +
   1.294 +  _iterColumns: function* (aUrl, aLine, aColumn) {
   1.295 +    if (!this._breakpoints[aUrl] || !this._breakpoints[aUrl][aLine]) {
   1.296 +      return;
   1.297 +    }
   1.298 +
   1.299 +    if (aColumn != null) {
   1.300 +      if (this._breakpoints[aUrl][aLine][aColumn]) {
   1.301 +        yield aColumn;
   1.302 +      }
   1.303 +    } else {
   1.304 +      for (let column in this._breakpoints[aUrl][aLine]) {
   1.305 +        yield column;
   1.306 +      }
   1.307 +    }
   1.308 +  },
   1.309 +};
   1.310 +
   1.311 +/**
   1.312 + * Manages pushing event loops and automatically pops and exits them in the
   1.313 + * correct order as they are resolved.
   1.314 + *
   1.315 + * @param nsIJSInspector inspector
   1.316 + *        The underlying JS inspector we use to enter and exit nested event
   1.317 + *        loops.
   1.318 + * @param ThreadActor thread
   1.319 + *        The thread actor instance that owns this EventLoopStack.
   1.320 + * @param DebuggerServerConnection connection
   1.321 + *        The remote protocol connection associated with this event loop stack.
   1.322 + * @param Object hooks
   1.323 + *        An object with the following properties:
   1.324 + *          - url: The URL string of the debuggee we are spinning an event loop
   1.325 + *                 for.
   1.326 + *          - preNest: function called before entering a nested event loop
   1.327 + *          - postNest: function called after exiting a nested event loop
   1.328 + */
   1.329 +function EventLoopStack({ inspector, thread, connection, hooks }) {
   1.330 +  this._inspector = inspector;
   1.331 +  this._hooks = hooks;
   1.332 +  this._thread = thread;
   1.333 +  this._connection = connection;
   1.334 +}
   1.335 +
   1.336 +EventLoopStack.prototype = {
   1.337 +  /**
   1.338 +   * The number of nested event loops on the stack.
   1.339 +   */
   1.340 +  get size() {
   1.341 +    return this._inspector.eventLoopNestLevel;
   1.342 +  },
   1.343 +
   1.344 +  /**
   1.345 +   * The URL of the debuggee who pushed the event loop on top of the stack.
   1.346 +   */
   1.347 +  get lastPausedUrl() {
   1.348 +    let url = null;
   1.349 +    if (this.size > 0) {
   1.350 +      try {
   1.351 +        url = this._inspector.lastNestRequestor.url
   1.352 +      } catch (e) {
   1.353 +        // The tab's URL getter may throw if the tab is destroyed by the time
   1.354 +        // this code runs, but we don't really care at this point.
   1.355 +        dumpn(e);
   1.356 +      }
   1.357 +    }
   1.358 +    return url;
   1.359 +  },
   1.360 +
   1.361 +  /**
   1.362 +   * The DebuggerServerConnection of the debugger who pushed the event loop on
   1.363 +   * top of the stack
   1.364 +   */
   1.365 +  get lastConnection() {
   1.366 +    return this._inspector.lastNestRequestor._connection;
   1.367 +  },
   1.368 +
   1.369 +  /**
   1.370 +   * Push a new nested event loop onto the stack.
   1.371 +   *
   1.372 +   * @returns EventLoop
   1.373 +   */
   1.374 +  push: function () {
   1.375 +    return new EventLoop({
   1.376 +      inspector: this._inspector,
   1.377 +      thread: this._thread,
   1.378 +      connection: this._connection,
   1.379 +      hooks: this._hooks
   1.380 +    });
   1.381 +  }
   1.382 +};
   1.383 +
   1.384 +/**
   1.385 + * An object that represents a nested event loop. It is used as the nest
   1.386 + * requestor with nsIJSInspector instances.
   1.387 + *
   1.388 + * @param nsIJSInspector inspector
   1.389 + *        The JS Inspector that runs nested event loops.
   1.390 + * @param ThreadActor thread
   1.391 + *        The thread actor that is creating this nested event loop.
   1.392 + * @param DebuggerServerConnection connection
   1.393 + *        The remote protocol connection associated with this event loop.
   1.394 + * @param Object hooks
   1.395 + *        The same hooks object passed into EventLoopStack during its
   1.396 + *        initialization.
   1.397 + */
   1.398 +function EventLoop({ inspector, thread, connection, hooks }) {
   1.399 +  this._inspector = inspector;
   1.400 +  this._thread = thread;
   1.401 +  this._hooks = hooks;
   1.402 +  this._connection = connection;
   1.403 +
   1.404 +  this.enter = this.enter.bind(this);
   1.405 +  this.resolve = this.resolve.bind(this);
   1.406 +}
   1.407 +
   1.408 +EventLoop.prototype = {
   1.409 +  entered: false,
   1.410 +  resolved: false,
   1.411 +  get url() { return this._hooks.url; },
   1.412 +
   1.413 +  /**
   1.414 +   * Enter this nested event loop.
   1.415 +   */
   1.416 +  enter: function () {
   1.417 +    let nestData = this._hooks.preNest
   1.418 +      ? this._hooks.preNest()
   1.419 +      : null;
   1.420 +
   1.421 +    this.entered = true;
   1.422 +    this._inspector.enterNestedEventLoop(this);
   1.423 +
   1.424 +    // Keep exiting nested event loops while the last requestor is resolved.
   1.425 +    if (this._inspector.eventLoopNestLevel > 0) {
   1.426 +      const { resolved } = this._inspector.lastNestRequestor;
   1.427 +      if (resolved) {
   1.428 +        this._inspector.exitNestedEventLoop();
   1.429 +      }
   1.430 +    }
   1.431 +
   1.432 +    dbg_assert(this._thread.state === "running",
   1.433 +               "Should be in the running state");
   1.434 +
   1.435 +    if (this._hooks.postNest) {
   1.436 +      this._hooks.postNest(nestData);
   1.437 +    }
   1.438 +  },
   1.439 +
   1.440 +  /**
   1.441 +   * Resolve this nested event loop.
   1.442 +   *
   1.443 +   * @returns boolean
   1.444 +   *          True if we exited this nested event loop because it was on top of
   1.445 +   *          the stack, false if there is another nested event loop above this
   1.446 +   *          one that hasn't resolved yet.
   1.447 +   */
   1.448 +  resolve: function () {
   1.449 +    if (!this.entered) {
   1.450 +      throw new Error("Can't resolve an event loop before it has been entered!");
   1.451 +    }
   1.452 +    if (this.resolved) {
   1.453 +      throw new Error("Already resolved this nested event loop!");
   1.454 +    }
   1.455 +    this.resolved = true;
   1.456 +    if (this === this._inspector.lastNestRequestor) {
   1.457 +      this._inspector.exitNestedEventLoop();
   1.458 +      return true;
   1.459 +    }
   1.460 +    return false;
   1.461 +  },
   1.462 +};
   1.463 +
   1.464 +/**
   1.465 + * JSD2 actors.
   1.466 + */
   1.467 +/**
   1.468 + * Creates a ThreadActor.
   1.469 + *
   1.470 + * ThreadActors manage a JSInspector object and manage execution/inspection
   1.471 + * of debuggees.
   1.472 + *
   1.473 + * @param aHooks object
   1.474 + *        An object with preNest and postNest methods for calling when entering
   1.475 + *        and exiting a nested event loop.
   1.476 + * @param aGlobal object [optional]
   1.477 + *        An optional (for content debugging only) reference to the content
   1.478 + *        window.
   1.479 + */
   1.480 +function ThreadActor(aHooks, aGlobal)
   1.481 +{
   1.482 +  this._state = "detached";
   1.483 +  this._frameActors = [];
   1.484 +  this._hooks = aHooks;
   1.485 +  this.global = aGlobal;
   1.486 +  // A map of actorID -> actor for breakpoints created and managed by the server.
   1.487 +  this._hiddenBreakpoints = new Map();
   1.488 +
   1.489 +  this.findGlobals = this.globalManager.findGlobals.bind(this);
   1.490 +  this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
   1.491 +  this.onNewSource = this.onNewSource.bind(this);
   1.492 +  this._allEventsListener = this._allEventsListener.bind(this);
   1.493 +
   1.494 +  this._options = {
   1.495 +    useSourceMaps: false
   1.496 +  };
   1.497 +
   1.498 +  this._gripDepth = 0;
   1.499 +}
   1.500 +
   1.501 +/**
   1.502 + * The breakpoint store must be shared across instances of ThreadActor so that
   1.503 + * page reloads don't blow away all of our breakpoints.
   1.504 + */
   1.505 +ThreadActor.breakpointStore = new BreakpointStore();
   1.506 +
   1.507 +ThreadActor.prototype = {
   1.508 +  // Used by the ObjectActor to keep track of the depth of grip() calls.
   1.509 +  _gripDepth: null,
   1.510 +
   1.511 +  actorPrefix: "context",
   1.512 +
   1.513 +  get state() { return this._state; },
   1.514 +  get attached() this.state == "attached" ||
   1.515 +                 this.state == "running" ||
   1.516 +                 this.state == "paused",
   1.517 +
   1.518 +  get breakpointStore() { return ThreadActor.breakpointStore; },
   1.519 +
   1.520 +  get threadLifetimePool() {
   1.521 +    if (!this._threadLifetimePool) {
   1.522 +      this._threadLifetimePool = new ActorPool(this.conn);
   1.523 +      this.conn.addActorPool(this._threadLifetimePool);
   1.524 +      this._threadLifetimePool.objectActors = new WeakMap();
   1.525 +    }
   1.526 +    return this._threadLifetimePool;
   1.527 +  },
   1.528 +
   1.529 +  get sources() {
   1.530 +    if (!this._sources) {
   1.531 +      this._sources = new ThreadSources(this, this._options.useSourceMaps,
   1.532 +                                        this._allowSource, this.onNewSource);
   1.533 +    }
   1.534 +    return this._sources;
   1.535 +  },
   1.536 +
   1.537 +  get youngestFrame() {
   1.538 +    if (this.state != "paused") {
   1.539 +      return null;
   1.540 +    }
   1.541 +    return this.dbg.getNewestFrame();
   1.542 +  },
   1.543 +
   1.544 +  _prettyPrintWorker: null,
   1.545 +  get prettyPrintWorker() {
   1.546 +    if (!this._prettyPrintWorker) {
   1.547 +      this._prettyPrintWorker = new ChromeWorker(
   1.548 +        "resource://gre/modules/devtools/server/actors/pretty-print-worker.js");
   1.549 +
   1.550 +      this._prettyPrintWorker.addEventListener(
   1.551 +        "error", this._onPrettyPrintError, false);
   1.552 +
   1.553 +      if (dumpn.wantLogging) {
   1.554 +        this._prettyPrintWorker.addEventListener("message", this._onPrettyPrintMsg, false);
   1.555 +
   1.556 +        const postMsg = this._prettyPrintWorker.postMessage;
   1.557 +        this._prettyPrintWorker.postMessage = data => {
   1.558 +          dumpn("Sending message to prettyPrintWorker: "
   1.559 +                + JSON.stringify(data, null, 2) + "\n");
   1.560 +          return postMsg.call(this._prettyPrintWorker, data);
   1.561 +        };
   1.562 +      }
   1.563 +    }
   1.564 +    return this._prettyPrintWorker;
   1.565 +  },
   1.566 +
   1.567 +  _onPrettyPrintError: function ({ message, filename, lineno }) {
   1.568 +    reportError(new Error(message + " @ " + filename + ":" + lineno));
   1.569 +  },
   1.570 +
   1.571 +  _onPrettyPrintMsg: function ({ data }) {
   1.572 +    dumpn("Received message from prettyPrintWorker: "
   1.573 +          + JSON.stringify(data, null, 2) + "\n");
   1.574 +  },
   1.575 +
   1.576 +  /**
   1.577 +   * Keep track of all of the nested event loops we use to pause the debuggee
   1.578 +   * when we hit a breakpoint/debugger statement/etc in one place so we can
   1.579 +   * resolve them when we get resume packets. We have more than one (and keep
   1.580 +   * them in a stack) because we can pause within client evals.
   1.581 +   */
   1.582 +  _threadPauseEventLoops: null,
   1.583 +  _pushThreadPause: function () {
   1.584 +    if (!this._threadPauseEventLoops) {
   1.585 +      this._threadPauseEventLoops = [];
   1.586 +    }
   1.587 +    const eventLoop = this._nestedEventLoops.push();
   1.588 +    this._threadPauseEventLoops.push(eventLoop);
   1.589 +    eventLoop.enter();
   1.590 +  },
   1.591 +  _popThreadPause: function () {
   1.592 +    const eventLoop = this._threadPauseEventLoops.pop();
   1.593 +    dbg_assert(eventLoop, "Should have an event loop.");
   1.594 +    eventLoop.resolve();
   1.595 +  },
   1.596 +
   1.597 +  /**
   1.598 +   * Remove all debuggees and clear out the thread's sources.
   1.599 +   */
   1.600 +  clearDebuggees: function () {
   1.601 +    if (this.dbg) {
   1.602 +      this.dbg.removeAllDebuggees();
   1.603 +    }
   1.604 +    this._sources = null;
   1.605 +  },
   1.606 +
   1.607 +  /**
   1.608 +   * Add a debuggee global to the Debugger object.
   1.609 +   *
   1.610 +   * @returns the Debugger.Object that corresponds to the global.
   1.611 +   */
   1.612 +  addDebuggee: function (aGlobal) {
   1.613 +    let globalDebugObject;
   1.614 +    try {
   1.615 +      globalDebugObject = this.dbg.addDebuggee(aGlobal);
   1.616 +    } catch (e) {
   1.617 +      // Ignore attempts to add the debugger's compartment as a debuggee.
   1.618 +      dumpn("Ignoring request to add the debugger's compartment as a debuggee");
   1.619 +    }
   1.620 +    return globalDebugObject;
   1.621 +  },
   1.622 +
   1.623 +  /**
   1.624 +   * Initialize the Debugger.
   1.625 +   */
   1.626 +  _initDebugger: function () {
   1.627 +    this.dbg = new Debugger();
   1.628 +    this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
   1.629 +    this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
   1.630 +    this.dbg.onNewScript = this.onNewScript.bind(this);
   1.631 +    this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
   1.632 +    // Keep the debugger disabled until a client attaches.
   1.633 +    this.dbg.enabled = this._state != "detached";
   1.634 +  },
   1.635 +
   1.636 +  /**
   1.637 +   * Remove a debuggee global from the JSInspector.
   1.638 +   */
   1.639 +  removeDebugee: function (aGlobal) {
   1.640 +    try {
   1.641 +      this.dbg.removeDebuggee(aGlobal);
   1.642 +    } catch(ex) {
   1.643 +      // XXX: This debuggee has code currently executing on the stack,
   1.644 +      // we need to save this for later.
   1.645 +    }
   1.646 +  },
   1.647 +
   1.648 +  /**
   1.649 +   * Add the provided window and all windows in its frame tree as debuggees.
   1.650 +   *
   1.651 +   * @returns the Debugger.Object that corresponds to the window.
   1.652 +   */
   1.653 +  _addDebuggees: function (aWindow) {
   1.654 +    let globalDebugObject = this.addDebuggee(aWindow);
   1.655 +    let frames = aWindow.frames;
   1.656 +    if (frames) {
   1.657 +      for (let i = 0; i < frames.length; i++) {
   1.658 +        this._addDebuggees(frames[i]);
   1.659 +      }
   1.660 +    }
   1.661 +    return globalDebugObject;
   1.662 +  },
   1.663 +
   1.664 +  /**
   1.665 +   * An object that will be used by ThreadActors to tailor their behavior
   1.666 +   * depending on the debugging context being required (chrome or content).
   1.667 +   */
   1.668 +  globalManager: {
   1.669 +    findGlobals: function () {
   1.670 +      const { gDevToolsExtensions: {
   1.671 +        getContentGlobals
   1.672 +      } } = Cu.import("resource://gre/modules/devtools/DevToolsExtensions.jsm", {});
   1.673 +
   1.674 +      this.globalDebugObject = this._addDebuggees(this.global);
   1.675 +
   1.676 +      // global may not be a window
   1.677 +      try {
   1.678 +        getContentGlobals({
   1.679 +          'inner-window-id': getInnerId(this.global)
   1.680 +        }).forEach(this.addDebuggee.bind(this));
   1.681 +      }
   1.682 +      catch(e) {}
   1.683 +    },
   1.684 +
   1.685 +    /**
   1.686 +     * A function that the engine calls when a new global object
   1.687 +     * (for example a sandbox) has been created.
   1.688 +     *
   1.689 +     * @param aGlobal Debugger.Object
   1.690 +     *        The new global object that was created.
   1.691 +     */
   1.692 +    onNewGlobal: function (aGlobal) {
   1.693 +      let useGlobal = (aGlobal.hostAnnotations &&
   1.694 +                       aGlobal.hostAnnotations.type == "document" &&
   1.695 +                       aGlobal.hostAnnotations.element === this.global);
   1.696 +
   1.697 +      // check if the global is a sdk page-mod sandbox
   1.698 +      if (!useGlobal) {
   1.699 +        let metadata = {};
   1.700 +        let id = "";
   1.701 +        try {
   1.702 +          id = getInnerId(this.global);
   1.703 +          metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
   1.704 +        }
   1.705 +        catch (e) {}
   1.706 +
   1.707 +        useGlobal = (metadata['inner-window-id'] && metadata['inner-window-id'] == id);
   1.708 +      }
   1.709 +
   1.710 +      // Content debugging only cares about new globals in the contant window,
   1.711 +      // like iframe children.
   1.712 +      if (useGlobal) {
   1.713 +        this.addDebuggee(aGlobal);
   1.714 +        // Notify the client.
   1.715 +        this.conn.send({
   1.716 +          from: this.actorID,
   1.717 +          type: "newGlobal",
   1.718 +          // TODO: after bug 801084 lands see if we need to JSONify this.
   1.719 +          hostAnnotations: aGlobal.hostAnnotations
   1.720 +        });
   1.721 +      }
   1.722 +    }
   1.723 +  },
   1.724 +
   1.725 +  disconnect: function () {
   1.726 +    dumpn("in ThreadActor.prototype.disconnect");
   1.727 +    if (this._state == "paused") {
   1.728 +      this.onResume();
   1.729 +    }
   1.730 +
   1.731 +    this.clearDebuggees();
   1.732 +    this.conn.removeActorPool(this._threadLifetimePool);
   1.733 +    this._threadLifetimePool = null;
   1.734 +
   1.735 +    if (this._prettyPrintWorker) {
   1.736 +      this._prettyPrintWorker.removeEventListener(
   1.737 +        "error", this._onPrettyPrintError, false);
   1.738 +      this._prettyPrintWorker.removeEventListener(
   1.739 +        "message", this._onPrettyPrintMsg, false);
   1.740 +      this._prettyPrintWorker.terminate();
   1.741 +      this._prettyPrintWorker = null;
   1.742 +    }
   1.743 +
   1.744 +    if (!this.dbg) {
   1.745 +      return;
   1.746 +    }
   1.747 +    this.dbg.enabled = false;
   1.748 +    this.dbg = null;
   1.749 +  },
   1.750 +
   1.751 +  /**
   1.752 +   * Disconnect the debugger and put the actor in the exited state.
   1.753 +   */
   1.754 +  exit: function () {
   1.755 +    this.disconnect();
   1.756 +    this._state = "exited";
   1.757 +  },
   1.758 +
   1.759 +  // Request handlers
   1.760 +  onAttach: function (aRequest) {
   1.761 +    if (this.state === "exited") {
   1.762 +      return { type: "exited" };
   1.763 +    }
   1.764 +
   1.765 +    if (this.state !== "detached") {
   1.766 +      return { error: "wrongState",
   1.767 +               message: "Current state is " + this.state };
   1.768 +    }
   1.769 +
   1.770 +    this._state = "attached";
   1.771 +
   1.772 +    update(this._options, aRequest.options || {});
   1.773 +
   1.774 +    // Initialize an event loop stack. This can't be done in the constructor,
   1.775 +    // because this.conn is not yet initialized by the actor pool at that time.
   1.776 +    this._nestedEventLoops = new EventLoopStack({
   1.777 +      inspector: DebuggerServer.xpcInspector,
   1.778 +      hooks: this._hooks,
   1.779 +      connection: this.conn,
   1.780 +      thread: this
   1.781 +    });
   1.782 +
   1.783 +    if (!this.dbg) {
   1.784 +      this._initDebugger();
   1.785 +    }
   1.786 +    this.findGlobals();
   1.787 +    this.dbg.enabled = true;
   1.788 +    try {
   1.789 +      // Put ourselves in the paused state.
   1.790 +      let packet = this._paused();
   1.791 +      if (!packet) {
   1.792 +        return { error: "notAttached" };
   1.793 +      }
   1.794 +      packet.why = { type: "attached" };
   1.795 +
   1.796 +      this._restoreBreakpoints();
   1.797 +
   1.798 +      // Send the response to the attach request now (rather than
   1.799 +      // returning it), because we're going to start a nested event loop
   1.800 +      // here.
   1.801 +      this.conn.send(packet);
   1.802 +
   1.803 +      // Start a nested event loop.
   1.804 +      this._pushThreadPause();
   1.805 +
   1.806 +      // We already sent a response to this request, don't send one
   1.807 +      // now.
   1.808 +      return null;
   1.809 +    } catch (e) {
   1.810 +      reportError(e);
   1.811 +      return { error: "notAttached", message: e.toString() };
   1.812 +    }
   1.813 +  },
   1.814 +
   1.815 +  onDetach: function (aRequest) {
   1.816 +    this.disconnect();
   1.817 +    this._state = "detached";
   1.818 +
   1.819 +    dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet");
   1.820 +    return {
   1.821 +      type: "detached"
   1.822 +    };
   1.823 +  },
   1.824 +
   1.825 +  onReconfigure: function (aRequest) {
   1.826 +    if (this.state == "exited") {
   1.827 +      return { error: "wrongState" };
   1.828 +    }
   1.829 +
   1.830 +    update(this._options, aRequest.options || {});
   1.831 +    // Clear existing sources, so they can be recreated on next access.
   1.832 +    this._sources = null;
   1.833 +
   1.834 +    return {};
   1.835 +  },
   1.836 +
   1.837 +  /**
   1.838 +   * Pause the debuggee, by entering a nested event loop, and return a 'paused'
   1.839 +   * packet to the client.
   1.840 +   *
   1.841 +   * @param Debugger.Frame aFrame
   1.842 +   *        The newest debuggee frame in the stack.
   1.843 +   * @param object aReason
   1.844 +   *        An object with a 'type' property containing the reason for the pause.
   1.845 +   * @param function onPacket
   1.846 +   *        Hook to modify the packet before it is sent. Feel free to return a
   1.847 +   *        promise.
   1.848 +   */
   1.849 +  _pauseAndRespond: function (aFrame, aReason, onPacket=function (k) { return k; }) {
   1.850 +    try {
   1.851 +      let packet = this._paused(aFrame);
   1.852 +      if (!packet) {
   1.853 +        return undefined;
   1.854 +      }
   1.855 +      packet.why = aReason;
   1.856 +
   1.857 +      this.sources.getOriginalLocation(packet.frame.where).then(aOrigPosition => {
   1.858 +        packet.frame.where = aOrigPosition;
   1.859 +        resolve(onPacket(packet))
   1.860 +          .then(null, error => {
   1.861 +            reportError(error);
   1.862 +            return {
   1.863 +              error: "unknownError",
   1.864 +              message: error.message + "\n" + error.stack
   1.865 +            };
   1.866 +          })
   1.867 +          .then(packet => {
   1.868 +            this.conn.send(packet);
   1.869 +          });
   1.870 +      });
   1.871 +
   1.872 +      this._pushThreadPause();
   1.873 +    } catch(e) {
   1.874 +      reportError(e, "Got an exception during TA__pauseAndRespond: ");
   1.875 +    }
   1.876 +
   1.877 +    return undefined;
   1.878 +  },
   1.879 +
   1.880 +  /**
   1.881 +   * Handle resume requests that include a forceCompletion request.
   1.882 +   *
   1.883 +   * @param Object aRequest
   1.884 +   *        The request packet received over the RDP.
   1.885 +   * @returns A response packet.
   1.886 +   */
   1.887 +  _forceCompletion: function (aRequest) {
   1.888 +    // TODO: remove this when Debugger.Frame.prototype.pop is implemented in
   1.889 +    // bug 736733.
   1.890 +    return {
   1.891 +      error: "notImplemented",
   1.892 +      message: "forced completion is not yet implemented."
   1.893 +    };
   1.894 +  },
   1.895 +
   1.896 +  _makeOnEnterFrame: function ({ pauseAndRespond }) {
   1.897 +    return aFrame => {
   1.898 +      const generatedLocation = getFrameLocation(aFrame);
   1.899 +      let { url } = this.synchronize(this.sources.getOriginalLocation(
   1.900 +        generatedLocation));
   1.901 +
   1.902 +      return this.sources.isBlackBoxed(url)
   1.903 +        ? undefined
   1.904 +        : pauseAndRespond(aFrame);
   1.905 +    };
   1.906 +  },
   1.907 +
   1.908 +  _makeOnPop: function ({ thread, pauseAndRespond, createValueGrip }) {
   1.909 +    return function (aCompletion) {
   1.910 +      // onPop is called with 'this' set to the current frame.
   1.911 +
   1.912 +      const generatedLocation = getFrameLocation(this);
   1.913 +      const { url } = thread.synchronize(thread.sources.getOriginalLocation(
   1.914 +        generatedLocation));
   1.915 +
   1.916 +      if (thread.sources.isBlackBoxed(url)) {
   1.917 +        return undefined;
   1.918 +      }
   1.919 +
   1.920 +      // Note that we're popping this frame; we need to watch for
   1.921 +      // subsequent step events on its caller.
   1.922 +      this.reportedPop = true;
   1.923 +
   1.924 +      return pauseAndRespond(this, aPacket => {
   1.925 +        aPacket.why.frameFinished = {};
   1.926 +        if (!aCompletion) {
   1.927 +          aPacket.why.frameFinished.terminated = true;
   1.928 +        } else if (aCompletion.hasOwnProperty("return")) {
   1.929 +          aPacket.why.frameFinished.return = createValueGrip(aCompletion.return);
   1.930 +        } else if (aCompletion.hasOwnProperty("yield")) {
   1.931 +          aPacket.why.frameFinished.return = createValueGrip(aCompletion.yield);
   1.932 +        } else {
   1.933 +          aPacket.why.frameFinished.throw = createValueGrip(aCompletion.throw);
   1.934 +        }
   1.935 +        return aPacket;
   1.936 +      });
   1.937 +    };
   1.938 +  },
   1.939 +
   1.940 +  _makeOnStep: function ({ thread, pauseAndRespond, startFrame,
   1.941 +                           startLocation }) {
   1.942 +    return function () {
   1.943 +      // onStep is called with 'this' set to the current frame.
   1.944 +
   1.945 +      const generatedLocation = getFrameLocation(this);
   1.946 +      const newLocation = thread.synchronize(thread.sources.getOriginalLocation(
   1.947 +        generatedLocation));
   1.948 +
   1.949 +      // Cases when we should pause because we have executed enough to consider
   1.950 +      // a "step" to have occured:
   1.951 +      //
   1.952 +      // 1.1. We change frames.
   1.953 +      // 1.2. We change URLs (can happen without changing frames thanks to
   1.954 +      //      source mapping).
   1.955 +      // 1.3. We change lines.
   1.956 +      //
   1.957 +      // Cases when we should always continue execution, even if one of the
   1.958 +      // above cases is true:
   1.959 +      //
   1.960 +      // 2.1. We are in a source mapped region, but inside a null mapping
   1.961 +      //      (doesn't correlate to any region of original source)
   1.962 +      // 2.2. The source we are in is black boxed.
   1.963 +
   1.964 +      // Cases 2.1 and 2.2
   1.965 +      if (newLocation.url == null
   1.966 +          || thread.sources.isBlackBoxed(newLocation.url)) {
   1.967 +        return undefined;
   1.968 +      }
   1.969 +
   1.970 +      // Cases 1.1, 1.2 and 1.3
   1.971 +      if (this !== startFrame
   1.972 +          || startLocation.url !== newLocation.url
   1.973 +          || startLocation.line !== newLocation.line) {
   1.974 +        return pauseAndRespond(this);
   1.975 +      }
   1.976 +
   1.977 +      // Otherwise, let execution continue (we haven't executed enough code to
   1.978 +      // consider this a "step" yet).
   1.979 +      return undefined;
   1.980 +    };
   1.981 +  },
   1.982 +
   1.983 +  /**
   1.984 +   * Define the JS hook functions for stepping.
   1.985 +   */
   1.986 +  _makeSteppingHooks: function (aStartLocation) {
   1.987 +    // Bind these methods and state because some of the hooks are called
   1.988 +    // with 'this' set to the current frame. Rather than repeating the
   1.989 +    // binding in each _makeOnX method, just do it once here and pass it
   1.990 +    // in to each function.
   1.991 +    const steppingHookState = {
   1.992 +      pauseAndRespond: (aFrame, onPacket=(k)=>k) => {
   1.993 +        this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket);
   1.994 +      },
   1.995 +      createValueGrip: this.createValueGrip.bind(this),
   1.996 +      thread: this,
   1.997 +      startFrame: this.youngestFrame,
   1.998 +      startLocation: aStartLocation
   1.999 +    };
  1.1000 +
  1.1001 +    return {
  1.1002 +      onEnterFrame: this._makeOnEnterFrame(steppingHookState),
  1.1003 +      onPop: this._makeOnPop(steppingHookState),
  1.1004 +      onStep: this._makeOnStep(steppingHookState)
  1.1005 +    };
  1.1006 +  },
  1.1007 +
  1.1008 +  /**
  1.1009 +   * Handle attaching the various stepping hooks we need to attach when we
  1.1010 +   * receive a resume request with a resumeLimit property.
  1.1011 +   *
  1.1012 +   * @param Object aRequest
  1.1013 +   *        The request packet received over the RDP.
  1.1014 +   * @returns A promise that resolves to true once the hooks are attached, or is
  1.1015 +   *          rejected with an error packet.
  1.1016 +   */
  1.1017 +  _handleResumeLimit: function (aRequest) {
  1.1018 +    let steppingType = aRequest.resumeLimit.type;
  1.1019 +    if (["step", "next", "finish"].indexOf(steppingType) == -1) {
  1.1020 +      return reject({ error: "badParameterType",
  1.1021 +                      message: "Unknown resumeLimit type" });
  1.1022 +    }
  1.1023 +
  1.1024 +    const generatedLocation = getFrameLocation(this.youngestFrame);
  1.1025 +    return this.sources.getOriginalLocation(generatedLocation)
  1.1026 +      .then(originalLocation => {
  1.1027 +        const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation);
  1.1028 +
  1.1029 +        // Make sure there is still a frame on the stack if we are to continue
  1.1030 +        // stepping.
  1.1031 +        let stepFrame = this._getNextStepFrame(this.youngestFrame);
  1.1032 +        if (stepFrame) {
  1.1033 +          switch (steppingType) {
  1.1034 +            case "step":
  1.1035 +              this.dbg.onEnterFrame = onEnterFrame;
  1.1036 +              // Fall through.
  1.1037 +            case "next":
  1.1038 +              if (stepFrame.script) {
  1.1039 +                  stepFrame.onStep = onStep;
  1.1040 +              }
  1.1041 +              stepFrame.onPop = onPop;
  1.1042 +              break;
  1.1043 +            case "finish":
  1.1044 +              stepFrame.onPop = onPop;
  1.1045 +          }
  1.1046 +        }
  1.1047 +
  1.1048 +        return true;
  1.1049 +      });
  1.1050 +  },
  1.1051 +
  1.1052 +  /**
  1.1053 +   * Clear the onStep and onPop hooks from the given frame and all of the frames
  1.1054 +   * below it.
  1.1055 +   *
  1.1056 +   * @param Debugger.Frame aFrame
  1.1057 +   *        The frame we want to clear the stepping hooks from.
  1.1058 +   */
  1.1059 +  _clearSteppingHooks: function (aFrame) {
  1.1060 +    while (aFrame) {
  1.1061 +      aFrame.onStep = undefined;
  1.1062 +      aFrame.onPop = undefined;
  1.1063 +      aFrame = aFrame.older;
  1.1064 +    }
  1.1065 +  },
  1.1066 +
  1.1067 +  /**
  1.1068 +   * Listen to the debuggee's DOM events if we received a request to do so.
  1.1069 +   *
  1.1070 +   * @param Object aRequest
  1.1071 +   *        The resume request packet received over the RDP.
  1.1072 +   */
  1.1073 +  _maybeListenToEvents: function (aRequest) {
  1.1074 +    // Break-on-DOMEvents is only supported in content debugging.
  1.1075 +    let events = aRequest.pauseOnDOMEvents;
  1.1076 +    if (this.global && events &&
  1.1077 +        (events == "*" ||
  1.1078 +        (Array.isArray(events) && events.length))) {
  1.1079 +      this._pauseOnDOMEvents = events;
  1.1080 +      let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1.1081 +                .getService(Ci.nsIEventListenerService);
  1.1082 +      els.addListenerForAllEvents(this.global, this._allEventsListener, true);
  1.1083 +    }
  1.1084 +  },
  1.1085 +
  1.1086 +  /**
  1.1087 +   * Handle a protocol request to resume execution of the debuggee.
  1.1088 +   */
  1.1089 +  onResume: function (aRequest) {
  1.1090 +    if (this._state !== "paused") {
  1.1091 +      return {
  1.1092 +        error: "wrongState",
  1.1093 +        message: "Can't resume when debuggee isn't paused. Current state is '"
  1.1094 +          + this._state + "'"
  1.1095 +      };
  1.1096 +    }
  1.1097 +
  1.1098 +    // In case of multiple nested event loops (due to multiple debuggers open in
  1.1099 +    // different tabs or multiple debugger clients connected to the same tab)
  1.1100 +    // only allow resumption in a LIFO order.
  1.1101 +    if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl
  1.1102 +        && (this._nestedEventLoops.lastPausedUrl !== this._hooks.url
  1.1103 +        || this._nestedEventLoops.lastConnection !== this.conn)) {
  1.1104 +      return {
  1.1105 +        error: "wrongOrder",
  1.1106 +        message: "trying to resume in the wrong order.",
  1.1107 +        lastPausedUrl: this._nestedEventLoops.lastPausedUrl
  1.1108 +      };
  1.1109 +    }
  1.1110 +
  1.1111 +    if (aRequest && aRequest.forceCompletion) {
  1.1112 +      return this._forceCompletion(aRequest);
  1.1113 +    }
  1.1114 +
  1.1115 +    let resumeLimitHandled;
  1.1116 +    if (aRequest && aRequest.resumeLimit) {
  1.1117 +      resumeLimitHandled = this._handleResumeLimit(aRequest)
  1.1118 +    } else {
  1.1119 +      this._clearSteppingHooks(this.youngestFrame);
  1.1120 +      resumeLimitHandled = resolve(true);
  1.1121 +    }
  1.1122 +
  1.1123 +    return resumeLimitHandled.then(() => {
  1.1124 +      if (aRequest) {
  1.1125 +        this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
  1.1126 +        this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
  1.1127 +        this.maybePauseOnExceptions();
  1.1128 +        this._maybeListenToEvents(aRequest);
  1.1129 +      }
  1.1130 +
  1.1131 +      let packet = this._resumed();
  1.1132 +      this._popThreadPause();
  1.1133 +      return packet;
  1.1134 +    }, error => {
  1.1135 +      return error instanceof Error
  1.1136 +        ? { error: "unknownError",
  1.1137 +            message: DevToolsUtils.safeErrorString(error) }
  1.1138 +        // It is a known error, and the promise was rejected with an error
  1.1139 +        // packet.
  1.1140 +        : error;
  1.1141 +    });
  1.1142 +  },
  1.1143 +
  1.1144 +  /**
  1.1145 +   * Spin up a nested event loop so we can synchronously resolve a promise.
  1.1146 +   *
  1.1147 +   * @param aPromise
  1.1148 +   *        The promise we want to resolve.
  1.1149 +   * @returns The promise's resolution.
  1.1150 +   */
  1.1151 +  synchronize: function(aPromise) {
  1.1152 +    let needNest = true;
  1.1153 +    let eventLoop;
  1.1154 +    let returnVal;
  1.1155 +
  1.1156 +    aPromise
  1.1157 +      .then((aResolvedVal) => {
  1.1158 +        needNest = false;
  1.1159 +        returnVal = aResolvedVal;
  1.1160 +      })
  1.1161 +      .then(null, (aError) => {
  1.1162 +        reportError(aError, "Error inside synchronize:");
  1.1163 +      })
  1.1164 +      .then(() => {
  1.1165 +        if (eventLoop) {
  1.1166 +          eventLoop.resolve();
  1.1167 +        }
  1.1168 +      });
  1.1169 +
  1.1170 +    if (needNest) {
  1.1171 +      eventLoop = this._nestedEventLoops.push();
  1.1172 +      eventLoop.enter();
  1.1173 +    }
  1.1174 +
  1.1175 +    return returnVal;
  1.1176 +  },
  1.1177 +
  1.1178 +  /**
  1.1179 +   * Set the debugging hook to pause on exceptions if configured to do so.
  1.1180 +   */
  1.1181 +  maybePauseOnExceptions: function() {
  1.1182 +    if (this._options.pauseOnExceptions) {
  1.1183 +      this.dbg.onExceptionUnwind = this.onExceptionUnwind.bind(this);
  1.1184 +    }
  1.1185 +  },
  1.1186 +
  1.1187 +  /**
  1.1188 +   * A listener that gets called for every event fired on the page, when a list
  1.1189 +   * of interesting events was provided with the pauseOnDOMEvents property. It
  1.1190 +   * is used to set server-managed breakpoints on any existing event listeners
  1.1191 +   * for those events.
  1.1192 +   *
  1.1193 +   * @param Event event
  1.1194 +   *        The event that was fired.
  1.1195 +   */
  1.1196 +  _allEventsListener: function(event) {
  1.1197 +    if (this._pauseOnDOMEvents == "*" ||
  1.1198 +        this._pauseOnDOMEvents.indexOf(event.type) != -1) {
  1.1199 +      for (let listener of this._getAllEventListeners(event.target)) {
  1.1200 +        if (event.type == listener.type || this._pauseOnDOMEvents == "*") {
  1.1201 +          this._breakOnEnter(listener.script);
  1.1202 +        }
  1.1203 +      }
  1.1204 +    }
  1.1205 +  },
  1.1206 +
  1.1207 +  /**
  1.1208 +   * Return an array containing all the event listeners attached to the
  1.1209 +   * specified event target and its ancestors in the event target chain.
  1.1210 +   *
  1.1211 +   * @param EventTarget eventTarget
  1.1212 +   *        The target the event was dispatched on.
  1.1213 +   * @returns Array
  1.1214 +   */
  1.1215 +  _getAllEventListeners: function(eventTarget) {
  1.1216 +    let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1.1217 +                .getService(Ci.nsIEventListenerService);
  1.1218 +
  1.1219 +    let targets = els.getEventTargetChainFor(eventTarget);
  1.1220 +    let listeners = [];
  1.1221 +
  1.1222 +    for (let target of targets) {
  1.1223 +      let handlers = els.getListenerInfoFor(target);
  1.1224 +      for (let handler of handlers) {
  1.1225 +        // Null is returned for all-events handlers, and native event listeners
  1.1226 +        // don't provide any listenerObject, which makes them not that useful to
  1.1227 +        // a JS debugger.
  1.1228 +        if (!handler || !handler.listenerObject || !handler.type)
  1.1229 +          continue;
  1.1230 +        // Create a listener-like object suitable for our purposes.
  1.1231 +        let l = Object.create(null);
  1.1232 +        l.type = handler.type;
  1.1233 +        let listener = handler.listenerObject;
  1.1234 +        l.script = this.globalDebugObject.makeDebuggeeValue(listener).script;
  1.1235 +        // Chrome listeners won't be converted to debuggee values, since their
  1.1236 +        // compartment is not added as a debuggee.
  1.1237 +        if (!l.script)
  1.1238 +          continue;
  1.1239 +        listeners.push(l);
  1.1240 +      }
  1.1241 +    }
  1.1242 +    return listeners;
  1.1243 +  },
  1.1244 +
  1.1245 +  /**
  1.1246 +   * Set a breakpoint on the first bytecode offset in the provided script.
  1.1247 +   */
  1.1248 +  _breakOnEnter: function(script) {
  1.1249 +    let offsets = script.getAllOffsets();
  1.1250 +    for (let line = 0, n = offsets.length; line < n; line++) {
  1.1251 +      if (offsets[line]) {
  1.1252 +        let location = { url: script.url, line: line };
  1.1253 +        let resp = this._createAndStoreBreakpoint(location);
  1.1254 +        dbg_assert(!resp.actualLocation, "No actualLocation should be returned");
  1.1255 +        if (resp.error) {
  1.1256 +          reportError(new Error("Unable to set breakpoint on event listener"));
  1.1257 +          return;
  1.1258 +        }
  1.1259 +        let bp = this.breakpointStore.getBreakpoint(location);
  1.1260 +        let bpActor = bp.actor;
  1.1261 +        dbg_assert(bp, "Breakpoint must exist");
  1.1262 +        dbg_assert(bpActor, "Breakpoint actor must be created");
  1.1263 +        this._hiddenBreakpoints.set(bpActor.actorID, bpActor);
  1.1264 +        break;
  1.1265 +      }
  1.1266 +    }
  1.1267 +  },
  1.1268 +
  1.1269 +  /**
  1.1270 +   * Helper method that returns the next frame when stepping.
  1.1271 +   */
  1.1272 +  _getNextStepFrame: function (aFrame) {
  1.1273 +    let stepFrame = aFrame.reportedPop ? aFrame.older : aFrame;
  1.1274 +    if (!stepFrame || !stepFrame.script) {
  1.1275 +      stepFrame = null;
  1.1276 +    }
  1.1277 +    return stepFrame;
  1.1278 +  },
  1.1279 +
  1.1280 +  onClientEvaluate: function (aRequest) {
  1.1281 +    if (this.state !== "paused") {
  1.1282 +      return { error: "wrongState",
  1.1283 +               message: "Debuggee must be paused to evaluate code." };
  1.1284 +    }
  1.1285 +
  1.1286 +    let frame = this._requestFrame(aRequest.frame);
  1.1287 +    if (!frame) {
  1.1288 +      return { error: "unknownFrame",
  1.1289 +               message: "Evaluation frame not found" };
  1.1290 +    }
  1.1291 +
  1.1292 +    if (!frame.environment) {
  1.1293 +      return { error: "notDebuggee",
  1.1294 +               message: "cannot access the environment of this frame." };
  1.1295 +    }
  1.1296 +
  1.1297 +    let youngest = this.youngestFrame;
  1.1298 +
  1.1299 +    // Put ourselves back in the running state and inform the client.
  1.1300 +    let resumedPacket = this._resumed();
  1.1301 +    this.conn.send(resumedPacket);
  1.1302 +
  1.1303 +    // Run the expression.
  1.1304 +    // XXX: test syntax errors
  1.1305 +    let completion = frame.eval(aRequest.expression);
  1.1306 +
  1.1307 +    // Put ourselves back in the pause state.
  1.1308 +    let packet = this._paused(youngest);
  1.1309 +    packet.why = { type: "clientEvaluated",
  1.1310 +                   frameFinished: this.createProtocolCompletionValue(completion) };
  1.1311 +
  1.1312 +    // Return back to our previous pause's event loop.
  1.1313 +    return packet;
  1.1314 +  },
  1.1315 +
  1.1316 +  onFrames: function (aRequest) {
  1.1317 +    if (this.state !== "paused") {
  1.1318 +      return { error: "wrongState",
  1.1319 +               message: "Stack frames are only available while the debuggee is paused."};
  1.1320 +    }
  1.1321 +
  1.1322 +    let start = aRequest.start ? aRequest.start : 0;
  1.1323 +    let count = aRequest.count;
  1.1324 +
  1.1325 +    // Find the starting frame...
  1.1326 +    let frame = this.youngestFrame;
  1.1327 +    let i = 0;
  1.1328 +    while (frame && (i < start)) {
  1.1329 +      frame = frame.older;
  1.1330 +      i++;
  1.1331 +    }
  1.1332 +
  1.1333 +    // Return request.count frames, or all remaining
  1.1334 +    // frames if count is not defined.
  1.1335 +    let frames = [];
  1.1336 +    let promises = [];
  1.1337 +    for (; frame && (!count || i < (start + count)); i++, frame=frame.older) {
  1.1338 +      let form = this._createFrameActor(frame).form();
  1.1339 +      form.depth = i;
  1.1340 +      frames.push(form);
  1.1341 +
  1.1342 +      let promise = this.sources.getOriginalLocation(form.where)
  1.1343 +        .then((aOrigLocation) => {
  1.1344 +          form.where = aOrigLocation;
  1.1345 +          let source = this.sources.source({ url: form.where.url });
  1.1346 +          if (source) {
  1.1347 +            form.source = source.form();
  1.1348 +          }
  1.1349 +        });
  1.1350 +      promises.push(promise);
  1.1351 +    }
  1.1352 +
  1.1353 +    return all(promises).then(function () {
  1.1354 +      return { frames: frames };
  1.1355 +    });
  1.1356 +  },
  1.1357 +
  1.1358 +  onReleaseMany: function (aRequest) {
  1.1359 +    if (!aRequest.actors) {
  1.1360 +      return { error: "missingParameter",
  1.1361 +               message: "no actors were specified" };
  1.1362 +    }
  1.1363 +
  1.1364 +    let res;
  1.1365 +    for each (let actorID in aRequest.actors) {
  1.1366 +      let actor = this.threadLifetimePool.get(actorID);
  1.1367 +      if (!actor) {
  1.1368 +        if (!res) {
  1.1369 +          res = { error: "notReleasable",
  1.1370 +                  message: "Only thread-lifetime actors can be released." };
  1.1371 +        }
  1.1372 +        continue;
  1.1373 +      }
  1.1374 +      actor.onRelease();
  1.1375 +    }
  1.1376 +    return res ? res : {};
  1.1377 +  },
  1.1378 +
  1.1379 +  /**
  1.1380 +   * Handle a protocol request to set a breakpoint.
  1.1381 +   */
  1.1382 +  onSetBreakpoint: function (aRequest) {
  1.1383 +    if (this.state !== "paused") {
  1.1384 +      return { error: "wrongState",
  1.1385 +               message: "Breakpoints can only be set while the debuggee is paused."};
  1.1386 +    }
  1.1387 +
  1.1388 +    let { url: originalSource,
  1.1389 +          line: originalLine,
  1.1390 +          column: originalColumn } = aRequest.location;
  1.1391 +
  1.1392 +    let locationPromise = this.sources.getGeneratedLocation(aRequest.location);
  1.1393 +    return locationPromise.then(({url, line, column}) => {
  1.1394 +      if (line == null ||
  1.1395 +          line < 0 ||
  1.1396 +          this.dbg.findScripts({ url: url }).length == 0) {
  1.1397 +        return {
  1.1398 +          error: "noScript",
  1.1399 +          message: "Requested setting a breakpoint on "
  1.1400 +            + url + ":" + line
  1.1401 +            + (column != null ? ":" + column : "")
  1.1402 +            + " but there is no Debugger.Script at that location"
  1.1403 +        };
  1.1404 +      }
  1.1405 +
  1.1406 +      let response = this._createAndStoreBreakpoint({
  1.1407 +        url: url,
  1.1408 +        line: line,
  1.1409 +        column: column,
  1.1410 +        condition: aRequest.condition
  1.1411 +      });
  1.1412 +      // If the original location of our generated location is different from
  1.1413 +      // the original location we attempted to set the breakpoint on, we will
  1.1414 +      // need to know so that we can set actualLocation on the response.
  1.1415 +      let originalLocation = this.sources.getOriginalLocation({
  1.1416 +        url: url,
  1.1417 +        line: line,
  1.1418 +        column: column
  1.1419 +      });
  1.1420 +
  1.1421 +      return all([response, originalLocation])
  1.1422 +        .then(([aResponse, {url, line}]) => {
  1.1423 +          if (aResponse.actualLocation) {
  1.1424 +            let actualOrigLocation = this.sources.getOriginalLocation(aResponse.actualLocation);
  1.1425 +            return actualOrigLocation.then(({ url, line, column }) => {
  1.1426 +              if (url !== originalSource
  1.1427 +                  || line !== originalLine
  1.1428 +                  || column !== originalColumn) {
  1.1429 +                aResponse.actualLocation = {
  1.1430 +                  url: url,
  1.1431 +                  line: line,
  1.1432 +                  column: column
  1.1433 +                };
  1.1434 +              }
  1.1435 +              return aResponse;
  1.1436 +            });
  1.1437 +          }
  1.1438 +
  1.1439 +          if (url !== originalSource || line !== originalLine) {
  1.1440 +            aResponse.actualLocation = { url: url, line: line };
  1.1441 +          }
  1.1442 +
  1.1443 +          return aResponse;
  1.1444 +        });
  1.1445 +    });
  1.1446 +  },
  1.1447 +
  1.1448 +  /**
  1.1449 +   * Create a breakpoint at the specified location and store it in the
  1.1450 +   * cache. Takes ownership of `aLocation`.
  1.1451 +   *
  1.1452 +   * @param Object aLocation
  1.1453 +   *        An object of the form { url, line[, column] }
  1.1454 +   */
  1.1455 +  _createAndStoreBreakpoint: function (aLocation) {
  1.1456 +    // Add the breakpoint to the store for later reuse, in case it belongs to a
  1.1457 +    // script that hasn't appeared yet.
  1.1458 +    this.breakpointStore.addBreakpoint(aLocation);
  1.1459 +    return this._setBreakpoint(aLocation);
  1.1460 +  },
  1.1461 +
  1.1462 +  /**
  1.1463 +   * Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint
  1.1464 +   * is being set contains no code, then the breakpoint will slide down to the
  1.1465 +   * next line that has runnable code. In this case the server breakpoint cache
  1.1466 +   * will be updated, so callers that iterate over the breakpoint cache should
  1.1467 +   * take that into account.
  1.1468 +   *
  1.1469 +   * @param object aLocation
  1.1470 +   *        The location of the breakpoint (in the generated source, if source
  1.1471 +   *        mapping).
  1.1472 +   */
  1.1473 +  _setBreakpoint: function (aLocation) {
  1.1474 +    let actor;
  1.1475 +    let storedBp = this.breakpointStore.getBreakpoint(aLocation);
  1.1476 +    if (storedBp.actor) {
  1.1477 +      actor = storedBp.actor;
  1.1478 +      actor.condition = aLocation.condition;
  1.1479 +    } else {
  1.1480 +      storedBp.actor = actor = new BreakpointActor(this, {
  1.1481 +        url: aLocation.url,
  1.1482 +        line: aLocation.line,
  1.1483 +        column: aLocation.column,
  1.1484 +        condition: aLocation.condition
  1.1485 +      });
  1.1486 +      this.threadLifetimePool.addActor(actor);
  1.1487 +    }
  1.1488 +
  1.1489 +    // Find all scripts matching the given location
  1.1490 +    let scripts = this.dbg.findScripts(aLocation);
  1.1491 +    if (scripts.length == 0) {
  1.1492 +      return {
  1.1493 +        error: "noScript",
  1.1494 +        message: "Requested setting a breakpoint on "
  1.1495 +          + aLocation.url + ":" + aLocation.line
  1.1496 +          + (aLocation.column != null ? ":" + aLocation.column : "")
  1.1497 +          + " but there is no Debugger.Script at that location",
  1.1498 +        actor: actor.actorID
  1.1499 +      };
  1.1500 +    }
  1.1501 +
  1.1502 +   /**
  1.1503 +    * For each script, if the given line has at least one entry point, set a
  1.1504 +    * breakpoint on the bytecode offets for each of them.
  1.1505 +    */
  1.1506 +
  1.1507 +    // Debugger.Script -> array of offset mappings
  1.1508 +    let scriptsAndOffsetMappings = new Map();
  1.1509 +
  1.1510 +    for (let script of scripts) {
  1.1511 +      this._findClosestOffsetMappings(aLocation,
  1.1512 +                                      script,
  1.1513 +                                      scriptsAndOffsetMappings);
  1.1514 +    }
  1.1515 +
  1.1516 +    if (scriptsAndOffsetMappings.size > 0) {
  1.1517 +      for (let [script, mappings] of scriptsAndOffsetMappings) {
  1.1518 +        for (let offsetMapping of mappings) {
  1.1519 +          script.setBreakpoint(offsetMapping.offset, actor);
  1.1520 +        }
  1.1521 +        actor.addScript(script, this);
  1.1522 +      }
  1.1523 +
  1.1524 +      return {
  1.1525 +        actor: actor.actorID
  1.1526 +      };
  1.1527 +    }
  1.1528 +
  1.1529 +   /**
  1.1530 +    * If we get here, no breakpoint was set. This is because the given line
  1.1531 +    * has no entry points, for example because it is empty. As a fallback
  1.1532 +    * strategy, we try to set the breakpoint on the smallest line greater
  1.1533 +    * than or equal to the given line that as at least one entry point.
  1.1534 +    */
  1.1535 +
  1.1536 +    // Find all innermost scripts matching the given location
  1.1537 +    let scripts = this.dbg.findScripts({
  1.1538 +      url: aLocation.url,
  1.1539 +      line: aLocation.line,
  1.1540 +      innermost: true
  1.1541 +    });
  1.1542 +
  1.1543 +    /**
  1.1544 +     * For each innermost script, look for the smallest line greater than or
  1.1545 +     * equal to the given line that has one or more entry points. If found, set
  1.1546 +     * a breakpoint on the bytecode offset for each of its entry points.
  1.1547 +     */
  1.1548 +    let actualLocation;
  1.1549 +    let found = false;
  1.1550 +    for (let script of scripts) {
  1.1551 +      let offsets = script.getAllOffsets();
  1.1552 +      for (let line = aLocation.line; line < offsets.length; ++line) {
  1.1553 +        if (offsets[line]) {
  1.1554 +          for (let offset of offsets[line]) {
  1.1555 +            script.setBreakpoint(offset, actor);
  1.1556 +          }
  1.1557 +          actor.addScript(script, this);
  1.1558 +          if (!actualLocation) {
  1.1559 +            actualLocation = {
  1.1560 +              url: aLocation.url,
  1.1561 +              line: line
  1.1562 +            };
  1.1563 +          }
  1.1564 +          found = true;
  1.1565 +          break;
  1.1566 +        }
  1.1567 +      }
  1.1568 +    }
  1.1569 +    if (found) {
  1.1570 +      let existingBp = this.breakpointStore.hasBreakpoint(actualLocation);
  1.1571 +
  1.1572 +      if (existingBp && existingBp.actor) {
  1.1573 +        /**
  1.1574 +         * We already have a breakpoint actor for the actual location, so
  1.1575 +         * actor we created earlier is now redundant. Delete it, update the
  1.1576 +         * breakpoint store, and return the actor for the actual location.
  1.1577 +         */
  1.1578 +        actor.onDelete();
  1.1579 +        this.breakpointStore.removeBreakpoint(aLocation);
  1.1580 +        return {
  1.1581 +          actor: existingBp.actor.actorID,
  1.1582 +          actualLocation: actualLocation
  1.1583 +        };
  1.1584 +      } else {
  1.1585 +        /**
  1.1586 +         * We don't have a breakpoint actor for the actual location yet.
  1.1587 +         * Instead or creating a new actor, reuse the actor we created earlier,
  1.1588 +         * and update the breakpoint store.
  1.1589 +         */
  1.1590 +        actor.location = actualLocation;
  1.1591 +        this.breakpointStore.addBreakpoint({
  1.1592 +          actor: actor,
  1.1593 +          url: actualLocation.url,
  1.1594 +          line: actualLocation.line,
  1.1595 +          column: actualLocation.column
  1.1596 +        });
  1.1597 +        this.breakpointStore.removeBreakpoint(aLocation);
  1.1598 +        return {
  1.1599 +          actor: actor.actorID,
  1.1600 +          actualLocation: actualLocation
  1.1601 +        };
  1.1602 +      }
  1.1603 +    }
  1.1604 +
  1.1605 +    /**
  1.1606 +     * If we get here, no line matching the given line was found, so just
  1.1607 +     * fail epically.
  1.1608 +     */
  1.1609 +    return {
  1.1610 +      error: "noCodeAtLineColumn",
  1.1611 +      actor: actor.actorID
  1.1612 +    };
  1.1613 +  },
  1.1614 +
  1.1615 +  /**
  1.1616 +   * Find all of the offset mappings associated with `aScript` that are closest
  1.1617 +   * to `aTargetLocation`. If new offset mappings are found that are closer to
  1.1618 +   * `aTargetOffset` than the existing offset mappings inside
  1.1619 +   * `aScriptsAndOffsetMappings`, we empty that map and only consider the
  1.1620 +   * closest offset mappings. If there is no column in `aTargetLocation`, we add
  1.1621 +   * all offset mappings that are on the given line.
  1.1622 +   *
  1.1623 +   * @param Object aTargetLocation
  1.1624 +   *        An object of the form { url, line[, column] }.
  1.1625 +   * @param Debugger.Script aScript
  1.1626 +   *        The script in which we are searching for offsets.
  1.1627 +   * @param Map aScriptsAndOffsetMappings
  1.1628 +   *        A Map object which maps Debugger.Script instances to arrays of
  1.1629 +   *        offset mappings. This is an out param.
  1.1630 +   */
  1.1631 +  _findClosestOffsetMappings: function (aTargetLocation,
  1.1632 +                                        aScript,
  1.1633 +                                        aScriptsAndOffsetMappings) {
  1.1634 +    // If we are given a column, we will try and break only at that location,
  1.1635 +    // otherwise we will break anytime we get on that line.
  1.1636 +
  1.1637 +    if (aTargetLocation.column == null) {
  1.1638 +      let offsetMappings = aScript.getLineOffsets(aTargetLocation.line)
  1.1639 +        .map(o => ({
  1.1640 +          line: aTargetLocation.line,
  1.1641 +          offset: o
  1.1642 +        }));
  1.1643 +      if (offsetMappings.length) {
  1.1644 +        aScriptsAndOffsetMappings.set(aScript, offsetMappings);
  1.1645 +      }
  1.1646 +      return;
  1.1647 +    }
  1.1648 +
  1.1649 +    let offsetMappings = aScript.getAllColumnOffsets()
  1.1650 +      .filter(({ lineNumber }) => lineNumber === aTargetLocation.line);
  1.1651 +
  1.1652 +    // Attempt to find the current closest offset distance from the target
  1.1653 +    // location by grabbing any offset mapping in the map by doing one iteration
  1.1654 +    // and then breaking (they all have the same distance from the target
  1.1655 +    // location).
  1.1656 +    let closestDistance = Infinity;
  1.1657 +    if (aScriptsAndOffsetMappings.size) {
  1.1658 +      for (let mappings of aScriptsAndOffsetMappings.values()) {
  1.1659 +        closestDistance = Math.abs(aTargetLocation.column - mappings[0].columnNumber);
  1.1660 +        break;
  1.1661 +      }
  1.1662 +    }
  1.1663 +
  1.1664 +    for (let mapping of offsetMappings) {
  1.1665 +      let currentDistance = Math.abs(aTargetLocation.column - mapping.columnNumber);
  1.1666 +
  1.1667 +      if (currentDistance > closestDistance) {
  1.1668 +        continue;
  1.1669 +      } else if (currentDistance < closestDistance) {
  1.1670 +        closestDistance = currentDistance;
  1.1671 +        aScriptsAndOffsetMappings.clear();
  1.1672 +        aScriptsAndOffsetMappings.set(aScript, [mapping]);
  1.1673 +      } else {
  1.1674 +        if (!aScriptsAndOffsetMappings.has(aScript)) {
  1.1675 +          aScriptsAndOffsetMappings.set(aScript, []);
  1.1676 +        }
  1.1677 +        aScriptsAndOffsetMappings.get(aScript).push(mapping);
  1.1678 +      }
  1.1679 +    }
  1.1680 +  },
  1.1681 +
  1.1682 +  /**
  1.1683 +   * Get the script and source lists from the debugger.
  1.1684 +   */
  1.1685 +  _discoverSources: function () {
  1.1686 +    // Only get one script per url.
  1.1687 +    const sourcesToScripts = new Map();
  1.1688 +    for (let s of this.dbg.findScripts()) {
  1.1689 +      if (s.source) {
  1.1690 +        sourcesToScripts.set(s.source, s);
  1.1691 +      }
  1.1692 +    }
  1.1693 +
  1.1694 +    return all([this.sources.sourcesForScript(script)
  1.1695 +                for (script of sourcesToScripts.values())]);
  1.1696 +  },
  1.1697 +
  1.1698 +  onSources: function (aRequest) {
  1.1699 +    return this._discoverSources().then(() => {
  1.1700 +      return {
  1.1701 +        sources: [s.form() for (s of this.sources.iter())]
  1.1702 +      };
  1.1703 +    });
  1.1704 +  },
  1.1705 +
  1.1706 +  /**
  1.1707 +   * Disassociate all breakpoint actors from their scripts and clear the
  1.1708 +   * breakpoint handlers. This method can be used when the thread actor intends
  1.1709 +   * to keep the breakpoint store, but needs to clear any actual breakpoints,
  1.1710 +   * e.g. due to a page navigation. This way the breakpoint actors' script
  1.1711 +   * caches won't hold on to the Debugger.Script objects leaking memory.
  1.1712 +   */
  1.1713 +  disableAllBreakpoints: function () {
  1.1714 +    for (let bp of this.breakpointStore.findBreakpoints()) {
  1.1715 +      if (bp.actor) {
  1.1716 +        bp.actor.removeScripts();
  1.1717 +      }
  1.1718 +    }
  1.1719 +  },
  1.1720 +
  1.1721 +  /**
  1.1722 +   * Handle a protocol request to pause the debuggee.
  1.1723 +   */
  1.1724 +  onInterrupt: function (aRequest) {
  1.1725 +    if (this.state == "exited") {
  1.1726 +      return { type: "exited" };
  1.1727 +    } else if (this.state == "paused") {
  1.1728 +      // TODO: return the actual reason for the existing pause.
  1.1729 +      return { type: "paused", why: { type: "alreadyPaused" } };
  1.1730 +    } else if (this.state != "running") {
  1.1731 +      return { error: "wrongState",
  1.1732 +               message: "Received interrupt request in " + this.state +
  1.1733 +                        " state." };
  1.1734 +    }
  1.1735 +
  1.1736 +    try {
  1.1737 +      // Put ourselves in the paused state.
  1.1738 +      let packet = this._paused();
  1.1739 +      if (!packet) {
  1.1740 +        return { error: "notInterrupted" };
  1.1741 +      }
  1.1742 +      packet.why = { type: "interrupted" };
  1.1743 +
  1.1744 +      // Send the response to the interrupt request now (rather than
  1.1745 +      // returning it), because we're going to start a nested event loop
  1.1746 +      // here.
  1.1747 +      this.conn.send(packet);
  1.1748 +
  1.1749 +      // Start a nested event loop.
  1.1750 +      this._pushThreadPause();
  1.1751 +
  1.1752 +      // We already sent a response to this request, don't send one
  1.1753 +      // now.
  1.1754 +      return null;
  1.1755 +    } catch (e) {
  1.1756 +      reportError(e);
  1.1757 +      return { error: "notInterrupted", message: e.toString() };
  1.1758 +    }
  1.1759 +  },
  1.1760 +
  1.1761 +  /**
  1.1762 +   * Handle a protocol request to retrieve all the event listeners on the page.
  1.1763 +   */
  1.1764 +  onEventListeners: function (aRequest) {
  1.1765 +    // This request is only supported in content debugging.
  1.1766 +    if (!this.global) {
  1.1767 +      return {
  1.1768 +        error: "notImplemented",
  1.1769 +        message: "eventListeners request is only supported in content debugging"
  1.1770 +      };
  1.1771 +    }
  1.1772 +
  1.1773 +    let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1.1774 +                .getService(Ci.nsIEventListenerService);
  1.1775 +
  1.1776 +    let nodes = this.global.document.getElementsByTagName("*");
  1.1777 +    nodes = [this.global].concat([].slice.call(nodes));
  1.1778 +    let listeners = [];
  1.1779 +
  1.1780 +    for (let node of nodes) {
  1.1781 +      let handlers = els.getListenerInfoFor(node);
  1.1782 +
  1.1783 +      for (let handler of handlers) {
  1.1784 +        // Create a form object for serializing the listener via the protocol.
  1.1785 +        let listenerForm = Object.create(null);
  1.1786 +        let listener = handler.listenerObject;
  1.1787 +        // Native event listeners don't provide any listenerObject or type and
  1.1788 +        // are not that useful to a JS debugger.
  1.1789 +        if (!listener || !handler.type) {
  1.1790 +          continue;
  1.1791 +        }
  1.1792 +
  1.1793 +        // There will be no tagName if the event listener is set on the window.
  1.1794 +        let selector = node.tagName ? findCssSelector(node) : "window";
  1.1795 +        let nodeDO = this.globalDebugObject.makeDebuggeeValue(node);
  1.1796 +        listenerForm.node = {
  1.1797 +          selector: selector,
  1.1798 +          object: this.createValueGrip(nodeDO)
  1.1799 +        };
  1.1800 +        listenerForm.type = handler.type;
  1.1801 +        listenerForm.capturing = handler.capturing;
  1.1802 +        listenerForm.allowsUntrusted = handler.allowsUntrusted;
  1.1803 +        listenerForm.inSystemEventGroup = handler.inSystemEventGroup;
  1.1804 +        listenerForm.isEventHandler = !!node["on" + listenerForm.type];
  1.1805 +        // Get the Debugger.Object for the listener object.
  1.1806 +        let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
  1.1807 +        listenerForm.function = this.createValueGrip(listenerDO);
  1.1808 +        listeners.push(listenerForm);
  1.1809 +      }
  1.1810 +    }
  1.1811 +    return { listeners: listeners };
  1.1812 +  },
  1.1813 +
  1.1814 +  /**
  1.1815 +   * Return the Debug.Frame for a frame mentioned by the protocol.
  1.1816 +   */
  1.1817 +  _requestFrame: function (aFrameID) {
  1.1818 +    if (!aFrameID) {
  1.1819 +      return this.youngestFrame;
  1.1820 +    }
  1.1821 +
  1.1822 +    if (this._framePool.has(aFrameID)) {
  1.1823 +      return this._framePool.get(aFrameID).frame;
  1.1824 +    }
  1.1825 +
  1.1826 +    return undefined;
  1.1827 +  },
  1.1828 +
  1.1829 +  _paused: function (aFrame) {
  1.1830 +    // We don't handle nested pauses correctly.  Don't try - if we're
  1.1831 +    // paused, just continue running whatever code triggered the pause.
  1.1832 +    // We don't want to actually have nested pauses (although we
  1.1833 +    // have nested event loops).  If code runs in the debuggee during
  1.1834 +    // a pause, it should cause the actor to resume (dropping
  1.1835 +    // pause-lifetime actors etc) and then repause when complete.
  1.1836 +
  1.1837 +    if (this.state === "paused") {
  1.1838 +      return undefined;
  1.1839 +    }
  1.1840 +
  1.1841 +    // Clear stepping hooks.
  1.1842 +    this.dbg.onEnterFrame = undefined;
  1.1843 +    this.dbg.onExceptionUnwind = undefined;
  1.1844 +    if (aFrame) {
  1.1845 +      aFrame.onStep = undefined;
  1.1846 +      aFrame.onPop = undefined;
  1.1847 +    }
  1.1848 +    // Clear DOM event breakpoints.
  1.1849 +    // XPCShell tests don't use actual DOM windows for globals and cause
  1.1850 +    // removeListenerForAllEvents to throw.
  1.1851 +    if (this.global && !this.global.toString().contains("Sandbox")) {
  1.1852 +      let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1.1853 +                .getService(Ci.nsIEventListenerService);
  1.1854 +      els.removeListenerForAllEvents(this.global, this._allEventsListener, true);
  1.1855 +      for (let [,bp] of this._hiddenBreakpoints) {
  1.1856 +        bp.onDelete();
  1.1857 +      }
  1.1858 +      this._hiddenBreakpoints.clear();
  1.1859 +    }
  1.1860 +
  1.1861 +    this._state = "paused";
  1.1862 +
  1.1863 +    // Create the actor pool that will hold the pause actor and its
  1.1864 +    // children.
  1.1865 +    dbg_assert(!this._pausePool, "No pause pool should exist yet");
  1.1866 +    this._pausePool = new ActorPool(this.conn);
  1.1867 +    this.conn.addActorPool(this._pausePool);
  1.1868 +
  1.1869 +    // Give children of the pause pool a quick link back to the
  1.1870 +    // thread...
  1.1871 +    this._pausePool.threadActor = this;
  1.1872 +
  1.1873 +    // Create the pause actor itself...
  1.1874 +    dbg_assert(!this._pauseActor, "No pause actor should exist yet");
  1.1875 +    this._pauseActor = new PauseActor(this._pausePool);
  1.1876 +    this._pausePool.addActor(this._pauseActor);
  1.1877 +
  1.1878 +    // Update the list of frames.
  1.1879 +    let poppedFrames = this._updateFrames();
  1.1880 +
  1.1881 +    // Send off the paused packet and spin an event loop.
  1.1882 +    let packet = { from: this.actorID,
  1.1883 +                   type: "paused",
  1.1884 +                   actor: this._pauseActor.actorID };
  1.1885 +    if (aFrame) {
  1.1886 +      packet.frame = this._createFrameActor(aFrame).form();
  1.1887 +    }
  1.1888 +
  1.1889 +    if (poppedFrames) {
  1.1890 +      packet.poppedFrames = poppedFrames;
  1.1891 +    }
  1.1892 +
  1.1893 +    return packet;
  1.1894 +  },
  1.1895 +
  1.1896 +  _resumed: function () {
  1.1897 +    this._state = "running";
  1.1898 +
  1.1899 +    // Drop the actors in the pause actor pool.
  1.1900 +    this.conn.removeActorPool(this._pausePool);
  1.1901 +
  1.1902 +    this._pausePool = null;
  1.1903 +    this._pauseActor = null;
  1.1904 +
  1.1905 +    return { from: this.actorID, type: "resumed" };
  1.1906 +  },
  1.1907 +
  1.1908 +  /**
  1.1909 +   * Expire frame actors for frames that have been popped.
  1.1910 +   *
  1.1911 +   * @returns A list of actor IDs whose frames have been popped.
  1.1912 +   */
  1.1913 +  _updateFrames: function () {
  1.1914 +    let popped = [];
  1.1915 +
  1.1916 +    // Create the actor pool that will hold the still-living frames.
  1.1917 +    let framePool = new ActorPool(this.conn);
  1.1918 +    let frameList = [];
  1.1919 +
  1.1920 +    for each (let frameActor in this._frameActors) {
  1.1921 +      if (frameActor.frame.live) {
  1.1922 +        framePool.addActor(frameActor);
  1.1923 +        frameList.push(frameActor);
  1.1924 +      } else {
  1.1925 +        popped.push(frameActor.actorID);
  1.1926 +      }
  1.1927 +    }
  1.1928 +
  1.1929 +    // Remove the old frame actor pool, this will expire
  1.1930 +    // any actors that weren't added to the new pool.
  1.1931 +    if (this._framePool) {
  1.1932 +      this.conn.removeActorPool(this._framePool);
  1.1933 +    }
  1.1934 +
  1.1935 +    this._frameActors = frameList;
  1.1936 +    this._framePool = framePool;
  1.1937 +    this.conn.addActorPool(framePool);
  1.1938 +
  1.1939 +    return popped;
  1.1940 +  },
  1.1941 +
  1.1942 +  _createFrameActor: function (aFrame) {
  1.1943 +    if (aFrame.actor) {
  1.1944 +      return aFrame.actor;
  1.1945 +    }
  1.1946 +
  1.1947 +    let actor = new FrameActor(aFrame, this);
  1.1948 +    this._frameActors.push(actor);
  1.1949 +    this._framePool.addActor(actor);
  1.1950 +    aFrame.actor = actor;
  1.1951 +
  1.1952 +    return actor;
  1.1953 +  },
  1.1954 +
  1.1955 +  /**
  1.1956 +   * Create and return an environment actor that corresponds to the provided
  1.1957 +   * Debugger.Environment.
  1.1958 +   * @param Debugger.Environment aEnvironment
  1.1959 +   *        The lexical environment we want to extract.
  1.1960 +   * @param object aPool
  1.1961 +   *        The pool where the newly-created actor will be placed.
  1.1962 +   * @return The EnvironmentActor for aEnvironment or undefined for host
  1.1963 +   *         functions or functions scoped to a non-debuggee global.
  1.1964 +   */
  1.1965 +  createEnvironmentActor: function (aEnvironment, aPool) {
  1.1966 +    if (!aEnvironment) {
  1.1967 +      return undefined;
  1.1968 +    }
  1.1969 +
  1.1970 +    if (aEnvironment.actor) {
  1.1971 +      return aEnvironment.actor;
  1.1972 +    }
  1.1973 +
  1.1974 +    let actor = new EnvironmentActor(aEnvironment, this);
  1.1975 +    aPool.addActor(actor);
  1.1976 +    aEnvironment.actor = actor;
  1.1977 +
  1.1978 +    return actor;
  1.1979 +  },
  1.1980 +
  1.1981 +  /**
  1.1982 +   * Create a grip for the given debuggee value.  If the value is an
  1.1983 +   * object, will create an actor with the given lifetime.
  1.1984 +   */
  1.1985 +  createValueGrip: function (aValue, aPool=false) {
  1.1986 +    if (!aPool) {
  1.1987 +      aPool = this._pausePool;
  1.1988 +    }
  1.1989 +
  1.1990 +    switch (typeof aValue) {
  1.1991 +      case "boolean":
  1.1992 +        return aValue;
  1.1993 +      case "string":
  1.1994 +        if (this._stringIsLong(aValue)) {
  1.1995 +          return this.longStringGrip(aValue, aPool);
  1.1996 +        }
  1.1997 +        return aValue;
  1.1998 +      case "number":
  1.1999 +        if (aValue === Infinity) {
  1.2000 +          return { type: "Infinity" };
  1.2001 +        } else if (aValue === -Infinity) {
  1.2002 +          return { type: "-Infinity" };
  1.2003 +        } else if (Number.isNaN(aValue)) {
  1.2004 +          return { type: "NaN" };
  1.2005 +        } else if (!aValue && 1 / aValue === -Infinity) {
  1.2006 +          return { type: "-0" };
  1.2007 +        }
  1.2008 +        return aValue;
  1.2009 +      case "undefined":
  1.2010 +        return { type: "undefined" };
  1.2011 +      case "object":
  1.2012 +        if (aValue === null) {
  1.2013 +          return { type: "null" };
  1.2014 +        }
  1.2015 +        return this.objectGrip(aValue, aPool);
  1.2016 +      default:
  1.2017 +        dbg_assert(false, "Failed to provide a grip for: " + aValue);
  1.2018 +        return null;
  1.2019 +    }
  1.2020 +  },
  1.2021 +
  1.2022 +  /**
  1.2023 +   * Return a protocol completion value representing the given
  1.2024 +   * Debugger-provided completion value.
  1.2025 +   */
  1.2026 +  createProtocolCompletionValue: function (aCompletion) {
  1.2027 +    let protoValue = {};
  1.2028 +    if ("return" in aCompletion) {
  1.2029 +      protoValue.return = this.createValueGrip(aCompletion.return);
  1.2030 +    } else if ("yield" in aCompletion) {
  1.2031 +      protoValue.return = this.createValueGrip(aCompletion.yield);
  1.2032 +    } else if ("throw" in aCompletion) {
  1.2033 +      protoValue.throw = this.createValueGrip(aCompletion.throw);
  1.2034 +    } else {
  1.2035 +      protoValue.terminated = true;
  1.2036 +    }
  1.2037 +    return protoValue;
  1.2038 +  },
  1.2039 +
  1.2040 +  /**
  1.2041 +   * Create a grip for the given debuggee object.
  1.2042 +   *
  1.2043 +   * @param aValue Debugger.Object
  1.2044 +   *        The debuggee object value.
  1.2045 +   * @param aPool ActorPool
  1.2046 +   *        The actor pool where the new object actor will be added.
  1.2047 +   */
  1.2048 +  objectGrip: function (aValue, aPool) {
  1.2049 +    if (!aPool.objectActors) {
  1.2050 +      aPool.objectActors = new WeakMap();
  1.2051 +    }
  1.2052 +
  1.2053 +    if (aPool.objectActors.has(aValue)) {
  1.2054 +      return aPool.objectActors.get(aValue).grip();
  1.2055 +    } else if (this.threadLifetimePool.objectActors.has(aValue)) {
  1.2056 +      return this.threadLifetimePool.objectActors.get(aValue).grip();
  1.2057 +    }
  1.2058 +
  1.2059 +    let actor = new PauseScopedObjectActor(aValue, this);
  1.2060 +    aPool.addActor(actor);
  1.2061 +    aPool.objectActors.set(aValue, actor);
  1.2062 +    return actor.grip();
  1.2063 +  },
  1.2064 +
  1.2065 +  /**
  1.2066 +   * Create a grip for the given debuggee object with a pause lifetime.
  1.2067 +   *
  1.2068 +   * @param aValue Debugger.Object
  1.2069 +   *        The debuggee object value.
  1.2070 +   */
  1.2071 +  pauseObjectGrip: function (aValue) {
  1.2072 +    if (!this._pausePool) {
  1.2073 +      throw "Object grip requested while not paused.";
  1.2074 +    }
  1.2075 +
  1.2076 +    return this.objectGrip(aValue, this._pausePool);
  1.2077 +  },
  1.2078 +
  1.2079 +  /**
  1.2080 +   * Extend the lifetime of the provided object actor to thread lifetime.
  1.2081 +   *
  1.2082 +   * @param aActor object
  1.2083 +   *        The object actor.
  1.2084 +   */
  1.2085 +  threadObjectGrip: function (aActor) {
  1.2086 +    // We want to reuse the existing actor ID, so we just remove it from the
  1.2087 +    // current pool's weak map and then let pool.addActor do the rest.
  1.2088 +    aActor.registeredPool.objectActors.delete(aActor.obj);
  1.2089 +    this.threadLifetimePool.addActor(aActor);
  1.2090 +    this.threadLifetimePool.objectActors.set(aActor.obj, aActor);
  1.2091 +  },
  1.2092 +
  1.2093 +  /**
  1.2094 +   * Handle a protocol request to promote multiple pause-lifetime grips to
  1.2095 +   * thread-lifetime grips.
  1.2096 +   *
  1.2097 +   * @param aRequest object
  1.2098 +   *        The protocol request object.
  1.2099 +   */
  1.2100 +  onThreadGrips: function (aRequest) {
  1.2101 +    if (this.state != "paused") {
  1.2102 +      return { error: "wrongState" };
  1.2103 +    }
  1.2104 +
  1.2105 +    if (!aRequest.actors) {
  1.2106 +      return { error: "missingParameter",
  1.2107 +               message: "no actors were specified" };
  1.2108 +    }
  1.2109 +
  1.2110 +    for (let actorID of aRequest.actors) {
  1.2111 +      let actor = this._pausePool.get(actorID);
  1.2112 +      if (actor) {
  1.2113 +        this.threadObjectGrip(actor);
  1.2114 +      }
  1.2115 +    }
  1.2116 +    return {};
  1.2117 +  },
  1.2118 +
  1.2119 +  /**
  1.2120 +   * Create a grip for the given string.
  1.2121 +   *
  1.2122 +   * @param aString String
  1.2123 +   *        The string we are creating a grip for.
  1.2124 +   * @param aPool ActorPool
  1.2125 +   *        The actor pool where the new actor will be added.
  1.2126 +   */
  1.2127 +  longStringGrip: function (aString, aPool) {
  1.2128 +    if (!aPool.longStringActors) {
  1.2129 +      aPool.longStringActors = {};
  1.2130 +    }
  1.2131 +
  1.2132 +    if (aPool.longStringActors.hasOwnProperty(aString)) {
  1.2133 +      return aPool.longStringActors[aString].grip();
  1.2134 +    }
  1.2135 +
  1.2136 +    let actor = new LongStringActor(aString, this);
  1.2137 +    aPool.addActor(actor);
  1.2138 +    aPool.longStringActors[aString] = actor;
  1.2139 +    return actor.grip();
  1.2140 +  },
  1.2141 +
  1.2142 +  /**
  1.2143 +   * Create a long string grip that is scoped to a pause.
  1.2144 +   *
  1.2145 +   * @param aString String
  1.2146 +   *        The string we are creating a grip for.
  1.2147 +   */
  1.2148 +  pauseLongStringGrip: function (aString) {
  1.2149 +    return this.longStringGrip(aString, this._pausePool);
  1.2150 +  },
  1.2151 +
  1.2152 +  /**
  1.2153 +   * Create a long string grip that is scoped to a thread.
  1.2154 +   *
  1.2155 +   * @param aString String
  1.2156 +   *        The string we are creating a grip for.
  1.2157 +   */
  1.2158 +  threadLongStringGrip: function (aString) {
  1.2159 +    return this.longStringGrip(aString, this._threadLifetimePool);
  1.2160 +  },
  1.2161 +
  1.2162 +  /**
  1.2163 +   * Returns true if the string is long enough to use a LongStringActor instead
  1.2164 +   * of passing the value directly over the protocol.
  1.2165 +   *
  1.2166 +   * @param aString String
  1.2167 +   *        The string we are checking the length of.
  1.2168 +   */
  1.2169 +  _stringIsLong: function (aString) {
  1.2170 +    return aString.length >= DebuggerServer.LONG_STRING_LENGTH;
  1.2171 +  },
  1.2172 +
  1.2173 +  // JS Debugger API hooks.
  1.2174 +
  1.2175 +  /**
  1.2176 +   * A function that the engine calls when a call to a debug event hook,
  1.2177 +   * breakpoint handler, watchpoint handler, or similar function throws some
  1.2178 +   * exception.
  1.2179 +   *
  1.2180 +   * @param aException exception
  1.2181 +   *        The exception that was thrown in the debugger code.
  1.2182 +   */
  1.2183 +  uncaughtExceptionHook: function (aException) {
  1.2184 +    dumpn("Got an exception: " + aException.message + "\n" + aException.stack);
  1.2185 +  },
  1.2186 +
  1.2187 +  /**
  1.2188 +   * A function that the engine calls when a debugger statement has been
  1.2189 +   * executed in the specified frame.
  1.2190 +   *
  1.2191 +   * @param aFrame Debugger.Frame
  1.2192 +   *        The stack frame that contained the debugger statement.
  1.2193 +   */
  1.2194 +  onDebuggerStatement: function (aFrame) {
  1.2195 +    // Don't pause if we are currently stepping (in or over) or the frame is
  1.2196 +    // black-boxed.
  1.2197 +    const generatedLocation = getFrameLocation(aFrame);
  1.2198 +    const { url } = this.synchronize(this.sources.getOriginalLocation(
  1.2199 +      generatedLocation));
  1.2200 +
  1.2201 +    return this.sources.isBlackBoxed(url) || aFrame.onStep
  1.2202 +      ? undefined
  1.2203 +      : this._pauseAndRespond(aFrame, { type: "debuggerStatement" });
  1.2204 +  },
  1.2205 +
  1.2206 +  /**
  1.2207 +   * A function that the engine calls when an exception has been thrown and has
  1.2208 +   * propagated to the specified frame.
  1.2209 +   *
  1.2210 +   * @param aFrame Debugger.Frame
  1.2211 +   *        The youngest remaining stack frame.
  1.2212 +   * @param aValue object
  1.2213 +   *        The exception that was thrown.
  1.2214 +   */
  1.2215 +  onExceptionUnwind: function (aFrame, aValue) {
  1.2216 +    let willBeCaught = false;
  1.2217 +    for (let frame = aFrame; frame != null; frame = frame.older) {
  1.2218 +      if (frame.script.isInCatchScope(frame.offset)) {
  1.2219 +        willBeCaught = true;
  1.2220 +        break;
  1.2221 +      }
  1.2222 +    }
  1.2223 +
  1.2224 +    if (willBeCaught && this._options.ignoreCaughtExceptions) {
  1.2225 +      return undefined;
  1.2226 +    }
  1.2227 +
  1.2228 +    const generatedLocation = getFrameLocation(aFrame);
  1.2229 +    const { url } = this.synchronize(this.sources.getOriginalLocation(
  1.2230 +      generatedLocation));
  1.2231 +
  1.2232 +    if (this.sources.isBlackBoxed(url)) {
  1.2233 +      return undefined;
  1.2234 +    }
  1.2235 +
  1.2236 +    try {
  1.2237 +      let packet = this._paused(aFrame);
  1.2238 +      if (!packet) {
  1.2239 +        return undefined;
  1.2240 +      }
  1.2241 +
  1.2242 +      packet.why = { type: "exception",
  1.2243 +                     exception: this.createValueGrip(aValue) };
  1.2244 +      this.conn.send(packet);
  1.2245 +
  1.2246 +      this._pushThreadPause();
  1.2247 +    } catch(e) {
  1.2248 +      reportError(e, "Got an exception during TA_onExceptionUnwind: ");
  1.2249 +    }
  1.2250 +
  1.2251 +    return undefined;
  1.2252 +  },
  1.2253 +
  1.2254 +  /**
  1.2255 +   * A function that the engine calls when a new script has been loaded into the
  1.2256 +   * scope of the specified debuggee global.
  1.2257 +   *
  1.2258 +   * @param aScript Debugger.Script
  1.2259 +   *        The source script that has been loaded into a debuggee compartment.
  1.2260 +   * @param aGlobal Debugger.Object
  1.2261 +   *        A Debugger.Object instance whose referent is the global object.
  1.2262 +   */
  1.2263 +  onNewScript: function (aScript, aGlobal) {
  1.2264 +    this._addScript(aScript);
  1.2265 +
  1.2266 +    // |onNewScript| is only fired for top level scripts (AKA staticLevel == 0),
  1.2267 +    // so we have to make sure to call |_addScript| on every child script as
  1.2268 +    // well to restore breakpoints in those scripts.
  1.2269 +    for (let s of aScript.getChildScripts()) {
  1.2270 +      this._addScript(s);
  1.2271 +    }
  1.2272 +
  1.2273 +    this.sources.sourcesForScript(aScript);
  1.2274 +  },
  1.2275 +
  1.2276 +  onNewSource: function (aSource) {
  1.2277 +    this.conn.send({
  1.2278 +      from: this.actorID,
  1.2279 +      type: "newSource",
  1.2280 +      source: aSource.form()
  1.2281 +    });
  1.2282 +  },
  1.2283 +
  1.2284 +  /**
  1.2285 +   * Check if scripts from the provided source URL are allowed to be stored in
  1.2286 +   * the cache.
  1.2287 +   *
  1.2288 +   * @param aSourceUrl String
  1.2289 +   *        The url of the script's source that will be stored.
  1.2290 +   * @returns true, if the script can be added, false otherwise.
  1.2291 +   */
  1.2292 +  _allowSource: function (aSourceUrl) {
  1.2293 +    // Ignore anything we don't have a URL for (eval scripts, for example).
  1.2294 +    if (!aSourceUrl)
  1.2295 +      return false;
  1.2296 +    // Ignore XBL bindings for content debugging.
  1.2297 +    if (aSourceUrl.indexOf("chrome://") == 0) {
  1.2298 +      return false;
  1.2299 +    }
  1.2300 +    // Ignore about:* pages for content debugging.
  1.2301 +    if (aSourceUrl.indexOf("about:") == 0) {
  1.2302 +      return false;
  1.2303 +    }
  1.2304 +    return true;
  1.2305 +  },
  1.2306 +
  1.2307 +  /**
  1.2308 +   * Restore any pre-existing breakpoints to the scripts that we have access to.
  1.2309 +   */
  1.2310 +  _restoreBreakpoints: function () {
  1.2311 +    if (this.breakpointStore.size === 0) {
  1.2312 +      return;
  1.2313 +    }
  1.2314 +
  1.2315 +    for (let s of this.dbg.findScripts()) {
  1.2316 +      this._addScript(s);
  1.2317 +    }
  1.2318 +  },
  1.2319 +
  1.2320 +  /**
  1.2321 +   * Add the provided script to the server cache.
  1.2322 +   *
  1.2323 +   * @param aScript Debugger.Script
  1.2324 +   *        The source script that will be stored.
  1.2325 +   * @returns true, if the script was added; false otherwise.
  1.2326 +   */
  1.2327 +  _addScript: function (aScript) {
  1.2328 +    if (!this._allowSource(aScript.url)) {
  1.2329 +      return false;
  1.2330 +    }
  1.2331 +
  1.2332 +    // Set any stored breakpoints.
  1.2333 +
  1.2334 +    let endLine = aScript.startLine + aScript.lineCount - 1;
  1.2335 +    for (let bp of this.breakpointStore.findBreakpoints({ url: aScript.url })) {
  1.2336 +      // Only consider breakpoints that are not already associated with
  1.2337 +      // scripts, and limit search to the line numbers contained in the new
  1.2338 +      // script.
  1.2339 +      if (!bp.actor.scripts.length
  1.2340 +          && bp.line >= aScript.startLine
  1.2341 +          && bp.line <= endLine) {
  1.2342 +        this._setBreakpoint(bp);
  1.2343 +      }
  1.2344 +    }
  1.2345 +
  1.2346 +    return true;
  1.2347 +  },
  1.2348 +
  1.2349 +
  1.2350 +  /**
  1.2351 +   * Get prototypes and properties of multiple objects.
  1.2352 +   */
  1.2353 +  onPrototypesAndProperties: function (aRequest) {
  1.2354 +    let result = {};
  1.2355 +    for (let actorID of aRequest.actors) {
  1.2356 +      // This code assumes that there are no lazily loaded actors returned
  1.2357 +      // by this call.
  1.2358 +      let actor = this.conn.getActor(actorID);
  1.2359 +      if (!actor) {
  1.2360 +        return { from: this.actorID,
  1.2361 +                 error: "noSuchActor" };
  1.2362 +      }
  1.2363 +      let handler = actor.onPrototypeAndProperties;
  1.2364 +      if (!handler) {
  1.2365 +        return { from: this.actorID,
  1.2366 +                 error: "unrecognizedPacketType",
  1.2367 +                 message: ('Actor "' + actorID +
  1.2368 +                           '" does not recognize the packet type ' +
  1.2369 +                           '"prototypeAndProperties"') };
  1.2370 +      }
  1.2371 +      result[actorID] = handler.call(actor, {});
  1.2372 +    }
  1.2373 +    return { from: this.actorID,
  1.2374 +             actors: result };
  1.2375 +  }
  1.2376 +
  1.2377 +};
  1.2378 +
  1.2379 +ThreadActor.prototype.requestTypes = {
  1.2380 +  "attach": ThreadActor.prototype.onAttach,
  1.2381 +  "detach": ThreadActor.prototype.onDetach,
  1.2382 +  "reconfigure": ThreadActor.prototype.onReconfigure,
  1.2383 +  "resume": ThreadActor.prototype.onResume,
  1.2384 +  "clientEvaluate": ThreadActor.prototype.onClientEvaluate,
  1.2385 +  "frames": ThreadActor.prototype.onFrames,
  1.2386 +  "interrupt": ThreadActor.prototype.onInterrupt,
  1.2387 +  "eventListeners": ThreadActor.prototype.onEventListeners,
  1.2388 +  "releaseMany": ThreadActor.prototype.onReleaseMany,
  1.2389 +  "setBreakpoint": ThreadActor.prototype.onSetBreakpoint,
  1.2390 +  "sources": ThreadActor.prototype.onSources,
  1.2391 +  "threadGrips": ThreadActor.prototype.onThreadGrips,
  1.2392 +  "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
  1.2393 +};
  1.2394 +
  1.2395 +
  1.2396 +/**
  1.2397 + * Creates a PauseActor.
  1.2398 + *
  1.2399 + * PauseActors exist for the lifetime of a given debuggee pause.  Used to
  1.2400 + * scope pause-lifetime grips.
  1.2401 + *
  1.2402 + * @param ActorPool aPool
  1.2403 + *        The actor pool created for this pause.
  1.2404 + */
  1.2405 +function PauseActor(aPool)
  1.2406 +{
  1.2407 +  this.pool = aPool;
  1.2408 +}
  1.2409 +
  1.2410 +PauseActor.prototype = {
  1.2411 +  actorPrefix: "pause"
  1.2412 +};
  1.2413 +
  1.2414 +
  1.2415 +/**
  1.2416 + * A base actor for any actors that should only respond receive messages in the
  1.2417 + * paused state. Subclasses may expose a `threadActor` which is used to help
  1.2418 + * determine when we are in a paused state. Subclasses should set their own
  1.2419 + * "constructor" property if they want better error messages. You should never
  1.2420 + * instantiate a PauseScopedActor directly, only through subclasses.
  1.2421 + */
  1.2422 +function PauseScopedActor()
  1.2423 +{
  1.2424 +}
  1.2425 +
  1.2426 +/**
  1.2427 + * A function decorator for creating methods to handle protocol messages that
  1.2428 + * should only be received while in the paused state.
  1.2429 + *
  1.2430 + * @param aMethod Function
  1.2431 + *        The function we are decorating.
  1.2432 + */
  1.2433 +PauseScopedActor.withPaused = function (aMethod) {
  1.2434 +  return function () {
  1.2435 +    if (this.isPaused()) {
  1.2436 +      return aMethod.apply(this, arguments);
  1.2437 +    } else {
  1.2438 +      return this._wrongState();
  1.2439 +    }
  1.2440 +  };
  1.2441 +};
  1.2442 +
  1.2443 +PauseScopedActor.prototype = {
  1.2444 +
  1.2445 +  /**
  1.2446 +   * Returns true if we are in the paused state.
  1.2447 +   */
  1.2448 +  isPaused: function () {
  1.2449 +    // When there is not a ThreadActor available (like in the webconsole) we
  1.2450 +    // have to be optimistic and assume that we are paused so that we can
  1.2451 +    // respond to requests.
  1.2452 +    return this.threadActor ? this.threadActor.state === "paused" : true;
  1.2453 +  },
  1.2454 +
  1.2455 +  /**
  1.2456 +   * Returns the wrongState response packet for this actor.
  1.2457 +   */
  1.2458 +  _wrongState: function () {
  1.2459 +    return {
  1.2460 +      error: "wrongState",
  1.2461 +      message: this.constructor.name +
  1.2462 +        " actors can only be accessed while the thread is paused."
  1.2463 +    };
  1.2464 +  }
  1.2465 +};
  1.2466 +
  1.2467 +/**
  1.2468 + * Resolve a URI back to physical file.
  1.2469 + *
  1.2470 + * Of course, this works only for URIs pointing to local resources.
  1.2471 + *
  1.2472 + * @param  aURI
  1.2473 + *         URI to resolve
  1.2474 + * @return
  1.2475 + *         resolved nsIURI
  1.2476 + */
  1.2477 +function resolveURIToLocalPath(aURI) {
  1.2478 +  switch (aURI.scheme) {
  1.2479 +    case "jar":
  1.2480 +    case "file":
  1.2481 +      return aURI;
  1.2482 +
  1.2483 +    case "chrome":
  1.2484 +      let resolved = Cc["@mozilla.org/chrome/chrome-registry;1"].
  1.2485 +                     getService(Ci.nsIChromeRegistry).convertChromeURL(aURI);
  1.2486 +      return resolveURIToLocalPath(resolved);
  1.2487 +
  1.2488 +    case "resource":
  1.2489 +      resolved = Cc["@mozilla.org/network/protocol;1?name=resource"].
  1.2490 +                 getService(Ci.nsIResProtocolHandler).resolveURI(aURI);
  1.2491 +      aURI = Services.io.newURI(resolved, null, null);
  1.2492 +      return resolveURIToLocalPath(aURI);
  1.2493 +
  1.2494 +    default:
  1.2495 +      return null;
  1.2496 +  }
  1.2497 +}
  1.2498 +
  1.2499 +/**
  1.2500 + * A SourceActor provides information about the source of a script.
  1.2501 + *
  1.2502 + * @param String url
  1.2503 + *        The url of the source we are representing.
  1.2504 + * @param ThreadActor thread
  1.2505 + *        The current thread actor.
  1.2506 + * @param SourceMapConsumer sourceMap
  1.2507 + *        Optional. The source map that introduced this source, if available.
  1.2508 + * @param String generatedSource
  1.2509 + *        Optional, passed in when aSourceMap is also passed in. The generated
  1.2510 + *        source url that introduced this source.
  1.2511 + * @param String text
  1.2512 + *        Optional. The content text of this source, if immediately available.
  1.2513 + * @param String contentType
  1.2514 + *        Optional. The content type of this source, if immediately available.
  1.2515 + */
  1.2516 +function SourceActor({ url, thread, sourceMap, generatedSource, text,
  1.2517 +                       contentType }) {
  1.2518 +  this._threadActor = thread;
  1.2519 +  this._url = url;
  1.2520 +  this._sourceMap = sourceMap;
  1.2521 +  this._generatedSource = generatedSource;
  1.2522 +  this._text = text;
  1.2523 +  this._contentType = contentType;
  1.2524 +
  1.2525 +  this.onSource = this.onSource.bind(this);
  1.2526 +  this._invertSourceMap = this._invertSourceMap.bind(this);
  1.2527 +  this._saveMap = this._saveMap.bind(this);
  1.2528 +  this._getSourceText = this._getSourceText.bind(this);
  1.2529 +
  1.2530 +  this._mapSourceToAddon();
  1.2531 +
  1.2532 +  if (this.threadActor.sources.isPrettyPrinted(this.url)) {
  1.2533 +    this._init = this.onPrettyPrint({
  1.2534 +      indent: this.threadActor.sources.prettyPrintIndent(this.url)
  1.2535 +    }).then(null, error => {
  1.2536 +      DevToolsUtils.reportException("SourceActor", error);
  1.2537 +    });
  1.2538 +  } else {
  1.2539 +    this._init = null;
  1.2540 +  }
  1.2541 +}
  1.2542 +
  1.2543 +SourceActor.prototype = {
  1.2544 +  constructor: SourceActor,
  1.2545 +  actorPrefix: "source",
  1.2546 +
  1.2547 +  _oldSourceMap: null,
  1.2548 +  _init: null,
  1.2549 +  _addonID: null,
  1.2550 +  _addonPath: null,
  1.2551 +
  1.2552 +  get threadActor() this._threadActor,
  1.2553 +  get url() this._url,
  1.2554 +  get addonID() this._addonID,
  1.2555 +  get addonPath() this._addonPath,
  1.2556 +
  1.2557 +  get prettyPrintWorker() {
  1.2558 +    return this.threadActor.prettyPrintWorker;
  1.2559 +  },
  1.2560 +
  1.2561 +  form: function () {
  1.2562 +    return {
  1.2563 +      actor: this.actorID,
  1.2564 +      url: this._url,
  1.2565 +      addonID: this._addonID,
  1.2566 +      addonPath: this._addonPath,
  1.2567 +      isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url),
  1.2568 +      isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url)
  1.2569 +      // TODO bug 637572: introductionScript
  1.2570 +    };
  1.2571 +  },
  1.2572 +
  1.2573 +  disconnect: function () {
  1.2574 +    if (this.registeredPool && this.registeredPool.sourceActors) {
  1.2575 +      delete this.registeredPool.sourceActors[this.actorID];
  1.2576 +    }
  1.2577 +  },
  1.2578 +
  1.2579 +  _mapSourceToAddon: function() {
  1.2580 +    try {
  1.2581 +      var nsuri = Services.io.newURI(this._url.split(" -> ").pop(), null, null);
  1.2582 +    }
  1.2583 +    catch (e) {
  1.2584 +      // We can't do anything with an invalid URI
  1.2585 +      return;
  1.2586 +    }
  1.2587 +
  1.2588 +    let localURI = resolveURIToLocalPath(nsuri);
  1.2589 +
  1.2590 +    let id = {};
  1.2591 +    if (localURI && mapURIToAddonID(localURI, id)) {
  1.2592 +      this._addonID = id.value;
  1.2593 +
  1.2594 +      if (localURI instanceof Ci.nsIJARURI) {
  1.2595 +        // The path in the add-on is easy for jar: uris
  1.2596 +        this._addonPath = localURI.JAREntry;
  1.2597 +      }
  1.2598 +      else if (localURI instanceof Ci.nsIFileURL) {
  1.2599 +        // For file: uris walk up to find the last directory that is part of the
  1.2600 +        // add-on
  1.2601 +        let target = localURI.file;
  1.2602 +        let path = target.leafName;
  1.2603 +
  1.2604 +        // We can assume that the directory containing the source file is part
  1.2605 +        // of the add-on
  1.2606 +        let root = target.parent;
  1.2607 +        let file = root.parent;
  1.2608 +        while (file && mapURIToAddonID(Services.io.newFileURI(file), {})) {
  1.2609 +          path = root.leafName + "/" + path;
  1.2610 +          root = file;
  1.2611 +          file = file.parent;
  1.2612 +        }
  1.2613 +
  1.2614 +        if (!file) {
  1.2615 +          const error = new Error("Could not find the root of the add-on for " + this._url);
  1.2616 +          DevToolsUtils.reportException("SourceActor.prototype._mapSourceToAddon", error)
  1.2617 +          return;
  1.2618 +        }
  1.2619 +
  1.2620 +        this._addonPath = path;
  1.2621 +      }
  1.2622 +    }
  1.2623 +  },
  1.2624 +
  1.2625 +  _getSourceText: function () {
  1.2626 +    const toResolvedContent = t => resolve({
  1.2627 +      content: t,
  1.2628 +      contentType: this._contentType
  1.2629 +    });
  1.2630 +
  1.2631 +    let sc;
  1.2632 +    if (this._sourceMap && (sc = this._sourceMap.sourceContentFor(this._url))) {
  1.2633 +      return toResolvedContent(sc);
  1.2634 +    }
  1.2635 +
  1.2636 +    if (this._text) {
  1.2637 +      return toResolvedContent(this._text);
  1.2638 +    }
  1.2639 +
  1.2640 +    // XXX bug 865252: Don't load from the cache if this is a source mapped
  1.2641 +    // source because we can't guarantee that the cache has the most up to date
  1.2642 +    // content for this source like we can if it isn't source mapped.
  1.2643 +    let sourceFetched = fetch(this._url, { loadFromCache: !this._sourceMap });
  1.2644 +
  1.2645 +    // Record the contentType we just learned during fetching
  1.2646 +    sourceFetched.then(({ contentType }) => {
  1.2647 +      this._contentType = contentType;
  1.2648 +    });
  1.2649 +
  1.2650 +    return sourceFetched;
  1.2651 +  },
  1.2652 +
  1.2653 +  /**
  1.2654 +   * Handler for the "source" packet.
  1.2655 +   */
  1.2656 +  onSource: function () {
  1.2657 +    return resolve(this._init)
  1.2658 +      .then(this._getSourceText)
  1.2659 +      .then(({ content, contentType }) => {
  1.2660 +        return {
  1.2661 +          from: this.actorID,
  1.2662 +          source: this.threadActor.createValueGrip(
  1.2663 +            content, this.threadActor.threadLifetimePool),
  1.2664 +          contentType: contentType
  1.2665 +        };
  1.2666 +      })
  1.2667 +      .then(null, aError => {
  1.2668 +        reportError(aError, "Got an exception during SA_onSource: ");
  1.2669 +        return {
  1.2670 +          "from": this.actorID,
  1.2671 +          "error": "loadSourceError",
  1.2672 +          "message": "Could not load the source for " + this._url + ".\n"
  1.2673 +            + DevToolsUtils.safeErrorString(aError)
  1.2674 +        };
  1.2675 +      });
  1.2676 +  },
  1.2677 +
  1.2678 +  /**
  1.2679 +   * Handler for the "prettyPrint" packet.
  1.2680 +   */
  1.2681 +  onPrettyPrint: function ({ indent }) {
  1.2682 +    this.threadActor.sources.prettyPrint(this._url, indent);
  1.2683 +    return this._getSourceText()
  1.2684 +      .then(this._sendToPrettyPrintWorker(indent))
  1.2685 +      .then(this._invertSourceMap)
  1.2686 +      .then(this._saveMap)
  1.2687 +      .then(() => {
  1.2688 +        // We need to reset `_init` now because we have already done the work of
  1.2689 +        // pretty printing, and don't want onSource to wait forever for
  1.2690 +        // initialization to complete.
  1.2691 +        this._init = null;
  1.2692 +      })
  1.2693 +      .then(this.onSource)
  1.2694 +      .then(null, error => {
  1.2695 +        this.onDisablePrettyPrint();
  1.2696 +        return {
  1.2697 +          from: this.actorID,
  1.2698 +          error: "prettyPrintError",
  1.2699 +          message: DevToolsUtils.safeErrorString(error)
  1.2700 +        };
  1.2701 +      });
  1.2702 +  },
  1.2703 +
  1.2704 +  /**
  1.2705 +   * Return a function that sends a request to the pretty print worker, waits on
  1.2706 +   * the worker's response, and then returns the pretty printed code.
  1.2707 +   *
  1.2708 +   * @param Number aIndent
  1.2709 +   *        The number of spaces to indent by the code by, when we send the
  1.2710 +   *        request to the pretty print worker.
  1.2711 +   * @returns Function
  1.2712 +   *          Returns a function which takes an AST, and returns a promise that
  1.2713 +   *          is resolved with `{ code, mappings }` where `code` is the pretty
  1.2714 +   *          printed code, and `mappings` is an array of source mappings.
  1.2715 +   */
  1.2716 +  _sendToPrettyPrintWorker: function (aIndent) {
  1.2717 +    return ({ content }) => {
  1.2718 +      const deferred = promise.defer();
  1.2719 +      const id = Math.random();
  1.2720 +
  1.2721 +      const onReply = ({ data }) => {
  1.2722 +        if (data.id !== id) {
  1.2723 +          return;
  1.2724 +        }
  1.2725 +        this.prettyPrintWorker.removeEventListener("message", onReply, false);
  1.2726 +
  1.2727 +        if (data.error) {
  1.2728 +          deferred.reject(new Error(data.error));
  1.2729 +        } else {
  1.2730 +          deferred.resolve(data);
  1.2731 +        }
  1.2732 +      };
  1.2733 +
  1.2734 +      this.prettyPrintWorker.addEventListener("message", onReply, false);
  1.2735 +      this.prettyPrintWorker.postMessage({
  1.2736 +        id: id,
  1.2737 +        url: this._url,
  1.2738 +        indent: aIndent,
  1.2739 +        source: content
  1.2740 +      });
  1.2741 +
  1.2742 +      return deferred.promise;
  1.2743 +    };
  1.2744 +  },
  1.2745 +
  1.2746 +  /**
  1.2747 +   * Invert a source map. So if a source map maps from a to b, return a new
  1.2748 +   * source map from b to a. We need to do this because the source map we get
  1.2749 +   * from _generatePrettyCodeAndMap goes the opposite way we want it to for
  1.2750 +   * debugging.
  1.2751 +   *
  1.2752 +   * Note that the source map is modified in place.
  1.2753 +   */
  1.2754 +  _invertSourceMap: function ({ code, mappings }) {
  1.2755 +    const generator = new SourceMapGenerator({ file: this._url });
  1.2756 +    return DevToolsUtils.yieldingEach(mappings, m => {
  1.2757 +      let mapping = {
  1.2758 +        generated: {
  1.2759 +          line: m.generatedLine,
  1.2760 +          column: m.generatedColumn
  1.2761 +        }
  1.2762 +      };
  1.2763 +      if (m.source) {
  1.2764 +        mapping.source = m.source;
  1.2765 +        mapping.original = {
  1.2766 +          line: m.originalLine,
  1.2767 +          column: m.originalColumn
  1.2768 +        };
  1.2769 +        mapping.name = m.name;
  1.2770 +      }
  1.2771 +      generator.addMapping(mapping);
  1.2772 +    }).then(() => {
  1.2773 +      generator.setSourceContent(this._url, code);
  1.2774 +      const consumer = SourceMapConsumer.fromSourceMap(generator);
  1.2775 +
  1.2776 +      // XXX bug 918802: Monkey punch the source map consumer, because iterating
  1.2777 +      // over all mappings and inverting each of them, and then creating a new
  1.2778 +      // SourceMapConsumer is slow.
  1.2779 +
  1.2780 +      const getOrigPos = consumer.originalPositionFor.bind(consumer);
  1.2781 +      const getGenPos = consumer.generatedPositionFor.bind(consumer);
  1.2782 +
  1.2783 +      consumer.originalPositionFor = ({ line, column }) => {
  1.2784 +        const location = getGenPos({
  1.2785 +          line: line,
  1.2786 +          column: column,
  1.2787 +          source: this._url
  1.2788 +        });
  1.2789 +        location.source = this._url;
  1.2790 +        return location;
  1.2791 +      };
  1.2792 +
  1.2793 +      consumer.generatedPositionFor = ({ line, column }) => getOrigPos({
  1.2794 +        line: line,
  1.2795 +        column: column
  1.2796 +      });
  1.2797 +
  1.2798 +      return {
  1.2799 +        code: code,
  1.2800 +        map: consumer
  1.2801 +      };
  1.2802 +    });
  1.2803 +  },
  1.2804 +
  1.2805 +  /**
  1.2806 +   * Save the source map back to our thread's ThreadSources object so that
  1.2807 +   * stepping, breakpoints, debugger statements, etc can use it. If we are
  1.2808 +   * pretty printing a source mapped source, we need to compose the existing
  1.2809 +   * source map with our new one.
  1.2810 +   */
  1.2811 +  _saveMap: function ({ map }) {
  1.2812 +    if (this._sourceMap) {
  1.2813 +      // Compose the source maps
  1.2814 +      this._oldSourceMap = this._sourceMap;
  1.2815 +      this._sourceMap = SourceMapGenerator.fromSourceMap(this._sourceMap);
  1.2816 +      this._sourceMap.applySourceMap(map, this._url);
  1.2817 +      this._sourceMap = SourceMapConsumer.fromSourceMap(this._sourceMap);
  1.2818 +      this._threadActor.sources.saveSourceMap(this._sourceMap,
  1.2819 +                                              this._generatedSource);
  1.2820 +    } else {
  1.2821 +      this._sourceMap = map;
  1.2822 +      this._threadActor.sources.saveSourceMap(this._sourceMap, this._url);
  1.2823 +    }
  1.2824 +  },
  1.2825 +
  1.2826 +  /**
  1.2827 +   * Handler for the "disablePrettyPrint" packet.
  1.2828 +   */
  1.2829 +  onDisablePrettyPrint: function () {
  1.2830 +    this._sourceMap = this._oldSourceMap;
  1.2831 +    this.threadActor.sources.saveSourceMap(this._sourceMap,
  1.2832 +                                           this._generatedSource || this._url);
  1.2833 +    this.threadActor.sources.disablePrettyPrint(this._url);
  1.2834 +    return this.onSource();
  1.2835 +  },
  1.2836 +
  1.2837 +  /**
  1.2838 +   * Handler for the "blackbox" packet.
  1.2839 +   */
  1.2840 +  onBlackBox: function (aRequest) {
  1.2841 +    this.threadActor.sources.blackBox(this.url);
  1.2842 +    let packet = {
  1.2843 +      from: this.actorID
  1.2844 +    };
  1.2845 +    if (this.threadActor.state == "paused"
  1.2846 +        && this.threadActor.youngestFrame
  1.2847 +        && this.threadActor.youngestFrame.script.url == this.url) {
  1.2848 +      packet.pausedInSource = true;
  1.2849 +    }
  1.2850 +    return packet;
  1.2851 +  },
  1.2852 +
  1.2853 +  /**
  1.2854 +   * Handler for the "unblackbox" packet.
  1.2855 +   */
  1.2856 +  onUnblackBox: function (aRequest) {
  1.2857 +    this.threadActor.sources.unblackBox(this.url);
  1.2858 +    return {
  1.2859 +      from: this.actorID
  1.2860 +    };
  1.2861 +  }
  1.2862 +};
  1.2863 +
  1.2864 +SourceActor.prototype.requestTypes = {
  1.2865 +  "source": SourceActor.prototype.onSource,
  1.2866 +  "blackbox": SourceActor.prototype.onBlackBox,
  1.2867 +  "unblackbox": SourceActor.prototype.onUnblackBox,
  1.2868 +  "prettyPrint": SourceActor.prototype.onPrettyPrint,
  1.2869 +  "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint
  1.2870 +};
  1.2871 +
  1.2872 +
  1.2873 +/**
  1.2874 + * Determine if a given value is non-primitive.
  1.2875 + *
  1.2876 + * @param Any aValue
  1.2877 + *        The value to test.
  1.2878 + * @return Boolean
  1.2879 + *         Whether the value is non-primitive.
  1.2880 + */
  1.2881 +function isObject(aValue) {
  1.2882 +  const type = typeof aValue;
  1.2883 +  return type == "object" ? aValue !== null : type == "function";
  1.2884 +}
  1.2885 +
  1.2886 +/**
  1.2887 + * Create a function that can safely stringify Debugger.Objects of a given
  1.2888 + * builtin type.
  1.2889 + *
  1.2890 + * @param Function aCtor
  1.2891 + *        The builtin class constructor.
  1.2892 + * @return Function
  1.2893 + *         The stringifier for the class.
  1.2894 + */
  1.2895 +function createBuiltinStringifier(aCtor) {
  1.2896 +  return aObj => aCtor.prototype.toString.call(aObj.unsafeDereference());
  1.2897 +}
  1.2898 +
  1.2899 +/**
  1.2900 + * Stringify a Debugger.Object-wrapped Error instance.
  1.2901 + *
  1.2902 + * @param Debugger.Object aObj
  1.2903 + *        The object to stringify.
  1.2904 + * @return String
  1.2905 + *         The stringification of the object.
  1.2906 + */
  1.2907 +function errorStringify(aObj) {
  1.2908 +  let name = DevToolsUtils.getProperty(aObj, "name");
  1.2909 +  if (name === "" || name === undefined) {
  1.2910 +    name = aObj.class;
  1.2911 +  } else if (isObject(name)) {
  1.2912 +    name = stringify(name);
  1.2913 +  }
  1.2914 +
  1.2915 +  let message = DevToolsUtils.getProperty(aObj, "message");
  1.2916 +  if (isObject(message)) {
  1.2917 +    message = stringify(message);
  1.2918 +  }
  1.2919 +
  1.2920 +  if (message === "" || message === undefined) {
  1.2921 +    return name;
  1.2922 +  }
  1.2923 +  return name + ": " + message;
  1.2924 +}
  1.2925 +
  1.2926 +/**
  1.2927 + * Stringify a Debugger.Object based on its class.
  1.2928 + *
  1.2929 + * @param Debugger.Object aObj
  1.2930 + *        The object to stringify.
  1.2931 + * @return String
  1.2932 + *         The stringification for the object.
  1.2933 + */
  1.2934 +function stringify(aObj) {
  1.2935 +  if (aObj.class == "DeadObject") {
  1.2936 +    const error = new Error("Dead object encountered.");
  1.2937 +    DevToolsUtils.reportException("stringify", error);
  1.2938 +    return "<dead object>";
  1.2939 +  }
  1.2940 +  const stringifier = stringifiers[aObj.class] || stringifiers.Object;
  1.2941 +  return stringifier(aObj);
  1.2942 +}
  1.2943 +
  1.2944 +// Used to prevent infinite recursion when an array is found inside itself.
  1.2945 +let seen = null;
  1.2946 +
  1.2947 +let stringifiers = {
  1.2948 +  Error: errorStringify,
  1.2949 +  EvalError: errorStringify,
  1.2950 +  RangeError: errorStringify,
  1.2951 +  ReferenceError: errorStringify,
  1.2952 +  SyntaxError: errorStringify,
  1.2953 +  TypeError: errorStringify,
  1.2954 +  URIError: errorStringify,
  1.2955 +  Boolean: createBuiltinStringifier(Boolean),
  1.2956 +  Function: createBuiltinStringifier(Function),
  1.2957 +  Number: createBuiltinStringifier(Number),
  1.2958 +  RegExp: createBuiltinStringifier(RegExp),
  1.2959 +  String: createBuiltinStringifier(String),
  1.2960 +  Object: obj => "[object " + obj.class + "]",
  1.2961 +  Array: obj => {
  1.2962 +    // If we're at the top level then we need to create the Set for tracking
  1.2963 +    // previously stringified arrays.
  1.2964 +    const topLevel = !seen;
  1.2965 +    if (topLevel) {
  1.2966 +      seen = new Set();
  1.2967 +    } else if (seen.has(obj)) {
  1.2968 +      return "";
  1.2969 +    }
  1.2970 +
  1.2971 +    seen.add(obj);
  1.2972 +
  1.2973 +    const len = DevToolsUtils.getProperty(obj, "length");
  1.2974 +    let string = "";
  1.2975 +
  1.2976 +    // The following check is only required because the debuggee could possibly
  1.2977 +    // be a Proxy and return any value. For normal objects, array.length is
  1.2978 +    // always a non-negative integer.
  1.2979 +    if (typeof len == "number" && len > 0) {
  1.2980 +      for (let i = 0; i < len; i++) {
  1.2981 +        const desc = obj.getOwnPropertyDescriptor(i);
  1.2982 +        if (desc) {
  1.2983 +          const { value } = desc;
  1.2984 +          if (value != null) {
  1.2985 +            string += isObject(value) ? stringify(value) : value;
  1.2986 +          }
  1.2987 +        }
  1.2988 +
  1.2989 +        if (i < len - 1) {
  1.2990 +          string += ",";
  1.2991 +        }
  1.2992 +      }
  1.2993 +    }
  1.2994 +
  1.2995 +    if (topLevel) {
  1.2996 +      seen = null;
  1.2997 +    }
  1.2998 +
  1.2999 +    return string;
  1.3000 +  },
  1.3001 +  DOMException: obj => {
  1.3002 +    const message = DevToolsUtils.getProperty(obj, "message") || "<no message>";
  1.3003 +    const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16);
  1.3004 +    const code = DevToolsUtils.getProperty(obj, "code");
  1.3005 +    const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>";
  1.3006 +
  1.3007 +    return '[Exception... "' + message + '" ' +
  1.3008 +           'code: "' + code +'" ' +
  1.3009 +           'nsresult: "0x' + result + ' (' + name + ')"]';
  1.3010 +  }
  1.3011 +};
  1.3012 +
  1.3013 +/**
  1.3014 + * Creates an actor for the specified object.
  1.3015 + *
  1.3016 + * @param aObj Debugger.Object
  1.3017 + *        The debuggee object.
  1.3018 + * @param aThreadActor ThreadActor
  1.3019 + *        The parent thread actor for this object.
  1.3020 + */
  1.3021 +function ObjectActor(aObj, aThreadActor)
  1.3022 +{
  1.3023 +  dbg_assert(!aObj.optimizedOut, "Should not create object actors for optimized out values!");
  1.3024 +  this.obj = aObj;
  1.3025 +  this.threadActor = aThreadActor;
  1.3026 +}
  1.3027 +
  1.3028 +ObjectActor.prototype = {
  1.3029 +  actorPrefix: "obj",
  1.3030 +
  1.3031 +  /**
  1.3032 +   * Returns a grip for this actor for returning in a protocol message.
  1.3033 +   */
  1.3034 +  grip: function () {
  1.3035 +    this.threadActor._gripDepth++;
  1.3036 +
  1.3037 +    let g = {
  1.3038 +      "type": "object",
  1.3039 +      "class": this.obj.class,
  1.3040 +      "actor": this.actorID,
  1.3041 +      "extensible": this.obj.isExtensible(),
  1.3042 +      "frozen": this.obj.isFrozen(),
  1.3043 +      "sealed": this.obj.isSealed()
  1.3044 +    };
  1.3045 +
  1.3046 +    if (this.obj.class != "DeadObject") {
  1.3047 +      let raw = Cu.unwaiveXrays(this.obj.unsafeDereference());
  1.3048 +      if (!DevToolsUtils.isSafeJSObject(raw)) {
  1.3049 +        raw = null;
  1.3050 +      }
  1.3051 +
  1.3052 +      let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] ||
  1.3053 +                       DebuggerServer.ObjectActorPreviewers.Object;
  1.3054 +      for (let fn of previewers) {
  1.3055 +        try {
  1.3056 +          if (fn(this, g, raw)) {
  1.3057 +            break;
  1.3058 +          }
  1.3059 +        } catch (e) {
  1.3060 +          DevToolsUtils.reportException("ObjectActor.prototype.grip previewer function", e);
  1.3061 +        }
  1.3062 +      }
  1.3063 +    }
  1.3064 +
  1.3065 +    this.threadActor._gripDepth--;
  1.3066 +    return g;
  1.3067 +  },
  1.3068 +
  1.3069 +  /**
  1.3070 +   * Releases this actor from the pool.
  1.3071 +   */
  1.3072 +  release: function () {
  1.3073 +    if (this.registeredPool.objectActors) {
  1.3074 +      this.registeredPool.objectActors.delete(this.obj);
  1.3075 +    }
  1.3076 +    this.registeredPool.removeActor(this);
  1.3077 +  },
  1.3078 +
  1.3079 +  /**
  1.3080 +   * Handle a protocol request to provide the definition site of this function
  1.3081 +   * object.
  1.3082 +   *
  1.3083 +   * @param aRequest object
  1.3084 +   *        The protocol request object.
  1.3085 +   */
  1.3086 +  onDefinitionSite: function OA_onDefinitionSite(aRequest) {
  1.3087 +    if (this.obj.class != "Function") {
  1.3088 +      return {
  1.3089 +        from: this.actorID,
  1.3090 +        error: "objectNotFunction",
  1.3091 +        message: this.actorID + " is not a function."
  1.3092 +      };
  1.3093 +    }
  1.3094 +
  1.3095 +    if (!this.obj.script) {
  1.3096 +      return {
  1.3097 +        from: this.actorID,
  1.3098 +        error: "noScript",
  1.3099 +        message: this.actorID + " has no Debugger.Script"
  1.3100 +      };
  1.3101 +    }
  1.3102 +
  1.3103 +    const generatedLocation = {
  1.3104 +      url: this.obj.script.url,
  1.3105 +      line: this.obj.script.startLine,
  1.3106 +      // TODO bug 901138: use Debugger.Script.prototype.startColumn.
  1.3107 +      column: 0
  1.3108 +    };
  1.3109 +
  1.3110 +    return this.threadActor.sources.getOriginalLocation(generatedLocation)
  1.3111 +      .then(({ url, line, column }) => {
  1.3112 +        return {
  1.3113 +          from: this.actorID,
  1.3114 +          url: url,
  1.3115 +          line: line,
  1.3116 +          column: column
  1.3117 +        };
  1.3118 +      });
  1.3119 +  },
  1.3120 +
  1.3121 +  /**
  1.3122 +   * Handle a protocol request to provide the names of the properties defined on
  1.3123 +   * the object and not its prototype.
  1.3124 +   *
  1.3125 +   * @param aRequest object
  1.3126 +   *        The protocol request object.
  1.3127 +   */
  1.3128 +  onOwnPropertyNames: function (aRequest) {
  1.3129 +    return { from: this.actorID,
  1.3130 +             ownPropertyNames: this.obj.getOwnPropertyNames() };
  1.3131 +  },
  1.3132 +
  1.3133 +  /**
  1.3134 +   * Handle a protocol request to provide the prototype and own properties of
  1.3135 +   * the object.
  1.3136 +   *
  1.3137 +   * @param aRequest object
  1.3138 +   *        The protocol request object.
  1.3139 +   */
  1.3140 +  onPrototypeAndProperties: function (aRequest) {
  1.3141 +    let ownProperties = Object.create(null);
  1.3142 +    let names;
  1.3143 +    try {
  1.3144 +      names = this.obj.getOwnPropertyNames();
  1.3145 +    } catch (ex) {
  1.3146 +      // The above can throw if this.obj points to a dead object.
  1.3147 +      // TODO: we should use Cu.isDeadWrapper() - see bug 885800.
  1.3148 +      return { from: this.actorID,
  1.3149 +               prototype: this.threadActor.createValueGrip(null),
  1.3150 +               ownProperties: ownProperties,
  1.3151 +               safeGetterValues: Object.create(null) };
  1.3152 +    }
  1.3153 +    for (let name of names) {
  1.3154 +      ownProperties[name] = this._propertyDescriptor(name);
  1.3155 +    }
  1.3156 +    return { from: this.actorID,
  1.3157 +             prototype: this.threadActor.createValueGrip(this.obj.proto),
  1.3158 +             ownProperties: ownProperties,
  1.3159 +             safeGetterValues: this._findSafeGetterValues(ownProperties) };
  1.3160 +  },
  1.3161 +
  1.3162 +  /**
  1.3163 +   * Find the safe getter values for the current Debugger.Object, |this.obj|.
  1.3164 +   *
  1.3165 +   * @private
  1.3166 +   * @param object aOwnProperties
  1.3167 +   *        The object that holds the list of known ownProperties for
  1.3168 +   *        |this.obj|.
  1.3169 +   * @param number [aLimit=0]
  1.3170 +   *        Optional limit of getter values to find.
  1.3171 +   * @return object
  1.3172 +   *         An object that maps property names to safe getter descriptors as
  1.3173 +   *         defined by the remote debugging protocol.
  1.3174 +   */
  1.3175 +  _findSafeGetterValues: function (aOwnProperties, aLimit = 0)
  1.3176 +  {
  1.3177 +    let safeGetterValues = Object.create(null);
  1.3178 +    let obj = this.obj;
  1.3179 +    let level = 0, i = 0;
  1.3180 +
  1.3181 +    while (obj) {
  1.3182 +      let getters = this._findSafeGetters(obj);
  1.3183 +      for (let name of getters) {
  1.3184 +        // Avoid overwriting properties from prototypes closer to this.obj. Also
  1.3185 +        // avoid providing safeGetterValues from prototypes if property |name|
  1.3186 +        // is already defined as an own property.
  1.3187 +        if (name in safeGetterValues ||
  1.3188 +            (obj != this.obj && name in aOwnProperties)) {
  1.3189 +          continue;
  1.3190 +        }
  1.3191 +
  1.3192 +        let desc = null, getter = null;
  1.3193 +        try {
  1.3194 +          desc = obj.getOwnPropertyDescriptor(name);
  1.3195 +          getter = desc.get;
  1.3196 +        } catch (ex) {
  1.3197 +          // The above can throw if the cache becomes stale.
  1.3198 +        }
  1.3199 +        if (!getter) {
  1.3200 +          obj._safeGetters = null;
  1.3201 +          continue;
  1.3202 +        }
  1.3203 +
  1.3204 +        let result = getter.call(this.obj);
  1.3205 +        if (result && !("throw" in result)) {
  1.3206 +          let getterValue = undefined;
  1.3207 +          if ("return" in result) {
  1.3208 +            getterValue = result.return;
  1.3209 +          } else if ("yield" in result) {
  1.3210 +            getterValue = result.yield;
  1.3211 +          }
  1.3212 +          // WebIDL attributes specified with the LenientThis extended attribute
  1.3213 +          // return undefined and should be ignored.
  1.3214 +          if (getterValue !== undefined) {
  1.3215 +            safeGetterValues[name] = {
  1.3216 +              getterValue: this.threadActor.createValueGrip(getterValue),
  1.3217 +              getterPrototypeLevel: level,
  1.3218 +              enumerable: desc.enumerable,
  1.3219 +              writable: level == 0 ? desc.writable : true,
  1.3220 +            };
  1.3221 +            if (aLimit && ++i == aLimit) {
  1.3222 +              break;
  1.3223 +            }
  1.3224 +          }
  1.3225 +        }
  1.3226 +      }
  1.3227 +      if (aLimit && i == aLimit) {
  1.3228 +        break;
  1.3229 +      }
  1.3230 +
  1.3231 +      obj = obj.proto;
  1.3232 +      level++;
  1.3233 +    }
  1.3234 +
  1.3235 +    return safeGetterValues;
  1.3236 +  },
  1.3237 +
  1.3238 +  /**
  1.3239 +   * Find the safe getters for a given Debugger.Object. Safe getters are native
  1.3240 +   * getters which are safe to execute.
  1.3241 +   *
  1.3242 +   * @private
  1.3243 +   * @param Debugger.Object aObject
  1.3244 +   *        The Debugger.Object where you want to find safe getters.
  1.3245 +   * @return Set
  1.3246 +   *         A Set of names of safe getters. This result is cached for each
  1.3247 +   *         Debugger.Object.
  1.3248 +   */
  1.3249 +  _findSafeGetters: function (aObject)
  1.3250 +  {
  1.3251 +    if (aObject._safeGetters) {
  1.3252 +      return aObject._safeGetters;
  1.3253 +    }
  1.3254 +
  1.3255 +    let getters = new Set();
  1.3256 +    let names = [];
  1.3257 +    try {
  1.3258 +      names = aObject.getOwnPropertyNames()
  1.3259 +    } catch (ex) {
  1.3260 +      // Calling getOwnPropertyNames() on some wrapped native prototypes is not
  1.3261 +      // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
  1.3262 +    }
  1.3263 +
  1.3264 +    for (let name of names) {
  1.3265 +      let desc = null;
  1.3266 +      try {
  1.3267 +        desc = aObject.getOwnPropertyDescriptor(name);
  1.3268 +      } catch (e) {
  1.3269 +        // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
  1.3270 +        // allowed (bug 560072).
  1.3271 +      }
  1.3272 +      if (!desc || desc.value !== undefined || !("get" in desc)) {
  1.3273 +        continue;
  1.3274 +      }
  1.3275 +
  1.3276 +      if (DevToolsUtils.hasSafeGetter(desc)) {
  1.3277 +        getters.add(name);
  1.3278 +      }
  1.3279 +    }
  1.3280 +
  1.3281 +    aObject._safeGetters = getters;
  1.3282 +    return getters;
  1.3283 +  },
  1.3284 +
  1.3285 +  /**
  1.3286 +   * Handle a protocol request to provide the prototype of the object.
  1.3287 +   *
  1.3288 +   * @param aRequest object
  1.3289 +   *        The protocol request object.
  1.3290 +   */
  1.3291 +  onPrototype: function (aRequest) {
  1.3292 +    return { from: this.actorID,
  1.3293 +             prototype: this.threadActor.createValueGrip(this.obj.proto) };
  1.3294 +  },
  1.3295 +
  1.3296 +  /**
  1.3297 +   * Handle a protocol request to provide the property descriptor of the
  1.3298 +   * object's specified property.
  1.3299 +   *
  1.3300 +   * @param aRequest object
  1.3301 +   *        The protocol request object.
  1.3302 +   */
  1.3303 +  onProperty: function (aRequest) {
  1.3304 +    if (!aRequest.name) {
  1.3305 +      return { error: "missingParameter",
  1.3306 +               message: "no property name was specified" };
  1.3307 +    }
  1.3308 +
  1.3309 +    return { from: this.actorID,
  1.3310 +             descriptor: this._propertyDescriptor(aRequest.name) };
  1.3311 +  },
  1.3312 +
  1.3313 +  /**
  1.3314 +   * Handle a protocol request to provide the display string for the object.
  1.3315 +   *
  1.3316 +   * @param aRequest object
  1.3317 +   *        The protocol request object.
  1.3318 +   */
  1.3319 +  onDisplayString: function (aRequest) {
  1.3320 +    const string = stringify(this.obj);
  1.3321 +    return { from: this.actorID,
  1.3322 +             displayString: this.threadActor.createValueGrip(string) };
  1.3323 +  },
  1.3324 +
  1.3325 +  /**
  1.3326 +   * A helper method that creates a property descriptor for the provided object,
  1.3327 +   * properly formatted for sending in a protocol response.
  1.3328 +   *
  1.3329 +   * @private
  1.3330 +   * @param string aName
  1.3331 +   *        The property that the descriptor is generated for.
  1.3332 +   * @param boolean [aOnlyEnumerable]
  1.3333 +   *        Optional: true if you want a descriptor only for an enumerable
  1.3334 +   *        property, false otherwise.
  1.3335 +   * @return object|undefined
  1.3336 +   *         The property descriptor, or undefined if this is not an enumerable
  1.3337 +   *         property and aOnlyEnumerable=true.
  1.3338 +   */
  1.3339 +  _propertyDescriptor: function (aName, aOnlyEnumerable) {
  1.3340 +    let desc;
  1.3341 +    try {
  1.3342 +      desc = this.obj.getOwnPropertyDescriptor(aName);
  1.3343 +    } catch (e) {
  1.3344 +      // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
  1.3345 +      // allowed (bug 560072). Inform the user with a bogus, but hopefully
  1.3346 +      // explanatory, descriptor.
  1.3347 +      return {
  1.3348 +        configurable: false,
  1.3349 +        writable: false,
  1.3350 +        enumerable: false,
  1.3351 +        value: e.name
  1.3352 +      };
  1.3353 +    }
  1.3354 +
  1.3355 +    if (!desc || aOnlyEnumerable && !desc.enumerable) {
  1.3356 +      return undefined;
  1.3357 +    }
  1.3358 +
  1.3359 +    let retval = {
  1.3360 +      configurable: desc.configurable,
  1.3361 +      enumerable: desc.enumerable
  1.3362 +    };
  1.3363 +
  1.3364 +    if ("value" in desc) {
  1.3365 +      retval.writable = desc.writable;
  1.3366 +      retval.value = this.threadActor.createValueGrip(desc.value);
  1.3367 +    } else {
  1.3368 +      if ("get" in desc) {
  1.3369 +        retval.get = this.threadActor.createValueGrip(desc.get);
  1.3370 +      }
  1.3371 +      if ("set" in desc) {
  1.3372 +        retval.set = this.threadActor.createValueGrip(desc.set);
  1.3373 +      }
  1.3374 +    }
  1.3375 +    return retval;
  1.3376 +  },
  1.3377 +
  1.3378 +  /**
  1.3379 +   * Handle a protocol request to provide the source code of a function.
  1.3380 +   *
  1.3381 +   * @param aRequest object
  1.3382 +   *        The protocol request object.
  1.3383 +   */
  1.3384 +  onDecompile: function (aRequest) {
  1.3385 +    if (this.obj.class !== "Function") {
  1.3386 +      return { error: "objectNotFunction",
  1.3387 +               message: "decompile request is only valid for object grips " +
  1.3388 +                        "with a 'Function' class." };
  1.3389 +    }
  1.3390 +
  1.3391 +    return { from: this.actorID,
  1.3392 +             decompiledCode: this.obj.decompile(!!aRequest.pretty) };
  1.3393 +  },
  1.3394 +
  1.3395 +  /**
  1.3396 +   * Handle a protocol request to provide the parameters of a function.
  1.3397 +   *
  1.3398 +   * @param aRequest object
  1.3399 +   *        The protocol request object.
  1.3400 +   */
  1.3401 +  onParameterNames: function (aRequest) {
  1.3402 +    if (this.obj.class !== "Function") {
  1.3403 +      return { error: "objectNotFunction",
  1.3404 +               message: "'parameterNames' request is only valid for object " +
  1.3405 +                        "grips with a 'Function' class." };
  1.3406 +    }
  1.3407 +
  1.3408 +    return { parameterNames: this.obj.parameterNames };
  1.3409 +  },
  1.3410 +
  1.3411 +  /**
  1.3412 +   * Handle a protocol request to release a thread-lifetime grip.
  1.3413 +   *
  1.3414 +   * @param aRequest object
  1.3415 +   *        The protocol request object.
  1.3416 +   */
  1.3417 +  onRelease: function (aRequest) {
  1.3418 +    this.release();
  1.3419 +    return {};
  1.3420 +  },
  1.3421 +
  1.3422 +  /**
  1.3423 +   * Handle a protocol request to provide the lexical scope of a function.
  1.3424 +   *
  1.3425 +   * @param aRequest object
  1.3426 +   *        The protocol request object.
  1.3427 +   */
  1.3428 +  onScope: function (aRequest) {
  1.3429 +    if (this.obj.class !== "Function") {
  1.3430 +      return { error: "objectNotFunction",
  1.3431 +               message: "scope request is only valid for object grips with a" +
  1.3432 +                        " 'Function' class." };
  1.3433 +    }
  1.3434 +
  1.3435 +    let envActor = this.threadActor.createEnvironmentActor(this.obj.environment,
  1.3436 +                                                           this.registeredPool);
  1.3437 +    if (!envActor) {
  1.3438 +      return { error: "notDebuggee",
  1.3439 +               message: "cannot access the environment of this function." };
  1.3440 +    }
  1.3441 +
  1.3442 +    return { from: this.actorID, scope: envActor.form() };
  1.3443 +  }
  1.3444 +};
  1.3445 +
  1.3446 +ObjectActor.prototype.requestTypes = {
  1.3447 +  "definitionSite": ObjectActor.prototype.onDefinitionSite,
  1.3448 +  "parameterNames": ObjectActor.prototype.onParameterNames,
  1.3449 +  "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
  1.3450 +  "prototype": ObjectActor.prototype.onPrototype,
  1.3451 +  "property": ObjectActor.prototype.onProperty,
  1.3452 +  "displayString": ObjectActor.prototype.onDisplayString,
  1.3453 +  "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
  1.3454 +  "decompile": ObjectActor.prototype.onDecompile,
  1.3455 +  "release": ObjectActor.prototype.onRelease,
  1.3456 +  "scope": ObjectActor.prototype.onScope,
  1.3457 +};
  1.3458 +
  1.3459 +
  1.3460 +/**
  1.3461 + * Functions for adding information to ObjectActor grips for the purpose of
  1.3462 + * having customized output. This object holds arrays mapped by
  1.3463 + * Debugger.Object.prototype.class.
  1.3464 + *
  1.3465 + * In each array you can add functions that take two
  1.3466 + * arguments:
  1.3467 + *   - the ObjectActor instance to make a preview for,
  1.3468 + *   - the grip object being prepared for the client,
  1.3469 + *   - the raw JS object after calling Debugger.Object.unsafeDereference(). This
  1.3470 + *   argument is only provided if the object is safe for reading properties and
  1.3471 + *   executing methods. See DevToolsUtils.isSafeJSObject().
  1.3472 + *
  1.3473 + * Functions must return false if they cannot provide preview
  1.3474 + * information for the debugger object, or true otherwise.
  1.3475 + */
  1.3476 +DebuggerServer.ObjectActorPreviewers = {
  1.3477 +  String: [function({obj, threadActor}, aGrip) {
  1.3478 +    let result = genericObjectPreviewer("String", String, obj, threadActor);
  1.3479 +    if (result) {
  1.3480 +      let length = DevToolsUtils.getProperty(obj, "length");
  1.3481 +      if (typeof length != "number") {
  1.3482 +        return false;
  1.3483 +      }
  1.3484 +
  1.3485 +      aGrip.displayString = result.value;
  1.3486 +      return true;
  1.3487 +    }
  1.3488 +
  1.3489 +    return true;
  1.3490 +  }],
  1.3491 +
  1.3492 +  Boolean: [function({obj, threadActor}, aGrip) {
  1.3493 +    let result = genericObjectPreviewer("Boolean", Boolean, obj, threadActor);
  1.3494 +    if (result) {
  1.3495 +      aGrip.preview = result;
  1.3496 +      return true;
  1.3497 +    }
  1.3498 +
  1.3499 +    return false;
  1.3500 +  }],
  1.3501 +
  1.3502 +  Number: [function({obj, threadActor}, aGrip) {
  1.3503 +    let result = genericObjectPreviewer("Number", Number, obj, threadActor);
  1.3504 +    if (result) {
  1.3505 +      aGrip.preview = result;
  1.3506 +      return true;
  1.3507 +    }
  1.3508 +
  1.3509 +    return false;
  1.3510 +  }],
  1.3511 +
  1.3512 +  Function: [function({obj, threadActor}, aGrip) {
  1.3513 +    if (obj.name) {
  1.3514 +      aGrip.name = obj.name;
  1.3515 +    }
  1.3516 +
  1.3517 +    if (obj.displayName) {
  1.3518 +      aGrip.displayName = obj.displayName.substr(0, 500);
  1.3519 +    }
  1.3520 +
  1.3521 +    if (obj.parameterNames) {
  1.3522 +      aGrip.parameterNames = obj.parameterNames;
  1.3523 +    }
  1.3524 +
  1.3525 +    // Check if the developer has added a de-facto standard displayName
  1.3526 +    // property for us to use.
  1.3527 +    let userDisplayName;
  1.3528 +    try {
  1.3529 +      userDisplayName = obj.getOwnPropertyDescriptor("displayName");
  1.3530 +    } catch (e) {
  1.3531 +      // Calling getOwnPropertyDescriptor with displayName might throw
  1.3532 +      // with "permission denied" errors for some functions.
  1.3533 +      dumpn(e);
  1.3534 +    }
  1.3535 +
  1.3536 +    if (userDisplayName && typeof userDisplayName.value == "string" &&
  1.3537 +        userDisplayName.value) {
  1.3538 +      aGrip.userDisplayName = threadActor.createValueGrip(userDisplayName.value);
  1.3539 +    }
  1.3540 +
  1.3541 +    return true;
  1.3542 +  }],
  1.3543 +
  1.3544 +  RegExp: [function({obj, threadActor}, aGrip) {
  1.3545 +    // Avoid having any special preview for the RegExp.prototype itself.
  1.3546 +    if (!obj.proto || obj.proto.class != "RegExp") {
  1.3547 +      return false;
  1.3548 +    }
  1.3549 +
  1.3550 +    let str = RegExp.prototype.toString.call(obj.unsafeDereference());
  1.3551 +    aGrip.displayString = threadActor.createValueGrip(str);
  1.3552 +    return true;
  1.3553 +  }],
  1.3554 +
  1.3555 +  Date: [function({obj, threadActor}, aGrip) {
  1.3556 +    if (!obj.proto || obj.proto.class != "Date") {
  1.3557 +      return false;
  1.3558 +    }
  1.3559 +
  1.3560 +    let time = Date.prototype.getTime.call(obj.unsafeDereference());
  1.3561 +
  1.3562 +    aGrip.preview = {
  1.3563 +      timestamp: threadActor.createValueGrip(time),
  1.3564 +    };
  1.3565 +    return true;
  1.3566 +  }],
  1.3567 +
  1.3568 +  Array: [function({obj, threadActor}, aGrip) {
  1.3569 +    let length = DevToolsUtils.getProperty(obj, "length");
  1.3570 +    if (typeof length != "number") {
  1.3571 +      return false;
  1.3572 +    }
  1.3573 +
  1.3574 +    aGrip.preview = {
  1.3575 +      kind: "ArrayLike",
  1.3576 +      length: length,
  1.3577 +    };
  1.3578 +
  1.3579 +    if (threadActor._gripDepth > 1) {
  1.3580 +      return true;
  1.3581 +    }
  1.3582 +
  1.3583 +    let raw = obj.unsafeDereference();
  1.3584 +    let items = aGrip.preview.items = [];
  1.3585 +
  1.3586 +    for (let [i, value] of Array.prototype.entries.call(raw)) {
  1.3587 +      if (Object.hasOwnProperty.call(raw, i)) {
  1.3588 +        value = makeDebuggeeValueIfNeeded(obj, value);
  1.3589 +        items.push(threadActor.createValueGrip(value));
  1.3590 +      } else {
  1.3591 +        items.push(null);
  1.3592 +      }
  1.3593 +
  1.3594 +      if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
  1.3595 +        break;
  1.3596 +      }
  1.3597 +    }
  1.3598 +
  1.3599 +    return true;
  1.3600 +  }], // Array
  1.3601 +
  1.3602 +  Set: [function({obj, threadActor}, aGrip) {
  1.3603 +    let size = DevToolsUtils.getProperty(obj, "size");
  1.3604 +    if (typeof size != "number") {
  1.3605 +      return false;
  1.3606 +    }
  1.3607 +
  1.3608 +    aGrip.preview = {
  1.3609 +      kind: "ArrayLike",
  1.3610 +      length: size,
  1.3611 +    };
  1.3612 +
  1.3613 +    // Avoid recursive object grips.
  1.3614 +    if (threadActor._gripDepth > 1) {
  1.3615 +      return true;
  1.3616 +    }
  1.3617 +
  1.3618 +    let raw = obj.unsafeDereference();
  1.3619 +    let items = aGrip.preview.items = [];
  1.3620 +    for (let item of Set.prototype.values.call(raw)) {
  1.3621 +      item = makeDebuggeeValueIfNeeded(obj, item);
  1.3622 +      items.push(threadActor.createValueGrip(item));
  1.3623 +      if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
  1.3624 +        break;
  1.3625 +      }
  1.3626 +    }
  1.3627 +
  1.3628 +    return true;
  1.3629 +  }], // Set
  1.3630 +
  1.3631 +  Map: [function({obj, threadActor}, aGrip) {
  1.3632 +    let size = DevToolsUtils.getProperty(obj, "size");
  1.3633 +    if (typeof size != "number") {
  1.3634 +      return false;
  1.3635 +    }
  1.3636 +
  1.3637 +    aGrip.preview = {
  1.3638 +      kind: "MapLike",
  1.3639 +      size: size,
  1.3640 +    };
  1.3641 +
  1.3642 +    if (threadActor._gripDepth > 1) {
  1.3643 +      return true;
  1.3644 +    }
  1.3645 +
  1.3646 +    let raw = obj.unsafeDereference();
  1.3647 +    let entries = aGrip.preview.entries = [];
  1.3648 +    for (let [key, value] of Map.prototype.entries.call(raw)) {
  1.3649 +      key = makeDebuggeeValueIfNeeded(obj, key);
  1.3650 +      value = makeDebuggeeValueIfNeeded(obj, value);
  1.3651 +      entries.push([threadActor.createValueGrip(key),
  1.3652 +                    threadActor.createValueGrip(value)]);
  1.3653 +      if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
  1.3654 +        break;
  1.3655 +      }
  1.3656 +    }
  1.3657 +
  1.3658 +    return true;
  1.3659 +  }], // Map
  1.3660 +
  1.3661 +  DOMStringMap: [function({obj, threadActor}, aGrip, aRawObj) {
  1.3662 +    if (!aRawObj) {
  1.3663 +      return false;
  1.3664 +    }
  1.3665 +
  1.3666 +    let keys = obj.getOwnPropertyNames();
  1.3667 +    aGrip.preview = {
  1.3668 +      kind: "MapLike",
  1.3669 +      size: keys.length,
  1.3670 +    };
  1.3671 +
  1.3672 +    if (threadActor._gripDepth > 1) {
  1.3673 +      return true;
  1.3674 +    }
  1.3675 +
  1.3676 +    let entries = aGrip.preview.entries = [];
  1.3677 +    for (let key of keys) {
  1.3678 +      let value = makeDebuggeeValueIfNeeded(obj, aRawObj[key]);
  1.3679 +      entries.push([key, threadActor.createValueGrip(value)]);
  1.3680 +      if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) {
  1.3681 +        break;
  1.3682 +      }
  1.3683 +    }
  1.3684 +
  1.3685 +    return true;
  1.3686 +  }], // DOMStringMap
  1.3687 +}; // DebuggerServer.ObjectActorPreviewers
  1.3688 +
  1.3689 +/**
  1.3690 + * Generic previewer for "simple" classes like String, Number and Boolean.
  1.3691 + *
  1.3692 + * @param string aClassName
  1.3693 + *        Class name to expect.
  1.3694 + * @param object aClass
  1.3695 + *        The class to expect, eg. String. The valueOf() method of the class is
  1.3696 + *        invoked on the given object.
  1.3697 + * @param Debugger.Object aObj
  1.3698 + *        The debugger object we need to preview.
  1.3699 + * @param object aThreadActor
  1.3700 + *        The thread actor to use to create a value grip.
  1.3701 + * @return object|null
  1.3702 + *         An object with one property, "value", which holds the value grip that
  1.3703 + *         represents the given object. Null is returned if we cant preview the
  1.3704 + *         object.
  1.3705 + */
  1.3706 +function genericObjectPreviewer(aClassName, aClass, aObj, aThreadActor) {
  1.3707 +  if (!aObj.proto || aObj.proto.class != aClassName) {
  1.3708 +    return null;
  1.3709 +  }
  1.3710 +
  1.3711 +  let raw = aObj.unsafeDereference();
  1.3712 +  let v = null;
  1.3713 +  try {
  1.3714 +    v = aClass.prototype.valueOf.call(raw);
  1.3715 +  } catch (ex) {
  1.3716 +    // valueOf() can throw if the raw JS object is "misbehaved".
  1.3717 +    return null;
  1.3718 +  }
  1.3719 +
  1.3720 +  if (v !== null) {
  1.3721 +    v = aThreadActor.createValueGrip(makeDebuggeeValueIfNeeded(aObj, v));
  1.3722 +    return { value: v };
  1.3723 +  }
  1.3724 +
  1.3725 +  return null;
  1.3726 +}
  1.3727 +
  1.3728 +// Preview functions that do not rely on the object class.
  1.3729 +DebuggerServer.ObjectActorPreviewers.Object = [
  1.3730 +  function TypedArray({obj, threadActor}, aGrip) {
  1.3731 +    if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) {
  1.3732 +      return false;
  1.3733 +    }
  1.3734 +
  1.3735 +    let length = DevToolsUtils.getProperty(obj, "length");
  1.3736 +    if (typeof length != "number") {
  1.3737 +      return false;
  1.3738 +    }
  1.3739 +
  1.3740 +    aGrip.preview = {
  1.3741 +      kind: "ArrayLike",
  1.3742 +      length: length,
  1.3743 +    };
  1.3744 +
  1.3745 +    if (threadActor._gripDepth > 1) {
  1.3746 +      return true;
  1.3747 +    }
  1.3748 +
  1.3749 +    let raw = obj.unsafeDereference();
  1.3750 +    let global = Cu.getGlobalForObject(DebuggerServer);
  1.3751 +    let classProto = global[obj.class].prototype;
  1.3752 +    let safeView = classProto.subarray.call(raw, 0, OBJECT_PREVIEW_MAX_ITEMS);
  1.3753 +    let items = aGrip.preview.items = [];
  1.3754 +    for (let i = 0; i < safeView.length; i++) {
  1.3755 +      items.push(safeView[i]);
  1.3756 +    }
  1.3757 +
  1.3758 +    return true;
  1.3759 +  },
  1.3760 +
  1.3761 +  function Error({obj, threadActor}, aGrip) {
  1.3762 +    switch (obj.class) {
  1.3763 +      case "Error":
  1.3764 +      case "EvalError":
  1.3765 +      case "RangeError":
  1.3766 +      case "ReferenceError":
  1.3767 +      case "SyntaxError":
  1.3768 +      case "TypeError":
  1.3769 +      case "URIError":
  1.3770 +        let name = DevToolsUtils.getProperty(obj, "name");
  1.3771 +        let msg = DevToolsUtils.getProperty(obj, "message");
  1.3772 +        let stack = DevToolsUtils.getProperty(obj, "stack");
  1.3773 +        let fileName = DevToolsUtils.getProperty(obj, "fileName");
  1.3774 +        let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber");
  1.3775 +        let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber");
  1.3776 +        aGrip.preview = {
  1.3777 +          kind: "Error",
  1.3778 +          name: threadActor.createValueGrip(name),
  1.3779 +          message: threadActor.createValueGrip(msg),
  1.3780 +          stack: threadActor.createValueGrip(stack),
  1.3781 +          fileName: threadActor.createValueGrip(fileName),
  1.3782 +          lineNumber: threadActor.createValueGrip(lineNumber),
  1.3783 +          columnNumber: threadActor.createValueGrip(columnNumber),
  1.3784 +        };
  1.3785 +        return true;
  1.3786 +      default:
  1.3787 +        return false;
  1.3788 +    }
  1.3789 +  },
  1.3790 +
  1.3791 +  function CSSMediaRule({obj, threadActor}, aGrip, aRawObj) {
  1.3792 +    if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSMediaRule)) {
  1.3793 +      return false;
  1.3794 +    }
  1.3795 +    aGrip.preview = {
  1.3796 +      kind: "ObjectWithText",
  1.3797 +      text: threadActor.createValueGrip(aRawObj.conditionText),
  1.3798 +    };
  1.3799 +    return true;
  1.3800 +  },
  1.3801 +
  1.3802 +  function CSSStyleRule({obj, threadActor}, aGrip, aRawObj) {
  1.3803 +    if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleRule)) {
  1.3804 +      return false;
  1.3805 +    }
  1.3806 +    aGrip.preview = {
  1.3807 +      kind: "ObjectWithText",
  1.3808 +      text: threadActor.createValueGrip(aRawObj.selectorText),
  1.3809 +    };
  1.3810 +    return true;
  1.3811 +  },
  1.3812 +
  1.3813 +  function ObjectWithURL({obj, threadActor}, aGrip, aRawObj) {
  1.3814 +    if (!aRawObj ||
  1.3815 +        !(aRawObj instanceof Ci.nsIDOMCSSImportRule ||
  1.3816 +          aRawObj instanceof Ci.nsIDOMCSSStyleSheet ||
  1.3817 +          aRawObj instanceof Ci.nsIDOMLocation ||
  1.3818 +          aRawObj instanceof Ci.nsIDOMWindow)) {
  1.3819 +      return false;
  1.3820 +    }
  1.3821 +
  1.3822 +    let url;
  1.3823 +    if (aRawObj instanceof Ci.nsIDOMWindow && aRawObj.location) {
  1.3824 +      url = aRawObj.location.href;
  1.3825 +    } else if (aRawObj.href) {
  1.3826 +      url = aRawObj.href;
  1.3827 +    } else {
  1.3828 +      return false;
  1.3829 +    }
  1.3830 +
  1.3831 +    aGrip.preview = {
  1.3832 +      kind: "ObjectWithURL",
  1.3833 +      url: threadActor.createValueGrip(url),
  1.3834 +    };
  1.3835 +
  1.3836 +    return true;
  1.3837 +  },
  1.3838 +
  1.3839 +  function ArrayLike({obj, threadActor}, aGrip, aRawObj) {
  1.3840 +    if (!aRawObj ||
  1.3841 +        obj.class != "DOMStringList" &&
  1.3842 +        obj.class != "DOMTokenList" &&
  1.3843 +        !(aRawObj instanceof Ci.nsIDOMMozNamedAttrMap ||
  1.3844 +          aRawObj instanceof Ci.nsIDOMCSSRuleList ||
  1.3845 +          aRawObj instanceof Ci.nsIDOMCSSValueList ||
  1.3846 +          aRawObj instanceof Ci.nsIDOMFileList ||
  1.3847 +          aRawObj instanceof Ci.nsIDOMFontFaceList ||
  1.3848 +          aRawObj instanceof Ci.nsIDOMMediaList ||
  1.3849 +          aRawObj instanceof Ci.nsIDOMNodeList ||
  1.3850 +          aRawObj instanceof Ci.nsIDOMStyleSheetList)) {
  1.3851 +      return false;
  1.3852 +    }
  1.3853 +
  1.3854 +    if (typeof aRawObj.length != "number") {
  1.3855 +      return false;
  1.3856 +    }
  1.3857 +
  1.3858 +    aGrip.preview = {
  1.3859 +      kind: "ArrayLike",
  1.3860 +      length: aRawObj.length,
  1.3861 +    };
  1.3862 +
  1.3863 +    if (threadActor._gripDepth > 1) {
  1.3864 +      return true;
  1.3865 +    }
  1.3866 +
  1.3867 +    let items = aGrip.preview.items = [];
  1.3868 +
  1.3869 +    for (let i = 0; i < aRawObj.length &&
  1.3870 +                    items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) {
  1.3871 +      let value = makeDebuggeeValueIfNeeded(obj, aRawObj[i]);
  1.3872 +      items.push(threadActor.createValueGrip(value));
  1.3873 +    }
  1.3874 +
  1.3875 +    return true;
  1.3876 +  }, // ArrayLike
  1.3877 +
  1.3878 +  function CSSStyleDeclaration({obj, threadActor}, aGrip, aRawObj) {
  1.3879 +    if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) {
  1.3880 +      return false;
  1.3881 +    }
  1.3882 +
  1.3883 +    aGrip.preview = {
  1.3884 +      kind: "MapLike",
  1.3885 +      size: aRawObj.length,
  1.3886 +    };
  1.3887 +
  1.3888 +    let entries = aGrip.preview.entries = [];
  1.3889 +
  1.3890 +    for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS &&
  1.3891 +                    i < aRawObj.length; i++) {
  1.3892 +      let prop = aRawObj[i];
  1.3893 +      let value = aRawObj.getPropertyValue(prop);
  1.3894 +      entries.push([prop, threadActor.createValueGrip(value)]);
  1.3895 +    }
  1.3896 +
  1.3897 +    return true;
  1.3898 +  },
  1.3899 +
  1.3900 +  function DOMNode({obj, threadActor}, aGrip, aRawObj) {
  1.3901 +    if (obj.class == "Object" || !aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) {
  1.3902 +      return false;
  1.3903 +    }
  1.3904 +
  1.3905 +    let preview = aGrip.preview = {
  1.3906 +      kind: "DOMNode",
  1.3907 +      nodeType: aRawObj.nodeType,
  1.3908 +      nodeName: aRawObj.nodeName,
  1.3909 +    };
  1.3910 +
  1.3911 +    if (aRawObj instanceof Ci.nsIDOMDocument && aRawObj.location) {
  1.3912 +      preview.location = threadActor.createValueGrip(aRawObj.location.href);
  1.3913 +    } else if (aRawObj instanceof Ci.nsIDOMDocumentFragment) {
  1.3914 +      preview.childNodesLength = aRawObj.childNodes.length;
  1.3915 +
  1.3916 +      if (threadActor._gripDepth < 2) {
  1.3917 +        preview.childNodes = [];
  1.3918 +        for (let node of aRawObj.childNodes) {
  1.3919 +          let actor = threadActor.createValueGrip(obj.makeDebuggeeValue(node));
  1.3920 +          preview.childNodes.push(actor);
  1.3921 +          if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) {
  1.3922 +            break;
  1.3923 +          }
  1.3924 +        }
  1.3925 +      }
  1.3926 +    } else if (aRawObj instanceof Ci.nsIDOMElement) {
  1.3927 +      // Add preview for DOM element attributes.
  1.3928 +      if (aRawObj instanceof Ci.nsIDOMHTMLElement) {
  1.3929 +        preview.nodeName = preview.nodeName.toLowerCase();
  1.3930 +      }
  1.3931 +
  1.3932 +      let i = 0;
  1.3933 +      preview.attributes = {};
  1.3934 +      preview.attributesLength = aRawObj.attributes.length;
  1.3935 +      for (let attr of aRawObj.attributes) {
  1.3936 +        preview.attributes[attr.nodeName] = threadActor.createValueGrip(attr.value);
  1.3937 +        if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
  1.3938 +          break;
  1.3939 +        }
  1.3940 +      }
  1.3941 +    } else if (aRawObj instanceof Ci.nsIDOMAttr) {
  1.3942 +      preview.value = threadActor.createValueGrip(aRawObj.value);
  1.3943 +    } else if (aRawObj instanceof Ci.nsIDOMText ||
  1.3944 +               aRawObj instanceof Ci.nsIDOMComment) {
  1.3945 +      preview.textContent = threadActor.createValueGrip(aRawObj.textContent);
  1.3946 +    }
  1.3947 +
  1.3948 +    return true;
  1.3949 +  }, // DOMNode
  1.3950 +
  1.3951 +  function DOMEvent({obj, threadActor}, aGrip, aRawObj) {
  1.3952 +    if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMEvent)) {
  1.3953 +      return false;
  1.3954 +    }
  1.3955 +
  1.3956 +    let preview = aGrip.preview = {
  1.3957 +      kind: "DOMEvent",
  1.3958 +      type: aRawObj.type,
  1.3959 +      properties: Object.create(null),
  1.3960 +    };
  1.3961 +
  1.3962 +    if (threadActor._gripDepth < 2) {
  1.3963 +      let target = obj.makeDebuggeeValue(aRawObj.target);
  1.3964 +      preview.target = threadActor.createValueGrip(target);
  1.3965 +    }
  1.3966 +
  1.3967 +    let props = [];
  1.3968 +    if (aRawObj instanceof Ci.nsIDOMMouseEvent) {
  1.3969 +      props.push("buttons", "clientX", "clientY", "layerX", "layerY");
  1.3970 +    } else if (aRawObj instanceof Ci.nsIDOMKeyEvent) {
  1.3971 +      let modifiers = [];
  1.3972 +      if (aRawObj.altKey) {
  1.3973 +        modifiers.push("Alt");
  1.3974 +      }
  1.3975 +      if (aRawObj.ctrlKey) {
  1.3976 +        modifiers.push("Control");
  1.3977 +      }
  1.3978 +      if (aRawObj.metaKey) {
  1.3979 +        modifiers.push("Meta");
  1.3980 +      }
  1.3981 +      if (aRawObj.shiftKey) {
  1.3982 +        modifiers.push("Shift");
  1.3983 +      }
  1.3984 +      preview.eventKind = "key";
  1.3985 +      preview.modifiers = modifiers;
  1.3986 +
  1.3987 +      props.push("key", "charCode", "keyCode");
  1.3988 +    } else if (aRawObj instanceof Ci.nsIDOMTransitionEvent ||
  1.3989 +               aRawObj instanceof Ci.nsIDOMAnimationEvent) {
  1.3990 +      props.push("animationName", "pseudoElement");
  1.3991 +    } else if (aRawObj instanceof Ci.nsIDOMClipboardEvent) {
  1.3992 +      props.push("clipboardData");
  1.3993 +    }
  1.3994 +
  1.3995 +    // Add event-specific properties.
  1.3996 +    for (let prop of props) {
  1.3997 +      let value = aRawObj[prop];
  1.3998 +      if (value && (typeof value == "object" || typeof value == "function")) {
  1.3999 +        // Skip properties pointing to objects.
  1.4000 +        if (threadActor._gripDepth > 1) {
  1.4001 +          continue;
  1.4002 +        }
  1.4003 +        value = obj.makeDebuggeeValue(value);
  1.4004 +      }
  1.4005 +      preview.properties[prop] = threadActor.createValueGrip(value);
  1.4006 +    }
  1.4007 +
  1.4008 +    // Add any properties we find on the event object.
  1.4009 +    if (!props.length) {
  1.4010 +      let i = 0;
  1.4011 +      for (let prop in aRawObj) {
  1.4012 +        let value = aRawObj[prop];
  1.4013 +        if (prop == "target" || prop == "type" || value === null ||
  1.4014 +            typeof value == "function") {
  1.4015 +          continue;
  1.4016 +        }
  1.4017 +        if (value && typeof value == "object") {
  1.4018 +          if (threadActor._gripDepth > 1) {
  1.4019 +            continue;
  1.4020 +          }
  1.4021 +          value = obj.makeDebuggeeValue(value);
  1.4022 +        }
  1.4023 +        preview.properties[prop] = threadActor.createValueGrip(value);
  1.4024 +        if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
  1.4025 +          break;
  1.4026 +        }
  1.4027 +      }
  1.4028 +    }
  1.4029 +
  1.4030 +    return true;
  1.4031 +  }, // DOMEvent
  1.4032 +
  1.4033 +  function DOMException({obj, threadActor}, aGrip, aRawObj) {
  1.4034 +    if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMDOMException)) {
  1.4035 +      return false;
  1.4036 +    }
  1.4037 +
  1.4038 +    aGrip.preview = {
  1.4039 +      kind: "DOMException",
  1.4040 +      name: threadActor.createValueGrip(aRawObj.name),
  1.4041 +      message: threadActor.createValueGrip(aRawObj.message),
  1.4042 +      code: threadActor.createValueGrip(aRawObj.code),
  1.4043 +      result: threadActor.createValueGrip(aRawObj.result),
  1.4044 +      filename: threadActor.createValueGrip(aRawObj.filename),
  1.4045 +      lineNumber: threadActor.createValueGrip(aRawObj.lineNumber),
  1.4046 +      columnNumber: threadActor.createValueGrip(aRawObj.columnNumber),
  1.4047 +    };
  1.4048 +
  1.4049 +    return true;
  1.4050 +  },
  1.4051 +
  1.4052 +  function GenericObject(aObjectActor, aGrip) {
  1.4053 +    let {obj, threadActor} = aObjectActor;
  1.4054 +    if (aGrip.preview || aGrip.displayString || threadActor._gripDepth > 1) {
  1.4055 +      return false;
  1.4056 +    }
  1.4057 +
  1.4058 +    let i = 0, names = [];
  1.4059 +    let preview = aGrip.preview = {
  1.4060 +      kind: "Object",
  1.4061 +      ownProperties: Object.create(null),
  1.4062 +    };
  1.4063 +
  1.4064 +    try {
  1.4065 +      names = obj.getOwnPropertyNames();
  1.4066 +    } catch (ex) {
  1.4067 +      // Calling getOwnPropertyNames() on some wrapped native prototypes is not
  1.4068 +      // allowed: "cannot modify properties of a WrappedNative". See bug 952093.
  1.4069 +    }
  1.4070 +
  1.4071 +    preview.ownPropertiesLength = names.length;
  1.4072 +
  1.4073 +    for (let name of names) {
  1.4074 +      let desc = aObjectActor._propertyDescriptor(name, true);
  1.4075 +      if (!desc) {
  1.4076 +        continue;
  1.4077 +      }
  1.4078 +
  1.4079 +      preview.ownProperties[name] = desc;
  1.4080 +      if (++i == OBJECT_PREVIEW_MAX_ITEMS) {
  1.4081 +        break;
  1.4082 +      }
  1.4083 +    }
  1.4084 +
  1.4085 +    if (i < OBJECT_PREVIEW_MAX_ITEMS) {
  1.4086 +      preview.safeGetterValues = aObjectActor.
  1.4087 +                                 _findSafeGetterValues(preview.ownProperties,
  1.4088 +                                                       OBJECT_PREVIEW_MAX_ITEMS - i);
  1.4089 +    }
  1.4090 +
  1.4091 +    return true;
  1.4092 +  }, // GenericObject
  1.4093 +]; // DebuggerServer.ObjectActorPreviewers.Object
  1.4094 +
  1.4095 +/**
  1.4096 + * Creates a pause-scoped actor for the specified object.
  1.4097 + * @see ObjectActor
  1.4098 + */
  1.4099 +function PauseScopedObjectActor()
  1.4100 +{
  1.4101 +  ObjectActor.apply(this, arguments);
  1.4102 +}
  1.4103 +
  1.4104 +PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
  1.4105 +
  1.4106 +update(PauseScopedObjectActor.prototype, ObjectActor.prototype);
  1.4107 +
  1.4108 +update(PauseScopedObjectActor.prototype, {
  1.4109 +  constructor: PauseScopedObjectActor,
  1.4110 +  actorPrefix: "pausedobj",
  1.4111 +
  1.4112 +  onOwnPropertyNames:
  1.4113 +    PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
  1.4114 +
  1.4115 +  onPrototypeAndProperties:
  1.4116 +    PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
  1.4117 +
  1.4118 +  onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
  1.4119 +  onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
  1.4120 +  onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
  1.4121 +
  1.4122 +  onDisplayString:
  1.4123 +    PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
  1.4124 +
  1.4125 +  onParameterNames:
  1.4126 +    PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
  1.4127 +
  1.4128 +  /**
  1.4129 +   * Handle a protocol request to promote a pause-lifetime grip to a
  1.4130 +   * thread-lifetime grip.
  1.4131 +   *
  1.4132 +   * @param aRequest object
  1.4133 +   *        The protocol request object.
  1.4134 +   */
  1.4135 +  onThreadGrip: PauseScopedActor.withPaused(function (aRequest) {
  1.4136 +    this.threadActor.threadObjectGrip(this);
  1.4137 +    return {};
  1.4138 +  }),
  1.4139 +
  1.4140 +  /**
  1.4141 +   * Handle a protocol request to release a thread-lifetime grip.
  1.4142 +   *
  1.4143 +   * @param aRequest object
  1.4144 +   *        The protocol request object.
  1.4145 +   */
  1.4146 +  onRelease: PauseScopedActor.withPaused(function (aRequest) {
  1.4147 +    if (this.registeredPool !== this.threadActor.threadLifetimePool) {
  1.4148 +      return { error: "notReleasable",
  1.4149 +               message: "Only thread-lifetime actors can be released." };
  1.4150 +    }
  1.4151 +
  1.4152 +    this.release();
  1.4153 +    return {};
  1.4154 +  }),
  1.4155 +});
  1.4156 +
  1.4157 +update(PauseScopedObjectActor.prototype.requestTypes, {
  1.4158 +  "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
  1.4159 +});
  1.4160 +
  1.4161 +
  1.4162 +/**
  1.4163 + * Creates an actor for the specied "very long" string. "Very long" is specified
  1.4164 + * at the server's discretion.
  1.4165 + *
  1.4166 + * @param aString String
  1.4167 + *        The string.
  1.4168 + */
  1.4169 +function LongStringActor(aString)
  1.4170 +{
  1.4171 +  this.string = aString;
  1.4172 +  this.stringLength = aString.length;
  1.4173 +}
  1.4174 +
  1.4175 +LongStringActor.prototype = {
  1.4176 +
  1.4177 +  actorPrefix: "longString",
  1.4178 +
  1.4179 +  disconnect: function () {
  1.4180 +    // Because longStringActors is not a weak map, we won't automatically leave
  1.4181 +    // it so we need to manually leave on disconnect so that we don't leak
  1.4182 +    // memory.
  1.4183 +    if (this.registeredPool && this.registeredPool.longStringActors) {
  1.4184 +      delete this.registeredPool.longStringActors[this.actorID];
  1.4185 +    }
  1.4186 +  },
  1.4187 +
  1.4188 +  /**
  1.4189 +   * Returns a grip for this actor for returning in a protocol message.
  1.4190 +   */
  1.4191 +  grip: function () {
  1.4192 +    return {
  1.4193 +      "type": "longString",
  1.4194 +      "initial": this.string.substring(
  1.4195 +        0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
  1.4196 +      "length": this.stringLength,
  1.4197 +      "actor": this.actorID
  1.4198 +    };
  1.4199 +  },
  1.4200 +
  1.4201 +  /**
  1.4202 +   * Handle a request to extract part of this actor's string.
  1.4203 +   *
  1.4204 +   * @param aRequest object
  1.4205 +   *        The protocol request object.
  1.4206 +   */
  1.4207 +  onSubstring: function (aRequest) {
  1.4208 +    return {
  1.4209 +      "from": this.actorID,
  1.4210 +      "substring": this.string.substring(aRequest.start, aRequest.end)
  1.4211 +    };
  1.4212 +  },
  1.4213 +
  1.4214 +  /**
  1.4215 +   * Handle a request to release this LongStringActor instance.
  1.4216 +   */
  1.4217 +  onRelease: function () {
  1.4218 +    // TODO: also check if registeredPool === threadActor.threadLifetimePool
  1.4219 +    // when the web console moves aray from manually releasing pause-scoped
  1.4220 +    // actors.
  1.4221 +    if (this.registeredPool.longStringActors) {
  1.4222 +      delete this.registeredPool.longStringActors[this.actorID];
  1.4223 +    }
  1.4224 +    this.registeredPool.removeActor(this);
  1.4225 +    return {};
  1.4226 +  },
  1.4227 +};
  1.4228 +
  1.4229 +LongStringActor.prototype.requestTypes = {
  1.4230 +  "substring": LongStringActor.prototype.onSubstring,
  1.4231 +  "release": LongStringActor.prototype.onRelease
  1.4232 +};
  1.4233 +
  1.4234 +
  1.4235 +/**
  1.4236 + * Creates an actor for the specified stack frame.
  1.4237 + *
  1.4238 + * @param aFrame Debugger.Frame
  1.4239 + *        The debuggee frame.
  1.4240 + * @param aThreadActor ThreadActor
  1.4241 + *        The parent thread actor for this frame.
  1.4242 + */
  1.4243 +function FrameActor(aFrame, aThreadActor)
  1.4244 +{
  1.4245 +  this.frame = aFrame;
  1.4246 +  this.threadActor = aThreadActor;
  1.4247 +}
  1.4248 +
  1.4249 +FrameActor.prototype = {
  1.4250 +  actorPrefix: "frame",
  1.4251 +
  1.4252 +  /**
  1.4253 +   * A pool that contains frame-lifetime objects, like the environment.
  1.4254 +   */
  1.4255 +  _frameLifetimePool: null,
  1.4256 +  get frameLifetimePool() {
  1.4257 +    if (!this._frameLifetimePool) {
  1.4258 +      this._frameLifetimePool = new ActorPool(this.conn);
  1.4259 +      this.conn.addActorPool(this._frameLifetimePool);
  1.4260 +    }
  1.4261 +    return this._frameLifetimePool;
  1.4262 +  },
  1.4263 +
  1.4264 +  /**
  1.4265 +   * Finalization handler that is called when the actor is being evicted from
  1.4266 +   * the pool.
  1.4267 +   */
  1.4268 +  disconnect: function () {
  1.4269 +    this.conn.removeActorPool(this._frameLifetimePool);
  1.4270 +    this._frameLifetimePool = null;
  1.4271 +  },
  1.4272 +
  1.4273 +  /**
  1.4274 +   * Returns a frame form for use in a protocol message.
  1.4275 +   */
  1.4276 +  form: function () {
  1.4277 +    let form = { actor: this.actorID,
  1.4278 +                 type: this.frame.type };
  1.4279 +    if (this.frame.type === "call") {
  1.4280 +      form.callee = this.threadActor.createValueGrip(this.frame.callee);
  1.4281 +    }
  1.4282 +
  1.4283 +    if (this.frame.environment) {
  1.4284 +      let envActor = this.threadActor
  1.4285 +        .createEnvironmentActor(this.frame.environment,
  1.4286 +                                this.frameLifetimePool);
  1.4287 +      form.environment = envActor.form();
  1.4288 +    }
  1.4289 +    form.this = this.threadActor.createValueGrip(this.frame.this);
  1.4290 +    form.arguments = this._args();
  1.4291 +    if (this.frame.script) {
  1.4292 +      form.where = getFrameLocation(this.frame);
  1.4293 +    }
  1.4294 +
  1.4295 +    if (!this.frame.older) {
  1.4296 +      form.oldest = true;
  1.4297 +    }
  1.4298 +
  1.4299 +    return form;
  1.4300 +  },
  1.4301 +
  1.4302 +  _args: function () {
  1.4303 +    if (!this.frame.arguments) {
  1.4304 +      return [];
  1.4305 +    }
  1.4306 +
  1.4307 +    return [this.threadActor.createValueGrip(arg)
  1.4308 +            for each (arg in this.frame.arguments)];
  1.4309 +  },
  1.4310 +
  1.4311 +  /**
  1.4312 +   * Handle a protocol request to pop this frame from the stack.
  1.4313 +   *
  1.4314 +   * @param aRequest object
  1.4315 +   *        The protocol request object.
  1.4316 +   */
  1.4317 +  onPop: function (aRequest) {
  1.4318 +    // TODO: remove this when Debugger.Frame.prototype.pop is implemented
  1.4319 +    if (typeof this.frame.pop != "function") {
  1.4320 +      return { error: "notImplemented",
  1.4321 +               message: "Popping frames is not yet implemented." };
  1.4322 +    }
  1.4323 +
  1.4324 +    while (this.frame != this.threadActor.dbg.getNewestFrame()) {
  1.4325 +      this.threadActor.dbg.getNewestFrame().pop();
  1.4326 +    }
  1.4327 +    this.frame.pop(aRequest.completionValue);
  1.4328 +
  1.4329 +    // TODO: return the watches property when frame pop watch actors are
  1.4330 +    // implemented.
  1.4331 +    return { from: this.actorID };
  1.4332 +  }
  1.4333 +};
  1.4334 +
  1.4335 +FrameActor.prototype.requestTypes = {
  1.4336 +  "pop": FrameActor.prototype.onPop,
  1.4337 +};
  1.4338 +
  1.4339 +
  1.4340 +/**
  1.4341 + * Creates a BreakpointActor. BreakpointActors exist for the lifetime of their
  1.4342 + * containing thread and are responsible for deleting breakpoints, handling
  1.4343 + * breakpoint hits and associating breakpoints with scripts.
  1.4344 + *
  1.4345 + * @param ThreadActor aThreadActor
  1.4346 + *        The parent thread actor that contains this breakpoint.
  1.4347 + * @param object aLocation
  1.4348 + *        The location of the breakpoint as specified in the protocol.
  1.4349 + */
  1.4350 +function BreakpointActor(aThreadActor, { url, line, column, condition })
  1.4351 +{
  1.4352 +  this.scripts = [];
  1.4353 +  this.threadActor = aThreadActor;
  1.4354 +  this.location = { url: url, line: line, column: column };
  1.4355 +  this.condition = condition;
  1.4356 +}
  1.4357 +
  1.4358 +BreakpointActor.prototype = {
  1.4359 +  actorPrefix: "breakpoint",
  1.4360 +  condition: null,
  1.4361 +
  1.4362 +  /**
  1.4363 +   * Called when this same breakpoint is added to another Debugger.Script
  1.4364 +   * instance, in the case of a page reload.
  1.4365 +   *
  1.4366 +   * @param aScript Debugger.Script
  1.4367 +   *        The new source script on which the breakpoint has been set.
  1.4368 +   * @param ThreadActor aThreadActor
  1.4369 +   *        The parent thread actor that contains this breakpoint.
  1.4370 +   */
  1.4371 +  addScript: function (aScript, aThreadActor) {
  1.4372 +    this.threadActor = aThreadActor;
  1.4373 +    this.scripts.push(aScript);
  1.4374 +  },
  1.4375 +
  1.4376 +  /**
  1.4377 +   * Remove the breakpoints from associated scripts and clear the script cache.
  1.4378 +   */
  1.4379 +  removeScripts: function () {
  1.4380 +    for (let script of this.scripts) {
  1.4381 +      script.clearBreakpoint(this);
  1.4382 +    }
  1.4383 +    this.scripts = [];
  1.4384 +  },
  1.4385 +
  1.4386 +  /**
  1.4387 +   * Check if this breakpoint has a condition that doesn't error and
  1.4388 +   * evaluates to true in aFrame
  1.4389 +   *
  1.4390 +   * @param aFrame Debugger.Frame
  1.4391 +   *        The frame to evaluate the condition in
  1.4392 +   */
  1.4393 +  isValidCondition: function(aFrame) {
  1.4394 +    if(!this.condition) {
  1.4395 +      return true;
  1.4396 +    }
  1.4397 +    var res = aFrame.eval(this.condition);
  1.4398 +    return res.return;
  1.4399 +  },
  1.4400 +
  1.4401 +  /**
  1.4402 +   * A function that the engine calls when a breakpoint has been hit.
  1.4403 +   *
  1.4404 +   * @param aFrame Debugger.Frame
  1.4405 +   *        The stack frame that contained the breakpoint.
  1.4406 +   */
  1.4407 +  hit: function (aFrame) {
  1.4408 +    // Don't pause if we are currently stepping (in or over) or the frame is
  1.4409 +    // black-boxed.
  1.4410 +    let { url } = this.threadActor.synchronize(
  1.4411 +      this.threadActor.sources.getOriginalLocation({
  1.4412 +        url: this.location.url,
  1.4413 +        line: this.location.line,
  1.4414 +        column: this.location.column
  1.4415 +      }));
  1.4416 +
  1.4417 +    if (this.threadActor.sources.isBlackBoxed(url)
  1.4418 +        || aFrame.onStep
  1.4419 +        || !this.isValidCondition(aFrame)) {
  1.4420 +      return undefined;
  1.4421 +    }
  1.4422 +
  1.4423 +    let reason = {};
  1.4424 +    if (this.threadActor._hiddenBreakpoints.has(this.actorID)) {
  1.4425 +      reason.type = "pauseOnDOMEvents";
  1.4426 +    } else {
  1.4427 +      reason.type = "breakpoint";
  1.4428 +      // TODO: add the rest of the breakpoints on that line (bug 676602).
  1.4429 +      reason.actors = [ this.actorID ];
  1.4430 +    }
  1.4431 +    return this.threadActor._pauseAndRespond(aFrame, reason);
  1.4432 +  },
  1.4433 +
  1.4434 +  /**
  1.4435 +   * Handle a protocol request to remove this breakpoint.
  1.4436 +   *
  1.4437 +   * @param aRequest object
  1.4438 +   *        The protocol request object.
  1.4439 +   */
  1.4440 +  onDelete: function (aRequest) {
  1.4441 +    // Remove from the breakpoint store.
  1.4442 +    this.threadActor.breakpointStore.removeBreakpoint(this.location);
  1.4443 +    this.threadActor.threadLifetimePool.removeActor(this);
  1.4444 +    // Remove the actual breakpoint from the associated scripts.
  1.4445 +    this.removeScripts();
  1.4446 +    return { from: this.actorID };
  1.4447 +  }
  1.4448 +};
  1.4449 +
  1.4450 +BreakpointActor.prototype.requestTypes = {
  1.4451 +  "delete": BreakpointActor.prototype.onDelete
  1.4452 +};
  1.4453 +
  1.4454 +
  1.4455 +/**
  1.4456 + * Creates an EnvironmentActor. EnvironmentActors are responsible for listing
  1.4457 + * the bindings introduced by a lexical environment and assigning new values to
  1.4458 + * those identifier bindings.
  1.4459 + *
  1.4460 + * @param Debugger.Environment aEnvironment
  1.4461 + *        The lexical environment that will be used to create the actor.
  1.4462 + * @param ThreadActor aThreadActor
  1.4463 + *        The parent thread actor that contains this environment.
  1.4464 + */
  1.4465 +function EnvironmentActor(aEnvironment, aThreadActor)
  1.4466 +{
  1.4467 +  this.obj = aEnvironment;
  1.4468 +  this.threadActor = aThreadActor;
  1.4469 +}
  1.4470 +
  1.4471 +EnvironmentActor.prototype = {
  1.4472 +  actorPrefix: "environment",
  1.4473 +
  1.4474 +  /**
  1.4475 +   * Return an environment form for use in a protocol message.
  1.4476 +   */
  1.4477 +  form: function () {
  1.4478 +    let form = { actor: this.actorID };
  1.4479 +
  1.4480 +    // What is this environment's type?
  1.4481 +    if (this.obj.type == "declarative") {
  1.4482 +      form.type = this.obj.callee ? "function" : "block";
  1.4483 +    } else {
  1.4484 +      form.type = this.obj.type;
  1.4485 +    }
  1.4486 +
  1.4487 +    // Does this environment have a parent?
  1.4488 +    if (this.obj.parent) {
  1.4489 +      form.parent = (this.threadActor
  1.4490 +                     .createEnvironmentActor(this.obj.parent,
  1.4491 +                                             this.registeredPool)
  1.4492 +                     .form());
  1.4493 +    }
  1.4494 +
  1.4495 +    // Does this environment reflect the properties of an object as variables?
  1.4496 +    if (this.obj.type == "object" || this.obj.type == "with") {
  1.4497 +      form.object = this.threadActor.createValueGrip(this.obj.object);
  1.4498 +    }
  1.4499 +
  1.4500 +    // Is this the environment created for a function call?
  1.4501 +    if (this.obj.callee) {
  1.4502 +      form.function = this.threadActor.createValueGrip(this.obj.callee);
  1.4503 +    }
  1.4504 +
  1.4505 +    // Shall we list this environment's bindings?
  1.4506 +    if (this.obj.type == "declarative") {
  1.4507 +      form.bindings = this._bindings();
  1.4508 +    }
  1.4509 +
  1.4510 +    return form;
  1.4511 +  },
  1.4512 +
  1.4513 +  /**
  1.4514 +   * Return the identifier bindings object as required by the remote protocol
  1.4515 +   * specification.
  1.4516 +   */
  1.4517 +  _bindings: function () {
  1.4518 +    let bindings = { arguments: [], variables: {} };
  1.4519 +
  1.4520 +    // TODO: this part should be removed in favor of the commented-out part
  1.4521 +    // below when getVariableDescriptor lands (bug 725815).
  1.4522 +    if (typeof this.obj.getVariable != "function") {
  1.4523 +    //if (typeof this.obj.getVariableDescriptor != "function") {
  1.4524 +      return bindings;
  1.4525 +    }
  1.4526 +
  1.4527 +    let parameterNames;
  1.4528 +    if (this.obj.callee) {
  1.4529 +      parameterNames = this.obj.callee.parameterNames;
  1.4530 +    }
  1.4531 +    for each (let name in parameterNames) {
  1.4532 +      let arg = {};
  1.4533 +
  1.4534 +      let value = this.obj.getVariable(name);
  1.4535 +      // The slot is optimized out.
  1.4536 +      // FIXME: Need actual UI, bug 941287.
  1.4537 +      if (value && value.optimizedOut) {
  1.4538 +        continue;
  1.4539 +      }
  1.4540 +
  1.4541 +      // TODO: this part should be removed in favor of the commented-out part
  1.4542 +      // below when getVariableDescriptor lands (bug 725815).
  1.4543 +      let desc = {
  1.4544 +        value: value,
  1.4545 +        configurable: false,
  1.4546 +        writable: true,
  1.4547 +        enumerable: true
  1.4548 +      };
  1.4549 +
  1.4550 +      // let desc = this.obj.getVariableDescriptor(name);
  1.4551 +      let descForm = {
  1.4552 +        enumerable: true,
  1.4553 +        configurable: desc.configurable
  1.4554 +      };
  1.4555 +      if ("value" in desc) {
  1.4556 +        descForm.value = this.threadActor.createValueGrip(desc.value);
  1.4557 +        descForm.writable = desc.writable;
  1.4558 +      } else {
  1.4559 +        descForm.get = this.threadActor.createValueGrip(desc.get);
  1.4560 +        descForm.set = this.threadActor.createValueGrip(desc.set);
  1.4561 +      }
  1.4562 +      arg[name] = descForm;
  1.4563 +      bindings.arguments.push(arg);
  1.4564 +    }
  1.4565 +
  1.4566 +    for each (let name in this.obj.names()) {
  1.4567 +      if (bindings.arguments.some(function exists(element) {
  1.4568 +                                    return !!element[name];
  1.4569 +                                  })) {
  1.4570 +        continue;
  1.4571 +      }
  1.4572 +
  1.4573 +      let value = this.obj.getVariable(name);
  1.4574 +      // The slot is optimized out or arguments on a dead scope.
  1.4575 +      // FIXME: Need actual UI, bug 941287.
  1.4576 +      if (value && (value.optimizedOut || value.missingArguments)) {
  1.4577 +        continue;
  1.4578 +      }
  1.4579 +
  1.4580 +      // TODO: this part should be removed in favor of the commented-out part
  1.4581 +      // below when getVariableDescriptor lands.
  1.4582 +      let desc = {
  1.4583 +        value: value,
  1.4584 +        configurable: false,
  1.4585 +        writable: true,
  1.4586 +        enumerable: true
  1.4587 +      };
  1.4588 +
  1.4589 +      //let desc = this.obj.getVariableDescriptor(name);
  1.4590 +      let descForm = {
  1.4591 +        enumerable: true,
  1.4592 +        configurable: desc.configurable
  1.4593 +      };
  1.4594 +      if ("value" in desc) {
  1.4595 +        descForm.value = this.threadActor.createValueGrip(desc.value);
  1.4596 +        descForm.writable = desc.writable;
  1.4597 +      } else {
  1.4598 +        descForm.get = this.threadActor.createValueGrip(desc.get);
  1.4599 +        descForm.set = this.threadActor.createValueGrip(desc.set);
  1.4600 +      }
  1.4601 +      bindings.variables[name] = descForm;
  1.4602 +    }
  1.4603 +
  1.4604 +    return bindings;
  1.4605 +  },
  1.4606 +
  1.4607 +  /**
  1.4608 +   * Handle a protocol request to change the value of a variable bound in this
  1.4609 +   * lexical environment.
  1.4610 +   *
  1.4611 +   * @param aRequest object
  1.4612 +   *        The protocol request object.
  1.4613 +   */
  1.4614 +  onAssign: function (aRequest) {
  1.4615 +    // TODO: enable the commented-out part when getVariableDescriptor lands
  1.4616 +    // (bug 725815).
  1.4617 +    /*let desc = this.obj.getVariableDescriptor(aRequest.name);
  1.4618 +
  1.4619 +    if (!desc.writable) {
  1.4620 +      return { error: "immutableBinding",
  1.4621 +               message: "Changing the value of an immutable binding is not " +
  1.4622 +                        "allowed" };
  1.4623 +    }*/
  1.4624 +
  1.4625 +    try {
  1.4626 +      this.obj.setVariable(aRequest.name, aRequest.value);
  1.4627 +    } catch (e if e instanceof Debugger.DebuggeeWouldRun) {
  1.4628 +        return { error: "threadWouldRun",
  1.4629 +                 cause: e.cause ? e.cause : "setter",
  1.4630 +                 message: "Assigning a value would cause the debuggee to run" };
  1.4631 +    }
  1.4632 +    return { from: this.actorID };
  1.4633 +  },
  1.4634 +
  1.4635 +  /**
  1.4636 +   * Handle a protocol request to fully enumerate the bindings introduced by the
  1.4637 +   * lexical environment.
  1.4638 +   *
  1.4639 +   * @param aRequest object
  1.4640 +   *        The protocol request object.
  1.4641 +   */
  1.4642 +  onBindings: function (aRequest) {
  1.4643 +    return { from: this.actorID,
  1.4644 +             bindings: this._bindings() };
  1.4645 +  }
  1.4646 +};
  1.4647 +
  1.4648 +EnvironmentActor.prototype.requestTypes = {
  1.4649 +  "assign": EnvironmentActor.prototype.onAssign,
  1.4650 +  "bindings": EnvironmentActor.prototype.onBindings
  1.4651 +};
  1.4652 +
  1.4653 +/**
  1.4654 + * Override the toString method in order to get more meaningful script output
  1.4655 + * for debugging the debugger.
  1.4656 + */
  1.4657 +Debugger.Script.prototype.toString = function() {
  1.4658 +  let output = "";
  1.4659 +  if (this.url) {
  1.4660 +    output += this.url;
  1.4661 +  }
  1.4662 +  if (typeof this.startLine != "undefined") {
  1.4663 +    output += ":" + this.startLine;
  1.4664 +    if (this.lineCount && this.lineCount > 1) {
  1.4665 +      output += "-" + (this.startLine + this.lineCount - 1);
  1.4666 +    }
  1.4667 +  }
  1.4668 +  if (this.strictMode) {
  1.4669 +    output += ":strict";
  1.4670 +  }
  1.4671 +  return output;
  1.4672 +};
  1.4673 +
  1.4674 +/**
  1.4675 + * Helper property for quickly getting to the line number a stack frame is
  1.4676 + * currently paused at.
  1.4677 + */
  1.4678 +Object.defineProperty(Debugger.Frame.prototype, "line", {
  1.4679 +  configurable: true,
  1.4680 +  get: function() {
  1.4681 +    if (this.script) {
  1.4682 +      return this.script.getOffsetLine(this.offset);
  1.4683 +    } else {
  1.4684 +      return null;
  1.4685 +    }
  1.4686 +  }
  1.4687 +});
  1.4688 +
  1.4689 +
  1.4690 +/**
  1.4691 + * Creates an actor for handling chrome debugging. ChromeDebuggerActor is a
  1.4692 + * thin wrapper over ThreadActor, slightly changing some of its behavior.
  1.4693 + *
  1.4694 + * @param aConnection object
  1.4695 + *        The DebuggerServerConnection with which this ChromeDebuggerActor
  1.4696 + *        is associated. (Currently unused, but required to make this
  1.4697 + *        constructor usable with addGlobalActor.)
  1.4698 + *
  1.4699 + * @param aHooks object
  1.4700 + *        An object with preNest and postNest methods for calling when entering
  1.4701 + *        and exiting a nested event loop.
  1.4702 + */
  1.4703 +function ChromeDebuggerActor(aConnection, aHooks)
  1.4704 +{
  1.4705 +  ThreadActor.call(this, aHooks);
  1.4706 +}
  1.4707 +
  1.4708 +ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype);
  1.4709 +
  1.4710 +update(ChromeDebuggerActor.prototype, {
  1.4711 +  constructor: ChromeDebuggerActor,
  1.4712 +
  1.4713 +  // A constant prefix that will be used to form the actor ID by the server.
  1.4714 +  actorPrefix: "chromeDebugger",
  1.4715 +
  1.4716 +  /**
  1.4717 +   * Override the eligibility check for scripts and sources to make sure every
  1.4718 +   * script and source with a URL is stored when debugging chrome.
  1.4719 +   */
  1.4720 +  _allowSource: function(aSourceURL) !!aSourceURL,
  1.4721 +
  1.4722 +   /**
  1.4723 +   * An object that will be used by ThreadActors to tailor their behavior
  1.4724 +   * depending on the debugging context being required (chrome or content).
  1.4725 +   * The methods that this object provides must be bound to the ThreadActor
  1.4726 +   * before use.
  1.4727 +   */
  1.4728 +  globalManager: {
  1.4729 +    findGlobals: function () {
  1.4730 +      // Add every global known to the debugger as debuggee.
  1.4731 +      this.dbg.addAllGlobalsAsDebuggees();
  1.4732 +    },
  1.4733 +
  1.4734 +    /**
  1.4735 +     * A function that the engine calls when a new global object has been
  1.4736 +     * created.
  1.4737 +     *
  1.4738 +     * @param aGlobal Debugger.Object
  1.4739 +     *        The new global object that was created.
  1.4740 +     */
  1.4741 +    onNewGlobal: function (aGlobal) {
  1.4742 +      this.addDebuggee(aGlobal);
  1.4743 +      // Notify the client.
  1.4744 +      this.conn.send({
  1.4745 +        from: this.actorID,
  1.4746 +        type: "newGlobal",
  1.4747 +        // TODO: after bug 801084 lands see if we need to JSONify this.
  1.4748 +        hostAnnotations: aGlobal.hostAnnotations
  1.4749 +      });
  1.4750 +    }
  1.4751 +  }
  1.4752 +});
  1.4753 +
  1.4754 +/**
  1.4755 + * Creates an actor for handling add-on debugging. AddonThreadActor is
  1.4756 + * a thin wrapper over ThreadActor.
  1.4757 + *
  1.4758 + * @param aConnection object
  1.4759 + *        The DebuggerServerConnection with which this AddonThreadActor
  1.4760 + *        is associated. (Currently unused, but required to make this
  1.4761 + *        constructor usable with addGlobalActor.)
  1.4762 + *
  1.4763 + * @param aHooks object
  1.4764 + *        An object with preNest and postNest methods for calling
  1.4765 + *        when entering and exiting a nested event loops.
  1.4766 + *
  1.4767 + * @param aAddonID string
  1.4768 + *        ID of the add-on this actor will debug. It will be used to
  1.4769 + *        filter out globals marked for debugging.
  1.4770 + */
  1.4771 +
  1.4772 +function AddonThreadActor(aConnect, aHooks, aAddonID) {
  1.4773 +  this.addonID = aAddonID;
  1.4774 +  ThreadActor.call(this, aHooks);
  1.4775 +}
  1.4776 +
  1.4777 +AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
  1.4778 +
  1.4779 +update(AddonThreadActor.prototype, {
  1.4780 +  constructor: AddonThreadActor,
  1.4781 +
  1.4782 +  // A constant prefix that will be used to form the actor ID by the server.
  1.4783 +  actorPrefix: "addonThread",
  1.4784 +
  1.4785 +  onAttach: function(aRequest) {
  1.4786 +    if (!this.attached) {
  1.4787 +      Services.obs.addObserver(this, "chrome-document-global-created", false);
  1.4788 +      Services.obs.addObserver(this, "content-document-global-created", false);
  1.4789 +    }
  1.4790 +    return ThreadActor.prototype.onAttach.call(this, aRequest);
  1.4791 +  },
  1.4792 +
  1.4793 +  disconnect: function() {
  1.4794 +    if (this.attached) {
  1.4795 +      Services.obs.removeObserver(this, "content-document-global-created");
  1.4796 +      Services.obs.removeObserver(this, "chrome-document-global-created");
  1.4797 +    }
  1.4798 +    return ThreadActor.prototype.disconnect.call(this);
  1.4799 +  },
  1.4800 +
  1.4801 +  /**
  1.4802 +   * Called when a new DOM document global is created. Check if the DOM was
  1.4803 +   * loaded from an add-on and if so make the window a debuggee.
  1.4804 +   */
  1.4805 +  observe: function(aSubject, aTopic, aData) {
  1.4806 +    let id = {};
  1.4807 +    if (mapURIToAddonID(aSubject.location, id) && id.value === this.addonID) {
  1.4808 +      this.dbg.addDebuggee(aSubject.defaultView);
  1.4809 +    }
  1.4810 +  },
  1.4811 +
  1.4812 +  /**
  1.4813 +   * Override the eligibility check for scripts and sources to make
  1.4814 +   * sure every script and source with a URL is stored when debugging
  1.4815 +   * add-ons.
  1.4816 +   */
  1.4817 +  _allowSource: function(aSourceURL) {
  1.4818 +    // Hide eval scripts
  1.4819 +    if (!aSourceURL) {
  1.4820 +      return false;
  1.4821 +    }
  1.4822 +
  1.4823 +    // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it
  1.4824 +    if (aSourceURL == "resource://gre/modules/addons/XPIProvider.jsm") {
  1.4825 +      return false;
  1.4826 +    }
  1.4827 +
  1.4828 +    return true;
  1.4829 +  },
  1.4830 +
  1.4831 +  /**
  1.4832 +   * An object that will be used by ThreadActors to tailor their
  1.4833 +   * behaviour depending on the debugging context being required (chrome,
  1.4834 +   * addon or content). The methods that this object provides must
  1.4835 +   * be bound to the ThreadActor before use.
  1.4836 +   */
  1.4837 +  globalManager: {
  1.4838 +    findGlobals: function ADA_findGlobals() {
  1.4839 +      for (let global of this.dbg.findAllGlobals()) {
  1.4840 +        if (this._checkGlobal(global)) {
  1.4841 +          this.dbg.addDebuggee(global);
  1.4842 +        }
  1.4843 +      }
  1.4844 +    },
  1.4845 +
  1.4846 +    /**
  1.4847 +     * A function that the engine calls when a new global object
  1.4848 +     * has been created.
  1.4849 +     *
  1.4850 +     * @param aGlobal Debugger.Object
  1.4851 +     *        The new global object that was created.
  1.4852 +     */
  1.4853 +    onNewGlobal: function ADA_onNewGlobal(aGlobal) {
  1.4854 +      if (this._checkGlobal(aGlobal)) {
  1.4855 +        this.addDebuggee(aGlobal);
  1.4856 +        // Notify the client.
  1.4857 +        this.conn.send({
  1.4858 +          from: this.actorID,
  1.4859 +          type: "newGlobal",
  1.4860 +          // TODO: after bug 801084 lands see if we need to JSONify this.
  1.4861 +          hostAnnotations: aGlobal.hostAnnotations
  1.4862 +        });
  1.4863 +      }
  1.4864 +    }
  1.4865 +  },
  1.4866 +
  1.4867 +  /**
  1.4868 +   * Checks if the provided global belongs to the debugged add-on.
  1.4869 +   *
  1.4870 +   * @param aGlobal Debugger.Object
  1.4871 +   */
  1.4872 +  _checkGlobal: function ADA_checkGlobal(aGlobal) {
  1.4873 +    let obj = null;
  1.4874 +    try {
  1.4875 +      obj = aGlobal.unsafeDereference();
  1.4876 +    }
  1.4877 +    catch (e) {
  1.4878 +      // Because of bug 991399 we sometimes get bad objects here. If we can't
  1.4879 +      // dereference them then they won't be useful to us
  1.4880 +      return false;
  1.4881 +    }
  1.4882 +
  1.4883 +    try {
  1.4884 +      // This will fail for non-Sandbox objects, hence the try-catch block.
  1.4885 +      let metadata = Cu.getSandboxMetadata(obj);
  1.4886 +      if (metadata) {
  1.4887 +        return metadata.addonID === this.addonID;
  1.4888 +      }
  1.4889 +    } catch (e) {
  1.4890 +    }
  1.4891 +
  1.4892 +    if (obj instanceof Ci.nsIDOMWindow) {
  1.4893 +      let id = {};
  1.4894 +      if (mapURIToAddonID(obj.document.documentURIObject, id)) {
  1.4895 +        return id.value === this.addonID;
  1.4896 +      }
  1.4897 +      return false;
  1.4898 +    }
  1.4899 +
  1.4900 +    // Check the global for a __URI__ property and then try to map that to an
  1.4901 +    // add-on
  1.4902 +    let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
  1.4903 +    if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
  1.4904 +      let uri;
  1.4905 +      try {
  1.4906 +        uri = Services.io.newURI(uridescriptor.value, null, null);
  1.4907 +      }
  1.4908 +      catch (e) {
  1.4909 +        DevToolsUtils.reportException("AddonThreadActor.prototype._checkGlobal",
  1.4910 +                                      new Error("Invalid URI: " + uridescriptor.value));
  1.4911 +        return false;
  1.4912 +      }
  1.4913 +
  1.4914 +      let id = {};
  1.4915 +      if (mapURIToAddonID(uri, id)) {
  1.4916 +        return id.value === this.addonID;
  1.4917 +      }
  1.4918 +    }
  1.4919 +
  1.4920 +    return false;
  1.4921 +  }
  1.4922 +});
  1.4923 +
  1.4924 +AddonThreadActor.prototype.requestTypes = Object.create(ThreadActor.prototype.requestTypes);
  1.4925 +update(AddonThreadActor.prototype.requestTypes, {
  1.4926 +  "attach": AddonThreadActor.prototype.onAttach
  1.4927 +});
  1.4928 +
  1.4929 +/**
  1.4930 + * Manages the sources for a thread. Handles source maps, locations in the
  1.4931 + * sources, etc for ThreadActors.
  1.4932 + */
  1.4933 +function ThreadSources(aThreadActor, aUseSourceMaps, aAllowPredicate,
  1.4934 +                       aOnNewSource) {
  1.4935 +  this._thread = aThreadActor;
  1.4936 +  this._useSourceMaps = aUseSourceMaps;
  1.4937 +  this._allow = aAllowPredicate;
  1.4938 +  this._onNewSource = aOnNewSource;
  1.4939 +
  1.4940 +  // generated source url --> promise of SourceMapConsumer
  1.4941 +  this._sourceMapsByGeneratedSource = Object.create(null);
  1.4942 +  // original source url --> promise of SourceMapConsumer
  1.4943 +  this._sourceMapsByOriginalSource = Object.create(null);
  1.4944 +  // source url --> SourceActor
  1.4945 +  this._sourceActors = Object.create(null);
  1.4946 +  // original url --> generated url
  1.4947 +  this._generatedUrlsByOriginalUrl = Object.create(null);
  1.4948 +}
  1.4949 +
  1.4950 +/**
  1.4951 + * Must be a class property because it needs to persist across reloads, same as
  1.4952 + * the breakpoint store.
  1.4953 + */
  1.4954 +ThreadSources._blackBoxedSources = new Set(["self-hosted"]);
  1.4955 +ThreadSources._prettyPrintedSources = new Map();
  1.4956 +
  1.4957 +ThreadSources.prototype = {
  1.4958 +  /**
  1.4959 +   * Return the source actor representing |url|, creating one if none
  1.4960 +   * exists already. Returns null if |url| is not allowed by the 'allow'
  1.4961 +   * predicate.
  1.4962 +   *
  1.4963 +   * Right now this takes a URL, but in the future it should
  1.4964 +   * take a Debugger.Source. See bug 637572.
  1.4965 +   *
  1.4966 +   * @param String url
  1.4967 +   *        The source URL.
  1.4968 +   * @param optional SourceMapConsumer sourceMap
  1.4969 +   *        The source map that introduced this source, if any.
  1.4970 +   * @param optional String generatedSource
  1.4971 +   *        The generated source url that introduced this source via source map,
  1.4972 +   *        if any.
  1.4973 +   * @param optional String text
  1.4974 +   *        The text content of the source, if immediately available.
  1.4975 +   * @param optional String contentType
  1.4976 +   *        The content type of the source, if immediately available.
  1.4977 +   * @returns a SourceActor representing the source at aURL or null.
  1.4978 +   */
  1.4979 +  source: function ({ url, sourceMap, generatedSource, text, contentType }) {
  1.4980 +    if (!this._allow(url)) {
  1.4981 +      return null;
  1.4982 +    }
  1.4983 +
  1.4984 +    if (url in this._sourceActors) {
  1.4985 +      return this._sourceActors[url];
  1.4986 +    }
  1.4987 +
  1.4988 +    let actor = new SourceActor({
  1.4989 +      url: url,
  1.4990 +      thread: this._thread,
  1.4991 +      sourceMap: sourceMap,
  1.4992 +      generatedSource: generatedSource,
  1.4993 +      text: text,
  1.4994 +      contentType: contentType
  1.4995 +    });
  1.4996 +    this._thread.threadLifetimePool.addActor(actor);
  1.4997 +    this._sourceActors[url] = actor;
  1.4998 +    try {
  1.4999 +      this._onNewSource(actor);
  1.5000 +    } catch (e) {
  1.5001 +      reportError(e);
  1.5002 +    }
  1.5003 +    return actor;
  1.5004 +  },
  1.5005 +
  1.5006 +  /**
  1.5007 +   * Only to be used when we aren't source mapping.
  1.5008 +   */
  1.5009 +  _sourceForScript: function (aScript) {
  1.5010 +    const spec = {
  1.5011 +      url: aScript.url
  1.5012 +    };
  1.5013 +
  1.5014 +    // XXX bug 915433: We can't rely on Debugger.Source.prototype.text if the
  1.5015 +    // source is an HTML-embedded <script> tag. Since we don't have an API
  1.5016 +    // implemented to detect whether this is the case, we need to be
  1.5017 +    // conservative and only use Debugger.Source.prototype.text if we get a
  1.5018 +    // normal .js file.
  1.5019 +    if (aScript.url) {
  1.5020 +      try {
  1.5021 +        const url = Services.io.newURI(aScript.url, null, null)
  1.5022 +          .QueryInterface(Ci.nsIURL);
  1.5023 +        if (url.fileExtension === "js") {
  1.5024 +          spec.contentType = "text/javascript";
  1.5025 +          spec.text = aScript.source.text;
  1.5026 +        }
  1.5027 +      } catch(ex) {
  1.5028 +        // Not a valid URI.
  1.5029 +      }
  1.5030 +    }
  1.5031 +
  1.5032 +    return this.source(spec);
  1.5033 +  },
  1.5034 +
  1.5035 +  /**
  1.5036 +   * Return a promise of an array of source actors representing all the
  1.5037 +   * sources of |aScript|.
  1.5038 +   *
  1.5039 +   * If source map handling is enabled and |aScript| has a source map, then
  1.5040 +   * use it to find all of |aScript|'s *original* sources; return a promise
  1.5041 +   * of an array of source actors for those.
  1.5042 +   */
  1.5043 +  sourcesForScript: function (aScript) {
  1.5044 +    if (!this._useSourceMaps || !aScript.sourceMapURL) {
  1.5045 +      return resolve([this._sourceForScript(aScript)].filter(isNotNull));
  1.5046 +    }
  1.5047 +
  1.5048 +    return this.sourceMap(aScript)
  1.5049 +      .then((aSourceMap) => {
  1.5050 +        return [
  1.5051 +          this.source({ url: s,
  1.5052 +                        sourceMap: aSourceMap,
  1.5053 +                        generatedSource: aScript.url })
  1.5054 +          for (s of aSourceMap.sources)
  1.5055 +        ];
  1.5056 +      })
  1.5057 +      .then(null, (e) => {
  1.5058 +        reportError(e);
  1.5059 +        delete this._sourceMapsByGeneratedSource[aScript.url];
  1.5060 +        return [this._sourceForScript(aScript)];
  1.5061 +      })
  1.5062 +      .then(ss => ss.filter(isNotNull));
  1.5063 +  },
  1.5064 +
  1.5065 +  /**
  1.5066 +   * Return a promise of a SourceMapConsumer for the source map for
  1.5067 +   * |aScript|; if we already have such a promise extant, return that.
  1.5068 +   * |aScript| must have a non-null sourceMapURL.
  1.5069 +   */
  1.5070 +  sourceMap: function (aScript) {
  1.5071 +    dbg_assert(aScript.sourceMapURL, "Script should have a sourceMapURL");
  1.5072 +    let sourceMapURL = this._normalize(aScript.sourceMapURL, aScript.url);
  1.5073 +    let map = this._fetchSourceMap(sourceMapURL, aScript.url)
  1.5074 +      .then(aSourceMap => this.saveSourceMap(aSourceMap, aScript.url));
  1.5075 +    this._sourceMapsByGeneratedSource[aScript.url] = map;
  1.5076 +    return map;
  1.5077 +  },
  1.5078 +
  1.5079 +  /**
  1.5080 +   * Save the given source map so that we can use it to query source locations
  1.5081 +   * down the line.
  1.5082 +   */
  1.5083 +  saveSourceMap: function (aSourceMap, aGeneratedSource) {
  1.5084 +    if (!aSourceMap) {
  1.5085 +      delete this._sourceMapsByGeneratedSource[aGeneratedSource];
  1.5086 +      return null;
  1.5087 +    }
  1.5088 +    this._sourceMapsByGeneratedSource[aGeneratedSource] = resolve(aSourceMap);
  1.5089 +    for (let s of aSourceMap.sources) {
  1.5090 +      this._generatedUrlsByOriginalUrl[s] = aGeneratedSource;
  1.5091 +      this._sourceMapsByOriginalSource[s] = resolve(aSourceMap);
  1.5092 +    }
  1.5093 +    return aSourceMap;
  1.5094 +  },
  1.5095 +
  1.5096 +  /**
  1.5097 +   * Return a promise of a SourceMapConsumer for the source map located at
  1.5098 +   * |aAbsSourceMapURL|, which must be absolute. If there is already such a
  1.5099 +   * promise extant, return it.
  1.5100 +   *
  1.5101 +   * @param string aAbsSourceMapURL
  1.5102 +   *        The source map URL, in absolute form, not relative.
  1.5103 +   * @param string aScriptURL
  1.5104 +   *        When the source map URL is a data URI, there is no sourceRoot on the
  1.5105 +   *        source map, and the source map's sources are relative, we resolve
  1.5106 +   *        them from aScriptURL.
  1.5107 +   */
  1.5108 +  _fetchSourceMap: function (aAbsSourceMapURL, aScriptURL) {
  1.5109 +    return fetch(aAbsSourceMapURL, { loadFromCache: false })
  1.5110 +      .then(({ content }) => {
  1.5111 +        let map = new SourceMapConsumer(content);
  1.5112 +        this._setSourceMapRoot(map, aAbsSourceMapURL, aScriptURL);
  1.5113 +        return map;
  1.5114 +      });
  1.5115 +  },
  1.5116 +
  1.5117 +  /**
  1.5118 +   * Sets the source map's sourceRoot to be relative to the source map url.
  1.5119 +   */
  1.5120 +  _setSourceMapRoot: function (aSourceMap, aAbsSourceMapURL, aScriptURL) {
  1.5121 +    const base = this._dirname(
  1.5122 +      aAbsSourceMapURL.indexOf("data:") === 0
  1.5123 +        ? aScriptURL
  1.5124 +        : aAbsSourceMapURL);
  1.5125 +    aSourceMap.sourceRoot = aSourceMap.sourceRoot
  1.5126 +      ? this._normalize(aSourceMap.sourceRoot, base)
  1.5127 +      : base;
  1.5128 +  },
  1.5129 +
  1.5130 +  _dirname: function (aPath) {
  1.5131 +    return Services.io.newURI(
  1.5132 +      ".", null, Services.io.newURI(aPath, null, null)).spec;
  1.5133 +  },
  1.5134 +
  1.5135 +  /**
  1.5136 +   * Returns a promise of the location in the original source if the source is
  1.5137 +   * source mapped, otherwise a promise of the same location.
  1.5138 +   */
  1.5139 +  getOriginalLocation: function ({ url, line, column }) {
  1.5140 +    if (url in this._sourceMapsByGeneratedSource) {
  1.5141 +      column = column || 0;
  1.5142 +      
  1.5143 +      return this._sourceMapsByGeneratedSource[url]
  1.5144 +        .then((aSourceMap) => {
  1.5145 +          let { source: aSourceURL, line: aLine, column: aColumn } = aSourceMap.originalPositionFor({
  1.5146 +            line: line,
  1.5147 +            column: column
  1.5148 +          });
  1.5149 +          return {
  1.5150 +            url: aSourceURL,
  1.5151 +            line: aLine,
  1.5152 +            column: aColumn
  1.5153 +          };
  1.5154 +        })
  1.5155 +        .then(null, error => {
  1.5156 +          if (!DevToolsUtils.reportingDisabled) {
  1.5157 +            DevToolsUtils.reportException("ThreadSources.prototype.getOriginalLocation", error);
  1.5158 +          }
  1.5159 +          return { url: null, line: null, column: null };
  1.5160 +        });
  1.5161 +    }
  1.5162 +
  1.5163 +    // No source map
  1.5164 +    return resolve({
  1.5165 +      url: url,
  1.5166 +      line: line,
  1.5167 +      column: column
  1.5168 +    });
  1.5169 +  },
  1.5170 +
  1.5171 +  /**
  1.5172 +   * Returns a promise of the location in the generated source corresponding to
  1.5173 +   * the original source and line given.
  1.5174 +   *
  1.5175 +   * When we pass a script S representing generated code to |sourceMap|,
  1.5176 +   * above, that returns a promise P. The process of resolving P populates
  1.5177 +   * the tables this function uses; thus, it won't know that S's original
  1.5178 +   * source URLs map to S until P is resolved.
  1.5179 +   */
  1.5180 +  getGeneratedLocation: function ({ url, line, column }) {
  1.5181 +    if (url in this._sourceMapsByOriginalSource) {
  1.5182 +      return this._sourceMapsByOriginalSource[url]
  1.5183 +        .then((aSourceMap) => {
  1.5184 +          let { line: aLine, column: aColumn } = aSourceMap.generatedPositionFor({
  1.5185 +            source: url,
  1.5186 +            line: line,
  1.5187 +            column: column == null ? Infinity : column
  1.5188 +          });
  1.5189 +          return {
  1.5190 +            url: this._generatedUrlsByOriginalUrl[url],
  1.5191 +            line: aLine,
  1.5192 +            column: aColumn
  1.5193 +          };
  1.5194 +        });
  1.5195 +    }
  1.5196 +
  1.5197 +    // No source map
  1.5198 +    return resolve({
  1.5199 +      url: url,
  1.5200 +      line: line,
  1.5201 +      column: column
  1.5202 +    });
  1.5203 +  },
  1.5204 +
  1.5205 +  /**
  1.5206 +   * Returns true if URL for the given source is black boxed.
  1.5207 +   *
  1.5208 +   * @param aURL String
  1.5209 +   *        The URL of the source which we are checking whether it is black
  1.5210 +   *        boxed or not.
  1.5211 +   */
  1.5212 +  isBlackBoxed: function (aURL) {
  1.5213 +    return ThreadSources._blackBoxedSources.has(aURL);
  1.5214 +  },
  1.5215 +
  1.5216 +  /**
  1.5217 +   * Add the given source URL to the set of sources that are black boxed.
  1.5218 +   *
  1.5219 +   * @param aURL String
  1.5220 +   *        The URL of the source which we are black boxing.
  1.5221 +   */
  1.5222 +  blackBox: function (aURL) {
  1.5223 +    ThreadSources._blackBoxedSources.add(aURL);
  1.5224 +  },
  1.5225 +
  1.5226 +  /**
  1.5227 +   * Remove the given source URL to the set of sources that are black boxed.
  1.5228 +   *
  1.5229 +   * @param aURL String
  1.5230 +   *        The URL of the source which we are no longer black boxing.
  1.5231 +   */
  1.5232 +  unblackBox: function (aURL) {
  1.5233 +    ThreadSources._blackBoxedSources.delete(aURL);
  1.5234 +  },
  1.5235 +
  1.5236 +  /**
  1.5237 +   * Returns true if the given URL is pretty printed.
  1.5238 +   *
  1.5239 +   * @param aURL String
  1.5240 +   *        The URL of the source that might be pretty printed.
  1.5241 +   */
  1.5242 +  isPrettyPrinted: function (aURL) {
  1.5243 +    return ThreadSources._prettyPrintedSources.has(aURL);
  1.5244 +  },
  1.5245 +
  1.5246 +  /**
  1.5247 +   * Add the given URL to the set of sources that are pretty printed.
  1.5248 +   *
  1.5249 +   * @param aURL String
  1.5250 +   *        The URL of the source to be pretty printed.
  1.5251 +   */
  1.5252 +  prettyPrint: function (aURL, aIndent) {
  1.5253 +    ThreadSources._prettyPrintedSources.set(aURL, aIndent);
  1.5254 +  },
  1.5255 +
  1.5256 +  /**
  1.5257 +   * Return the indent the given URL was pretty printed by.
  1.5258 +   */
  1.5259 +  prettyPrintIndent: function (aURL) {
  1.5260 +    return ThreadSources._prettyPrintedSources.get(aURL);
  1.5261 +  },
  1.5262 +
  1.5263 +  /**
  1.5264 +   * Remove the given URL from the set of sources that are pretty printed.
  1.5265 +   *
  1.5266 +   * @param aURL String
  1.5267 +   *        The URL of the source that is no longer pretty printed.
  1.5268 +   */
  1.5269 +  disablePrettyPrint: function (aURL) {
  1.5270 +    ThreadSources._prettyPrintedSources.delete(aURL);
  1.5271 +  },
  1.5272 +
  1.5273 +  /**
  1.5274 +   * Normalize multiple relative paths towards the base paths on the right.
  1.5275 +   */
  1.5276 +  _normalize: function (...aURLs) {
  1.5277 +    dbg_assert(aURLs.length > 1, "Should have more than 1 URL");
  1.5278 +    let base = Services.io.newURI(aURLs.pop(), null, null);
  1.5279 +    let url;
  1.5280 +    while ((url = aURLs.pop())) {
  1.5281 +      base = Services.io.newURI(url, null, base);
  1.5282 +    }
  1.5283 +    return base.spec;
  1.5284 +  },
  1.5285 +
  1.5286 +  iter: function* () {
  1.5287 +    for (let url in this._sourceActors) {
  1.5288 +      yield this._sourceActors[url];
  1.5289 +    }
  1.5290 +  }
  1.5291 +};
  1.5292 +
  1.5293 +// Utility functions.
  1.5294 +
  1.5295 +// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
  1.5296 +// implemented.
  1.5297 +function getOffsetColumn(aOffset, aScript) {
  1.5298 +  let bestOffsetMapping = null;
  1.5299 +  for (let offsetMapping of aScript.getAllColumnOffsets()) {
  1.5300 +    if (!bestOffsetMapping ||
  1.5301 +        (offsetMapping.offset <= aOffset &&
  1.5302 +         offsetMapping.offset > bestOffsetMapping.offset)) {
  1.5303 +      bestOffsetMapping = offsetMapping;
  1.5304 +    }
  1.5305 +  }
  1.5306 +
  1.5307 +  if (!bestOffsetMapping) {
  1.5308 +    // XXX: Try not to completely break the experience of using the debugger for
  1.5309 +    // the user by assuming column 0. Simultaneously, report the error so that
  1.5310 +    // there is a paper trail if the assumption is bad and the debugging
  1.5311 +    // experience becomes wonky.
  1.5312 +    reportError(new Error("Could not find a column for offset " + aOffset
  1.5313 +                          + " in the script " + aScript));
  1.5314 +    return 0;
  1.5315 +  }
  1.5316 +
  1.5317 +  return bestOffsetMapping.columnNumber;
  1.5318 +}
  1.5319 +
  1.5320 +/**
  1.5321 + * Return the non-source-mapped location of the given Debugger.Frame. If the
  1.5322 + * frame does not have a script, the location's properties are all null.
  1.5323 + *
  1.5324 + * @param Debugger.Frame aFrame
  1.5325 + *        The frame whose location we are getting.
  1.5326 + * @returns Object
  1.5327 + *          Returns an object of the form { url, line, column }
  1.5328 + */
  1.5329 +function getFrameLocation(aFrame) {
  1.5330 +  if (!aFrame || !aFrame.script) {
  1.5331 +    return { url: null, line: null, column: null };
  1.5332 +  }
  1.5333 +  return {
  1.5334 +    url: aFrame.script.url,
  1.5335 +    line: aFrame.script.getOffsetLine(aFrame.offset),
  1.5336 +    column: getOffsetColumn(aFrame.offset, aFrame.script)
  1.5337 +  }
  1.5338 +}
  1.5339 +
  1.5340 +/**
  1.5341 + * Utility function for updating an object with the properties of another
  1.5342 + * object.
  1.5343 + *
  1.5344 + * @param aTarget Object
  1.5345 + *        The object being updated.
  1.5346 + * @param aNewAttrs Object
  1.5347 + *        The new attributes being set on the target.
  1.5348 + */
  1.5349 +function update(aTarget, aNewAttrs) {
  1.5350 +  for (let key in aNewAttrs) {
  1.5351 +    let desc = Object.getOwnPropertyDescriptor(aNewAttrs, key);
  1.5352 +
  1.5353 +    if (desc) {
  1.5354 +      Object.defineProperty(aTarget, key, desc);
  1.5355 +    }
  1.5356 +  }
  1.5357 +}
  1.5358 +
  1.5359 +/**
  1.5360 + * Returns true if its argument is not null.
  1.5361 + */
  1.5362 +function isNotNull(aThing) {
  1.5363 +  return aThing !== null;
  1.5364 +}
  1.5365 +
  1.5366 +/**
  1.5367 + * Performs a request to load the desired URL and returns a promise.
  1.5368 + *
  1.5369 + * @param aURL String
  1.5370 + *        The URL we will request.
  1.5371 + * @returns Promise
  1.5372 + *        A promise of the document at that URL, as a string.
  1.5373 + *
  1.5374 + * XXX: It may be better to use nsITraceableChannel to get to the sources
  1.5375 + * without relying on caching when we can (not for eval, etc.):
  1.5376 + * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
  1.5377 + */
  1.5378 +function fetch(aURL, aOptions={ loadFromCache: true }) {
  1.5379 +  let deferred = defer();
  1.5380 +  let scheme;
  1.5381 +  let url = aURL.split(" -> ").pop();
  1.5382 +  let charset;
  1.5383 +  let contentType;
  1.5384 +
  1.5385 +  try {
  1.5386 +    scheme = Services.io.extractScheme(url);
  1.5387 +  } catch (e) {
  1.5388 +    // In the xpcshell tests, the script url is the absolute path of the test
  1.5389 +    // file, which will make a malformed URI error be thrown. Add the file
  1.5390 +    // scheme prefix ourselves.
  1.5391 +    url = "file://" + url;
  1.5392 +    scheme = Services.io.extractScheme(url);
  1.5393 +  }
  1.5394 +
  1.5395 +  switch (scheme) {
  1.5396 +    case "file":
  1.5397 +    case "chrome":
  1.5398 +    case "resource":
  1.5399 +      try {
  1.5400 +        NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) {
  1.5401 +          if (!Components.isSuccessCode(aStatus)) {
  1.5402 +            deferred.reject(new Error("Request failed with status code = "
  1.5403 +                                      + aStatus
  1.5404 +                                      + " after NetUtil.asyncFetch for url = "
  1.5405 +                                      + url));
  1.5406 +            return;
  1.5407 +          }
  1.5408 +
  1.5409 +          let source = NetUtil.readInputStreamToString(aStream, aStream.available());
  1.5410 +          contentType = aRequest.contentType;
  1.5411 +          deferred.resolve(source);
  1.5412 +          aStream.close();
  1.5413 +        });
  1.5414 +      } catch (ex) {
  1.5415 +        deferred.reject(ex);
  1.5416 +      }
  1.5417 +      break;
  1.5418 +
  1.5419 +    default:
  1.5420 +      let channel;
  1.5421 +      try {
  1.5422 +        channel = Services.io.newChannel(url, null, null);
  1.5423 +      } catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
  1.5424 +        // On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
  1.5425 +        // newChannel won't be able to handle it.
  1.5426 +        url = "file:///" + url;
  1.5427 +        channel = Services.io.newChannel(url, null, null);
  1.5428 +      }
  1.5429 +      let chunks = [];
  1.5430 +      let streamListener = {
  1.5431 +        onStartRequest: function(aRequest, aContext, aStatusCode) {
  1.5432 +          if (!Components.isSuccessCode(aStatusCode)) {
  1.5433 +            deferred.reject(new Error("Request failed with status code = "
  1.5434 +                                      + aStatusCode
  1.5435 +                                      + " in onStartRequest handler for url = "
  1.5436 +                                      + url));
  1.5437 +          }
  1.5438 +        },
  1.5439 +        onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
  1.5440 +          chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
  1.5441 +        },
  1.5442 +        onStopRequest: function(aRequest, aContext, aStatusCode) {
  1.5443 +          if (!Components.isSuccessCode(aStatusCode)) {
  1.5444 +            deferred.reject(new Error("Request failed with status code = "
  1.5445 +                                      + aStatusCode
  1.5446 +                                      + " in onStopRequest handler for url = "
  1.5447 +                                      + url));
  1.5448 +            return;
  1.5449 +          }
  1.5450 +
  1.5451 +          charset = channel.contentCharset;
  1.5452 +          contentType = channel.contentType;
  1.5453 +          deferred.resolve(chunks.join(""));
  1.5454 +        }
  1.5455 +      };
  1.5456 +
  1.5457 +      channel.loadFlags = aOptions.loadFromCache
  1.5458 +        ? channel.LOAD_FROM_CACHE
  1.5459 +        : channel.LOAD_BYPASS_CACHE;
  1.5460 +      channel.asyncOpen(streamListener, null);
  1.5461 +      break;
  1.5462 +  }
  1.5463 +
  1.5464 +  return deferred.promise.then(source => {
  1.5465 +    return {
  1.5466 +      content: convertToUnicode(source, charset),
  1.5467 +      contentType: contentType
  1.5468 +    };
  1.5469 +  });
  1.5470 +}
  1.5471 +
  1.5472 +/**
  1.5473 + * Convert a given string, encoded in a given character set, to unicode.
  1.5474 + *
  1.5475 + * @param string aString
  1.5476 + *        A string.
  1.5477 + * @param string aCharset
  1.5478 + *        A character set.
  1.5479 + */
  1.5480 +function convertToUnicode(aString, aCharset=null) {
  1.5481 +  // Decoding primitives.
  1.5482 +  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  1.5483 +    .createInstance(Ci.nsIScriptableUnicodeConverter);
  1.5484 +  try {
  1.5485 +    converter.charset = aCharset || "UTF-8";
  1.5486 +    return converter.ConvertToUnicode(aString);
  1.5487 +  } catch(e) {
  1.5488 +    return aString;
  1.5489 +  }
  1.5490 +}
  1.5491 +
  1.5492 +/**
  1.5493 + * Report the given error in the error console and to stdout.
  1.5494 + *
  1.5495 + * @param Error aError
  1.5496 + *        The error object you wish to report.
  1.5497 + * @param String aPrefix
  1.5498 + *        An optional prefix for the reported error message.
  1.5499 + */
  1.5500 +function reportError(aError, aPrefix="") {
  1.5501 +  dbg_assert(aError instanceof Error, "Must pass Error objects to reportError");
  1.5502 +  let msg = aPrefix + aError.message + ":\n" + aError.stack;
  1.5503 +  Cu.reportError(msg);
  1.5504 +  dumpn(msg);
  1.5505 +}
  1.5506 +
  1.5507 +// The following are copied here verbatim from css-logic.js, until we create a
  1.5508 +// server-friendly helper module.
  1.5509 +
  1.5510 +/**
  1.5511 + * Find a unique CSS selector for a given element
  1.5512 + * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
  1.5513 + * and ele.ownerDocument.querySelectorAll(reply).length === 1
  1.5514 + */
  1.5515 +function findCssSelector(ele) {
  1.5516 +  var document = ele.ownerDocument;
  1.5517 +  if (ele.id && document.getElementById(ele.id) === ele) {
  1.5518 +    return '#' + ele.id;
  1.5519 +  }
  1.5520 +
  1.5521 +  // Inherently unique by tag name
  1.5522 +  var tagName = ele.tagName.toLowerCase();
  1.5523 +  if (tagName === 'html') {
  1.5524 +    return 'html';
  1.5525 +  }
  1.5526 +  if (tagName === 'head') {
  1.5527 +    return 'head';
  1.5528 +  }
  1.5529 +  if (tagName === 'body') {
  1.5530 +    return 'body';
  1.5531 +  }
  1.5532 +
  1.5533 +  if (ele.parentNode == null) {
  1.5534 +    console.log('danger: ' + tagName);
  1.5535 +  }
  1.5536 +
  1.5537 +  // We might be able to find a unique class name
  1.5538 +  var selector, index, matches;
  1.5539 +  if (ele.classList.length > 0) {
  1.5540 +    for (var i = 0; i < ele.classList.length; i++) {
  1.5541 +      // Is this className unique by itself?
  1.5542 +      selector = '.' + ele.classList.item(i);
  1.5543 +      matches = document.querySelectorAll(selector);
  1.5544 +      if (matches.length === 1) {
  1.5545 +        return selector;
  1.5546 +      }
  1.5547 +      // Maybe it's unique with a tag name?
  1.5548 +      selector = tagName + selector;
  1.5549 +      matches = document.querySelectorAll(selector);
  1.5550 +      if (matches.length === 1) {
  1.5551 +        return selector;
  1.5552 +      }
  1.5553 +      // Maybe it's unique using a tag name and nth-child
  1.5554 +      index = positionInNodeList(ele, ele.parentNode.children) + 1;
  1.5555 +      selector = selector + ':nth-child(' + index + ')';
  1.5556 +      matches = document.querySelectorAll(selector);
  1.5557 +      if (matches.length === 1) {
  1.5558 +        return selector;
  1.5559 +      }
  1.5560 +    }
  1.5561 +  }
  1.5562 +
  1.5563 +  // So we can be unique w.r.t. our parent, and use recursion
  1.5564 +  index = positionInNodeList(ele, ele.parentNode.children) + 1;
  1.5565 +  selector = findCssSelector(ele.parentNode) + ' > ' +
  1.5566 +          tagName + ':nth-child(' + index + ')';
  1.5567 +
  1.5568 +  return selector;
  1.5569 +};
  1.5570 +
  1.5571 +/**
  1.5572 + * Find the position of [element] in [nodeList].
  1.5573 + * @returns an index of the match, or -1 if there is no match
  1.5574 + */
  1.5575 +function positionInNodeList(element, nodeList) {
  1.5576 +  for (var i = 0; i < nodeList.length; i++) {
  1.5577 +    if (element === nodeList[i]) {
  1.5578 +      return i;
  1.5579 +    }
  1.5580 +  }
  1.5581 +  return -1;
  1.5582 +}
  1.5583 +
  1.5584 +/**
  1.5585 + * Make a debuggee value for the given object, if needed. Primitive values
  1.5586 + * are left the same.
  1.5587 + *
  1.5588 + * Use case: you have a raw JS object (after unsafe dereference) and you want to
  1.5589 + * send it to the client. In that case you need to use an ObjectActor which
  1.5590 + * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue()
  1.5591 + * method works only for JS objects and functions.
  1.5592 + *
  1.5593 + * @param Debugger.Object obj
  1.5594 + * @param any value
  1.5595 + * @return object
  1.5596 + */
  1.5597 +function makeDebuggeeValueIfNeeded(obj, value) {
  1.5598 +  if (value && (typeof value == "object" || typeof value == "function")) {
  1.5599 +    return obj.makeDebuggeeValue(value);
  1.5600 +  }
  1.5601 +  return value;
  1.5602 +}
  1.5603 +
  1.5604 +function getInnerId(window) {
  1.5605 +  return window.QueryInterface(Ci.nsIInterfaceRequestor).
  1.5606 +                getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  1.5607 +};

mercurial