diff options
Diffstat (limited to 'devtools/server/actors/tracer.js')
-rw-r--r-- | devtools/server/actors/tracer.js | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/devtools/server/actors/tracer.js b/devtools/server/actors/tracer.js new file mode 100644 index 0000000000..2c213f155c --- /dev/null +++ b/devtools/server/actors/tracer.js @@ -0,0 +1,188 @@ +/* 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"; + +// Bug 1827382, as this module can be used from the worker thread, +// the following JSM may be loaded by the worker loader until +// we have proper support for ESM from workers. +const { + startTracing, + stopTracing, + addTracingListener, + removeTracingListener, +} = require("resource://devtools/server/tracer/tracer.jsm"); + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { tracerSpec } = require("resource://devtools/shared/specs/tracer.js"); + +const { throttle } = require("resource://devtools/shared/throttle.js"); + +const { + TYPES, + getResourceWatcher, +} = require("resource://devtools/server/actors/resources/index.js"); + +const LOG_METHODS = { + STDOUT: "stdout", + CONSOLE: "console", +}; + +const CONSOLE_ARGS_STYLES = [ + "color: var(--theme-toolbarbutton-checked-hover-background)", + "padding-inline: 4px; margin-inline: 2px; background-color: var(--theme-toolbarbutton-checked-hover-background); color: var(--theme-toolbarbutton-checked-hover-color);", + "", + "color: var(--theme-highlight-blue); margin-inline: 2px;", +]; + +const CONSOLE_THROTTLING_DELAY = 250; + +class TracerActor extends Actor { + constructor(conn, targetActor) { + super(conn, tracerSpec); + this.targetActor = targetActor; + this.sourcesManager = this.targetActor.sourcesManager; + + // Flag used by CONSOLE_MESSAGE resources + this.isChromeContext = /conn\d+\.parentProcessTarget\d+/.test( + this.targetActor.actorID + ); + + this.throttledConsoleMessages = []; + this.throttleLogMessages = throttle( + this.flushConsoleMessages.bind(this), + CONSOLE_THROTTLING_DELAY + ); + } + + destroy() { + this.stopTracing(); + } + + getLogMethod() { + return this.logMethod; + } + + startTracing(logMethod = LOG_METHODS.STDOUT) { + this.logMethod = logMethod; + this.tracingListener = { + onTracingFrame: this.onTracingFrame.bind(this), + onTracingInfiniteLoop: this.onTracingInfiniteLoop.bind(this), + }; + addTracingListener(this.tracingListener); + startTracing({ + global: this.targetActor.window || this.targetActor.workerGlobal, + }); + } + + stopTracing() { + if (!this.tracingListener) { + return; + } + stopTracing(); + removeTracingListener(this.tracingListener); + this.logMethod = null; + this.tracingListener = null; + } + + onTracingInfiniteLoop() { + if (this.logMethod == LOG_METHODS.STDOUT) { + return true; + } + const consoleMessageWatcher = getResourceWatcher( + this.targetActor, + TYPES.CONSOLE_MESSAGE + ); + if (!consoleMessageWatcher) { + return true; + } + + const message = + "Looks like an infinite recursion? We stopped the JavaScript tracer, but code may still be running!"; + consoleMessageWatcher.emitMessages([ + { + arguments: [message], + styles: [], + level: "logTrace", + chromeContext: this.isChromeContext, + timeStamp: ChromeUtils.dateNow(), + }, + ]); + + return false; + } + + /** + * Called by JavaScriptTracer class when a new JavaScript frame is executed. + * + * @param {Debugger.Frame} frame + * A descriptor object for the JavaScript frame. + * @param {Number} depth + * Represents the depth of the frame in the call stack. + * @param {String} formatedDisplayName + * A human readable name for the current frame. + * @param {String} prefix + * A string to be displayed as a prefix of any logged frame. + * @return {Boolean} + * Return true, if the JavaScriptTracer should log the frame to stdout. + */ + onTracingFrame({ frame, depth, formatedDisplayName, prefix }) { + const { script } = frame; + const { lineNumber, columnNumber } = script.getOffsetMetadata(frame.offset); + const url = script.source.url; + + // Ignore blackboxed sources + if (this.sourcesManager.isBlackBoxed(url, lineNumber, columnNumber)) { + return false; + } + + if (this.logMethod == LOG_METHODS.STDOUT) { + // By returning true, we let JavaScriptTracer class log the message to stdout. + return true; + } + + const args = [ + prefix + "—".repeat(depth + 1), + frame.implementation, + "⟶", + formatedDisplayName, + ]; + + // Create a message object that fits Console Message Watcher expectations + this.throttledConsoleMessages.push({ + filename: url, + lineNumber, + columnNumber, + arguments: args, + styles: CONSOLE_ARGS_STYLES, + level: "logTrace", + chromeContext: this.isChromeContext, + sourceId: script.source.id, + timeStamp: ChromeUtils.dateNow(), + }); + this.throttleLogMessages(); + + return false; + } + + /** + * This method is throttled and will notify all pending traces to be logged in the console + * via the console message watcher. + */ + flushConsoleMessages() { + const consoleMessageWatcher = getResourceWatcher( + this.targetActor, + TYPES.CONSOLE_MESSAGE + ); + // Ignore the request if the frontend isn't listening to console messages for that target. + if (!consoleMessageWatcher) { + return; + } + const messages = this.throttledConsoleMessages; + this.throttledConsoleMessages = []; + + consoleMessageWatcher.emitMessages(messages); + } +} +exports.TracerActor = TracerActor; |