272 lines
8.5 KiB
JavaScript
272 lines
8.5 KiB
JavaScript
/* 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";
|
|
|
|
const lazy = {};
|
|
ChromeUtils.defineESModuleGetters(
|
|
lazy,
|
|
{
|
|
JSTracer: "resource://devtools/server/tracer/tracer.sys.mjs",
|
|
},
|
|
{ global: "contextual" }
|
|
);
|
|
|
|
const { Actor } = require("resource://devtools/shared/protocol.js");
|
|
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
|
const {
|
|
ObjectActorPool,
|
|
} = require("resource://devtools/server/actors/object/ObjectActorPool.js");
|
|
const {
|
|
tracerSpec,
|
|
TRACER_LOG_METHODS,
|
|
} = require("resource://devtools/shared/specs/tracer.js");
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"StdoutTracingListener",
|
|
"resource://devtools/server/actors/tracer/stdout.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"ResourcesTracingListener",
|
|
"resource://devtools/server/actors/tracer/resources.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"ProfilerTracingListener",
|
|
"resource://devtools/server/actors/tracer/profiler.js",
|
|
true
|
|
);
|
|
|
|
// Indexes of each data type within the array describing a trace
|
|
exports.TRACER_FIELDS_INDEXES = {
|
|
// This is shared with all the data types
|
|
TYPE: 0,
|
|
|
|
// Frame traces are slightly special and do not share any field with the other data types
|
|
FRAME_IMPLEMENTATION: 1,
|
|
FRAME_NAME: 2,
|
|
FRAME_SOURCEID: 3,
|
|
FRAME_LINE: 4,
|
|
FRAME_COLUMN: 5,
|
|
FRAME_URL: 6,
|
|
|
|
// These fields are shared with all but frame data types
|
|
PREFIX: 1,
|
|
FRAME_INDEX: 2,
|
|
TIMESTAMP: 3,
|
|
DEPTH: 4,
|
|
|
|
EVENT_NAME: 5,
|
|
|
|
ENTER_ARGS: 5,
|
|
ENTER_ARG_NAMES: 6,
|
|
|
|
EXIT_PARENT_FRAME_ID: 5,
|
|
EXIT_RETURNED_VALUE: 6,
|
|
EXIT_WHY: 7,
|
|
|
|
DOM_MUTATION_TYPE: 5,
|
|
DOM_MUTATION_ELEMENT: 6,
|
|
};
|
|
|
|
const VALID_LOG_METHODS = Object.values(TRACER_LOG_METHODS);
|
|
|
|
class TracerActor extends Actor {
|
|
constructor(conn, targetActor) {
|
|
super(conn, tracerSpec);
|
|
this.targetActor = targetActor;
|
|
}
|
|
|
|
// When the tracer is stopped, save the result of the Listener Class.
|
|
// This is used by the profiler log method and the getProfile method.
|
|
#stopResult = null;
|
|
|
|
// A Pool for all JS values emitted by the Tracer Actor.
|
|
// This helps instantiate a unique Object Actor per JS Object communicated to the client.
|
|
// This also helps share the same Object Actor instances when evaluating JS via
|
|
// the console actor.
|
|
// This pool is created lazily, only once we start a new trace.
|
|
// We also clear the pool before starting the trace.
|
|
#tracerPool = null;
|
|
|
|
destroy() {
|
|
this.stopTracing();
|
|
}
|
|
|
|
getLogMethod() {
|
|
return this.logMethod;
|
|
}
|
|
|
|
/**
|
|
* Toggle tracing JavaScript.
|
|
* Meant for the WebConsole command in order to pass advanced
|
|
* configuration directly to JavaScriptTracer class.
|
|
*
|
|
* @param {Object} options
|
|
* Options used to configure JavaScriptTracer.
|
|
* See `JavaScriptTracer.startTracing`.
|
|
* @return {Boolean}
|
|
* True if the tracer starts, or false if it was stopped.
|
|
*/
|
|
toggleTracing(options) {
|
|
if (!this.tracingListener) {
|
|
this.startTracing(options);
|
|
return true;
|
|
}
|
|
this.stopTracing();
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Start tracing.
|
|
*
|
|
* @param {Object} options
|
|
* Options used to configure JavaScriptTracer.
|
|
* See `JavaScriptTracer.startTracing`.
|
|
*/
|
|
// eslint-disable-next-line complexity
|
|
startTracing(options = {}) {
|
|
if (options.logMethod && !VALID_LOG_METHODS.includes(options.logMethod)) {
|
|
throw new Error(
|
|
`Invalid log method '${options.logMethod}'. Only supports: ${VALID_LOG_METHODS}`
|
|
);
|
|
}
|
|
if (options.prefix && typeof options.prefix != "string") {
|
|
throw new Error("Invalid prefix, only support string type");
|
|
}
|
|
if (options.maxDepth && typeof options.maxDepth != "number") {
|
|
throw new Error("Invalid max-depth, only support numbers");
|
|
}
|
|
if (options.maxRecords && typeof options.maxRecords != "number") {
|
|
throw new Error("Invalid max-records, only support numbers");
|
|
}
|
|
|
|
// When tracing on next user interaction is enabled,
|
|
// disable logging from workers as this makes the tracer work
|
|
// against visible documents and is actived per document thread.
|
|
if (options.traceOnNextInteraction && isWorker) {
|
|
return;
|
|
}
|
|
|
|
// Ignore WindowGlobal target actors for WindowGlobal of iframes running in the same process and thread as their parent document.
|
|
// isProcessRoot will be true for each WindowGlobal being the top parent within a given process.
|
|
// It will typically be true for WindowGlobal of iframe running in a distinct origin and process,
|
|
// but only for the top iframe document. It will also be true for the top level tab document.
|
|
if (
|
|
this.targetActor.window &&
|
|
!this.targetActor.window.windowGlobalChild?.isProcessRoot
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Flush any previous recorded data only when we start a new tracer
|
|
// as we may still analyse trace data after stopping the trace.
|
|
// The pool will then be re-created on demand from createValueGrip.
|
|
if (this.#tracerPool) {
|
|
this.#tracerPool.destroy();
|
|
this.#tracerPool = null;
|
|
}
|
|
|
|
this.logMethod = options.logMethod || TRACER_LOG_METHODS.STDOUT;
|
|
|
|
let ListenerClass = null;
|
|
// Currently only the profiler output is supported with the native tracer.
|
|
let useNativeTracing = false;
|
|
switch (this.logMethod) {
|
|
case TRACER_LOG_METHODS.STDOUT:
|
|
ListenerClass = StdoutTracingListener;
|
|
break;
|
|
case TRACER_LOG_METHODS.CONSOLE:
|
|
case TRACER_LOG_METHODS.DEBUGGER_SIDEBAR:
|
|
// Console and debugger sidebar are both using JSTRACE_STATE/JSTRACE_TRACE resources
|
|
// to receive tracing data.
|
|
ListenerClass = ResourcesTracingListener;
|
|
break;
|
|
case TRACER_LOG_METHODS.PROFILER:
|
|
ListenerClass = ProfilerTracingListener;
|
|
// Recording function returns is mandatory when recording profiler output.
|
|
// Otherwise frames are not closed and mixed up in the profiler frontend.
|
|
options.traceFunctionReturn = true;
|
|
useNativeTracing = true;
|
|
break;
|
|
}
|
|
this.tracingListener = new ListenerClass({
|
|
targetActor: this.targetActor,
|
|
traceValues: !!options.traceValues,
|
|
traceActor: this,
|
|
});
|
|
lazy.JSTracer.addTracingListener(this.tracingListener);
|
|
|
|
this.traceValues = !!options.traceValues;
|
|
try {
|
|
lazy.JSTracer.startTracing({
|
|
global: this.targetActor.targetGlobal,
|
|
prefix: options.prefix || "",
|
|
// Enable receiving the `currentDOMEvent` being passed to `onTracingFrame`
|
|
traceDOMEvents: true,
|
|
// Enable tracing DOM Mutations
|
|
traceDOMMutations: options.traceDOMMutations,
|
|
// Enable tracing function arguments as well as returned values
|
|
traceValues: !!options.traceValues,
|
|
// Enable tracing only on next user interaction
|
|
traceOnNextInteraction: !!options.traceOnNextInteraction,
|
|
// Notify about frame exit / function call returning
|
|
traceFunctionReturn: !!options.traceFunctionReturn,
|
|
// Use the native tracing implementation
|
|
useNativeTracing,
|
|
// Ignore frames beyond the given depth
|
|
maxDepth: options.maxDepth,
|
|
// Stop the tracing after a number of top level frames
|
|
maxRecords: options.maxRecords,
|
|
});
|
|
} catch (e) {
|
|
// If startTracing throws, it probably rejected one of its options and we should
|
|
// unregister the tracing listener.
|
|
this.stopTracing();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async stopTracing() {
|
|
if (!this.tracingListener) {
|
|
return;
|
|
}
|
|
// Remove before stopping to prevent receiving the stop notification
|
|
lazy.JSTracer.removeTracingListener(this.tracingListener);
|
|
// Save the result of the stop request for the profiler and the getProfile RDP method
|
|
this.#stopResult = this.tracingListener.stop();
|
|
this.tracingListener = null;
|
|
|
|
lazy.JSTracer.stopTracing();
|
|
this.logMethod = null;
|
|
}
|
|
|
|
/**
|
|
* Queried by THREAD_STATE watcher to send the gecko profiler data
|
|
* as part of THREAD STATE "stop" resource.
|
|
*
|
|
* @return {Object} Gecko profiler profile object.
|
|
*/
|
|
async getProfile() {
|
|
// #stopResult is a promise
|
|
return this.#stopResult;
|
|
}
|
|
|
|
createValueGrip(value) {
|
|
if (!this.#tracerPool) {
|
|
this.#tracerPool = new ObjectActorPool(
|
|
this.targetActor.threadActor,
|
|
"tracer",
|
|
true
|
|
);
|
|
this.manage(this.#tracerPool);
|
|
}
|
|
return createValueGrip(this, value, this.#tracerPool);
|
|
}
|
|
}
|
|
exports.TracerActor = TracerActor;
|