зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1485378 - Replace CallWatcher actor with helper; r=jdescottes
creates a CallWatcherActor only for tests and migrates other functionality to the helper Differential Revision: https://phabricator.services.mozilla.com/D5534 --HG-- rename : devtools/server/actors/call-watcher.js => devtools/client/canvasdebugger/test/call-watcher-actor.js rename : devtools/shared/fronts/call-watcher.js => devtools/client/canvasdebugger/test/call-watcher-front.js rename : devtools/shared/specs/call-watcher.js => devtools/client/canvasdebugger/test/call-watcher-spec.js rename : devtools/server/actors/call-watcher.js => devtools/server/actors/utils/call-watcher.js rename : devtools/server/actors/call-watcher.js => devtools/server/actors/utils/function-call.js rename : devtools/shared/fronts/call-watcher.js => devtools/shared/fronts/function-call.js rename : devtools/shared/specs/call-watcher.js => devtools/shared/specs/function-call.js extra : moz-landing-system : lando
This commit is contained in:
Родитель
4f0efc181d
Коммит
825e041cf6
|
@ -5,6 +5,7 @@
|
|||
/* globals window, document */
|
||||
"use strict";
|
||||
|
||||
const { METHOD_FUNCTION } = require("devtools/shared/fronts/function-call");
|
||||
/**
|
||||
* Functions handling details about a single recorded animation frame snapshot
|
||||
* (the calls list, rendering preview, thumbnails filmstrip etc.).
|
||||
|
@ -93,7 +94,7 @@ var CallsListView = extend(WidgetMethods, {
|
|||
argsPreview.setAttribute("crop", "end");
|
||||
argsPreview.setAttribute("flex", "100");
|
||||
// Getters and setters are displayed differently from regular methods.
|
||||
if (call.type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
if (call.type == METHOD_FUNCTION) {
|
||||
argsPreview.setAttribute("value", "(" + call.argsPreview + ")");
|
||||
} else {
|
||||
argsPreview.setAttribute("value", " = " + call.argsPreview);
|
||||
|
|
|
@ -8,7 +8,6 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
|||
const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
|
||||
const Services = require("Services");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
|
||||
const { CanvasFront } = require("devtools/shared/fronts/canvas");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { extend } = require("devtools/shared/extend");
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
call-watcher-actor.js
|
||||
call-watcher-front.js
|
||||
call-watcher-spec.js
|
||||
doc_raf-begin.html
|
||||
doc_settimeout.html
|
||||
doc_no-canvas.html
|
||||
|
|
|
@ -31,7 +31,7 @@ async function ifTestingSupported() {
|
|||
ok(functionCalls.length > 0,
|
||||
"There's at least one function call actor available.");
|
||||
|
||||
is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[0].type, METHOD_FUNCTION,
|
||||
"The called function is correctly identified as a method.");
|
||||
is(functionCalls[0].name, "clearRect",
|
||||
"The called function's name is correct.");
|
||||
|
|
|
@ -31,7 +31,7 @@ async function ifTestingSupported() {
|
|||
is(functionCalls.length, 8,
|
||||
"The number of function call actors is correct.");
|
||||
|
||||
is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[0].type, METHOD_FUNCTION,
|
||||
"The first called function is correctly identified as a method.");
|
||||
is(functionCalls[0].name, "clearRect",
|
||||
"The first called function's name is correct.");
|
||||
|
@ -44,7 +44,7 @@ async function ifTestingSupported() {
|
|||
is(functionCalls[0].callerPreview, "Object",
|
||||
"The first called function's caller preview is correct.");
|
||||
|
||||
is(functionCalls[6].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[6].type, METHOD_FUNCTION,
|
||||
"The penultimate called function is correctly identified as a method.");
|
||||
is(functionCalls[6].name, "fillRect",
|
||||
"The penultimate called function's name is correct.");
|
||||
|
@ -57,7 +57,7 @@ async function ifTestingSupported() {
|
|||
is(functionCalls[6].callerPreview, "Object",
|
||||
"The penultimate called function's caller preview is correct.");
|
||||
|
||||
is(functionCalls[7].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[7].type, METHOD_FUNCTION,
|
||||
"The last called function is correctly identified as a method.");
|
||||
is(functionCalls[7].name, "requestAnimationFrame",
|
||||
"The last called function's name is correct.");
|
||||
|
|
|
@ -31,7 +31,7 @@ async function ifTestingSupported() {
|
|||
is(functionCalls.length, 8,
|
||||
"The number of function call actors is correct.");
|
||||
|
||||
is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[0].type, METHOD_FUNCTION,
|
||||
"The first called function is correctly identified as a method.");
|
||||
is(functionCalls[0].name, "clearRect",
|
||||
"The first called function's name is correct.");
|
||||
|
@ -44,7 +44,7 @@ async function ifTestingSupported() {
|
|||
is(functionCalls[0].callerPreview, "Object",
|
||||
"The first called function's caller preview is correct.");
|
||||
|
||||
is(functionCalls[6].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[6].type, METHOD_FUNCTION,
|
||||
"The penultimate called function is correctly identified as a method.");
|
||||
is(functionCalls[6].name, "fillRect",
|
||||
"The penultimate called function's name is correct.");
|
||||
|
@ -57,7 +57,7 @@ async function ifTestingSupported() {
|
|||
is(functionCalls[6].callerPreview, "Object",
|
||||
"The penultimate called function's caller preview is correct.");
|
||||
|
||||
is(functionCalls[7].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(functionCalls[7].type, METHOD_FUNCTION,
|
||||
"The last called function is correctly identified as a method.");
|
||||
is(functionCalls[7].name, "setTimeout",
|
||||
"The last called function's name is correct.");
|
||||
|
|
|
@ -30,7 +30,7 @@ async function ifTestingSupported() {
|
|||
is(CallsListView.visibleItems.length, 1,
|
||||
"Only one item should now be visible in the calls list.");
|
||||
|
||||
is(CallsListView.visibleItems[0].attachment.actor.type, CallWatcherFront.METHOD_FUNCTION,
|
||||
is(CallsListView.visibleItems[0].attachment.actor.type, METHOD_FUNCTION,
|
||||
"The visible item's type has the expected value.");
|
||||
is(CallsListView.visibleItems[0].attachment.actor.name, "clearRect",
|
||||
"The visible item's name has the expected value.");
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/* 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 protocol = require("devtools/shared/protocol");
|
||||
const {
|
||||
callWatcherSpec
|
||||
} = require("chrome://mochitests/content/browser/devtools/client/canvasdebugger/test/call-watcher-spec");
|
||||
const {CallWatcher} = require("devtools/server/actors/utils/call-watcher");
|
||||
|
||||
/**
|
||||
* This actor observes function calls on certain objects or globals.
|
||||
* It wraps the CallWatcher Helper so that it can be observed by tests
|
||||
*/
|
||||
exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
||||
initialize: function(conn, targetActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
CallWatcher.call(this, conn, targetActor);
|
||||
},
|
||||
destroy: function(conn) {
|
||||
protocol.Actor.prototype.destroy.call(this, conn);
|
||||
this.finalize();
|
||||
},
|
||||
...CallWatcher.prototype,
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/* 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 {
|
||||
callWatcherSpec
|
||||
} = require("chrome://mochitests/content/browser/devtools/client/canvasdebugger/test/call-watcher-spec");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
|
||||
/**
|
||||
* The corresponding Front object for the CallWatcherActor.
|
||||
*/
|
||||
var CallWatcherFront =
|
||||
exports.CallWatcherFront =
|
||||
protocol.FrontClassWithSpec(callWatcherSpec, {
|
||||
initialize: function(client, { callWatcherActor }) {
|
||||
protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor });
|
||||
this.manage(this);
|
||||
}
|
||||
});
|
|
@ -6,36 +6,6 @@
|
|||
const protocol = require("devtools/shared/protocol");
|
||||
const { Arg, RetVal, Option, generateActorSpec } = protocol;
|
||||
|
||||
/**
|
||||
* Type describing a single function call in a stack trace.
|
||||
*/
|
||||
protocol.types.addDictType("call-stack-item", {
|
||||
name: "string",
|
||||
file: "string",
|
||||
line: "number"
|
||||
});
|
||||
|
||||
/**
|
||||
* Type describing an overview of a function call.
|
||||
*/
|
||||
protocol.types.addDictType("call-details", {
|
||||
type: "number",
|
||||
name: "string",
|
||||
stack: "array:call-stack-item"
|
||||
});
|
||||
|
||||
const functionCallSpec = generateActorSpec({
|
||||
typeName: "function-call",
|
||||
|
||||
methods: {
|
||||
getDetails: {
|
||||
response: { info: RetVal("call-details") }
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
exports.functionCallSpec = functionCallSpec;
|
||||
|
||||
const callWatcherSpec = generateActorSpec({
|
||||
typeName: "call-watcher",
|
||||
|
|
@ -21,7 +21,8 @@ var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUID
|
|||
|
||||
var { DebuggerClient } = require("devtools/shared/client/debugger-client");
|
||||
var { DebuggerServer } = require("devtools/server/main");
|
||||
var { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
|
||||
var { METHOD_FUNCTION } = require("devtools/shared/fronts/function-call");
|
||||
var { CallWatcherFront } = require("chrome://mochitests/content/browser/devtools/client/canvasdebugger/test/call-watcher-front");
|
||||
var { CanvasFront } = require("devtools/shared/fronts/canvas");
|
||||
var { Toolbox } = require("devtools/client/framework/toolbox");
|
||||
var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
|
||||
|
@ -127,6 +128,11 @@ function initCallWatcherBackend(aUrl) {
|
|||
return (async function() {
|
||||
const tab = await addTab(aUrl);
|
||||
const target = TargetFactory.forTab(tab);
|
||||
await registerActorInContentProcess("chrome://mochitests/content/browser/devtools/client/canvasdebugger/test/call-watcher-actor.js", {
|
||||
prefix: "callWatcher",
|
||||
constructor: "CallWatcherActor",
|
||||
type: { target: true }
|
||||
});
|
||||
|
||||
await target.makeRemote();
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
const defer = require("devtools/shared/defer");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const {CallWatcherActor} = require("devtools/server/actors/call-watcher");
|
||||
const {CallWatcherFront} = require("devtools/shared/fronts/call-watcher");
|
||||
const {CallWatcher} = require("devtools/server/actors/utils/call-watcher");
|
||||
const {METHOD_FUNCTION, SETTER_FUNCTION} = require("devtools/shared/fronts/function-call");
|
||||
const {WebGLPrimitiveCounter} = require("devtools/server/actors/canvas/primitive");
|
||||
const {
|
||||
frameSnapshotSpec,
|
||||
|
@ -148,7 +148,7 @@ exports.CanvasActor = protocol.ActorClassWithSpec(canvasSpec, {
|
|||
}
|
||||
this._initialized = true;
|
||||
|
||||
this._callWatcher = new CallWatcherActor(this.conn, this.targetActor);
|
||||
this._callWatcher = new CallWatcher(this.conn, this.targetActor);
|
||||
this._callWatcher.onCall = this._onContentFunctionCall;
|
||||
this._callWatcher.setup({
|
||||
tracedGlobals: CANVAS_CONTEXTS,
|
||||
|
@ -509,8 +509,8 @@ var ContextUtils = {
|
|||
* In case of a 2D context, a new canvas is created, since there's no
|
||||
* intrinsic state that can't be easily duplicated.
|
||||
*
|
||||
* @param number contexType
|
||||
* The type of context to use. See the CallWatcherFront scope types.
|
||||
* @param number contextType
|
||||
* The type of context to use. See the FunctionCallFront constants.
|
||||
* @param HTMLCanvasElement canvas
|
||||
* The canvas element which is the source of all context calls.
|
||||
* @param array calls
|
||||
|
@ -594,9 +594,9 @@ var ContextUtils = {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
if (type == METHOD_FUNCTION) {
|
||||
replayContext[name].apply(replayContext, args);
|
||||
} else if (type == CallWatcherFront.SETTER_FUNCTION) {
|
||||
} else if (type == SETTER_FUNCTION) {
|
||||
replayContext[name] = args;
|
||||
}
|
||||
if (CanvasFront.DRAW_CALLS.has(name)) {
|
||||
|
|
|
@ -27,7 +27,6 @@ DevToolsModules(
|
|||
'animation.js',
|
||||
'array-buffer.js',
|
||||
'breakpoint.js',
|
||||
'call-watcher.js',
|
||||
'canvas.js',
|
||||
'common.js',
|
||||
'css-properties.js',
|
||||
|
|
|
@ -6,247 +6,35 @@
|
|||
/* global XPCNativeWrapper */
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const ChromeUtils = require("ChromeUtils");
|
||||
// base-loader.js is a JSM without .jsm file extension, so it has to be loaded
|
||||
// via ChromeUtils.import and not require() which would consider it as a CommonJS module
|
||||
const {serializeStack, parseStack} = ChromeUtils.import("resource://devtools/shared/base-loader.js", {});
|
||||
|
||||
const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
|
||||
const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
|
||||
const {
|
||||
METHOD_FUNCTION,
|
||||
GETTER_FUNCTION,
|
||||
SETTER_FUNCTION
|
||||
} = require("devtools/shared/fronts/function-call");
|
||||
|
||||
const { FunctionCallActor } = require("devtools/server/actors/utils/function-call");
|
||||
|
||||
/**
|
||||
* This actor contains information about a function call, like the function
|
||||
* type, name, stack, arguments, returned value etc.
|
||||
* This helper observes function calls on certain objects or globals.
|
||||
*/
|
||||
var FunctionCallActor = protocol.ActorClassWithSpec(functionCallSpec, {
|
||||
/**
|
||||
* Creates the function call actor.
|
||||
*
|
||||
* @param DebuggerServerConnection conn
|
||||
* The server connection.
|
||||
* @param DOMWindow window
|
||||
* The content window.
|
||||
* @param string global
|
||||
* The name of the global object owning this function, like
|
||||
* "CanvasRenderingContext2D" or "WebGLRenderingContext".
|
||||
* @param object caller
|
||||
* The object owning the function when it was called.
|
||||
* For example, in `foo.bar()`, the caller is `foo`.
|
||||
* @param number type
|
||||
* Either METHOD_FUNCTION, METHOD_GETTER or METHOD_SETTER.
|
||||
* @param string name
|
||||
* The called function's name.
|
||||
* @param array stack
|
||||
* The called function's stack, as a list of { name, file, line } objects.
|
||||
* @param number timestamp
|
||||
* The performance.now() timestamp when the function was called.
|
||||
* @param array args
|
||||
* The called function's arguments.
|
||||
* @param any result
|
||||
* The value returned by the function call.
|
||||
* @param boolean holdWeak
|
||||
* Determines whether or not FunctionCallActor stores a weak reference
|
||||
* to the underlying objects.
|
||||
*/
|
||||
initialize: function(
|
||||
conn,
|
||||
[window, global, caller, type, name, stack, timestamp, args, result],
|
||||
holdWeak
|
||||
) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
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);
|
||||
}
|
||||
|
||||
this.details = {
|
||||
global: global,
|
||||
type: type,
|
||||
name: name,
|
||||
stack: stack,
|
||||
timestamp: timestamp
|
||||
};
|
||||
|
||||
// Store a weak reference to all objects so we don't
|
||||
// prevent natural GC if `holdWeak` was passed into
|
||||
// setup as truthy.
|
||||
if (holdWeak) {
|
||||
const weakRefs = {
|
||||
window: Cu.getWeakReference(window),
|
||||
caller: Cu.getWeakReference(caller),
|
||||
args: Cu.getWeakReference(args),
|
||||
result: Cu.getWeakReference(result),
|
||||
};
|
||||
|
||||
Object.defineProperties(this.details, {
|
||||
window: { get: () => weakRefs.window.get() },
|
||||
caller: { get: () => weakRefs.caller.get() },
|
||||
args: { get: () => weakRefs.args.get() },
|
||||
result: { get: () => weakRefs.result.get() },
|
||||
});
|
||||
} else {
|
||||
// Otherwise, hold strong references to the objects.
|
||||
this.details.window = window;
|
||||
this.details.caller = caller;
|
||||
this.details.args = args;
|
||||
this.details.result = result;
|
||||
}
|
||||
|
||||
// The caller, args and results are string names for now. It would
|
||||
// certainly be nicer if they were Object actors. Make this smarter, so
|
||||
// that the frontend can inspect each argument, be it object or primitive.
|
||||
// Bug 978960.
|
||||
this.details.previews = {
|
||||
caller: this._generateStringPreview(caller),
|
||||
args: this._generateArgsPreview(args),
|
||||
result: this._generateStringPreview(result)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Customize the marshalling of this actor to provide some generic information
|
||||
* directly on the Front instance.
|
||||
*/
|
||||
form: function() {
|
||||
return {
|
||||
actor: this.actorID,
|
||||
type: this.details.type,
|
||||
name: this.details.name,
|
||||
file: this.details.stack[0].file,
|
||||
line: this.details.stack[0].line,
|
||||
timestamp: this.details.timestamp,
|
||||
callerPreview: this.details.previews.caller,
|
||||
argsPreview: this.details.previews.args,
|
||||
resultPreview: this.details.previews.result
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets more information about this function call, which is not necessarily
|
||||
* available on the Front instance.
|
||||
*/
|
||||
getDetails: function() {
|
||||
const { type, name, stack, timestamp } = this.details;
|
||||
|
||||
// Since not all calls on the stack have corresponding owner files (e.g.
|
||||
// callbacks of a requestAnimationFrame etc.), there's no benefit in
|
||||
// returning them, as the user can't jump to the Debugger from them.
|
||||
for (let i = stack.length - 1; ;) {
|
||||
if (stack[i].file) {
|
||||
break;
|
||||
}
|
||||
stack.pop();
|
||||
i--;
|
||||
}
|
||||
|
||||
// XXX: Use grips for objects and serialize them properly, in order
|
||||
// to add the function's caller, arguments and return value. Bug 978957.
|
||||
return {
|
||||
type: type,
|
||||
name: name,
|
||||
stack: stack,
|
||||
timestamp: timestamp
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes the arguments so that they can be easily be transferred
|
||||
* as a string, but still be useful when displayed in a potential UI.
|
||||
*
|
||||
* @param array args
|
||||
* The source arguments.
|
||||
* @return string
|
||||
* The arguments as a string.
|
||||
*/
|
||||
_generateArgsPreview: function(args) {
|
||||
const { global, name, caller } = this.details;
|
||||
|
||||
// Get method signature to determine if there are any enums
|
||||
// used in this method.
|
||||
let methodSignatureEnums;
|
||||
|
||||
const knownGlobal = CallWatcherFront.KNOWN_METHODS[global];
|
||||
if (knownGlobal) {
|
||||
const knownMethod = knownGlobal[name];
|
||||
if (knownMethod) {
|
||||
const isOverloaded = typeof knownMethod.enums === "function";
|
||||
if (isOverloaded) {
|
||||
methodSignatureEnums = knownMethod.enums(args);
|
||||
} else {
|
||||
methodSignatureEnums = knownMethod.enums;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serializeArgs = () => args.map((arg, i) => {
|
||||
// XXX: Bug 978960.
|
||||
if (arg === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
if (arg === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof arg == "function") {
|
||||
return "Function";
|
||||
}
|
||||
if (typeof arg == "object") {
|
||||
return "Object";
|
||||
}
|
||||
// If this argument matches the method's signature
|
||||
// and is an enum, change it to its constant name.
|
||||
if (methodSignatureEnums && methodSignatureEnums.has(i)) {
|
||||
return getBitToEnumValue(global, caller, arg);
|
||||
}
|
||||
return arg + "";
|
||||
});
|
||||
|
||||
return serializeArgs().join(", ");
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes the data so that it can be easily be transferred
|
||||
* as a string, but still be useful when displayed in a potential UI.
|
||||
*
|
||||
* @param object data
|
||||
* The source data.
|
||||
* @return string
|
||||
* The arguments as a string.
|
||||
*/
|
||||
_generateStringPreview: function(data) {
|
||||
// XXX: Bug 978960.
|
||||
if (data === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
if (data === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof data == "function") {
|
||||
return "Function";
|
||||
}
|
||||
if (typeof data == "object") {
|
||||
return "Object";
|
||||
}
|
||||
return data + "";
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This actor observes function calls on certain objects or globals.
|
||||
*/
|
||||
exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
||||
initialize: function(conn, targetActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.targetActor = targetActor;
|
||||
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);
|
||||
},
|
||||
destroy: function(conn) {
|
||||
protocol.Actor.prototype.destroy.call(this, conn);
|
||||
this.targetActor.off("window-ready", this._onGlobalCreated);
|
||||
this.targetActor.off("window-destroyed", this._onGlobalDestroyed);
|
||||
this.finalize();
|
||||
},
|
||||
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,
|
||||
|
@ -288,6 +76,8 @@ exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
|||
* actor is destroyed.
|
||||
*/
|
||||
finalize: function() {
|
||||
this.targetActor.off("window-ready", this._onGlobalCreated);
|
||||
this.targetActor.off("window-destroyed", this._onGlobalDestroyed);
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
@ -397,7 +187,7 @@ exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
|||
}
|
||||
|
||||
if (self._recording) {
|
||||
const type = CallWatcherFront.METHOD_FUNCTION;
|
||||
const type = METHOD_FUNCTION;
|
||||
const stack = getStack(name);
|
||||
const now = self.targetActor.window.performance.now();
|
||||
const timestamp = now - self._timestampEpoch;
|
||||
|
@ -431,7 +221,7 @@ exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
|||
const result = Cu.waiveXrays(originalGetter.apply(this, args));
|
||||
|
||||
if (self._recording) {
|
||||
const type = CallWatcherFront.GETTER_FUNCTION;
|
||||
const type = GETTER_FUNCTION;
|
||||
const stack = getStack(name);
|
||||
const now = self.targetActor.window.performance.now();
|
||||
const timestamp = now - self._timestampEpoch;
|
||||
|
@ -447,7 +237,7 @@ exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
|||
originalSetter.apply(this, args);
|
||||
|
||||
if (self._recording) {
|
||||
const type = CallWatcherFront.SETTER_FUNCTION;
|
||||
const type = SETTER_FUNCTION;
|
||||
const stack = getStack(name);
|
||||
const now = self.targetActor.window.performance.now();
|
||||
const timestamp = now - self._timestampEpoch;
|
||||
|
@ -549,64 +339,7 @@ exports.CallWatcherActor = protocol.ActorClassWithSpec(callWatcherSpec, {
|
|||
this.emit("call", functionCall);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A lookup table for cross-referencing flags or properties with their name
|
||||
* assuming they look LIKE_THIS most of the time.
|
||||
*
|
||||
* For example, when gl.clear(gl.COLOR_BUFFER_BIT) is called, the actual passed
|
||||
* argument's value is 16384, which we want identified as "COLOR_BUFFER_BIT".
|
||||
*/
|
||||
var gEnumRegex = /^[A-Z][A-Z0-9_]+$/;
|
||||
var gEnumsLookupTable = {};
|
||||
|
||||
// These values are returned from errors, or empty values,
|
||||
// and need to be ignored when checking arguments due to the bitwise math.
|
||||
var INVALID_ENUMS = [
|
||||
"INVALID_ENUM", "NO_ERROR", "INVALID_VALUE", "OUT_OF_MEMORY", "NONE"
|
||||
];
|
||||
|
||||
function getBitToEnumValue(type, object, arg) {
|
||||
let table = gEnumsLookupTable[type];
|
||||
|
||||
// If mapping not yet created, do it on the first run.
|
||||
if (!table) {
|
||||
table = gEnumsLookupTable[type] = {};
|
||||
|
||||
for (const key in object) {
|
||||
if (key.match(gEnumRegex)) {
|
||||
// Maps `16384` to `"COLOR_BUFFER_BIT"`, etc.
|
||||
table[object[key]] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a single bit value, just return it.
|
||||
if (table[arg]) {
|
||||
return table[arg];
|
||||
}
|
||||
|
||||
// Otherwise, attempt to reduce it to the original bit flags:
|
||||
// `16640` -> "COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT"
|
||||
const flags = [];
|
||||
for (let flag in table) {
|
||||
if (INVALID_ENUMS.includes(table[flag])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cast to integer as all values are stored as strings
|
||||
// in `table`
|
||||
flag = flag | 0;
|
||||
if (flag && (arg & flag) === flag) {
|
||||
flags.push(table[flag]);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the combined bitmask value
|
||||
table[arg] = flags.join(" | ") || arg;
|
||||
return table[arg];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new error from an error that originated from content but was called
|
|
@ -0,0 +1,279 @@
|
|||
/* 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 {Cu} = require("chrome");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
|
||||
const { functionCallSpec } = require("devtools/shared/specs/function-call");
|
||||
const { KNOWN_METHODS } = require("devtools/shared/fronts/function-call");
|
||||
|
||||
/**
|
||||
* This actor contains information about a function call, like the function
|
||||
* type, name, stack, arguments, returned value etc.
|
||||
*/
|
||||
exports.FunctionCallActor = protocol.ActorClassWithSpec(functionCallSpec, {
|
||||
/**
|
||||
* Creates the function call actor.
|
||||
*
|
||||
* @param DebuggerServerConnection conn
|
||||
* The server connection.
|
||||
* @param DOMWindow window
|
||||
* The content window.
|
||||
* @param string global
|
||||
* The name of the global object owning this function, like
|
||||
* "CanvasRenderingContext2D" or "WebGLRenderingContext".
|
||||
* @param object caller
|
||||
* The object owning the function when it was called.
|
||||
* For example, in `foo.bar()`, the caller is `foo`.
|
||||
* @param number type
|
||||
* Either METHOD_FUNCTION, METHOD_GETTER or METHOD_SETTER.
|
||||
* @param string name
|
||||
* The called function's name.
|
||||
* @param array stack
|
||||
* The called function's stack, as a list of { name, file, line } objects.
|
||||
* @param number timestamp
|
||||
* The performance.now() timestamp when the function was called.
|
||||
* @param array args
|
||||
* The called function's arguments.
|
||||
* @param any result
|
||||
* The value returned by the function call.
|
||||
* @param boolean holdWeak
|
||||
* Determines whether or not FunctionCallActor stores a weak reference
|
||||
* to the underlying objects.
|
||||
*/
|
||||
initialize: function(
|
||||
conn,
|
||||
[window, global, caller, type, name, stack, timestamp, args, result],
|
||||
holdWeak
|
||||
) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
this.details = {
|
||||
global: global,
|
||||
type: type,
|
||||
name: name,
|
||||
stack: stack,
|
||||
timestamp: timestamp
|
||||
};
|
||||
|
||||
// Store a weak reference to all objects so we don't
|
||||
// prevent natural GC if `holdWeak` was passed into
|
||||
// setup as truthy.
|
||||
if (holdWeak) {
|
||||
const weakRefs = {
|
||||
window: Cu.getWeakReference(window),
|
||||
caller: Cu.getWeakReference(caller),
|
||||
args: Cu.getWeakReference(args),
|
||||
result: Cu.getWeakReference(result),
|
||||
};
|
||||
|
||||
Object.defineProperties(this.details, {
|
||||
window: { get: () => weakRefs.window.get() },
|
||||
caller: { get: () => weakRefs.caller.get() },
|
||||
args: { get: () => weakRefs.args.get() },
|
||||
result: { get: () => weakRefs.result.get() },
|
||||
});
|
||||
} else {
|
||||
// Otherwise, hold strong references to the objects.
|
||||
this.details.window = window;
|
||||
this.details.caller = caller;
|
||||
this.details.args = args;
|
||||
this.details.result = result;
|
||||
}
|
||||
|
||||
// The caller, args and results are string names for now. It would
|
||||
// certainly be nicer if they were Object actors. Make this smarter, so
|
||||
// that the frontend can inspect each argument, be it object or primitive.
|
||||
// Bug 978960.
|
||||
this.details.previews = {
|
||||
caller: this._generateStringPreview(caller),
|
||||
args: this._generateArgsPreview(args),
|
||||
result: this._generateStringPreview(result)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Customize the marshalling of this actor to provide some generic information
|
||||
* directly on the Front instance.
|
||||
*/
|
||||
form: function() {
|
||||
return {
|
||||
actor: this.actorID,
|
||||
type: this.details.type,
|
||||
name: this.details.name,
|
||||
file: this.details.stack[0].file,
|
||||
line: this.details.stack[0].line,
|
||||
timestamp: this.details.timestamp,
|
||||
callerPreview: this.details.previews.caller,
|
||||
argsPreview: this.details.previews.args,
|
||||
resultPreview: this.details.previews.result
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets more information about this function call, which is not necessarily
|
||||
* available on the Front instance.
|
||||
*/
|
||||
getDetails: function() {
|
||||
const { type, name, stack, timestamp } = this.details;
|
||||
|
||||
// Since not all calls on the stack have corresponding owner files (e.g.
|
||||
// callbacks of a requestAnimationFrame etc.), there's no benefit in
|
||||
// returning them, as the user can't jump to the Debugger from them.
|
||||
for (let i = stack.length - 1; ;) {
|
||||
if (stack[i].file) {
|
||||
break;
|
||||
}
|
||||
stack.pop();
|
||||
i--;
|
||||
}
|
||||
|
||||
// XXX: Use grips for objects and serialize them properly, in order
|
||||
// to add the function's caller, arguments and return value. Bug 978957.
|
||||
return {
|
||||
type: type,
|
||||
name: name,
|
||||
stack: stack,
|
||||
timestamp: timestamp
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes the arguments so that they can be easily be transferred
|
||||
* as a string, but still be useful when displayed in a potential UI.
|
||||
*
|
||||
* @param array args
|
||||
* The source arguments.
|
||||
* @return string
|
||||
* The arguments as a string.
|
||||
*/
|
||||
_generateArgsPreview: function(args) {
|
||||
const { global, name, caller } = this.details;
|
||||
|
||||
// Get method signature to determine if there are any enums
|
||||
// used in this method.
|
||||
let methodSignatureEnums;
|
||||
|
||||
const knownGlobal = KNOWN_METHODS[global];
|
||||
if (knownGlobal) {
|
||||
const knownMethod = knownGlobal[name];
|
||||
if (knownMethod) {
|
||||
const isOverloaded = typeof knownMethod.enums === "function";
|
||||
if (isOverloaded) {
|
||||
methodSignatureEnums = knownMethod.enums(args);
|
||||
} else {
|
||||
methodSignatureEnums = knownMethod.enums;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serializeArgs = () => args.map((arg, i) => {
|
||||
// XXX: Bug 978960.
|
||||
if (arg === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
if (arg === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof arg == "function") {
|
||||
return "Function";
|
||||
}
|
||||
if (typeof arg == "object") {
|
||||
return "Object";
|
||||
}
|
||||
// If this argument matches the method's signature
|
||||
// and is an enum, change it to its constant name.
|
||||
if (methodSignatureEnums && methodSignatureEnums.has(i)) {
|
||||
return getBitToEnumValue(global, caller, arg);
|
||||
}
|
||||
return arg + "";
|
||||
});
|
||||
|
||||
return serializeArgs().join(", ");
|
||||
},
|
||||
|
||||
/**
|
||||
* Serializes the data so that it can be easily be transferred
|
||||
* as a string, but still be useful when displayed in a potential UI.
|
||||
*
|
||||
* @param object data
|
||||
* The source data.
|
||||
* @return string
|
||||
* The arguments as a string.
|
||||
*/
|
||||
_generateStringPreview: function(data) {
|
||||
// XXX: Bug 978960.
|
||||
if (data === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
if (data === null) {
|
||||
return "null";
|
||||
}
|
||||
if (typeof data == "function") {
|
||||
return "Function";
|
||||
}
|
||||
if (typeof data == "object") {
|
||||
return "Object";
|
||||
}
|
||||
return data + "";
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A lookup table for cross-referencing flags or properties with their name
|
||||
* assuming they look LIKE_THIS most of the time.
|
||||
*
|
||||
* For example, when gl.clear(gl.COLOR_BUFFER_BIT) is called, the actual passed
|
||||
* argument's value is 16384, which we want identified as "COLOR_BUFFER_BIT".
|
||||
*/
|
||||
var gEnumRegex = /^[A-Z][A-Z0-9_]+$/;
|
||||
var gEnumsLookupTable = {};
|
||||
|
||||
// These values are returned from errors, or empty values,
|
||||
// and need to be ignored when checking arguments due to the bitwise math.
|
||||
var INVALID_ENUMS = [
|
||||
"INVALID_ENUM", "NO_ERROR", "INVALID_VALUE", "OUT_OF_MEMORY", "NONE"
|
||||
];
|
||||
|
||||
function getBitToEnumValue(type, object, arg) {
|
||||
let table = gEnumsLookupTable[type];
|
||||
|
||||
// If mapping not yet created, do it on the first run.
|
||||
if (!table) {
|
||||
table = gEnumsLookupTable[type] = {};
|
||||
|
||||
for (const key in object) {
|
||||
if (key.match(gEnumRegex)) {
|
||||
// Maps `16384` to `"COLOR_BUFFER_BIT"`, etc.
|
||||
table[object[key]] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a single bit value, just return it.
|
||||
if (table[arg]) {
|
||||
return table[arg];
|
||||
}
|
||||
|
||||
// Otherwise, attempt to reduce it to the original bit flags:
|
||||
// `16640` -> "COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT"
|
||||
const flags = [];
|
||||
for (let flag in table) {
|
||||
if (INVALID_ENUMS.includes(table[flag])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cast to integer as all values are stored as strings
|
||||
// in `table`
|
||||
flag = flag | 0;
|
||||
if (flag && (arg & flag) === flag) {
|
||||
flags.push(table[flag]);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the combined bitmask value
|
||||
table[arg] = flags.join(" | ") || arg;
|
||||
return table[arg];
|
||||
}
|
|
@ -9,8 +9,10 @@ DevToolsModules(
|
|||
'audionodes.json',
|
||||
'automation-timeline.js',
|
||||
'breakpoint-actor-map.js',
|
||||
'call-watcher.js',
|
||||
'css-grid-utils.js',
|
||||
'event-loop.js',
|
||||
'function-call.js',
|
||||
'make-debugger.js',
|
||||
'map-uri-to-addon-id.js',
|
||||
'shapes-utils.js',
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
const { Cu } = require("chrome");
|
||||
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
const { CallWatcherActor } = require("devtools/server/actors/call-watcher");
|
||||
const { CallWatcher } = require("devtools/server/actors/utils/call-watcher");
|
||||
const { createValueGrip } = require("devtools/server/actors/object/utils");
|
||||
const AutomationTimeline = require("./utils/automation-timeline");
|
||||
const {
|
||||
|
@ -455,7 +455,7 @@ exports.WebAudioActor = protocol.ActorClassWithSpec(webAudioSpec, {
|
|||
|
||||
this._initialized = true;
|
||||
|
||||
this._callWatcher = new CallWatcherActor(this.conn, this.targetActor);
|
||||
this._callWatcher = new CallWatcher(this.conn, this.targetActor);
|
||||
this._callWatcher.onCall = this._onContentFunctionCall;
|
||||
this._callWatcher.setup({
|
||||
tracedGlobals: AUDIO_GLOBALS,
|
||||
|
|
|
@ -328,11 +328,6 @@ var DebuggerServer = {
|
|||
constructor: "InspectorActor",
|
||||
type: { target: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/call-watcher", {
|
||||
prefix: "callWatcher",
|
||||
constructor: "CallWatcherActor",
|
||||
type: { target: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/canvas", {
|
||||
prefix: "canvas",
|
||||
constructor: "CanvasActor",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
|
||||
const { functionCallSpec } = require("devtools/shared/specs/function-call");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
|
||||
/**
|
||||
|
@ -33,34 +33,20 @@ const FunctionCallFront = protocol.FrontClassWithSpec(functionCallSpec, {
|
|||
|
||||
exports.FunctionCallFront = FunctionCallFront;
|
||||
|
||||
/**
|
||||
* The corresponding Front object for the CallWatcherActor.
|
||||
*/
|
||||
var CallWatcherFront =
|
||||
exports.CallWatcherFront =
|
||||
protocol.FrontClassWithSpec(callWatcherSpec, {
|
||||
initialize: function(client, { callWatcherActor }) {
|
||||
protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor });
|
||||
this.manage(this);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Constants.
|
||||
*/
|
||||
CallWatcherFront.METHOD_FUNCTION = 0;
|
||||
CallWatcherFront.GETTER_FUNCTION = 1;
|
||||
CallWatcherFront.SETTER_FUNCTION = 2;
|
||||
exports.METHOD_FUNCTION = 0;
|
||||
exports.GETTER_FUNCTION = 1;
|
||||
exports.SETTER_FUNCTION = 2;
|
||||
|
||||
CallWatcherFront.KNOWN_METHODS = {};
|
||||
|
||||
CallWatcherFront.KNOWN_METHODS.CanvasRenderingContext2D = {
|
||||
const CanvasRenderingContext2D = {
|
||||
drawWindow: {
|
||||
enums: new Set([6])
|
||||
},
|
||||
};
|
||||
|
||||
CallWatcherFront.KNOWN_METHODS.WebGLRenderingContext = {
|
||||
const WebGLRenderingContext = {
|
||||
activeTexture: {
|
||||
enums: new Set([0]),
|
||||
},
|
||||
|
@ -221,3 +207,5 @@ CallWatcherFront.KNOWN_METHODS.WebGLRenderingContext = {
|
|||
enums: new Set([2])
|
||||
},
|
||||
};
|
||||
|
||||
exports.KNOWN_METHODS = { CanvasRenderingContext2D, WebGLRenderingContext };
|
|
@ -12,13 +12,13 @@ DevToolsModules(
|
|||
'accessibility.js',
|
||||
'actor-registry.js',
|
||||
'animation.js',
|
||||
'call-watcher.js',
|
||||
'canvas.js',
|
||||
'css-properties.js',
|
||||
'csscoverage.js',
|
||||
'device.js',
|
||||
'emulation.js',
|
||||
'framerate.js',
|
||||
'function-call.js',
|
||||
'highlighters.js',
|
||||
'inspector.js',
|
||||
'layout.js',
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* 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 protocol = require("devtools/shared/protocol");
|
||||
const { RetVal, generateActorSpec } = protocol;
|
||||
|
||||
/**
|
||||
* Type describing a single function call in a stack trace.
|
||||
*/
|
||||
protocol.types.addDictType("call-stack-item", {
|
||||
name: "string",
|
||||
file: "string",
|
||||
line: "number"
|
||||
});
|
||||
|
||||
/**
|
||||
* Type describing an overview of a function call.
|
||||
*/
|
||||
protocol.types.addDictType("call-details", {
|
||||
type: "number",
|
||||
name: "string",
|
||||
stack: "array:call-stack-item"
|
||||
});
|
||||
|
||||
const functionCallSpec = generateActorSpec({
|
||||
typeName: "function-call",
|
||||
|
||||
methods: {
|
||||
getDetails: {
|
||||
response: { info: RetVal("call-details") }
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
exports.functionCallSpec = functionCallSpec;
|
|
@ -53,11 +53,6 @@ const Types = exports.__TypesForTests = [
|
|||
spec: "devtools/shared/specs/breakpoint",
|
||||
front: null,
|
||||
},
|
||||
{
|
||||
types: ["function-call", "call-watcher"],
|
||||
spec: "devtools/shared/specs/call-watcher",
|
||||
front: "devtools/shared/fronts/call-watcher",
|
||||
},
|
||||
{
|
||||
types: ["frame-snapshot", "canvas"],
|
||||
spec: "devtools/shared/specs/canvas",
|
||||
|
@ -100,6 +95,11 @@ const Types = exports.__TypesForTests = [
|
|||
spec: "devtools/shared/specs/framerate",
|
||||
front: "devtools/shared/fronts/framerate",
|
||||
},
|
||||
{
|
||||
types: ["function-call"],
|
||||
spec: "devtools/shared/specs/function-call",
|
||||
front: "devtools/shared/fronts/function-call",
|
||||
},
|
||||
/* heap snapshot has old fashion client and no front */
|
||||
{
|
||||
types: ["heapSnapshotFile"],
|
||||
|
|
|
@ -15,7 +15,6 @@ DevToolsModules(
|
|||
'actor-registry.js',
|
||||
'animation.js',
|
||||
'breakpoint.js',
|
||||
'call-watcher.js',
|
||||
'canvas.js',
|
||||
'css-properties.js',
|
||||
'csscoverage.js',
|
||||
|
@ -24,6 +23,7 @@ DevToolsModules(
|
|||
'environment.js',
|
||||
'frame.js',
|
||||
'framerate.js',
|
||||
'function-call.js',
|
||||
'heap-snapshot-file.js',
|
||||
'highlighters.js',
|
||||
'index.js',
|
||||
|
|
Загрузка…
Ссылка в новой задаче