toolkit/devtools/server/actors/tracer.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const { Cu } = require("chrome");
michael@0 8 const { DebuggerServer } = require("devtools/server/main");
michael@0 9 const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/jsdebugger.jsm");
michael@0 12 addDebuggerToGlobal(this);
michael@0 13
michael@0 14 // TODO bug 943125: remove this polyfill and use Debugger.Frame.prototype.depth
michael@0 15 // once it is implemented.
michael@0 16 if (!Object.getOwnPropertyDescriptor(Debugger.Frame.prototype, "depth")) {
michael@0 17 Debugger.Frame.prototype._depth = null;
michael@0 18 Object.defineProperty(Debugger.Frame.prototype, "depth", {
michael@0 19 get: function () {
michael@0 20 if (this._depth === null) {
michael@0 21 if (!this.older) {
michael@0 22 this._depth = 0;
michael@0 23 } else {
michael@0 24 // Hide depth from self-hosted frames.
michael@0 25 const increment = this.script && this.script.url == "self-hosted"
michael@0 26 ? 0
michael@0 27 : 1;
michael@0 28 this._depth = increment + this.older.depth;
michael@0 29 }
michael@0 30 }
michael@0 31
michael@0 32 return this._depth;
michael@0 33 }
michael@0 34 });
michael@0 35 }
michael@0 36
michael@0 37 const { setTimeout } = require("sdk/timers");
michael@0 38
michael@0 39 /**
michael@0 40 * The number of milliseconds we should buffer frame enter/exit packets before
michael@0 41 * sending.
michael@0 42 */
michael@0 43 const BUFFER_SEND_DELAY = 50;
michael@0 44
michael@0 45 /**
michael@0 46 * The maximum number of arguments we will send for any single function call.
michael@0 47 */
michael@0 48 const MAX_ARGUMENTS = 3;
michael@0 49
michael@0 50 /**
michael@0 51 * The maximum number of an object's properties we will serialize.
michael@0 52 */
michael@0 53 const MAX_PROPERTIES = 3;
michael@0 54
michael@0 55 /**
michael@0 56 * The complete set of trace types supported.
michael@0 57 */
michael@0 58 const TRACE_TYPES = new Set([
michael@0 59 "time",
michael@0 60 "return",
michael@0 61 "throw",
michael@0 62 "yield",
michael@0 63 "name",
michael@0 64 "location",
michael@0 65 "callsite",
michael@0 66 "parameterNames",
michael@0 67 "arguments",
michael@0 68 "depth"
michael@0 69 ]);
michael@0 70
michael@0 71 /**
michael@0 72 * Creates a TraceActor. TraceActor provides a stream of function
michael@0 73 * call/return packets to a remote client gathering a full trace.
michael@0 74 */
michael@0 75 function TraceActor(aConn, aParentActor)
michael@0 76 {
michael@0 77 this._attached = false;
michael@0 78 this._activeTraces = new MapStack();
michael@0 79 this._totalTraces = 0;
michael@0 80 this._startTime = 0;
michael@0 81
michael@0 82 // Keep track of how many different trace requests have requested what kind of
michael@0 83 // tracing info. This way we can minimize the amount of data we are collecting
michael@0 84 // at any given time.
michael@0 85 this._requestsForTraceType = Object.create(null);
michael@0 86 for (let type of TRACE_TYPES) {
michael@0 87 this._requestsForTraceType[type] = 0;
michael@0 88 }
michael@0 89
michael@0 90 this._sequence = 0;
michael@0 91 this._bufferSendTimer = null;
michael@0 92 this._buffer = [];
michael@0 93 this.onExitFrame = this.onExitFrame.bind(this);
michael@0 94
michael@0 95 // aParentActor.window might be an Xray for a window, but it might also be a
michael@0 96 // double-wrapper for a Sandbox. We want to unwrap the latter but not the
michael@0 97 // former.
michael@0 98 this.global = aParentActor.window;
michael@0 99 if (!Cu.isXrayWrapper(this.global)) {
michael@0 100 this.global = this.global.wrappedJSObject;
michael@0 101 }
michael@0 102 }
michael@0 103
michael@0 104 TraceActor.prototype = {
michael@0 105 actorPrefix: "trace",
michael@0 106
michael@0 107 get attached() { return this._attached; },
michael@0 108 get idle() { return this._attached && this._activeTraces.size === 0; },
michael@0 109 get tracing() { return this._attached && this._activeTraces.size > 0; },
michael@0 110
michael@0 111 /**
michael@0 112 * Buffer traces and only send them every BUFFER_SEND_DELAY milliseconds.
michael@0 113 */
michael@0 114 _send: function(aPacket) {
michael@0 115 this._buffer.push(aPacket);
michael@0 116 if (this._bufferSendTimer === null) {
michael@0 117 this._bufferSendTimer = setTimeout(() => {
michael@0 118 this.conn.send({
michael@0 119 from: this.actorID,
michael@0 120 type: "traces",
michael@0 121 traces: this._buffer.splice(0, this._buffer.length)
michael@0 122 });
michael@0 123 this._bufferSendTimer = null;
michael@0 124 }, BUFFER_SEND_DELAY);
michael@0 125 }
michael@0 126 },
michael@0 127
michael@0 128 /**
michael@0 129 * Initializes a Debugger instance and adds listeners to it.
michael@0 130 */
michael@0 131 _initDebugger: function() {
michael@0 132 this.dbg = new Debugger();
michael@0 133 this.dbg.onEnterFrame = this.onEnterFrame.bind(this);
michael@0 134 this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
michael@0 135 this.dbg.enabled = false;
michael@0 136 },
michael@0 137
michael@0 138 /**
michael@0 139 * Add a debuggee global to the Debugger object.
michael@0 140 */
michael@0 141 _addDebuggee: function(aGlobal) {
michael@0 142 try {
michael@0 143 this.dbg.addDebuggee(aGlobal);
michael@0 144 } catch (e) {
michael@0 145 // Ignore attempts to add the debugger's compartment as a debuggee.
michael@0 146 DevToolsUtils.reportException("TraceActor",
michael@0 147 new Error("Ignoring request to add the debugger's "
michael@0 148 + "compartment as a debuggee"));
michael@0 149 }
michael@0 150 },
michael@0 151
michael@0 152 /**
michael@0 153 * Add the provided window and all windows in its frame tree as debuggees.
michael@0 154 */
michael@0 155 _addDebuggees: function(aWindow) {
michael@0 156 this._addDebuggee(aWindow);
michael@0 157 let frames = aWindow.frames;
michael@0 158 if (frames) {
michael@0 159 for (let i = 0; i < frames.length; i++) {
michael@0 160 this._addDebuggees(frames[i]);
michael@0 161 }
michael@0 162 }
michael@0 163 },
michael@0 164
michael@0 165 /**
michael@0 166 * An object used by TraceActors to tailor their behavior depending
michael@0 167 * on the debugging context required (chrome or content).
michael@0 168 */
michael@0 169 globalManager: {
michael@0 170 /**
michael@0 171 * Adds all globals in the global object as debuggees.
michael@0 172 */
michael@0 173 findGlobals: function() {
michael@0 174 this._addDebuggees(this.global);
michael@0 175 },
michael@0 176
michael@0 177 /**
michael@0 178 * A function that the engine calls when a new global object has been
michael@0 179 * created. Adds the global object as a debuggee if it is in the content
michael@0 180 * window.
michael@0 181 *
michael@0 182 * @param aGlobal Debugger.Object
michael@0 183 * The new global object that was created.
michael@0 184 */
michael@0 185 onNewGlobal: function(aGlobal) {
michael@0 186 // Content debugging only cares about new globals in the content
michael@0 187 // window, like iframe children.
michael@0 188 if (aGlobal.hostAnnotations &&
michael@0 189 aGlobal.hostAnnotations.type == "document" &&
michael@0 190 aGlobal.hostAnnotations.element === this.global) {
michael@0 191 this._addDebuggee(aGlobal);
michael@0 192 }
michael@0 193 },
michael@0 194 },
michael@0 195
michael@0 196 /**
michael@0 197 * Handle a protocol request to attach to the trace actor.
michael@0 198 *
michael@0 199 * @param aRequest object
michael@0 200 * The protocol request object.
michael@0 201 */
michael@0 202 onAttach: function(aRequest) {
michael@0 203 if (this.attached) {
michael@0 204 return {
michael@0 205 error: "wrongState",
michael@0 206 message: "Already attached to a client"
michael@0 207 };
michael@0 208 }
michael@0 209
michael@0 210 if (!this.dbg) {
michael@0 211 this._initDebugger();
michael@0 212 this.globalManager.findGlobals.call(this);
michael@0 213 }
michael@0 214
michael@0 215 this._attached = true;
michael@0 216
michael@0 217 return {
michael@0 218 type: "attached",
michael@0 219 traceTypes: Object.keys(this._requestsForTraceType)
michael@0 220 .filter(k => !!this._requestsForTraceType[k])
michael@0 221 };
michael@0 222 },
michael@0 223
michael@0 224 /**
michael@0 225 * Handle a protocol request to detach from the trace actor.
michael@0 226 *
michael@0 227 * @param aRequest object
michael@0 228 * The protocol request object.
michael@0 229 */
michael@0 230 onDetach: function() {
michael@0 231 while (this.tracing) {
michael@0 232 this.onStopTrace();
michael@0 233 }
michael@0 234
michael@0 235 this.dbg = null;
michael@0 236
michael@0 237 this._attached = false;
michael@0 238 return { type: "detached" };
michael@0 239 },
michael@0 240
michael@0 241 /**
michael@0 242 * Handle a protocol request to start a new trace.
michael@0 243 *
michael@0 244 * @param aRequest object
michael@0 245 * The protocol request object.
michael@0 246 */
michael@0 247 onStartTrace: function(aRequest) {
michael@0 248 for (let traceType of aRequest.trace) {
michael@0 249 if (!TRACE_TYPES.has(traceType)) {
michael@0 250 return {
michael@0 251 error: "badParameterType",
michael@0 252 message: "No such trace type: " + traceType
michael@0 253 };
michael@0 254 }
michael@0 255 }
michael@0 256
michael@0 257 if (this.idle) {
michael@0 258 this.dbg.enabled = true;
michael@0 259 this._sequence = 0;
michael@0 260 this._startTime = Date.now();
michael@0 261 }
michael@0 262
michael@0 263 // Start recording all requested trace types.
michael@0 264 for (let traceType of aRequest.trace) {
michael@0 265 this._requestsForTraceType[traceType]++;
michael@0 266 }
michael@0 267
michael@0 268 this._totalTraces++;
michael@0 269 let name = aRequest.name || "Trace " + this._totalTraces;
michael@0 270 this._activeTraces.push(name, aRequest.trace);
michael@0 271
michael@0 272 return { type: "startedTrace", why: "requested", name: name };
michael@0 273 },
michael@0 274
michael@0 275 /**
michael@0 276 * Handle a protocol request to end a trace.
michael@0 277 *
michael@0 278 * @param aRequest object
michael@0 279 * The protocol request object.
michael@0 280 */
michael@0 281 onStopTrace: function(aRequest) {
michael@0 282 if (!this.tracing) {
michael@0 283 return {
michael@0 284 error: "wrongState",
michael@0 285 message: "No active traces"
michael@0 286 };
michael@0 287 }
michael@0 288
michael@0 289 let stoppedTraceTypes, name;
michael@0 290 if (aRequest && aRequest.name) {
michael@0 291 name = aRequest.name;
michael@0 292 if (!this._activeTraces.has(name)) {
michael@0 293 return {
michael@0 294 error: "noSuchTrace",
michael@0 295 message: "No active trace with name: " + name
michael@0 296 };
michael@0 297 }
michael@0 298 stoppedTraceTypes = this._activeTraces.delete(name);
michael@0 299 } else {
michael@0 300 name = this._activeTraces.peekKey();
michael@0 301 stoppedTraceTypes = this._activeTraces.pop();
michael@0 302 }
michael@0 303
michael@0 304 for (let traceType of stoppedTraceTypes) {
michael@0 305 this._requestsForTraceType[traceType]--;
michael@0 306 }
michael@0 307
michael@0 308 if (this.idle) {
michael@0 309 this.dbg.enabled = false;
michael@0 310 }
michael@0 311
michael@0 312 return { type: "stoppedTrace", why: "requested", name: name };
michael@0 313 },
michael@0 314
michael@0 315 // JS Debugger API hooks.
michael@0 316
michael@0 317 /**
michael@0 318 * Called by the engine when a frame is entered. Sends an unsolicited packet
michael@0 319 * to the client carrying requested trace information.
michael@0 320 *
michael@0 321 * @param aFrame Debugger.frame
michael@0 322 * The stack frame that was entered.
michael@0 323 */
michael@0 324 onEnterFrame: function(aFrame) {
michael@0 325 if (aFrame.script && aFrame.script.url == "self-hosted") {
michael@0 326 return;
michael@0 327 }
michael@0 328
michael@0 329 let packet = {
michael@0 330 type: "enteredFrame",
michael@0 331 sequence: this._sequence++
michael@0 332 };
michael@0 333
michael@0 334 if (this._requestsForTraceType.name) {
michael@0 335 packet.name = aFrame.callee
michael@0 336 ? aFrame.callee.displayName || "(anonymous function)"
michael@0 337 : "(" + aFrame.type + ")";
michael@0 338 }
michael@0 339
michael@0 340 if (this._requestsForTraceType.location && aFrame.script) {
michael@0 341 // We should return the location of the start of the script, but
michael@0 342 // Debugger.Script does not provide complete start locations (bug
michael@0 343 // 901138). Instead, return the current offset (the location of the first
michael@0 344 // statement in the function).
michael@0 345 packet.location = {
michael@0 346 url: aFrame.script.url,
michael@0 347 line: aFrame.script.getOffsetLine(aFrame.offset),
michael@0 348 column: getOffsetColumn(aFrame.offset, aFrame.script)
michael@0 349 };
michael@0 350 }
michael@0 351
michael@0 352 if (this._requestsForTraceType.callsite
michael@0 353 && aFrame.older
michael@0 354 && aFrame.older.script) {
michael@0 355 let older = aFrame.older;
michael@0 356 packet.callsite = {
michael@0 357 url: older.script.url,
michael@0 358 line: older.script.getOffsetLine(older.offset),
michael@0 359 column: getOffsetColumn(older.offset, older.script)
michael@0 360 };
michael@0 361 }
michael@0 362
michael@0 363 if (this._requestsForTraceType.time) {
michael@0 364 packet.time = Date.now() - this._startTime;
michael@0 365 }
michael@0 366
michael@0 367 if (this._requestsForTraceType.parameterNames && aFrame.callee) {
michael@0 368 packet.parameterNames = aFrame.callee.parameterNames;
michael@0 369 }
michael@0 370
michael@0 371 if (this._requestsForTraceType.arguments && aFrame.arguments) {
michael@0 372 packet.arguments = [];
michael@0 373 let i = 0;
michael@0 374 for (let arg of aFrame.arguments) {
michael@0 375 if (i++ > MAX_ARGUMENTS) {
michael@0 376 break;
michael@0 377 }
michael@0 378 packet.arguments.push(createValueSnapshot(arg, true));
michael@0 379 }
michael@0 380 }
michael@0 381
michael@0 382 if (this._requestsForTraceType.depth) {
michael@0 383 packet.depth = aFrame.depth;
michael@0 384 }
michael@0 385
michael@0 386 const onExitFrame = this.onExitFrame;
michael@0 387 aFrame.onPop = function (aCompletion) {
michael@0 388 onExitFrame(this, aCompletion);
michael@0 389 };
michael@0 390
michael@0 391 this._send(packet);
michael@0 392 },
michael@0 393
michael@0 394 /**
michael@0 395 * Called by the engine when a frame is exited. Sends an unsolicited packet to
michael@0 396 * the client carrying requested trace information.
michael@0 397 *
michael@0 398 * @param Debugger.Frame aFrame
michael@0 399 * The Debugger.Frame that was just exited.
michael@0 400 * @param aCompletion object
michael@0 401 * The debugger completion value for the frame.
michael@0 402 */
michael@0 403 onExitFrame: function(aFrame, aCompletion) {
michael@0 404 let packet = {
michael@0 405 type: "exitedFrame",
michael@0 406 sequence: this._sequence++,
michael@0 407 };
michael@0 408
michael@0 409 if (!aCompletion) {
michael@0 410 packet.why = "terminated";
michael@0 411 } else if (aCompletion.hasOwnProperty("return")) {
michael@0 412 packet.why = "return";
michael@0 413 } else if (aCompletion.hasOwnProperty("yield")) {
michael@0 414 packet.why = "yield";
michael@0 415 } else {
michael@0 416 packet.why = "throw";
michael@0 417 }
michael@0 418
michael@0 419 if (this._requestsForTraceType.time) {
michael@0 420 packet.time = Date.now() - this._startTime;
michael@0 421 }
michael@0 422
michael@0 423 if (this._requestsForTraceType.depth) {
michael@0 424 packet.depth = aFrame.depth;
michael@0 425 }
michael@0 426
michael@0 427 if (aCompletion) {
michael@0 428 if (this._requestsForTraceType.return && "return" in aCompletion) {
michael@0 429 packet.return = createValueSnapshot(aCompletion.return, true);
michael@0 430 }
michael@0 431
michael@0 432 else if (this._requestsForTraceType.throw && "throw" in aCompletion) {
michael@0 433 packet.throw = createValueSnapshot(aCompletion.throw, true);
michael@0 434 }
michael@0 435
michael@0 436 else if (this._requestsForTraceType.yield && "yield" in aCompletion) {
michael@0 437 packet.yield = createValueSnapshot(aCompletion.yield, true);
michael@0 438 }
michael@0 439 }
michael@0 440
michael@0 441 this._send(packet);
michael@0 442 }
michael@0 443 };
michael@0 444
michael@0 445 /**
michael@0 446 * The request types this actor can handle.
michael@0 447 */
michael@0 448 TraceActor.prototype.requestTypes = {
michael@0 449 "attach": TraceActor.prototype.onAttach,
michael@0 450 "detach": TraceActor.prototype.onDetach,
michael@0 451 "startTrace": TraceActor.prototype.onStartTrace,
michael@0 452 "stopTrace": TraceActor.prototype.onStopTrace
michael@0 453 };
michael@0 454
michael@0 455 exports.register = function(handle) {
michael@0 456 handle.addTabActor(TraceActor, "traceActor");
michael@0 457 };
michael@0 458
michael@0 459 exports.unregister = function(handle) {
michael@0 460 handle.removeTabActor(TraceActor, "traceActor");
michael@0 461 };
michael@0 462
michael@0 463
michael@0 464 /**
michael@0 465 * MapStack is a collection of key/value pairs with stack ordering,
michael@0 466 * where keys are strings and values are any JS value. In addition to
michael@0 467 * the push and pop stack operations, supports a "delete" operation,
michael@0 468 * which removes the value associated with a given key from any
michael@0 469 * location in the stack.
michael@0 470 */
michael@0 471 function MapStack()
michael@0 472 {
michael@0 473 // Essentially a MapStack is just sugar-coating around a standard JS
michael@0 474 // object, plus the _stack array to track ordering.
michael@0 475 this._stack = [];
michael@0 476 this._map = Object.create(null);
michael@0 477 }
michael@0 478
michael@0 479 MapStack.prototype = {
michael@0 480 get size() { return this._stack.length; },
michael@0 481
michael@0 482 /**
michael@0 483 * Return the key for the value on the top of the stack, or
michael@0 484 * undefined if the stack is empty.
michael@0 485 */
michael@0 486 peekKey: function() {
michael@0 487 return this._stack[this.size - 1];
michael@0 488 },
michael@0 489
michael@0 490 /**
michael@0 491 * Return true iff a value has been associated with the given key.
michael@0 492 *
michael@0 493 * @param aKey string
michael@0 494 * The key whose presence is to be tested.
michael@0 495 */
michael@0 496 has: function(aKey) {
michael@0 497 return Object.prototype.hasOwnProperty.call(this._map, aKey);
michael@0 498 },
michael@0 499
michael@0 500 /**
michael@0 501 * Return the value associated with the given key, or undefined if
michael@0 502 * no value is associated with the key.
michael@0 503 *
michael@0 504 * @param aKey string
michael@0 505 * The key whose associated value is to be returned.
michael@0 506 */
michael@0 507 get: function(aKey) {
michael@0 508 return this._map[aKey];
michael@0 509 },
michael@0 510
michael@0 511 /**
michael@0 512 * Push a new value onto the stack. If another value with the same
michael@0 513 * key is already on the stack, it will be removed before the new
michael@0 514 * value is pushed onto the top of the stack.
michael@0 515 *
michael@0 516 * @param aKey string
michael@0 517 * The key of the object to push onto the stack.
michael@0 518 *
michael@0 519 * @param aValue
michael@0 520 * The value to push onto the stack.
michael@0 521 */
michael@0 522 push: function(aKey, aValue) {
michael@0 523 this.delete(aKey);
michael@0 524 this._stack.push(aKey);
michael@0 525 this._map[aKey] = aValue;
michael@0 526 },
michael@0 527
michael@0 528 /**
michael@0 529 * Remove the value from the top of the stack and return it.
michael@0 530 * Returns undefined if the stack is empty.
michael@0 531 */
michael@0 532 pop: function() {
michael@0 533 let key = this.peekKey();
michael@0 534 let value = this.get(key);
michael@0 535 this._stack.pop();
michael@0 536 delete this._map[key];
michael@0 537 return value;
michael@0 538 },
michael@0 539
michael@0 540 /**
michael@0 541 * Remove the value associated with the given key from the stack and
michael@0 542 * return it. Returns undefined if no value is associated with the
michael@0 543 * given key.
michael@0 544 *
michael@0 545 * @param aKey string
michael@0 546 * The key for the value to remove from the stack.
michael@0 547 */
michael@0 548 delete: function(aKey) {
michael@0 549 let value = this.get(aKey);
michael@0 550 if (this.has(aKey)) {
michael@0 551 let keyIndex = this._stack.lastIndexOf(aKey);
michael@0 552 this._stack.splice(keyIndex, 1);
michael@0 553 delete this._map[aKey];
michael@0 554 }
michael@0 555 return value;
michael@0 556 }
michael@0 557 };
michael@0 558
michael@0 559 // TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when
michael@0 560 // it is implemented.
michael@0 561 function getOffsetColumn(aOffset, aScript) {
michael@0 562 return 0;
michael@0 563 }
michael@0 564
michael@0 565 // Serialization helper functions. Largely copied from script.js and modified
michael@0 566 // for use in serialization rather than object actor requests.
michael@0 567
michael@0 568 /**
michael@0 569 * Create a grip for the given debuggee value.
michael@0 570 *
michael@0 571 * @param aValue Debugger.Object|primitive
michael@0 572 * The value to describe with the created grip.
michael@0 573 *
michael@0 574 * @param aDetailed boolean
michael@0 575 * If true, capture slightly more detailed information, like some
michael@0 576 * properties on an object.
michael@0 577 *
michael@0 578 * @return Object
michael@0 579 * A primitive value or a snapshot of an object.
michael@0 580 */
michael@0 581 function createValueSnapshot(aValue, aDetailed=false) {
michael@0 582 switch (typeof aValue) {
michael@0 583 case "boolean":
michael@0 584 return aValue;
michael@0 585 case "string":
michael@0 586 if (aValue.length >= DebuggerServer.LONG_STRING_LENGTH) {
michael@0 587 return {
michael@0 588 type: "longString",
michael@0 589 initial: aValue.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
michael@0 590 length: aValue.length
michael@0 591 };
michael@0 592 }
michael@0 593 return aValue;
michael@0 594 case "number":
michael@0 595 if (aValue === Infinity) {
michael@0 596 return { type: "Infinity" };
michael@0 597 } else if (aValue === -Infinity) {
michael@0 598 return { type: "-Infinity" };
michael@0 599 } else if (Number.isNaN(aValue)) {
michael@0 600 return { type: "NaN" };
michael@0 601 } else if (!aValue && 1 / aValue === -Infinity) {
michael@0 602 return { type: "-0" };
michael@0 603 }
michael@0 604 return aValue;
michael@0 605 case "undefined":
michael@0 606 return { type: "undefined" };
michael@0 607 case "object":
michael@0 608 if (aValue === null) {
michael@0 609 return { type: "null" };
michael@0 610 }
michael@0 611 return aDetailed
michael@0 612 ? detailedObjectSnapshot(aValue)
michael@0 613 : objectSnapshot(aValue);
michael@0 614 default:
michael@0 615 DevToolsUtils.reportException("TraceActor",
michael@0 616 new Error("Failed to provide a grip for: " + aValue));
michael@0 617 return null;
michael@0 618 }
michael@0 619 }
michael@0 620
michael@0 621 /**
michael@0 622 * Create a very minimal snapshot of the given debuggee object.
michael@0 623 *
michael@0 624 * @param aObject Debugger.Object
michael@0 625 * The object to describe with the created grip.
michael@0 626 */
michael@0 627 function objectSnapshot(aObject) {
michael@0 628 return {
michael@0 629 "type": "object",
michael@0 630 "class": aObject.class,
michael@0 631 };
michael@0 632 }
michael@0 633
michael@0 634 /**
michael@0 635 * Create a (slightly more) detailed snapshot of the given debuggee object.
michael@0 636 *
michael@0 637 * @param aObject Debugger.Object
michael@0 638 * The object to describe with the created descriptor.
michael@0 639 */
michael@0 640 function detailedObjectSnapshot(aObject) {
michael@0 641 let desc = objectSnapshot(aObject);
michael@0 642 let ownProperties = desc.ownProperties = Object.create(null);
michael@0 643
michael@0 644 if (aObject.class == "DeadObject") {
michael@0 645 return desc;
michael@0 646 }
michael@0 647
michael@0 648 let i = 0;
michael@0 649 for (let name of aObject.getOwnPropertyNames()) {
michael@0 650 if (i++ > MAX_PROPERTIES) {
michael@0 651 break;
michael@0 652 }
michael@0 653 let desc = propertySnapshot(name, aObject);
michael@0 654 if (desc) {
michael@0 655 ownProperties[name] = desc;
michael@0 656 }
michael@0 657 }
michael@0 658
michael@0 659 return desc;
michael@0 660 }
michael@0 661
michael@0 662 /**
michael@0 663 * A helper method that creates a snapshot of the object's |aName| property.
michael@0 664 *
michael@0 665 * @param aName string
michael@0 666 * The property of which the snapshot is taken.
michael@0 667 *
michael@0 668 * @param aObject Debugger.Object
michael@0 669 * The object whose property the snapshot is taken of.
michael@0 670 *
michael@0 671 * @return Object
michael@0 672 * The snapshot of the property.
michael@0 673 */
michael@0 674 function propertySnapshot(aName, aObject) {
michael@0 675 let desc;
michael@0 676 try {
michael@0 677 desc = aObject.getOwnPropertyDescriptor(aName);
michael@0 678 } catch (e) {
michael@0 679 // Calling getOwnPropertyDescriptor on wrapped native prototypes is not
michael@0 680 // allowed (bug 560072). Inform the user with a bogus, but hopefully
michael@0 681 // explanatory, descriptor.
michael@0 682 return {
michael@0 683 configurable: false,
michael@0 684 writable: false,
michael@0 685 enumerable: false,
michael@0 686 value: e.name
michael@0 687 };
michael@0 688 }
michael@0 689
michael@0 690 // Only create descriptors for simple values. We skip objects and properties
michael@0 691 // that have getters and setters; ain't nobody got time for that!
michael@0 692 if (!desc
michael@0 693 || typeof desc.value == "object" && desc.value !== null
michael@0 694 || !("value" in desc)) {
michael@0 695 return undefined;
michael@0 696 }
michael@0 697
michael@0 698 return {
michael@0 699 configurable: desc.configurable,
michael@0 700 enumerable: desc.enumerable,
michael@0 701 writable: desc.writable,
michael@0 702 value: createValueSnapshot(desc.value)
michael@0 703 };
michael@0 704 }

mercurial