toolkit/devtools/server/actors/tracer.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/server/actors/tracer.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,704 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const { Cu } = require("chrome");
    1.11 +const { DebuggerServer } = require("devtools/server/main");
    1.12 +const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
    1.13 +
    1.14 +Cu.import("resource://gre/modules/jsdebugger.jsm");
    1.15 +addDebuggerToGlobal(this);
    1.16 +
    1.17 +// TODO bug 943125: remove this polyfill and use Debugger.Frame.prototype.depth
    1.18 +// once it is implemented.
    1.19 +if (!Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, "depth")) {
    1.20 +  Debugger.Frame.prototype._depth = null;
    1.21 +  Object.defineProperty(Debugger.Frame.prototype, "depth", {
    1.22 +    get: function () {
    1.23 +      if (this._depth === null) {
    1.24 +        if (!this.older) {
    1.25 +          this._depth = 0;
    1.26 +        } else {
    1.27 +          // Hide depth from self-hosted frames.
    1.28 +          const increment = this.script && this.script.url == "self-hosted"
    1.29 +            ? 0
    1.30 +            : 1;
    1.31 +          this._depth = increment + this.older.depth;
    1.32 +        }
    1.33 +      }
    1.34 +
    1.35 +      return this._depth;
    1.36 +    }
    1.37 +  });
    1.38 +}
    1.39 +
    1.40 +const { setTimeout } = require("sdk/timers");
    1.41 +
    1.42 +/**
    1.43 + * The number of milliseconds we should buffer frame enter/exit packets before
    1.44 + * sending.
    1.45 + */
    1.46 +const BUFFER_SEND_DELAY = 50;
    1.47 +
    1.48 +/**
    1.49 + * The maximum number of arguments we will send for any single function call.
    1.50 + */
    1.51 +const MAX_ARGUMENTS = 3;
    1.52 +
    1.53 +/**
    1.54 + * The maximum number of an object's properties we will serialize.
    1.55 + */
    1.56 +const MAX_PROPERTIES = 3;
    1.57 +
    1.58 +/**
    1.59 + * The complete set of trace types supported.
    1.60 + */
    1.61 +const TRACE_TYPES = new Set([
    1.62 +  "time",
    1.63 +  "return",
    1.64 +  "throw",
    1.65 +  "yield",
    1.66 +  "name",
    1.67 +  "location",
    1.68 +  "callsite",
    1.69 +  "parameterNames",
    1.70 +  "arguments",
    1.71 +  "depth"
    1.72 +]);
    1.73 +
    1.74 +/**
    1.75 + * Creates a TraceActor. TraceActor provides a stream of function
    1.76 + * call/return packets to a remote client gathering a full trace.
    1.77 + */
    1.78 +function TraceActor(aConn, aParentActor)
    1.79 +{
    1.80 +  this._attached = false;
    1.81 +  this._activeTraces = new MapStack();
    1.82 +  this._totalTraces = 0;
    1.83 +  this._startTime = 0;
    1.84 +
    1.85 +  // Keep track of how many different trace requests have requested what kind of
    1.86 +  // tracing info. This way we can minimize the amount of data we are collecting
    1.87 +  // at any given time.
    1.88 +  this._requestsForTraceType = Object.create(null);
    1.89 +  for (let type of TRACE_TYPES) {
    1.90 +    this._requestsForTraceType[type] = 0;
    1.91 +  }
    1.92 +
    1.93 +  this._sequence = 0;
    1.94 +  this._bufferSendTimer = null;
    1.95 +  this._buffer = [];
    1.96 +  this.onExitFrame = this.onExitFrame.bind(this);
    1.97 +
    1.98 +  // aParentActor.window might be an Xray for a window, but it might also be a
    1.99 +  // double-wrapper for a Sandbox.  We want to unwrap the latter but not the
   1.100 +  // former.
   1.101 +  this.global = aParentActor.window;
   1.102 +  if (!Cu.isXrayWrapper(this.global)) {
   1.103 +      this.global = this.global.wrappedJSObject;
   1.104 +  }
   1.105 +}
   1.106 +
   1.107 +TraceActor.prototype = {
   1.108 +  actorPrefix: "trace",
   1.109 +
   1.110 +  get attached() { return this._attached; },
   1.111 +  get idle()     { return this._attached && this._activeTraces.size === 0; },
   1.112 +  get tracing()  { return this._attached && this._activeTraces.size > 0; },
   1.113 +
   1.114 +  /**
   1.115 +   * Buffer traces and only send them every BUFFER_SEND_DELAY milliseconds.
   1.116 +   */
   1.117 +  _send: function(aPacket) {
   1.118 +    this._buffer.push(aPacket);
   1.119 +    if (this._bufferSendTimer === null) {
   1.120 +      this._bufferSendTimer = setTimeout(() => {
   1.121 +        this.conn.send({
   1.122 +          from: this.actorID,
   1.123 +          type: "traces",
   1.124 +          traces: this._buffer.splice(0, this._buffer.length)
   1.125 +        });
   1.126 +        this._bufferSendTimer = null;
   1.127 +      }, BUFFER_SEND_DELAY);
   1.128 +    }
   1.129 +  },
   1.130 +
   1.131 +  /**
   1.132 +   * Initializes a Debugger instance and adds listeners to it.
   1.133 +   */
   1.134 +  _initDebugger: function() {
   1.135 +    this.dbg = new Debugger();
   1.136 +    this.dbg.onEnterFrame = this.onEnterFrame.bind(this);
   1.137 +    this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
   1.138 +    this.dbg.enabled = false;
   1.139 +  },
   1.140 +
   1.141 +  /**
   1.142 +   * Add a debuggee global to the Debugger object.
   1.143 +   */
   1.144 +  _addDebuggee: function(aGlobal) {
   1.145 +    try {
   1.146 +      this.dbg.addDebuggee(aGlobal);
   1.147 +    } catch (e) {
   1.148 +      // Ignore attempts to add the debugger's compartment as a debuggee.
   1.149 +      DevToolsUtils.reportException("TraceActor",
   1.150 +                      new Error("Ignoring request to add the debugger's "
   1.151 +                                + "compartment as a debuggee"));
   1.152 +    }
   1.153 +  },
   1.154 +
   1.155 +  /**
   1.156 +   * Add the provided window and all windows in its frame tree as debuggees.
   1.157 +   */
   1.158 +  _addDebuggees: function(aWindow) {
   1.159 +    this._addDebuggee(aWindow);
   1.160 +    let frames = aWindow.frames;
   1.161 +    if (frames) {
   1.162 +      for (let i = 0; i < frames.length; i++) {
   1.163 +        this._addDebuggees(frames[i]);
   1.164 +      }
   1.165 +    }
   1.166 +  },
   1.167 +
   1.168 +  /**
   1.169 +   * An object used by TraceActors to tailor their behavior depending
   1.170 +   * on the debugging context required (chrome or content).
   1.171 +   */
   1.172 +  globalManager: {
   1.173 +    /**
   1.174 +     * Adds all globals in the global object as debuggees.
   1.175 +     */
   1.176 +    findGlobals: function() {
   1.177 +      this._addDebuggees(this.global);
   1.178 +    },
   1.179 +
   1.180 +    /**
   1.181 +     * A function that the engine calls when a new global object has been
   1.182 +     * created. Adds the global object as a debuggee if it is in the content
   1.183 +     * window.
   1.184 +     *
   1.185 +     * @param aGlobal Debugger.Object
   1.186 +     *        The new global object that was created.
   1.187 +     */
   1.188 +    onNewGlobal: function(aGlobal) {
   1.189 +      // Content debugging only cares about new globals in the content
   1.190 +      // window, like iframe children.
   1.191 +      if (aGlobal.hostAnnotations &&
   1.192 +          aGlobal.hostAnnotations.type == "document" &&
   1.193 +          aGlobal.hostAnnotations.element === this.global) {
   1.194 +        this._addDebuggee(aGlobal);
   1.195 +      }
   1.196 +    },
   1.197 +  },
   1.198 +
   1.199 +  /**
   1.200 +   * Handle a protocol request to attach to the trace actor.
   1.201 +   *
   1.202 +   * @param aRequest object
   1.203 +   *        The protocol request object.
   1.204 +   */
   1.205 +  onAttach: function(aRequest) {
   1.206 +    if (this.attached) {
   1.207 +      return {
   1.208 +        error: "wrongState",
   1.209 +        message: "Already attached to a client"
   1.210 +      };
   1.211 +    }
   1.212 +
   1.213 +    if (!this.dbg) {
   1.214 +      this._initDebugger();
   1.215 +      this.globalManager.findGlobals.call(this);
   1.216 +    }
   1.217 +
   1.218 +    this._attached = true;
   1.219 +
   1.220 +    return {
   1.221 +      type: "attached",
   1.222 +      traceTypes: Object.keys(this._requestsForTraceType)
   1.223 +        .filter(k => !!this._requestsForTraceType[k])
   1.224 +    };
   1.225 +  },
   1.226 +
   1.227 +  /**
   1.228 +   * Handle a protocol request to detach from the trace actor.
   1.229 +   *
   1.230 +   * @param aRequest object
   1.231 +   *        The protocol request object.
   1.232 +   */
   1.233 +  onDetach: function() {
   1.234 +    while (this.tracing) {
   1.235 +      this.onStopTrace();
   1.236 +    }
   1.237 +
   1.238 +    this.dbg = null;
   1.239 +
   1.240 +    this._attached = false;
   1.241 +    return { type: "detached" };
   1.242 +  },
   1.243 +
   1.244 +  /**
   1.245 +   * Handle a protocol request to start a new trace.
   1.246 +   *
   1.247 +   * @param aRequest object
   1.248 +   *        The protocol request object.
   1.249 +   */
   1.250 +  onStartTrace: function(aRequest) {
   1.251 +    for (let traceType of aRequest.trace) {
   1.252 +      if (!TRACE_TYPES.has(traceType)) {
   1.253 +        return {
   1.254 +          error: "badParameterType",
   1.255 +          message: "No such trace type: " + traceType
   1.256 +        };
   1.257 +      }
   1.258 +    }
   1.259 +
   1.260 +    if (this.idle) {
   1.261 +      this.dbg.enabled = true;
   1.262 +      this._sequence = 0;
   1.263 +      this._startTime = Date.now();
   1.264 +    }
   1.265 +
   1.266 +    // Start recording all requested trace types.
   1.267 +    for (let traceType of aRequest.trace) {
   1.268 +      this._requestsForTraceType[traceType]++;
   1.269 +    }
   1.270 +
   1.271 +    this._totalTraces++;
   1.272 +    let name = aRequest.name || "Trace " + this._totalTraces;
   1.273 +    this._activeTraces.push(name, aRequest.trace);
   1.274 +
   1.275 +    return { type: "startedTrace", why: "requested", name: name };
   1.276 +  },
   1.277 +
   1.278 +  /**
   1.279 +   * Handle a protocol request to end a trace.
   1.280 +   *
   1.281 +   * @param aRequest object
   1.282 +   *        The protocol request object.
   1.283 +   */
   1.284 +  onStopTrace: function(aRequest) {
   1.285 +    if (!this.tracing) {
   1.286 +      return {
   1.287 +        error: "wrongState",
   1.288 +        message: "No active traces"
   1.289 +      };
   1.290 +    }
   1.291 +
   1.292 +    let stoppedTraceTypes, name;
   1.293 +    if (aRequest && aRequest.name) {
   1.294 +      name = aRequest.name;
   1.295 +      if (!this._activeTraces.has(name)) {
   1.296 +        return {
   1.297 +          error: "noSuchTrace",
   1.298 +          message: "No active trace with name: " + name
   1.299 +        };
   1.300 +      }
   1.301 +      stoppedTraceTypes = this._activeTraces.delete(name);
   1.302 +    } else {
   1.303 +      name = this._activeTraces.peekKey();
   1.304 +      stoppedTraceTypes = this._activeTraces.pop();
   1.305 +    }
   1.306 +
   1.307 +    for (let traceType of stoppedTraceTypes) {
   1.308 +      this._requestsForTraceType[traceType]--;
   1.309 +    }
   1.310 +
   1.311 +    if (this.idle) {
   1.312 +      this.dbg.enabled = false;
   1.313 +    }
   1.314 +
   1.315 +    return { type: "stoppedTrace", why: "requested", name: name };
   1.316 +  },
   1.317 +
   1.318 +  // JS Debugger API hooks.
   1.319 +
   1.320 +  /**
   1.321 +   * Called by the engine when a frame is entered. Sends an unsolicited packet
   1.322 +   * to the client carrying requested trace information.
   1.323 +   *
   1.324 +   * @param aFrame Debugger.frame
   1.325 +   *        The stack frame that was entered.
   1.326 +   */
   1.327 +  onEnterFrame: function(aFrame) {
   1.328 +    if (aFrame.script && aFrame.script.url == "self-hosted") {
   1.329 +      return;
   1.330 +    }
   1.331 +
   1.332 +    let packet = {
   1.333 +      type: "enteredFrame",
   1.334 +      sequence: this._sequence++
   1.335 +    };
   1.336 +
   1.337 +    if (this._requestsForTraceType.name) {
   1.338 +      packet.name = aFrame.callee
   1.339 +        ? aFrame.callee.displayName || "(anonymous function)"
   1.340 +        : "(" + aFrame.type + ")";
   1.341 +    }
   1.342 +
   1.343 +    if (this._requestsForTraceType.location && aFrame.script) {
   1.344 +      // We should return the location of the start of the script, but
   1.345 +      // Debugger.Script does not provide complete start locations (bug
   1.346 +      // 901138). Instead, return the current offset (the location of the first
   1.347 +      // statement in the function).
   1.348 +      packet.location = {
   1.349 +        url: aFrame.script.url,
   1.350 +        line: aFrame.script.getOffsetLine(aFrame.offset),
   1.351 +        column: getOffsetColumn(aFrame.offset, aFrame.script)
   1.352 +      };
   1.353 +    }
   1.354 +
   1.355 +    if (this._requestsForTraceType.callsite
   1.356 +        && aFrame.older
   1.357 +        && aFrame.older.script) {
   1.358 +      let older = aFrame.older;
   1.359 +      packet.callsite = {
   1.360 +        url: older.script.url,
   1.361 +        line: older.script.getOffsetLine(older.offset),
   1.362 +        column: getOffsetColumn(older.offset, older.script)
   1.363 +      };
   1.364 +    }
   1.365 +
   1.366 +    if (this._requestsForTraceType.time) {
   1.367 +      packet.time = Date.now() - this._startTime;
   1.368 +    }
   1.369 +
   1.370 +    if (this._requestsForTraceType.parameterNames && aFrame.callee) {
   1.371 +      packet.parameterNames = aFrame.callee.parameterNames;
   1.372 +    }
   1.373 +
   1.374 +    if (this._requestsForTraceType.arguments && aFrame.arguments) {
   1.375 +      packet.arguments = [];
   1.376 +      let i = 0;
   1.377 +      for (let arg of aFrame.arguments) {
   1.378 +        if (i++ > MAX_ARGUMENTS) {
   1.379 +          break;
   1.380 +        }
   1.381 +        packet.arguments.push(createValueSnapshot(arg, true));
   1.382 +      }
   1.383 +    }
   1.384 +
   1.385 +    if (this._requestsForTraceType.depth) {
   1.386 +      packet.depth = aFrame.depth;
   1.387 +    }
   1.388 +
   1.389 +    const onExitFrame = this.onExitFrame;
   1.390 +    aFrame.onPop = function (aCompletion) {
   1.391 +      onExitFrame(this, aCompletion);
   1.392 +    };
   1.393 +
   1.394 +    this._send(packet);
   1.395 +  },
   1.396 +
   1.397 +  /**
   1.398 +   * Called by the engine when a frame is exited. Sends an unsolicited packet to
   1.399 +   * the client carrying requested trace information.
   1.400 +   *
   1.401 +   * @param Debugger.Frame aFrame
   1.402 +   *        The Debugger.Frame that was just exited.
   1.403 +   * @param aCompletion object
   1.404 +   *        The debugger completion value for the frame.
   1.405 +   */
   1.406 +  onExitFrame: function(aFrame, aCompletion) {
   1.407 +    let packet = {
   1.408 +      type: "exitedFrame",
   1.409 +      sequence: this._sequence++,
   1.410 +    };
   1.411 +
   1.412 +    if (!aCompletion) {
   1.413 +      packet.why = "terminated";
   1.414 +    } else if (aCompletion.hasOwnProperty("return")) {
   1.415 +      packet.why = "return";
   1.416 +    } else if (aCompletion.hasOwnProperty("yield")) {
   1.417 +      packet.why = "yield";
   1.418 +    } else {
   1.419 +      packet.why = "throw";
   1.420 +    }
   1.421 +
   1.422 +    if (this._requestsForTraceType.time) {
   1.423 +      packet.time = Date.now() - this._startTime;
   1.424 +    }
   1.425 +
   1.426 +    if (this._requestsForTraceType.depth) {
   1.427 +      packet.depth = aFrame.depth;
   1.428 +    }
   1.429 +
   1.430 +    if (aCompletion) {
   1.431 +      if (this._requestsForTraceType.return && "return" in aCompletion) {
   1.432 +        packet.return = createValueSnapshot(aCompletion.return, true);
   1.433 +      }
   1.434 +
   1.435 +      else if (this._requestsForTraceType.throw && "throw" in aCompletion) {
   1.436 +        packet.throw = createValueSnapshot(aCompletion.throw, true);
   1.437 +      }
   1.438 +
   1.439 +      else if (this._requestsForTraceType.yield && "yield" in aCompletion) {
   1.440 +        packet.yield = createValueSnapshot(aCompletion.yield, true);
   1.441 +      }
   1.442 +    }
   1.443 +
   1.444 +    this._send(packet);
   1.445 +  }
   1.446 +};
   1.447 +
   1.448 +/**
   1.449 + * The request types this actor can handle.
   1.450 + */
   1.451 +TraceActor.prototype.requestTypes = {
   1.452 +  "attach": TraceActor.prototype.onAttach,
   1.453 +  "detach": TraceActor.prototype.onDetach,
   1.454 +  "startTrace": TraceActor.prototype.onStartTrace,
   1.455 +  "stopTrace": TraceActor.prototype.onStopTrace
   1.456 +};
   1.457 +
   1.458 +exports.register = function(handle) {
   1.459 +  handle.addTabActor(TraceActor, "traceActor");
   1.460 +};
   1.461 +
   1.462 +exports.unregister = function(handle) {
   1.463 +  handle.removeTabActor(TraceActor, "traceActor");
   1.464 +};
   1.465 +
   1.466 +
   1.467 +/**
   1.468 + * MapStack is a collection of key/value pairs with stack ordering,
   1.469 + * where keys are strings and values are any JS value. In addition to
   1.470 + * the push and pop stack operations, supports a "delete" operation,
   1.471 + * which removes the value associated with a given key from any
   1.472 + * location in the stack.
   1.473 + */
   1.474 +function MapStack()
   1.475 +{
   1.476 +  // Essentially a MapStack is just sugar-coating around a standard JS
   1.477 +  // object, plus the _stack array to track ordering.
   1.478 +  this._stack = [];
   1.479 +  this._map = Object.create(null);
   1.480 +}
   1.481 +
   1.482 +MapStack.prototype = {
   1.483 +  get size() { return this._stack.length; },
   1.484 +
   1.485 +  /**
   1.486 +   * Return the key for the value on the top of the stack, or
   1.487 +   * undefined if the stack is empty.
   1.488 +   */
   1.489 +  peekKey: function() {
   1.490 +    return this._stack[this.size - 1];
   1.491 +  },
   1.492 +
   1.493 +  /**
   1.494 +   * Return true iff a value has been associated with the given key.
   1.495 +   *
   1.496 +   * @param aKey string
   1.497 +   *        The key whose presence is to be tested.
   1.498 +   */
   1.499 +  has: function(aKey) {
   1.500 +    return Object.prototype.hasOwnProperty.call(this._map, aKey);
   1.501 +  },
   1.502 +
   1.503 +  /**
   1.504 +   * Return the value associated with the given key, or undefined if
   1.505 +   * no value is associated with the key.
   1.506 +   *
   1.507 +   * @param aKey string
   1.508 +   *        The key whose associated value is to be returned.
   1.509 +   */
   1.510 +  get: function(aKey) {
   1.511 +    return this._map[aKey];
   1.512 +  },
   1.513 +
   1.514 +  /**
   1.515 +   * Push a new value onto the stack. If another value with the same
   1.516 +   * key is already on the stack, it will be removed before the new
   1.517 +   * value is pushed onto the top of the stack.
   1.518 +   *
   1.519 +   * @param aKey string
   1.520 +   *        The key of the object to push onto the stack.
   1.521 +   *
   1.522 +   * @param aValue
   1.523 +   *        The value to push onto the stack.
   1.524 +   */
   1.525 +  push: function(aKey, aValue) {
   1.526 +    this.delete(aKey);
   1.527 +    this._stack.push(aKey);
   1.528 +    this._map[aKey] = aValue;
   1.529 +  },
   1.530 +
   1.531 +  /**
   1.532 +   * Remove the value from the top of the stack and return it.
   1.533 +   * Returns undefined if the stack is empty.
   1.534 +   */
   1.535 +  pop: function() {
   1.536 +    let key = this.peekKey();
   1.537 +    let value = this.get(key);
   1.538 +    this._stack.pop();
   1.539 +    delete this._map[key];
   1.540 +    return value;
   1.541 +  },
   1.542 +
   1.543 +  /**
   1.544 +   * Remove the value associated with the given key from the stack and
   1.545 +   * return it. Returns undefined if no value is associated with the
   1.546 +   * given key.
   1.547 +   *
   1.548 +   * @param aKey string
   1.549 +   *        The key for the value to remove from the stack.
   1.550 +   */
   1.551 +  delete: function(aKey) {
   1.552 +    let value = this.get(aKey);
   1.553 +    if (this.has(aKey)) {
   1.554 +      let keyIndex = this._stack.lastIndexOf(aKey);
   1.555 +      this._stack.splice(keyIndex, 1);
   1.556 +      delete this._map[aKey];
   1.557 +    }
   1.558 +    return value;
   1.559 +  }
   1.560 +};
   1.561 +
   1.562 +// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when
   1.563 +// it is implemented.
   1.564 +function getOffsetColumn(aOffset, aScript) {
   1.565 +  return 0;
   1.566 +}
   1.567 +
   1.568 +// Serialization helper functions. Largely copied from script.js and modified
   1.569 +// for use in serialization rather than object actor requests.
   1.570 +
   1.571 +/**
   1.572 + * Create a grip for the given debuggee value.
   1.573 + *
   1.574 + * @param aValue Debugger.Object|primitive
   1.575 + *        The value to describe with the created grip.
   1.576 + *
   1.577 + * @param aDetailed boolean
   1.578 + *        If true, capture slightly more detailed information, like some
   1.579 + *        properties on an object.
   1.580 + *
   1.581 + * @return Object
   1.582 + *         A primitive value or a snapshot of an object.
   1.583 + */
   1.584 +function createValueSnapshot(aValue, aDetailed=false) {
   1.585 +  switch (typeof aValue) {
   1.586 +    case "boolean":
   1.587 +      return aValue;
   1.588 +    case "string":
   1.589 +      if (aValue.length >= DebuggerServer.LONG_STRING_LENGTH) {
   1.590 +        return {
   1.591 +          type: "longString",
   1.592 +          initial: aValue.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
   1.593 +          length: aValue.length
   1.594 +        };
   1.595 +      }
   1.596 +      return aValue;
   1.597 +    case "number":
   1.598 +      if (aValue === Infinity) {
   1.599 +        return { type: "Infinity" };
   1.600 +      } else if (aValue === -Infinity) {
   1.601 +        return { type: "-Infinity" };
   1.602 +      } else if (Number.isNaN(aValue)) {
   1.603 +        return { type: "NaN" };
   1.604 +      } else if (!aValue && 1 / aValue === -Infinity) {
   1.605 +        return { type: "-0" };
   1.606 +      }
   1.607 +      return aValue;
   1.608 +    case "undefined":
   1.609 +      return { type: "undefined" };
   1.610 +    case "object":
   1.611 +      if (aValue === null) {
   1.612 +        return { type: "null" };
   1.613 +      }
   1.614 +      return aDetailed
   1.615 +        ? detailedObjectSnapshot(aValue)
   1.616 +        : objectSnapshot(aValue);
   1.617 +    default:
   1.618 +      DevToolsUtils.reportException("TraceActor",
   1.619 +                      new Error("Failed to provide a grip for: " + aValue));
   1.620 +      return null;
   1.621 +  }
   1.622 +}
   1.623 +
   1.624 +/**
   1.625 + * Create a very minimal snapshot of the given debuggee object.
   1.626 + *
   1.627 + * @param aObject Debugger.Object
   1.628 + *        The object to describe with the created grip.
   1.629 + */
   1.630 +function objectSnapshot(aObject) {
   1.631 +  return {
   1.632 +    "type": "object",
   1.633 +    "class": aObject.class,
   1.634 +  };
   1.635 +}
   1.636 +
   1.637 +/**
   1.638 + * Create a (slightly more) detailed snapshot of the given debuggee object.
   1.639 + *
   1.640 + * @param aObject Debugger.Object
   1.641 + *        The object to describe with the created descriptor.
   1.642 + */
   1.643 +function detailedObjectSnapshot(aObject) {
   1.644 +  let desc = objectSnapshot(aObject);
   1.645 +  let ownProperties = desc.ownProperties = Object.create(null);
   1.646 +
   1.647 +  if (aObject.class == "DeadObject") {
   1.648 +    return desc;
   1.649 +  }
   1.650 +
   1.651 +  let i = 0;
   1.652 +  for (let name of aObject.getOwnPropertyNames()) {
   1.653 +    if (i++ > MAX_PROPERTIES) {
   1.654 +      break;
   1.655 +    }
   1.656 +    let desc = propertySnapshot(name, aObject);
   1.657 +    if (desc) {
   1.658 +      ownProperties[name] = desc;
   1.659 +    }
   1.660 +  }
   1.661 +
   1.662 +  return desc;
   1.663 +}
   1.664 +
   1.665 +/**
   1.666 + * A helper method that creates a snapshot of the object's |aName| property.
   1.667 + *
   1.668 + * @param aName string
   1.669 + *        The property of which the snapshot is taken.
   1.670 + *
   1.671 + * @param aObject Debugger.Object
   1.672 + *        The object whose property the snapshot is taken of.
   1.673 + *
   1.674 + * @return Object
   1.675 + *         The snapshot of the property.
   1.676 + */
   1.677 +function propertySnapshot(aName, aObject) {
   1.678 +  let desc;
   1.679 +  try {
   1.680 +    desc = aObject.getOwnPropertyDescriptor(aName);
   1.681 +  } catch (e) {
   1.682 +    // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
   1.683 +    // allowed (bug 560072). Inform the user with a bogus, but hopefully
   1.684 +    // explanatory, descriptor.
   1.685 +    return {
   1.686 +      configurable: false,
   1.687 +      writable: false,
   1.688 +      enumerable: false,
   1.689 +      value: e.name
   1.690 +    };
   1.691 +  }
   1.692 +
   1.693 +  // Only create descriptors for simple values. We skip objects and properties
   1.694 +  // that have getters and setters; ain't nobody got time for that!
   1.695 +  if (!desc
   1.696 +      || typeof desc.value == "object" && desc.value !== null
   1.697 +      || !("value" in desc)) {
   1.698 +    return undefined;
   1.699 +  }
   1.700 +
   1.701 +  return {
   1.702 +    configurable: desc.configurable,
   1.703 +    enumerable: desc.enumerable,
   1.704 +    writable: desc.writable,
   1.705 +    value: createValueSnapshot(desc.value)
   1.706 +  };
   1.707 +}

mercurial