diff --git a/devtools/client/debugger/new/src/client/firefox/types.js b/devtools/client/debugger/new/src/client/firefox/types.js index 3041d30270b8..eafa2315da27 100644 --- a/devtools/client/debugger/new/src/client/firefox/types.js +++ b/devtools/client/debugger/new/src/client/firefox/types.js @@ -157,8 +157,6 @@ export type FramesResponse = { export type TabPayload = { actor: ActorId, animationsActor: ActorId, - callWatcherActor: ActorId, - canvasActor: ActorId, consoleActor: ActorId, cssPropertiesActor: ActorId, cssUsageActor: ActorId, @@ -166,7 +164,6 @@ export type TabPayload = { emulationActor: ActorId, eventLoopLagActor: ActorId, framerateActor: ActorId, - gcliActor: ActorId, inspectorActor: ActorId, memoryActor: ActorId, monitorActor: ActorId, @@ -183,7 +180,6 @@ export type TabPayload = { title: string, url: URL, webExtensionInspectedWindowActor: ActorId, - webglActor: ActorId }; /** diff --git a/devtools/server/actors/utils/call-watcher.js b/devtools/server/actors/utils/call-watcher.js deleted file mode 100644 index 0c990872a418..000000000000 --- a/devtools/server/actors/utils/call-watcher.js +++ /dev/null @@ -1,419 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -/* global XPCNativeWrapper */ - -const {Cu} = require("chrome"); -const ChromeUtils = require("ChromeUtils"); - -const { - METHOD_FUNCTION, - GETTER_FUNCTION, - SETTER_FUNCTION, -} = require("devtools/shared/fronts/function-call"); - -const { FunctionCallActor } = require("devtools/server/actors/utils/function-call"); - -/** - * This helper observes function calls on certain objects or globals. - */ -function CallWatcher(conn, targetActor) { - this.targetActor = targetActor; - this.conn = conn; - this._onGlobalCreated = this._onGlobalCreated.bind(this); - this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this); - this._onContentFunctionCall = this._onContentFunctionCall.bind(this); - this.targetActor.on("window-ready", this._onGlobalCreated); - this.targetActor.on("window-destroyed", this._onGlobalDestroyed); -} - -exports.CallWatcher = CallWatcher; - -CallWatcher.prototype = { - /** - * Lightweight listener invoked whenever an instrumented function is called - * while recording. We're doing this to avoid the event emitter overhead, - * since this is expected to be a very hot function. - */ - onCall: null, - - /** - * Starts waiting for the current target actor's document global to be - * created, in order to instrument the specified objects and become - * aware of everything the content does with them. - */ - setup: function({ - tracedGlobals, tracedFunctions, startRecording, performReload, holdWeak, storeCalls, - }) { - if (this._initialized) { - return; - } - this._initialized = true; - this._timestampEpoch = 0; - - this._functionCalls = []; - this._tracedGlobals = tracedGlobals || []; - this._tracedFunctions = tracedFunctions || []; - this._holdWeak = !!holdWeak; - this._storeCalls = !!storeCalls; - - if (startRecording) { - this.resumeRecording(); - } - if (performReload) { - this.targetActor.window.location.reload(); - } - }, - - /** - * Stops listening for document global changes and puts this actor - * to hibernation. This method is called automatically just before the - * actor is destroyed. - */ - finalize: function() { - this.targetActor.off("window-ready", this._onGlobalCreated); - this.targetActor.off("window-destroyed", this._onGlobalDestroyed); - if (!this._initialized) { - return; - } - this._initialized = false; - this._finalized = true; - - this._tracedGlobals = null; - this._tracedFunctions = null; - }, - - /** - * Returns whether the instrumented function calls are currently recorded. - */ - isRecording: function() { - return this._recording; - }, - - /** - * Initialize the timestamp epoch used to offset function call timestamps. - */ - initTimestampEpoch: function() { - this._timestampEpoch = this.targetActor.window.performance.now(); - }, - - /** - * Starts recording function calls. - */ - resumeRecording: function() { - this._recording = true; - }, - - /** - * Stops recording function calls. - */ - pauseRecording: function() { - this._recording = false; - return this._functionCalls; - }, - - /** - * Erases all the recorded function calls. - * Calling `resumeRecording` or `pauseRecording` does not erase history. - */ - eraseRecording: function() { - this._functionCalls = []; - }, - - /** - * Invoked whenever the current target actor's document global is created. - */ - _onGlobalCreated: function({window, id, isTopLevel}) { - if (!this._initialized) { - return; - } - - // TODO: bug 981748, support more than just the top-level documents. - if (!isTopLevel) { - return; - } - - const self = this; - this._tracedWindowId = id; - - const unwrappedWindow = XPCNativeWrapper.unwrap(window); - const callback = this._onContentFunctionCall; - - for (const global of this._tracedGlobals) { - const prototype = unwrappedWindow[global].prototype; - const properties = Object.keys(prototype); - properties.forEach(name => overrideSymbol(global, prototype, name, callback)); - } - - for (const name of this._tracedFunctions) { - overrideSymbol("window", unwrappedWindow, name, callback); - } - - /** - * Instruments a method, getter or setter on the specified target object to - * invoke a callback whenever it is called. - */ - function overrideSymbol(global, target, name, subcallback) { - const propertyDescriptor = Object.getOwnPropertyDescriptor(target, name); - - if (propertyDescriptor.get || propertyDescriptor.set) { - overrideAccessor(global, target, name, propertyDescriptor, subcallback); - return; - } - if (propertyDescriptor.writable && typeof propertyDescriptor.value == "function") { - overrideFunction(global, target, name, propertyDescriptor, subcallback); - } - } - - /** - * Instruments a function on the specified target object. - */ - function overrideFunction(global, target, name, descriptor, subcallback) { - // Invoking .apply on an unxrayed content function doesn't work, because - // the arguments array is inaccessible to it. Get Xrays back. - const 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) { - const type = METHOD_FUNCTION; - const stack = getStack(name); - const now = self.targetActor.window.performance.now(); - const timestamp = now - self._timestampEpoch; - subcallback(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, - }); - } - - /** - * Instruments a getter or setter on the specified target object. - */ - function overrideAccessor(global, target, name, descriptor, subcallback) { - // Invoking .apply on an unxrayed content function doesn't work, because - // the arguments array is inaccessible to it. Get Xrays back. - const originalGetter = Cu.unwaiveXrays(target.__lookupGetter__(name)); - const originalSetter = Cu.unwaiveXrays(target.__lookupSetter__(name)); - - Object.defineProperty(target, name, { - get: function(...args) { - if (!originalGetter) { - return undefined; - } - const result = Cu.waiveXrays(originalGetter.apply(this, args)); - - if (self._recording) { - const type = GETTER_FUNCTION; - const stack = getStack(name); - const now = self.targetActor.window.performance.now(); - const timestamp = now - self._timestampEpoch; - subcallback(unwrappedWindow, global, this, type, name, stack, timestamp, - args, result); - } - return result; - }, - set: function(...args) { - if (!originalSetter) { - return; - } - originalSetter.apply(this, args); - - if (self._recording) { - const type = SETTER_FUNCTION; - const stack = getStack(name); - const now = self.targetActor.window.performance.now(); - const timestamp = now - self._timestampEpoch; - subcallback(unwrappedWindow, global, this, type, name, stack, timestamp, - args, undefined); - } - }, - configurable: descriptor.configurable, - enumerable: descriptor.enumerable, - }); - } - - /** - * Stores the relevant information about calls on the stack when - * a function is called. - */ - function getStack(caller) { - let stack; - try { - // Using Components.stack wouldn't be a better idea, since it's - // much slower because it attempts to retrieve the C++ stack as well. - throw new Error(); - } catch (e) { - stack = e.stack; - } - - // Of course, using a simple regex like /(.*?)@(.*):(\d*):\d*/ would be - // much prettier, but this is a very hot function, so let's sqeeze - // every drop of performance out of it. - const calls = []; - let callIndex = 0; - let currNewLinePivot = stack.indexOf("\n") + 1; - let nextNewLinePivot = stack.indexOf("\n", currNewLinePivot); - - while (nextNewLinePivot > 0) { - const nameDelimiterIndex = stack.indexOf("@", currNewLinePivot); - const columnDelimiterIndex = stack.lastIndexOf(":", nextNewLinePivot - 1); - const lineDelimiterIndex = stack.lastIndexOf(":", columnDelimiterIndex - 1); - - if (!calls[callIndex]) { - calls[callIndex] = { name: "", file: "", line: 0 }; - } - if (!calls[callIndex + 1]) { - calls[callIndex + 1] = { name: "", file: "", line: 0 }; - } - - if (callIndex > 0) { - const file = stack.substring(nameDelimiterIndex + 1, lineDelimiterIndex); - const line = stack.substring(lineDelimiterIndex + 1, columnDelimiterIndex); - const name = stack.substring(currNewLinePivot, nameDelimiterIndex); - calls[callIndex].name = name; - calls[callIndex - 1].file = file; - calls[callIndex - 1].line = line; - } else { - // Since the topmost stack frame is actually our overwritten function, - // it will not have the expected name. - calls[0].name = caller; - } - - currNewLinePivot = nextNewLinePivot + 1; - nextNewLinePivot = stack.indexOf("\n", currNewLinePivot); - callIndex++; - } - - return calls; - } - }, - - /** - * Invoked whenever the current target actor's inner window is destroyed. - */ - _onGlobalDestroyed: function({window, id, isTopLevel}) { - if (this._tracedWindowId == id) { - this.pauseRecording(); - this.eraseRecording(); - this._timestampEpoch = 0; - } - }, - - /** - * Invoked whenever an instrumented function is called. - */ - _onContentFunctionCall: function(...details) { - // If the consuming tool has finalized call-watcher, ignore the - // still-instrumented calls. - if (this._finalized) { - return; - } - - const functionCall = new FunctionCallActor(this.conn, details, this._holdWeak); - - if (this._storeCalls) { - this._functionCalls.push(functionCall); - } - - if (this.onCall) { - this.onCall(functionCall); - } else { - this.emit("call", functionCall); - } - }, -}; - -function parseURI(uri) { - return String(uri).split(" -> ").pop(); -} - -function parseStack(stack) { - const lines = String(stack).split("\n"); - return lines.reduce(function(frames, line) { - if (line) { - const atIndex = line.indexOf("@"); - const columnIndex = line.lastIndexOf(":"); - const lineIndex = line.lastIndexOf(":", columnIndex - 1); - const fileName = parseURI(line.slice(atIndex + 1, lineIndex)); - const lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex), 10); - const columnNumber = parseInt(line.slice(columnIndex + 1), 10); - const name = line.slice(0, atIndex).split("(").shift(); - frames.unshift({ - fileName: fileName, - name: name, - lineNumber: lineNumber, - columnNumber: columnNumber, - }); - } - return frames; - }, []); -} - -function serializeStack(frames) { - return frames.reduce(function(stack, frame) { - return frame.name + "@" + - frame.fileName + ":" + - frame.lineNumber + ":" + - frame.columnNumber + "\n" + - stack; - }, ""); -} - -/** - * Creates a new error from an error that originated from content but was called - * from a wrapped overridden method. This is so we can make our own error - * that does not look like it originated from the call watcher. - * - * We use toolkit/loader's parseStack and serializeStack rather than the - * parsing done in the local `getStack` function, because it does not expose - * column number, would have to change the protocol models `call-stack-items` - * and `call-details` which hurts backwards compatibility, and the local `getStack` - * is an optimized, hot function. - */ -function createContentError(e, win) { - const { message, name, stack } = e; - const parsedStack = parseStack(stack); - const { fileName, lineNumber, columnNumber } = parsedStack[parsedStack.length - 1]; - let error; - - const isDOMException = ChromeUtils.getClassName(e) === "DOMException"; - const constructor = isDOMException ? win.DOMException : (win[e.name] || win.Error); - - if (isDOMException) { - error = new constructor(message, name); - Object.defineProperties(error, { - code: { value: e.code }, - // columnNumber is always 0 for DOMExceptions? - columnNumber: { value: 0 }, - // note the lowercase `filename` - filename: { value: fileName }, - lineNumber: { value: lineNumber }, - result: { value: e.result }, - stack: { value: serializeStack(parsedStack) }, - }); - } else { - // Constructing an error here retains all the stack information, - // and we can add message, fileName and lineNumber via constructor, though - // need to manually add columnNumber. - error = new constructor(message, fileName, lineNumber); - Object.defineProperty(error, "columnNumber", { - value: columnNumber, - }); - } - return error; -} diff --git a/devtools/server/actors/utils/moz.build b/devtools/server/actors/utils/moz.build index 4dc8db9feb71..178f2e26a133 100644 --- a/devtools/server/actors/utils/moz.build +++ b/devtools/server/actors/utils/moz.build @@ -9,7 +9,6 @@ DevToolsModules( 'actor-registry-utils.js', 'actor-registry.js', 'breakpoint-actor-map.js', - 'call-watcher.js', 'closest-scripts.js', 'css-grid-utils.js', 'dbg-source.js',