summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/tracer.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/tracer.js')
-rw-r--r--devtools/server/actors/tracer.js188
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;