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:
yulia 2018-09-20 13:41:26 +00:00
Родитель 4f0efc181d
Коммит 825e041cf6
23 изменённых файлов: 434 добавлений и 375 удалений

Просмотреть файл

@ -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',