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 +}