Bug 934163 - Improve performance of tracer actors; r=past

This commit is contained in:
Nick Fitzgerald 2013-11-25 10:11:53 -08:00
parent 3a05d32ab8
commit 12f3243085
8 changed files with 310 additions and 545 deletions

View File

@ -1700,11 +1700,6 @@ function TraceClient(aClient, aActor) {
this._activeTraces = new Set();
this._waitingPackets = new Map();
this._expectedPacket = 0;
this.onPacket = this.onPacket.bind(this);
this._client.addListener(UnsolicitedNotifications.enteredFrame, this.onPacket);
this._client.addListener(UnsolicitedNotifications.exitedFrame, this.onPacket);
this.request = this._client.request;
}
@ -1777,32 +1772,9 @@ TraceClient.prototype = {
return aResponse;
},
telemetry: "STOPTRACE"
}),
/**
* Called when the trace actor notifies that a frame has been
* entered or exited.
*
* @param aEvent string
* The type of the unsolicited packet (enteredFrame|exitedFrame).
*
* @param aPacket object
* Packet received over the RDP from the trace actor.
*/
onPacket: function JSTC_onPacket(aEvent, aPacket) {
this._waitingPackets.set(aPacket.sequence, aPacket);
while (this._waitingPackets.has(this._expectedPacket)) {
let packet = this._waitingPackets.get(this._expectedPacket);
this._waitingPackets.delete(this._expectedPacket);
this.notify(packet.type, packet);
this._expectedPacket++;
}
}
})
};
eventSource(TraceClient.prototype);
/**
* Grip clients are used to retrieve information about the relevant object.
*

View File

@ -14,6 +14,39 @@ const { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
const { setTimeout } = require("sdk/timers");
/**
* The number of milliseconds we should buffer frame enter/exit packets before
* sending.
*/
const BUFFER_SEND_DELAY = 50;
/**
* The maximum number of arguments we will send for any single function call.
*/
const MAX_ARGUMENTS = 5;
/**
* The maximum number of an object's properties we will serialize.
*/
const MAX_PROPERTIES = 5;
/**
* The complete set of trace types supported.
*/
const TRACE_TYPES = new Set([
"time",
"return",
"throw",
"yield",
"name",
"location",
"callsite",
"parameterNames",
"arguments"
]);
/**
* Creates a TraceActor. TraceActor provides a stream of function
* call/return packets to a remote client gathering a full trace.
@ -24,11 +57,18 @@ function TraceActor(aConn, aParentActor)
this._activeTraces = new MapStack();
this._totalTraces = 0;
this._startTime = 0;
// Keep track of how many different trace requests have requested what kind of
// tracing info. This way we can minimize the amount of data we are collecting
// at any given time.
this._requestsForTraceType = Object.create(null);
for (let type of TraceTypes.types) {
for (let type of TRACE_TYPES) {
this._requestsForTraceType[type] = 0;
}
this._sequence = 0;
this._bufferSendTimer = null;
this._buffer = [];
this.global = aParentActor.window.wrappedJSObject;
}
@ -41,24 +81,19 @@ TraceActor.prototype = {
get tracing() { return this._attached && this._activeTraces.size > 0; },
/**
* Handle a TraceTypes.Events event by calling each handler which has been
* requested by an active trace and adding its result to the packet.
*
* @param aEvent string
* The event to dispatch.
*
* @param aPacket object
* The debugger protocol packet.
*
* @param aArgs object
* The arguments object for the handler.
* Buffer traces and only send them every BUFFER_SEND_DELAY milliseconds.
*/
_handleEvent: function(aEvent, aPacket, aArgs) {
let handlersForEvent = TraceTypes.handlers[aEvent];
for (let traceType in handlersForEvent) {
if (this._requestsForTraceType[traceType]) {
aPacket[traceType] = handlersForEvent[traceType].call(null, aArgs);
}
_send: function(aPacket) {
this._buffer.push(aPacket);
if (this._bufferSendTimer === null) {
this._bufferSendTimer = setTimeout(() => {
this.conn.send({
from: this.actorID,
type: "traces",
traces: this._buffer.splice(0, this._buffer.length)
});
this._bufferSendTimer = null;
}, BUFFER_SEND_DELAY);
}
},
@ -151,7 +186,11 @@ TraceActor.prototype = {
this._attached = true;
return { type: "attached", traceTypes: TraceTypes.types };
return {
type: "attached",
traceTypes: Object.keys(this._requestsForTraceType)
.filter(k => !!this._requestsForTraceType[k])
};
},
/**
@ -168,7 +207,7 @@ TraceActor.prototype = {
this.dbg = null;
this._attached = false;
this.conn.send({ from: this.actorID, type: "detached" });
return { type: "detached" };
},
/**
@ -179,7 +218,7 @@ TraceActor.prototype = {
*/
onStartTrace: function(aRequest) {
for (let traceType of aRequest.trace) {
if (TraceTypes.types.indexOf(traceType) < 0) {
if (!TRACE_TYPES.has(traceType)) {
return {
error: "badParameterType",
message: "No such trace type: " + traceType
@ -190,7 +229,7 @@ TraceActor.prototype = {
if (this.idle) {
this.dbg.enabled = true;
this._sequence = 0;
this._startTime = +new Date;
this._startTime = Date.now();
}
// Start recording all requested trace types.
@ -257,19 +296,61 @@ TraceActor.prototype = {
onEnterFrame: function(aFrame) {
let callee = aFrame.callee;
let packet = {
from: this.actorID,
type: "enteredFrame",
sequence: this._sequence++
};
this._handleEvent(TraceTypes.Events.enterFrame, packet, {
frame: aFrame,
startTime: this._startTime
});
if (this._requestsForTraceType.name) {
packet.name = aFrame.callee
? aFrame.callee.displayName || "(anonymous function)"
: "(" + aFrame.type + ")";
}
if (this._requestsForTraceType.location && aFrame.script) {
// We should return the location of the start of the script, but
// Debugger.Script does not provide complete start locations (bug
// 901138). Instead, return the current offset (the location of the first
// statement in the function).
packet.location = {
url: aFrame.script.url,
line: aFrame.script.getOffsetLine(aFrame.offset),
column: getOffsetColumn(aFrame.offset, aFrame.script)
};
}
if (this._requestsForTraceType.callsite
&& aFrame.older
&& aFrame.older.script) {
let older = aFrame.older;
packet.callsite = {
url: older.script.url,
line: older.script.getOffsetLine(older.offset),
column: getOffsetColumn(older.offset, older.script)
};
}
if (this._requestsForTraceType.time) {
packet.time = Date.now() - this._startTime;
}
if (this._requestsForTraceType.parameterNames && aFrame.callee) {
packet.parameterNames = aFrame.callee.parameterNames;
}
if (this._requestsForTraceType.arguments && aFrame.arguments) {
packet.arguments = [];
let i = 0;
for (let arg of aFrame.arguments) {
if (i++ > MAX_ARGUMENTS) {
break;
}
packet.arguments.push(createValueGrip(arg, true));
}
}
aFrame.onPop = this.onExitFrame.bind(this);
this.conn.send(packet);
this._send(packet);
},
/**
@ -281,7 +362,6 @@ TraceActor.prototype = {
*/
onExitFrame: function(aCompletion) {
let packet = {
from: this.actorID,
type: "exitedFrame",
sequence: this._sequence++,
};
@ -296,12 +376,25 @@ TraceActor.prototype = {
packet.why = "throw";
}
this._handleEvent(TraceTypes.Events.exitFrame, packet, {
value: aCompletion,
startTime: this._startTime
});
if (this._requestsForTraceType.time) {
packet.time = Date.now() - this._startTime;
}
this.conn.send(packet);
if (aCompletion) {
if (this._requestsForTraceType.return) {
packet.return = createValueGrip(aCompletion.return, true);
}
if (this._requestsForTraceType.throw) {
packet.throw = createValueGrip(aCompletion.throw, true);
}
if (this._requestsForTraceType.yield) {
packet.yield = createValueGrip(aCompletion.yield, true);
}
}
this._send(packet);
}
};
@ -419,91 +512,6 @@ MapStack.prototype = {
}
};
/**
* TraceTypes is a collection of handlers which generate optional trace
* information. Handlers are associated with an event (from TraceTypes.Event)
* and a trace type, and return a value to be embedded in the packet associated
* with that event.
*/
let TraceTypes = {
handlers: {},
types: [],
register: function(aType, aEvent, aHandler) {
if (!this.handlers[aEvent]) {
this.handlers[aEvent] = {};
}
this.handlers[aEvent][aType] = aHandler;
if (this.types.indexOf(aType) < 0) {
this.types.push(aType);
}
}
};
TraceTypes.Events = {
"enterFrame": "enterFrame",
"exitFrame": "exitFrame"
};
TraceTypes.register("name", TraceTypes.Events.enterFrame, function({ frame }) {
return frame.callee
? frame.callee.displayName || "(anonymous function)"
: "(" + frame.type + ")";
});
TraceTypes.register("location", TraceTypes.Events.enterFrame, function({ frame }) {
if (!frame.script) {
return undefined;
}
// We should return the location of the start of the script, but
// Debugger.Script does not provide complete start locations
// (bug 901138). Instead, return the current offset (the location of
// the first statement in the function).
return {
url: frame.script.url,
line: frame.script.getOffsetLine(frame.offset),
column: getOffsetColumn(frame.offset, frame.script)
};
});
TraceTypes.register("callsite", TraceTypes.Events.enterFrame, function({ frame }) {
let older = frame.older;
if (!older || !older.script) {
return undefined;
}
return {
url: older.script.url,
line: older.script.getOffsetLine(older.offset),
column: getOffsetColumn(older.offset, older.script)
};
});
TraceTypes.register("time", TraceTypes.Events.enterFrame, timeSinceTraceStarted);
TraceTypes.register("time", TraceTypes.Events.exitFrame, timeSinceTraceStarted);
TraceTypes.register("parameterNames", TraceTypes.Events.enterFrame, function({ frame }) {
return frame.callee ? frame.callee.parameterNames : undefined;
});
TraceTypes.register("arguments", TraceTypes.Events.enterFrame, function({ frame }) {
if (!frame.arguments) {
return undefined;
}
let args = Array.prototype.slice.call(frame.arguments);
return args.map(arg => createValueGrip(arg, true));
});
TraceTypes.register("return", TraceTypes.Events.exitFrame,
serializeCompletionValue.bind(null, "return"));
TraceTypes.register("throw", TraceTypes.Events.exitFrame,
serializeCompletionValue.bind(null, "throw"));
TraceTypes.register("yield", TraceTypes.Events.exitFrame,
serializeCompletionValue.bind(null, "yield"));
// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when
// it is implemented.
function getOffsetColumn(aOffset, aScript) {
@ -530,27 +538,6 @@ function getOffsetColumn(aOffset, aScript) {
return bestOffsetMapping.columnNumber;
}
/**
* Returns elapsed time since the given start time.
*/
function timeSinceTraceStarted({ startTime }) {
return +new Date - startTime;
}
/**
* Creates a value grip for the given completion value, to be
* serialized by JSON.stringify.
*
* @param aType string
* The type of completion value to serialize (return, throw, or yield).
*/
function serializeCompletionValue(aType, { value }) {
if (!Object.hasOwnProperty.call(value, aType)) {
return undefined;
}
return createValueGrip(value[aType], true);
}
// Serialization helper functions. Largely copied from script.js and modified
// for use in serialization rather than object actor requests.
@ -668,13 +655,19 @@ function objectDescriptor(aObject) {
desc.safeGetterValues = Object.create(null);
return desc;
}
let i = 0;
for (let name of names) {
ownProperties[name] = propertyDescriptor(name, aObject);
if (i++ > MAX_PROPERTIES) {
break;
}
let desc = propertyDescriptor(name, aObject);
if (desc) {
ownProperties[name] = desc;
}
}
desc.prototype = createValueGrip(aObject.proto);
desc.ownProperties = ownProperties;
desc.safeGetterValues = findSafeGetterValues(ownProperties, aObject);
return desc;
}
@ -708,7 +701,8 @@ function propertyDescriptor(aName, aObject) {
};
}
if (!desc) {
// Skip objects since we only support shallow objects anyways.
if (!desc || typeof desc.value == "object" && desc.value !== null) {
return undefined;
}
@ -730,104 +724,3 @@ function propertyDescriptor(aName, aObject) {
}
return retval;
}
/**
* Find the safe getter values for the given Debugger.Object.
*
* @param aOwnProperties object
* The object that holds the list of known ownProperties for |aObject|.
*
* @param Debugger.Object object
* The object to find safe getter values for.
*
* @return object
* An object that maps property names to safe getter descriptors.
*/
function findSafeGetterValues(aOwnProperties, aObject) {
let safeGetterValues = Object.create(null);
let obj = aObject;
let level = 0;
while (obj) {
let getters = findSafeGetters(obj);
for (let name of getters) {
// Avoid overwriting properties from prototypes closer to this.obj. Also
// avoid providing safeGetterValues from prototypes if property |name|
// is already defined as an own property.
if (name in safeGetterValues ||
(obj != aObject && name in aOwnProperties)) {
continue;
}
let desc = null, getter = null;
try {
desc = obj.getOwnPropertyDescriptor(name);
getter = desc.get;
} catch (ex) {
// The above can throw if the cache becomes stale.
}
if (!getter) {
continue;
}
let result = getter.call(aObject);
if (result && !("throw" in result)) {
let getterValue = undefined;
if ("return" in result) {
getterValue = result.return;
} else if ("yield" in result) {
getterValue = result.yield;
}
// WebIDL attributes specified with the LenientThis extended attribute
// return undefined and should be ignored.
if (getterValue !== undefined) {
safeGetterValues[name] = {
getterValue: createValueGrip(getterValue),
getterPrototypeLevel: level,
enumerable: desc.enumerable,
writable: level == 0 ? desc.writable : true,
};
}
}
}
obj = obj.proto;
level++;
}
return safeGetterValues;
}
/**
* Find the safe getters for a given Debugger.Object. Safe getters are native
* getters which are safe to execute.
*
* @param Debugger.Object aObject
* The Debugger.Object where you want to find safe getters.
*
* @return Set
* A Set of names of safe getters.
*/
function findSafeGetters(aObject) {
let getters = new Set();
for (let name of aObject.getOwnPropertyNames()) {
let desc = null;
try {
desc = aObject.getOwnPropertyDescriptor(name);
} catch (e) {
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
// allowed (bug 560072).
}
if (!desc || desc.value !== undefined || !("get" in desc)) {
continue;
}
let fn = desc.get;
if (fn && fn.callable && fn.class == "Function" &&
fn.script === undefined) {
getters.add(name);
}
}
return getters;
}

View File

@ -31,43 +31,50 @@ function run_test()
function test_enter_exit_frame()
{
let packetsSeen = 0;
let packetNames = [];
let tracesSeen = 0;
let traceNames = [];
let traceStopped = defer();
gTraceClient.addListener("enteredFrame", function(aEvent, aPacket) {
packetsSeen++;
do_check_eq(aPacket.type, "enteredFrame",
'enteredFrame response should have type "enteredFrame"');
do_check_eq(typeof aPacket.sequence, "number",
'enteredFrame response should have sequence number');
do_check_true(!isNaN(aPacket.sequence),
'enteredFrame sequence should be a number');
do_check_eq(typeof aPacket.name, "string",
'enteredFrame response should have function name');
packetNames[aPacket.sequence] = aPacket.name;
});
gClient.addListener("traces", function onTraces(aEvent, { traces }) {
for (let t of traces) {
tracesSeen++;
gTraceClient.addListener("exitedFrame", function(aEvent, aPacket) {
packetsSeen++;
do_check_eq(aPacket.type, "exitedFrame",
'exitedFrame response should have type "exitedFrame"');
do_check_eq(typeof aPacket.sequence, "number",
'exitedFrame response should have sequence number');
do_check_true(!isNaN(aPacket.sequence),
'exitedFrame sequence should be a number');
if (t.type == "enteredFrame") {
do_check_eq(t.type, "enteredFrame",
'enteredFrame response should have type "enteredFrame"');
do_check_eq(typeof t.sequence, "number",
'enteredFrame response should have sequence number');
do_check_true(!isNaN(t.sequence),
'enteredFrame sequence should be a number');
do_check_eq(typeof t.name, "string",
'enteredFrame response should have function name');
traceNames[t.sequence] = t.name;
} else {
do_check_eq(t.type, "exitedFrame",
'exitedFrame response should have type "exitedFrame"');
do_check_eq(typeof t.sequence, "number",
'exitedFrame response should have sequence number');
do_check_true(!isNaN(t.sequence),
'exitedFrame sequence should be a number');
}
if (tracesSeen == 10) {
gClient.removeListener("traces", onTraces);
traceStopped.resolve();
}
}
});
start_trace()
.then(eval_code)
.then(() => traceStopped.promise)
.then(stop_trace)
.then(function() {
do_check_eq(packetsSeen, 10,
'Should have seen two packets for each of 5 stack frames');
do_check_eq(packetNames[2], "baz",
do_check_eq(traceNames[2], "baz",
'Should have entered "baz" frame in third packet');
do_check_eq(packetNames[3], "bar",
do_check_eq(traceNames[3], "bar",
'Should have entered "bar" frame in fourth packet');
do_check_eq(packetNames[4], "foo",
do_check_eq(traceNames[4], "foo",
'Should have entered "foo" frame in fifth packet');
finishClient(gClient);
});

View File

@ -6,7 +6,7 @@
* "arguments", and "return" trace types.
*/
let {defer} = devtools.require("sdk/core/promise");
let { defer } = devtools.require("sdk/core/promise");
var gDebuggee;
var gClient;
@ -28,106 +28,90 @@ function run_test()
do_test_pending();
}
function check_number(value, name)
function check_number(value)
{
do_check_eq(typeof value, "number", name + ' should be a number');
do_check_true(!isNaN(value), name + ' should be a number');
do_check_eq(typeof value, "number");
do_check_true(!isNaN(value));
}
function check_location(actual, expected, name)
function check_location(actual, expected)
{
do_check_eq(typeof actual, "object",
name + ' missing expected source location');
do_check_eq(typeof actual, "object");
check_number(actual.line, name + ' line');
check_number(actual.column, name + ' column');
check_number(actual.line);
check_number(actual.column);
do_check_eq(actual.url, expected.url,
name + ' location should have url ' + expected.url);
do_check_eq(actual.line, expected.line,
name + ' location should have source line of ' + expected.line);
do_check_eq(actual.column, expected.column,
name + ' location should have source column of ' + expected.line);
do_check_eq(actual.url, expected.url);
do_check_eq(actual.line, expected.line);
do_check_eq(actual.column, expected.column);
}
function test_enter_exit_frame()
{
let packets = [];
let traces = [];
let traceStopped = defer();
gTraceClient.addListener("enteredFrame", function(aEvent, aPacket) {
do_check_eq(aPacket.type, "enteredFrame",
'enteredFrame response should have type "enteredFrame"');
do_check_eq(typeof aPacket.name, "string",
'enteredFrame response should have function name');
do_check_eq(typeof aPacket.location, "object",
'enteredFrame response should have source location');
gClient.addListener("traces", function(aEvent, aPacket) {
for (let t of aPacket.traces) {
if (t.type == "enteredFrame") {
do_check_eq(typeof t.name, "string");
do_check_eq(typeof t.location, "object");
check_number(aPacket.sequence, 'enteredFrame sequence');
check_number(aPacket.time, 'enteredFrame time');
check_number(aPacket.location.line, 'enteredFrame source line');
check_number(aPacket.location.column, 'enteredFrame source column');
if (aPacket.callsite) {
check_number(aPacket.callsite.line, 'enteredFrame callsite line');
check_number(aPacket.callsite.column, 'enteredFrame callsite column');
check_number(t.sequence);
check_number(t.time);
check_number(t.location.line);
check_number(t.location.column);
if (t.callsite) {
check_number(t.callsite.line);
check_number(t.callsite.column);
}
} else {
check_number(t.sequence);
check_number(t.time);
}
traces[t.sequence] = t;
if (traces.length === 4) {
traceStopped.resolve();
}
}
packets[aPacket.sequence] = aPacket;
});
gTraceClient.addListener("exitedFrame", function(aEvent, aPacket) {
do_check_eq(aPacket.type, "exitedFrame",
'exitedFrame response should have type "exitedFrame"');
check_number(aPacket.sequence, 'exitedFrame sequence');
check_number(aPacket.time, 'exitedFrame time');
packets[aPacket.sequence] = aPacket;
});
start_trace()
.then(eval_code)
.then(() => traceStopped.promise)
.then(stop_trace)
.then(function() {
let url = getFileUrl("tracerlocations.js");
check_location(packets[0].location, { url: url, line: 1, column: 0 },
'global entry packet');
check_location(traces[0].location, { url: url, line: 1, column: 0 });
do_check_eq(packets[1].name, "foo",
'Second packet in sequence should be entry to "foo" frame');
do_check_eq(traces[1].name, "foo");
// foo's definition is at tracerlocations.js:3:0, but
// Debugger.Script does not provide complete definition
// locations (bug 901138). tracerlocations.js:4:2 is the first
// statement in the function (used as an approximation).
check_location(packets[1].location, { url: url, line: 4, column: 2 },
'foo source');
check_location(packets[1].callsite, { url: url, line: 8, column: 0 },
'foo callsite');
check_location(traces[1].location, { url: url, line: 4, column: 2 });
check_location(traces[1].callsite, { url: url, line: 8, column: 0 });
do_check_eq(typeof packets[1].parameterNames, "object",
'foo entry packet should have parameterNames');
do_check_eq(packets[1].parameterNames.length, 1,
'foo should have only one formal parameter');
do_check_eq(packets[1].parameterNames[0], "x",
'foo should have formal parameter "x"');
do_check_eq(typeof traces[1].parameterNames, "object");
do_check_eq(traces[1].parameterNames.length, 1);
do_check_eq(traces[1].parameterNames[0], "x");
do_check_eq(typeof packets[1].arguments, "object",
'foo entry packet should have arguments');
do_check_true(Array.isArray(packets[1].arguments),
'foo entry packet arguments should be an array');
do_check_eq(packets[1].arguments.length, 1,
'foo should have only one actual parameter');
do_check_eq(packets[1].arguments[0], 42,
'foo should have actual parameter 42');
do_check_eq(typeof traces[1].arguments, "object");
do_check_true(Array.isArray(traces[1].arguments));
do_check_eq(traces[1].arguments.length, 1);
do_check_eq(traces[1].arguments[0], 42);
do_check_eq(typeof packets[2].return, "string",
'Fourth packet in sequence should be exit from "foo" frame');
do_check_eq(packets[2].return, "bar",
'foo should return "bar"');
do_check_eq(typeof traces[2].return, "string");
do_check_eq(traces[2].return, "bar");
finishClient(gClient);
}, error => {
DevToolsUtils.reportException("test_trace_actor-05.js", error);
do_check_true(false);
});
}

View File

@ -6,7 +6,7 @@
* and exitedFrame packets.
*/
let {defer} = devtools.require("sdk/core/promise");
let { defer } = devtools.require("sdk/core/promise");
var gDebuggee;
var gClient;
@ -30,11 +30,20 @@ function run_test()
function test_enter_exit_frame()
{
gTraceClient.addListener("enteredFrame", check_packet);
gTraceClient.addListener("exitedFrame", check_packet);
const traceStopped = defer();
gClient.addListener("traces", (aEvent, { traces }) => {
for (let t of traces) {
check_trace(t);
if (t.sequence === 27) {
traceStopped.resolve();
}
}
});
start_trace()
.then(eval_code)
.then(() => traceStopped.promise)
.then(stop_trace)
.then(function() {
finishClient(gClient);
@ -58,17 +67,23 @@ function eval_code()
let circular = {};
circular.self = circular;
// Make sure there is only 5 properties per object because that is the value
// of MAX_PROPERTIES in the server.
let obj = {
num: 0,
str: "foo",
bool: false,
undef: undefined,
nil: null,
nil: null
};
let obj2 = {
inf: Infinity,
ninf: -Infinity,
nan: NaN,
nzero: -0,
obj: circular,
obj: circular
};
let obj3 = {
arr: [1,2,3,4,5]
};
@ -83,6 +98,8 @@ function eval_code()
identity(NaN);
identity(-0);
identity(obj);
identity(obj2);
identity(obj3);
} + ")()");
}
@ -93,17 +110,15 @@ function stop_trace()
return deferred.promise;
}
function check_packet(aEvent, aPacket)
function check_trace(aTrace)
{
let value = (aPacket.type === "enteredFrame" && aPacket.arguments)
? aPacket.arguments[0]
: aPacket.return;
switch(aPacket.sequence) {
let value = (aTrace.type === "enteredFrame" && aTrace.arguments)
? aTrace.arguments[0]
: aTrace.return;
switch(aTrace.sequence) {
case 2:
do_check_eq(typeof aPacket.arguments, "object",
"zero-argument function call should send arguments list");
do_check_eq(aPacket.arguments.length, 0,
"zero-argument function call should send zero-length arguments list");
do_check_eq(typeof aTrace.arguments, "object");
do_check_eq(aTrace.arguments.length, 0);
break;
case 3:
check_value(value, "object", "undefined");
@ -146,8 +161,15 @@ function check_packet(aEvent, aPacket)
break;
case 22:
case 23:
check_object(aPacket.type, value);
check_obj(aTrace.type, value);
break;
case 24:
case 25:
check_obj2(aTrace.type, value);
break;
case 26:
case 27:
check_obj3(aTrace.type, value);
}
}
@ -157,67 +179,50 @@ function check_value(aActual, aExpectedType, aExpectedValue)
do_check_eq(aExpectedType === "object" ? aActual.type : aActual, aExpectedValue);
}
function check_object(aType, aObj) {
do_check_eq(typeof aObj, "object",
'serialized object should be present in packet');
do_check_eq(typeof aObj.prototype, "object",
'serialized object should have prototype');
do_check_eq(typeof aObj.ownProperties, "object",
'serialized object should have ownProperties list');
do_check_eq(typeof aObj.safeGetterValues, "object",
'serialized object should have safeGetterValues');
function check_obj(aType, aObj) {
do_check_eq(typeof aObj, "object");
do_check_eq(typeof aObj.ownProperties, "object");
do_check_eq(typeof aObj.ownProperties.num, "object",
'serialized object should have property "num"');
do_check_eq(typeof aObj.ownProperties.str, "object",
'serialized object should have property "str"');
do_check_eq(typeof aObj.ownProperties.bool, "object",
'serialized object should have property "bool"');
do_check_eq(typeof aObj.ownProperties.undef, "object",
'serialized object should have property "undef"');
do_check_eq(typeof aObj.ownProperties.undef.value, "object",
'serialized object property "undef" should be a grip');
do_check_eq(typeof aObj.ownProperties.nil, "object",
'serialized object should have property "nil"');
do_check_eq(typeof aObj.ownProperties.nil.value, "object",
'serialized object property "nil" should be a grip');
do_check_eq(typeof aObj.ownProperties.obj, "object",
'serialized object should have property "aObj"');
do_check_eq(typeof aObj.ownProperties.obj.value, "object",
'serialized object property "aObj" should be a grip');
do_check_eq(typeof aObj.ownProperties.arr, "object",
'serialized object should have property "arr"');
do_check_eq(typeof aObj.ownProperties.arr.value, "object",
'serialized object property "arr" should be a grip');
do_check_eq(typeof aObj.ownProperties.inf, "object",
'serialized object should have property "inf"');
do_check_eq(typeof aObj.ownProperties.inf.value, "object",
'serialized object property "inf" should be a grip');
do_check_eq(typeof aObj.ownProperties.ninf, "object",
'serialized object should have property "ninf"');
do_check_eq(typeof aObj.ownProperties.ninf.value, "object",
'serialized object property "ninf" should be a grip');
do_check_eq(typeof aObj.ownProperties.nan, "object",
'serialized object should have property "nan"');
do_check_eq(typeof aObj.ownProperties.nan.value, "object",
'serialized object property "nan" should be a grip');
do_check_eq(typeof aObj.ownProperties.nzero, "object",
'serialized object should have property "nzero"');
do_check_eq(typeof aObj.ownProperties.nzero.value, "object",
'serialized object property "nzero" should be a grip');
do_check_eq(aObj.prototype.type, "object");
do_check_eq(typeof aObj.ownProperties.num, "object");
do_check_eq(aObj.ownProperties.num.value, 0);
do_check_eq(typeof aObj.ownProperties.str, "object");
do_check_eq(aObj.ownProperties.str.value, "foo");
do_check_eq(typeof aObj.ownProperties.bool, "object");
do_check_eq(aObj.ownProperties.bool.value, false);
do_check_eq(typeof aObj.ownProperties.undef, "object");
do_check_eq(typeof aObj.ownProperties.undef.value, "object");
do_check_eq(aObj.ownProperties.undef.value.type, "undefined");
do_check_eq(typeof aObj.ownProperties.nil, "object");
do_check_eq(typeof aObj.ownProperties.nil.value, "object");
do_check_eq(aObj.ownProperties.nil.value.type, "null");
do_check_eq(aObj.ownProperties.obj.value.type, "object");
do_check_eq(aObj.ownProperties.obj.value.class, "Object");
do_check_eq(aObj.ownProperties.arr.value.type, "object");
do_check_eq(aObj.ownProperties.arr.value.class, "Array");
do_check_eq(aObj.ownProperties.inf.value.type, "Infinity");
do_check_eq(aObj.ownProperties.ninf.value.type, "-Infinity");
do_check_eq(aObj.ownProperties.nan.value.type, "NaN");
do_check_eq(aObj.ownProperties.nzero.value.type, "-0");
}
function check_obj2(aType, aObj) {
do_check_eq(typeof aObj.ownProperties.inf, "object");
do_check_eq(typeof aObj.ownProperties.inf.value, "object");
do_check_eq(aObj.ownProperties.inf.value.type, "Infinity");
do_check_eq(typeof aObj.ownProperties.ninf, "object");
do_check_eq(typeof aObj.ownProperties.ninf.value, "object");
do_check_eq(aObj.ownProperties.ninf.value.type, "-Infinity");
do_check_eq(typeof aObj.ownProperties.nan, "object");
do_check_eq(typeof aObj.ownProperties.nan.value, "object");
do_check_eq(aObj.ownProperties.nan.value.type, "NaN");
do_check_eq(typeof aObj.ownProperties.nzero, "object");
do_check_eq(typeof aObj.ownProperties.nzero.value, "object");
do_check_eq(aObj.ownProperties.nzero.value.type, "-0");
// Sub-objects aren't added.
do_check_eq(typeof aObj.ownProperties.obj, "undefined");
}
function check_obj3(aType, aObj) {
// Sub-objects aren't added.
do_check_eq(typeof aObj.ownProperties.arr, "undefined");
}

View File

@ -29,7 +29,13 @@ function run_test()
function test_exit_frame_whys()
{
gTraceClient.addListener("exitedFrame", check_packet);
gClient.addListener("traces", (aEvent, { traces }) => {
for (let t of traces) {
if (t.type == "exitedFrame") {
check_trace(t);
}
}
});
start_trace()
.then(eval_code)
@ -83,7 +89,7 @@ function stop_trace()
return deferred.promise;
}
function check_packet(aEvent, { sequence, why })
function check_trace(aEvent, { sequence, why })
{
switch(sequence) {
case 3:

View File

@ -1,101 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that TraceClient emits enteredFrame and exitedFrame events in
* order when receiving packets out of order.
*/
let {defer, resolve} = devtools.require("sdk/core/promise");
var gDebuggee;
var gClient;
var gTraceClient;
function run_test()
{
initTestTracerServer();
gDebuggee = addTestGlobal("test-tracer-actor");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestTab(gClient, "test-tracer-actor", function(aResponse, aTabClient) {
gClient.attachTracer(aResponse.traceActor, function(aResponse, aTraceClient) {
gTraceClient = aTraceClient;
test_packet_order();
});
});
});
do_test_pending();
}
function test_packet_order()
{
let sequence = 0;
function check_packet(aEvent, aPacket) {
do_check_eq(aPacket.sequence, sequence,
'packet should have sequence number ' + sequence);
sequence++;
}
gTraceClient.addListener("enteredFrame", check_packet);
gTraceClient.addListener("exitedFrame", check_packet);
start_trace()
.then(mock_packets)
.then(start_trace)
.then(mock_packets.bind(null, 14))
.then(stop_trace)
.then(stop_trace)
.then(function() {
// All traces were stopped, so the sequence number resets
sequence = 0;
return resolve();
})
.then(start_trace)
.then(mock_packets)
.then(stop_trace)
.then(function() {
finishClient(gClient);
});
}
function start_trace()
{
let deferred = defer();
gTraceClient.startTrace([], null, function() { deferred.resolve(); });
return deferred.promise;
}
function stop_trace()
{
let deferred = defer();
gTraceClient.stopTrace(null, function() { deferred.resolve(); });
return deferred.promise;
}
function mock_packets(s = 0)
{
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 5 });
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 3 });
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 2 });
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 4 });
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 1 });
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 7 });
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 8 });
// Triggers 0-5
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 0 });
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 9 });
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 10 });
// Triggers 6-10
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 6 });
// Each following packet is expected; event is fired immediately
gTraceClient.onPacket("", { type: "enteredFrame", sequence: s + 11 });
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 12 });
gTraceClient.onPacket("", { type: "exitedFrame", sequence: s + 13 });
}

View File

@ -190,4 +190,3 @@ reason = bug 820380
[test_trace_actor-06.js]
[test_trace_actor-07.js]
[test_ignore_caught_exceptions.js]
[test_trace_client-01.js]