/** * Instruments a getter or setter on the specified target object. */ function overrideAccessor(global, target, name, descriptor, callback) { // Invoking .apply on an unxrayed content function doesn't work, because // the arguments array is inaccessible to it. Get Xrays back. let originalGetter = Cu.unwaiveXrays(target.__lookupGetter__(name)); let originalSetter = Cu.unwaiveXrays(target.__lookupSetter__(name)); Object.defineProperty(target, name, { get: function(...args) { if (!originalGetter) return undefined; let result = Cu.waiveXrays(originalGetter.apply(this, args)); if (self._recording) { let type = CallWatcherFront.GETTER_FUNCTION; let stack = getStack(name); let timestamp = self.tabActor.window.performance.now() - self._timestampEpoch; callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result); } return result; }, set: function(...args) { if (!originalSetter) return; originalSetter.apply(this, args); if (self._recording) { let type = CallWatcherFront.SETTER_FUNCTION; let stack = getStack(name); let timestamp = self.tabActor.window.performance.now() - self._timestampEpoch; callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, undefined); } }, configurable: descriptor.configurable, enumerable: descriptor.enumerable }); }
onBuildPreview: makeInfallible(function(actor, grip, rawObj) { if (!DevToolsUtils.getProperty(actor.obj, "jquery")) { return false; } let length = DevToolsUtils.getProperty(actor.obj, "length"); if (typeof length != "number") { return false; } let preview = grip.preview = { kind: "jQuery", length: length, }; if (actor.threadActor._gripDepth > 1) { return true; } Trace.sysout("FireQueryActor.onBuildPreview;", arguments); preview.items = []; let raw = actor.obj.unsafeDereference(); for (let i = 0; i < preview.length; ++i) { // Array Xrays filter out various possibly-unsafe properties (like // functions, and claim that the value is undefined instead. This // is generally the right thing for privileged code accessing untrusted // objects, but quite confusing for Object previews. So we manually // override this protection by waiving Xrays on the array, and re-applying // Xrays on any indexed value props that we pull off of it. let desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i); if (desc && !desc.get && !desc.set) { let value = Cu.unwaiveXrays(desc.value); value = makeDebuggeeValueIfNeeded(actor.obj, value); let grip = actor.threadActor.createValueGrip(value); preview.items.push(grip); let data = hasJQueryData(desc.value); if (data) { data = Cu.unwaiveXrays(data); data = makeDebuggeeValueIfNeeded(actor.obj, data); grip.preview.jQueryData = actor.threadActor.createValueGrip(data); // xxxHonza: generate preview for the data? } } else { preview.items.push(null); } if (preview.length == OBJECT_PREVIEW_MAX_ITEMS) { break; } } Trace.sysout("FireQueryActor.onPreview; jQuery preview", preview); return true; }),
/** * Instruments a function on the specified target object. */ function overrideFunction(global, target, name, descriptor, callback) { // Invoking .apply on an unxrayed content function doesn't work, because // the arguments array is inaccessible to it. Get Xrays back. let originalFunc = Cu.unwaiveXrays(target[name]); Cu.exportFunction(function(...args) { let result; try { result = Cu.waiveXrays(originalFunc.apply(this, args)); } catch (e) { throw createContentError(e, unwrappedWindow); } if (self._recording) { let type = CallWatcherFront.METHOD_FUNCTION; let stack = getStack(name); let timestamp = self.tabActor.window.performance.now() - self._timestampEpoch; callback(unwrappedWindow, global, this, type, name, stack, timestamp, args, result); } return result; }, target, { defineAs: name }); Object.defineProperty(target, name, { configurable: descriptor.configurable, enumerable: descriptor.enumerable, writable: true }); }
/** * Helper function to create a grip from a Map/Set entry */ function gripFromEntry({ obj, hooks }, entry) { if (!isWorker) { entry = Cu.unwaiveXrays(entry); } return hooks.createValueGrip( ObjectUtils.makeDebuggeeValueIfNeeded(obj, entry)); }
Array: [function({obj, hooks}, grip) { const length = ObjectUtils.getArrayLength(obj); grip.preview = { kind: "ArrayLike", length: length, }; if (hooks.getGripDepth() > 1) { return true; } const raw = obj.unsafeDereference(); const items = grip.preview.items = []; for (let i = 0; i < length; ++i) { if (raw) { // Array Xrays filter out various possibly-unsafe properties (like // functions, and claim that the value is undefined instead. This // is generally the right thing for privileged code accessing untrusted // objects, but quite confusing for Object previews. So we manually // override this protection by waiving Xrays on the array, and re-applying // Xrays on any indexed value props that we pull off of it. const desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i); if (desc && !desc.get && !desc.set) { let value = Cu.unwaiveXrays(desc.value); value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value); items.push(hooks.createValueGrip(value)); } else { items.push(null); } } else { // When recording/replaying we don't have a raw object, but also don't // need to deal with Xrays into the debuggee compartment. const value = DevToolsUtils.getProperty(obj, i); items.push(hooks.createValueGrip(value)); } if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { break; } } return true; }],
context[funcName] = function (...glArgs) { if (timing <= 0 && !observer.suppressHandlers) { let glBreak = observer[beforeFuncName](glArgs, cache, proxy); if (glBreak) { return undefined; } } // Invoking .apply on an unxrayed content function doesn't work, because // the arguments array is inaccessible to it. Get Xrays back. let glResult = Cu.waiveXrays(Cu.unwaiveXrays(originalFunc).apply(this, glArgs)); if (timing >= 0 && !observer.suppressHandlers) { let glBreak = observer[afterFuncName](glArgs, glResult, cache, proxy); if (glBreak) { return undefined; } } return glResult; };
form: function() { const g = { "type": "object", "actor": this.actorID, "class": this.obj.class, }; const unwrapped = DevToolsUtils.unwrap(this.obj); // Unsafe objects must be treated carefully. if (!DevToolsUtils.isSafeDebuggerObject(this.obj)) { if (DevToolsUtils.isCPOW(this.obj)) { // Cross-process object wrappers can't be accessed. g.class = "CPOW: " + g.class; } else if (unwrapped === undefined) { // Objects belonging to an invisible-to-debugger compartment might be proxies, // so just in case they shouldn't be accessed. g.class = "InvisibleToDebugger: " + g.class; } else if (unwrapped.isProxy) { // Proxy objects can run traps when accessed, so just create a preview with // the target and the handler. g.class = "Proxy"; this.hooks.incrementGripDepth(); previewers.Proxy[0](this, g, null); this.hooks.decrementGripDepth(); } return g; } // If the debuggee does not subsume the object's compartment, most properties won't // be accessible. Cross-orgin Window and Location objects might expose some, though. // Change the displayed class, but when creating the preview use the original one. if (unwrapped === null) { g.class = "Restricted"; } this.hooks.incrementGripDepth(); g.extensible = this.obj.isExtensible(); g.frozen = this.obj.isFrozen(); g.sealed = this.obj.isSealed(); if (g.class == "Promise") { g.promiseState = this._createPromiseState(); } // FF40+: Allow to know how many properties an object has to lazily display them // when there is a bunch. if (isTypedArray(g)) { // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays g.ownPropertyLength = getArrayLength(this.obj); } else if (isStorage(g)) { g.ownPropertyLength = getStorageLength(this.obj); } else if (isReplaying) { // When replaying we can get the number of properties directly, to avoid // needing to enumerate all of them. g.ownPropertyLength = this.obj.getOwnPropertyNamesCount(); } else { try { g.ownPropertyLength = this.obj.getOwnPropertyNames().length; } catch (err) { // The above can throw when the debuggee does not subsume the object's // compartment, or for some WrappedNatives like Cu.Sandbox. } } let raw = this.obj.unsafeDereference(); // If Cu is not defined, we are running on a worker thread, where xrays // don't exist. The raw object will be null/unavailable when interacting // with a replaying execution. if (raw && Cu) { raw = Cu.unwaiveXrays(raw); } if (raw && !DevToolsUtils.isSafeJSObject(raw)) { raw = null; } for (const fn of previewers[this.obj.class] || previewers.Object) { try { if (fn(this, g, raw)) { break; } } catch (e) { const msg = "ObjectActor.prototype.grip previewer function"; DevToolsUtils.reportException(msg, e); } } this.hooks.decrementGripDepth(); return g; },
getListeners(node, {checkOnly} = {}) { const handlers = []; const listeners = this.getDOMListeners(node); for (const listener of listeners) { // Ignore listeners without a type, e.g. // node.addEventListener("", function() {}) if (!listener.type) { continue; } // Get the listener object, either a Function or an Object. const obj = listener.listenerObject; // Ignore listeners without any listener, e.g. // node.addEventListener("mouseover", null); if (!obj) { continue; } let handler = null; // An object without a valid handleEvent is not a valid listener. if (typeof obj === "object") { const unwrapped = this.unwrap(obj); if (typeof unwrapped.handleEvent === "function") { handler = Cu.unwaiveXrays(unwrapped.handleEvent); } } else if (typeof obj === "function") { // Ignore DOM events used to trigger jQuery events as they are only // useful to the developers of the jQuery library. if (JQUERY_LIVE_REGEX.test(obj.toString())) { continue; } // Otherwise, the other valid listener type is function. handler = obj; } // Ignore listeners that have no handler. if (!handler) { continue; } // If we shouldn't be showing chrome events due to context and this is a // chrome handler we can ignore it. if (!this.chromeEnabled && this.isChromeHandler(handler)) { continue; } // If this is checking if a node has any listeners then we have found one // so return now. if (checkOnly) { return true; } const eventInfo = { capturing: listener.capturing, type: listener.type, handler: handler, }; handlers.push(eventInfo); } // If this is checking if a node has any listeners then none were found so // return false. if (checkOnly) { return false; } return handlers; }