2012-07-25 16:30:00 +00:00
|
|
|
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */
|
2012-02-07 17:22:30 +00:00
|
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
2012-05-21 11:12:37 +00:00
|
|
|
/* 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/. */
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
"use strict";
|
2012-08-30 21:10:07 +00:00
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
/**
|
|
|
|
* JSD2 actors.
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Creates a ThreadActor.
|
|
|
|
*
|
|
|
|
* ThreadActors manage a JSInspector object and manage execution/inspection
|
|
|
|
* of debuggees.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aHooks object
|
2012-02-10 07:46:11 +00:00
|
|
|
* An object with preNest and postNest methods for calling when entering
|
2012-06-03 13:39:50 +00:00
|
|
|
* and exiting a nested event loop, as well as addToBreakpointPool and
|
|
|
|
* removeFromBreakpointPool methods for handling breakpoint lifetime.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
|
|
|
function ThreadActor(aHooks)
|
|
|
|
{
|
|
|
|
this._state = "detached";
|
|
|
|
this._frameActors = [];
|
|
|
|
this._environmentActors = [];
|
|
|
|
this._hooks = aHooks ? aHooks : {};
|
2012-07-25 16:30:00 +00:00
|
|
|
this._scripts = {};
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
2012-08-06 09:32:00 +00:00
|
|
|
/**
|
|
|
|
* The breakpoint store must be shared across instances of ThreadActor so that
|
|
|
|
* page reloads don't blow away all of our breakpoints.
|
|
|
|
*/
|
|
|
|
ThreadActor._breakpointStore = {};
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
ThreadActor.prototype = {
|
|
|
|
actorPrefix: "context",
|
|
|
|
|
|
|
|
get state() { return this._state; },
|
|
|
|
|
2012-08-06 09:32:00 +00:00
|
|
|
get _breakpointStore() { return ThreadActor._breakpointStore; },
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
get threadLifetimePool() {
|
|
|
|
if (!this._threadLifetimePool) {
|
|
|
|
this._threadLifetimePool = new ActorPool(this.conn);
|
|
|
|
this.conn.addActorPool(this._threadLifetimePool);
|
|
|
|
}
|
|
|
|
return this._threadLifetimePool;
|
|
|
|
},
|
|
|
|
|
2012-08-24 07:41:02 +00:00
|
|
|
clearDebuggees: function TA_clearDebuggees() {
|
2012-09-20 21:15:15 +00:00
|
|
|
if (this.dbg) {
|
|
|
|
let debuggees = this.dbg.getDebuggees();
|
2012-08-24 07:41:02 +00:00
|
|
|
for (let debuggee of debuggees) {
|
2012-09-20 21:15:15 +00:00
|
|
|
this.dbg.removeDebuggee(debuggee);
|
2012-08-24 07:41:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.conn.removeActorPool(this._threadLifetimePool || undefined);
|
|
|
|
this._threadLifetimePool = null;
|
|
|
|
// Unless we carefully take apart the scripts table this way, we end up
|
|
|
|
// leaking documents. It would be nice to track this down carefully, once
|
|
|
|
// we have the appropriate tools.
|
|
|
|
for (let url in this._scripts) {
|
|
|
|
delete this._scripts[url];
|
|
|
|
}
|
|
|
|
this._scripts = {};
|
|
|
|
},
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
/**
|
2012-04-22 09:59:09 +00:00
|
|
|
* Add a debuggee global to the Debugger object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
|
|
|
addDebuggee: function TA_addDebuggee(aGlobal) {
|
|
|
|
// Use the inspector xpcom component to turn on debugging
|
|
|
|
// for aGlobal's compartment. Ideally this won't be necessary
|
|
|
|
// medium- to long-term, and will be managed by the engine
|
|
|
|
// instead.
|
|
|
|
|
2012-09-20 21:15:15 +00:00
|
|
|
if (!this.dbg) {
|
|
|
|
this.dbg = new Debugger();
|
|
|
|
this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
|
|
|
|
this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
|
|
|
|
this.dbg.onNewScript = this.onNewScript.bind(this);
|
2012-08-24 07:41:02 +00:00
|
|
|
// Keep the debugger disabled until a client attaches.
|
|
|
|
this.dbg.enabled = this._state != "detached";
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.dbg.addDebuggee(aGlobal);
|
2012-08-24 07:41:02 +00:00
|
|
|
for (let s of this.dbg.findScripts()) {
|
|
|
|
this._addScript(s);
|
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove a debuggee global from the JSInspector.
|
|
|
|
*/
|
|
|
|
removeDebugee: function TA_removeDebuggee(aGlobal) {
|
|
|
|
try {
|
|
|
|
this.dbg.removeDebuggee(aGlobal);
|
|
|
|
} catch(ex) {
|
|
|
|
// XXX: This debuggee has code currently executing on the stack,
|
|
|
|
// we need to save this for later.
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
disconnect: function TA_disconnect() {
|
2012-02-17 08:15:43 +00:00
|
|
|
if (this._state == "paused") {
|
|
|
|
this.onResume();
|
|
|
|
}
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
this._state = "exited";
|
2012-08-24 07:41:02 +00:00
|
|
|
|
|
|
|
this.clearDebuggees();
|
|
|
|
|
2012-09-20 21:15:15 +00:00
|
|
|
if (!this.dbg) {
|
2012-08-24 07:41:02 +00:00
|
|
|
return;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-09-20 21:15:15 +00:00
|
|
|
this.dbg.enabled = false;
|
|
|
|
this.dbg = null;
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disconnect the debugger and put the actor in the exited state.
|
|
|
|
*/
|
|
|
|
exit: function TA_exit() {
|
|
|
|
this.disconnect();
|
|
|
|
},
|
|
|
|
|
|
|
|
// Request handlers
|
|
|
|
onAttach: function TA_onAttach(aRequest) {
|
|
|
|
if (this.state === "exited") {
|
|
|
|
return { type: "exited" };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.state !== "detached") {
|
|
|
|
return { error: "wrongState" };
|
|
|
|
}
|
|
|
|
|
|
|
|
this._state = "attached";
|
|
|
|
|
|
|
|
this.dbg.enabled = true;
|
|
|
|
try {
|
|
|
|
// Put ourselves in the paused state.
|
|
|
|
// XXX: We need to put the debuggee in a paused state too.
|
|
|
|
let packet = this._paused();
|
|
|
|
if (!packet) {
|
|
|
|
return { error: "notAttached" };
|
|
|
|
}
|
|
|
|
packet.why = { type: "attached" };
|
|
|
|
|
|
|
|
// Send the response to the attach request now (rather than
|
|
|
|
// returning it), because we're going to start a nested event loop
|
|
|
|
// here.
|
|
|
|
this.conn.send(packet);
|
|
|
|
|
|
|
|
// Start a nested event loop.
|
|
|
|
this._nest();
|
|
|
|
|
|
|
|
// We already sent a response to this request, don't send one
|
|
|
|
// now.
|
|
|
|
return null;
|
|
|
|
} catch(e) {
|
2012-02-10 07:46:10 +00:00
|
|
|
Cu.reportError(e);
|
2012-02-07 17:22:30 +00:00
|
|
|
return { error: "notAttached", message: e.toString() };
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onDetach: function TA_onDetach(aRequest) {
|
|
|
|
this.disconnect();
|
|
|
|
return { type: "detached" };
|
|
|
|
},
|
|
|
|
|
2012-03-18 06:50:43 +00:00
|
|
|
/**
|
|
|
|
* Pause the debuggee, by entering a nested event loop, and return a 'paused'
|
|
|
|
* packet to the client.
|
|
|
|
*
|
|
|
|
* @param Debugger.Frame aFrame
|
|
|
|
* The newest debuggee frame in the stack.
|
|
|
|
* @param object aReason
|
|
|
|
* An object with a 'type' property containing the reason for the pause.
|
|
|
|
*/
|
|
|
|
_pauseAndRespond: function TA__pauseAndRespond(aFrame, aReason) {
|
|
|
|
try {
|
|
|
|
let packet = this._paused(aFrame);
|
|
|
|
if (!packet) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
packet.why = aReason;
|
|
|
|
this.conn.send(packet);
|
|
|
|
return this._nest();
|
|
|
|
} catch(e) {
|
2012-06-10 23:44:50 +00:00
|
|
|
let msg = "Got an exception during TA__pauseAndRespond: " + e +
|
|
|
|
": " + e.stack;
|
|
|
|
Cu.reportError(msg);
|
|
|
|
dumpn(msg);
|
2012-03-18 06:50:43 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to resume execution of the debuggee.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
onResume: function TA_onResume(aRequest) {
|
2012-03-18 06:50:43 +00:00
|
|
|
if (aRequest && aRequest.forceCompletion) {
|
|
|
|
// TODO: remove this when Debugger.Frame.prototype.pop is implemented in
|
|
|
|
// bug 736733.
|
|
|
|
if (typeof this.frame.pop != "function") {
|
|
|
|
return { error: "notImplemented",
|
|
|
|
message: "forced completion is not yet implemented." };
|
|
|
|
}
|
|
|
|
|
|
|
|
this.dbg.getNewestFrame().pop(aRequest.completionValue);
|
|
|
|
let packet = this._resumed();
|
|
|
|
DebuggerServer.xpcInspector.exitNestedEventLoop();
|
|
|
|
return { type: "resumeLimit", frameFinished: aRequest.forceCompletion };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aRequest && aRequest.resumeLimit) {
|
|
|
|
// Bind these methods because some of the hooks are called with 'this'
|
|
|
|
// set to the current frame.
|
|
|
|
let pauseAndRespond = this._pauseAndRespond.bind(this);
|
|
|
|
let createValueGrip = this.createValueGrip.bind(this);
|
|
|
|
|
|
|
|
let startFrame = this._youngestFrame;
|
|
|
|
let startLine;
|
|
|
|
if (this._youngestFrame.script) {
|
|
|
|
let offset = this._youngestFrame.offset;
|
|
|
|
startLine = this._youngestFrame.script.getOffsetLine(offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define the JS hook functions for stepping.
|
|
|
|
|
|
|
|
let onEnterFrame = function TA_onEnterFrame(aFrame) {
|
|
|
|
return pauseAndRespond(aFrame, { type: "resumeLimit" });
|
|
|
|
};
|
|
|
|
|
|
|
|
let onPop = function TA_onPop(aCompletion) {
|
|
|
|
// onPop is called with 'this' set to the current frame.
|
|
|
|
|
|
|
|
// Note that we're popping this frame; we need to watch for
|
|
|
|
// subsequent step events on its caller.
|
|
|
|
this.reportedPop = true;
|
|
|
|
|
|
|
|
return pauseAndRespond(this, { type: "resumeLimit" });
|
|
|
|
}
|
|
|
|
|
|
|
|
let onStep = function TA_onStep() {
|
|
|
|
// onStep is called with 'this' set to the current frame.
|
|
|
|
|
|
|
|
// If we've changed frame or line, then report that.
|
|
|
|
if (this !== startFrame ||
|
|
|
|
(this.script &&
|
|
|
|
this.script.getOffsetLine(this.offset) != startLine)) {
|
|
|
|
return pauseAndRespond(this, { type: "resumeLimit" });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, let execution continue.
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2012-09-12 11:10:05 +00:00
|
|
|
let steppingType = aRequest.resumeLimit.type;
|
|
|
|
if (["step", "next", "finish"].indexOf(steppingType) == -1) {
|
|
|
|
return { error: "badParameterType",
|
|
|
|
message: "Unknown resumeLimit type" };
|
|
|
|
}
|
|
|
|
// Make sure there is still a frame on the stack if we are to continue
|
|
|
|
// stepping.
|
|
|
|
let stepFrame = this._getNextStepFrame(startFrame);
|
|
|
|
if (stepFrame) {
|
|
|
|
switch (steppingType) {
|
|
|
|
case "step":
|
|
|
|
this.dbg.onEnterFrame = onEnterFrame;
|
|
|
|
// Fall through.
|
|
|
|
case "next":
|
2012-03-18 06:50:43 +00:00
|
|
|
stepFrame.onStep = onStep;
|
|
|
|
stepFrame.onPop = onPop;
|
2012-09-12 11:10:05 +00:00
|
|
|
break;
|
|
|
|
case "finish":
|
2012-03-18 06:50:43 +00:00
|
|
|
stepFrame.onPop = onPop;
|
2012-09-12 11:10:05 +00:00
|
|
|
}
|
2012-03-18 06:50:43 +00:00
|
|
|
}
|
|
|
|
}
|
2012-06-03 13:39:51 +00:00
|
|
|
|
|
|
|
if (aRequest && aRequest.pauseOnExceptions) {
|
|
|
|
this.dbg.onExceptionUnwind = this.onExceptionUnwind.bind(this);
|
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
let packet = this._resumed();
|
|
|
|
DebuggerServer.xpcInspector.exitNestedEventLoop();
|
|
|
|
return packet;
|
|
|
|
},
|
|
|
|
|
2012-03-18 06:50:43 +00:00
|
|
|
/**
|
|
|
|
* Helper method that returns the next frame when stepping.
|
|
|
|
*/
|
|
|
|
_getNextStepFrame: function TA__getNextStepFrame(aFrame) {
|
|
|
|
let stepFrame = aFrame.reportedPop ? aFrame.older : aFrame;
|
|
|
|
if (!stepFrame || !stepFrame.script) {
|
|
|
|
stepFrame = null;
|
|
|
|
}
|
|
|
|
return stepFrame;
|
|
|
|
},
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
onClientEvaluate: function TA_onClientEvaluate(aRequest) {
|
|
|
|
if (this.state !== "paused") {
|
2012-03-13 07:13:02 +00:00
|
|
|
return { error: "wrongState",
|
2012-02-07 17:22:30 +00:00
|
|
|
message: "Debuggee must be paused to evaluate code." };
|
|
|
|
};
|
|
|
|
|
|
|
|
let frame = this._requestFrame(aRequest.frame);
|
|
|
|
if (!frame) {
|
2012-03-13 07:13:02 +00:00
|
|
|
return { error: "unknownFrame",
|
2012-02-07 17:22:30 +00:00
|
|
|
message: "Evaluation frame not found" };
|
|
|
|
}
|
|
|
|
|
2012-03-13 07:13:02 +00:00
|
|
|
if (!frame.environment) {
|
|
|
|
return { error: "notDebuggee",
|
|
|
|
message: "cannot access the environment of this frame." };
|
|
|
|
};
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
// We'll clobber the youngest frame if the eval causes a pause, so
|
|
|
|
// save our frame now to be restored after eval returns.
|
|
|
|
// XXX: or we could just start using dbg.getNewestFrame() now that it
|
|
|
|
// works as expected.
|
|
|
|
let youngest = this._youngestFrame;
|
|
|
|
|
|
|
|
// Put ourselves back in the running state and inform the client.
|
|
|
|
let resumedPacket = this._resumed();
|
|
|
|
this.conn.send(resumedPacket);
|
|
|
|
|
|
|
|
// Run the expression.
|
|
|
|
// XXX: test syntax errors
|
|
|
|
let completion = frame.eval(aRequest.expression);
|
|
|
|
|
|
|
|
// Put ourselves back in the pause state.
|
|
|
|
let packet = this._paused(youngest);
|
2012-03-13 07:13:02 +00:00
|
|
|
packet.why = { type: "clientEvaluated",
|
|
|
|
frameFinished: this.createProtocolCompletionValue(completion) };
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
// Return back to our previous pause's event loop.
|
|
|
|
return packet;
|
|
|
|
},
|
|
|
|
|
|
|
|
onFrames: function TA_onFrames(aRequest) {
|
|
|
|
if (this.state !== "paused") {
|
|
|
|
return { error: "wrongState",
|
|
|
|
message: "Stack frames are only available while the debuggee is paused."};
|
|
|
|
}
|
|
|
|
|
|
|
|
let start = aRequest.start ? aRequest.start : 0;
|
|
|
|
let count = aRequest.count;
|
|
|
|
|
|
|
|
// Find the starting frame...
|
|
|
|
let frame = this._youngestFrame;
|
|
|
|
let i = 0;
|
|
|
|
while (frame && (i < start)) {
|
|
|
|
frame = frame.older;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return request.count frames, or all remaining
|
|
|
|
// frames if count is not defined.
|
|
|
|
let frames = [];
|
|
|
|
for (; frame && (!count || i < (start + count)); i++) {
|
2012-03-13 07:13:02 +00:00
|
|
|
let form = this._createFrameActor(frame).form();
|
|
|
|
form.depth = i;
|
|
|
|
frames.push(form);
|
2012-02-07 17:22:30 +00:00
|
|
|
frame = frame.older;
|
|
|
|
}
|
|
|
|
|
|
|
|
return { frames: frames };
|
|
|
|
},
|
|
|
|
|
|
|
|
onReleaseMany: function TA_onReleaseMany(aRequest) {
|
2012-03-13 07:13:02 +00:00
|
|
|
if (!aRequest.actors) {
|
|
|
|
return { error: "missingParameter",
|
|
|
|
message: "no actors were specified" };
|
|
|
|
}
|
|
|
|
|
2012-10-12 08:26:49 +00:00
|
|
|
let res;
|
2012-02-07 17:22:30 +00:00
|
|
|
for each (let actorID in aRequest.actors) {
|
|
|
|
let actor = this.threadLifetimePool.get(actorID);
|
2012-10-12 08:26:49 +00:00
|
|
|
if (!actor) {
|
|
|
|
if (!res) {
|
|
|
|
res = { error: "notReleasable",
|
|
|
|
message: "Only thread-lifetime actors can be released." };
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
actor.onRelease();
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-10-12 08:26:49 +00:00
|
|
|
return res ? res : {};
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to set a breakpoint.
|
|
|
|
*/
|
|
|
|
onSetBreakpoint: function TA_onSetBreakpoint(aRequest) {
|
|
|
|
if (this.state !== "paused") {
|
|
|
|
return { error: "wrongState",
|
|
|
|
message: "Breakpoints can only be set while the debuggee is paused."};
|
|
|
|
}
|
|
|
|
|
|
|
|
let location = aRequest.location;
|
2012-06-03 13:39:50 +00:00
|
|
|
let line = location.line;
|
|
|
|
if (!this._scripts[location.url] || line < 0) {
|
2012-02-10 07:46:12 +00:00
|
|
|
return { error: "noScript" };
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-06-03 13:39:50 +00:00
|
|
|
|
|
|
|
// Add the breakpoint to the store for later reuse, in case it belongs to a
|
|
|
|
// script that hasn't appeared yet.
|
|
|
|
if (!this._breakpointStore[location.url]) {
|
|
|
|
this._breakpointStore[location.url] = [];
|
|
|
|
}
|
|
|
|
let scriptBreakpoints = this._breakpointStore[location.url];
|
|
|
|
scriptBreakpoints[line] = {
|
|
|
|
url: location.url,
|
|
|
|
line: line,
|
|
|
|
column: location.column
|
|
|
|
};
|
|
|
|
|
|
|
|
return this._setBreakpoint(location);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-06-12 06:47:08 +00:00
|
|
|
* Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint
|
|
|
|
* is being set contains no code, then the breakpoint will slide down to the
|
|
|
|
* next line that has runnable code. In this case the server breakpoint cache
|
|
|
|
* will be updated, so callers that iterate over the breakpoint cache should
|
|
|
|
* take that into account.
|
2012-06-03 13:39:50 +00:00
|
|
|
*
|
|
|
|
* @param object aLocation
|
|
|
|
* The location of the breakpoint as specified in the protocol.
|
|
|
|
*/
|
|
|
|
_setBreakpoint: function TA__setBreakpoint(aLocation) {
|
2012-02-07 17:22:30 +00:00
|
|
|
// Fetch the list of scripts in that url.
|
2012-06-03 13:39:50 +00:00
|
|
|
let scripts = this._scripts[aLocation.url];
|
2012-02-07 17:22:30 +00:00
|
|
|
// Fetch the specified script in that list.
|
|
|
|
let script = null;
|
2012-06-03 13:39:50 +00:00
|
|
|
for (let i = aLocation.line; i >= 0; i--) {
|
2012-02-07 17:22:30 +00:00
|
|
|
// Stop when the first script that contains this location is found.
|
|
|
|
if (scripts[i]) {
|
|
|
|
// If that first script does not contain the line specified, it's no
|
|
|
|
// good.
|
2012-06-03 13:39:50 +00:00
|
|
|
if (i + scripts[i].lineCount < aLocation.line) {
|
2012-03-28 09:23:09 +00:00
|
|
|
continue;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
script = scripts[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-02-10 07:46:12 +00:00
|
|
|
|
2012-06-03 13:39:50 +00:00
|
|
|
let location = { url: aLocation.url, line: aLocation.line };
|
2012-06-12 06:47:08 +00:00
|
|
|
// Get the list of cached breakpoints in this URL.
|
|
|
|
let scriptBreakpoints = this._breakpointStore[location.url];
|
2012-06-03 13:39:50 +00:00
|
|
|
let bpActor;
|
2012-06-12 06:47:08 +00:00
|
|
|
if (scriptBreakpoints &&
|
|
|
|
scriptBreakpoints[location.line] &&
|
|
|
|
scriptBreakpoints[location.line].actor) {
|
|
|
|
bpActor = scriptBreakpoints[location.line].actor;
|
2012-06-03 13:39:50 +00:00
|
|
|
}
|
|
|
|
if (!bpActor) {
|
|
|
|
bpActor = new BreakpointActor(this, location);
|
|
|
|
this._hooks.addToBreakpointPool(bpActor);
|
2012-06-12 06:47:08 +00:00
|
|
|
if (scriptBreakpoints[location.line]) {
|
|
|
|
scriptBreakpoints[location.line].actor = bpActor;
|
|
|
|
}
|
2012-06-03 13:39:50 +00:00
|
|
|
}
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
if (!script) {
|
2012-06-03 13:39:50 +00:00
|
|
|
return { error: "noScript", actor: bpActor.actorID };
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-02-10 07:46:12 +00:00
|
|
|
|
2012-06-03 13:39:50 +00:00
|
|
|
script = this._getInnermostContainer(script, aLocation.line);
|
|
|
|
bpActor.addScript(script, this);
|
2012-02-10 07:46:12 +00:00
|
|
|
|
2012-06-03 13:39:50 +00:00
|
|
|
let offsets = script.getLineOffsets(aLocation.line);
|
2012-02-10 07:46:12 +00:00
|
|
|
let codeFound = false;
|
|
|
|
for (let i = 0; i < offsets.length; i++) {
|
2012-02-07 17:22:30 +00:00
|
|
|
script.setBreakpoint(offsets[i], bpActor);
|
2012-02-10 07:46:12 +00:00
|
|
|
codeFound = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let actualLocation;
|
|
|
|
if (offsets.length == 0) {
|
|
|
|
// No code at that line in any script, skipping forward.
|
|
|
|
let lines = script.getAllOffsets();
|
2012-06-12 06:47:08 +00:00
|
|
|
let oldLine = aLocation.line;
|
|
|
|
for (let line = oldLine; line < lines.length; ++line) {
|
2012-02-10 07:46:12 +00:00
|
|
|
if (lines[line]) {
|
|
|
|
for (let i = 0; i < lines[line].length; i++) {
|
|
|
|
script.setBreakpoint(lines[line][i], bpActor);
|
|
|
|
codeFound = true;
|
|
|
|
}
|
2012-06-12 06:47:08 +00:00
|
|
|
actualLocation = {
|
|
|
|
url: aLocation.url,
|
|
|
|
line: line,
|
|
|
|
column: aLocation.column
|
|
|
|
};
|
2012-06-03 13:39:50 +00:00
|
|
|
bpActor.location = actualLocation;
|
2012-06-12 06:47:08 +00:00
|
|
|
// Update the cache as well.
|
|
|
|
scriptBreakpoints[line] = scriptBreakpoints[oldLine];
|
|
|
|
scriptBreakpoints[line].line = line;
|
|
|
|
delete scriptBreakpoints[oldLine];
|
2012-02-10 07:46:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-02-10 07:46:12 +00:00
|
|
|
if (!codeFound) {
|
2012-06-03 13:39:50 +00:00
|
|
|
return { error: "noCodeAtLineColumn", actor: bpActor.actorID };
|
2012-02-10 07:46:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return { actor: bpActor.actorID, actualLocation: actualLocation };
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the innermost script that contains this line, by looking through child
|
|
|
|
* scripts of the supplied script.
|
|
|
|
*
|
|
|
|
* @param aScript Debugger.Script
|
|
|
|
* The source script.
|
|
|
|
* @param aLine number
|
|
|
|
* The line number.
|
|
|
|
*/
|
|
|
|
_getInnermostContainer: function TA__getInnermostContainer(aScript, aLine) {
|
|
|
|
let children = aScript.getChildScripts();
|
|
|
|
if (children.length > 0) {
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let child = children[i];
|
|
|
|
// Stop when the first script that contains this location is found.
|
|
|
|
if (child.startLine <= aLine &&
|
|
|
|
child.startLine + child.lineCount > aLine) {
|
|
|
|
return this._getInnermostContainer(child, aLine);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Location not found in children, this is the innermost containing script.
|
|
|
|
return aScript;
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to return the list of loaded scripts.
|
|
|
|
*/
|
|
|
|
onScripts: function TA_onScripts(aRequest) {
|
2012-03-30 08:25:52 +00:00
|
|
|
// Get the script list from the debugger.
|
|
|
|
for (let s of this.dbg.findScripts()) {
|
2012-06-10 23:44:50 +00:00
|
|
|
this._addScript(s);
|
2012-03-30 08:25:52 +00:00
|
|
|
}
|
|
|
|
// Build the cache.
|
2012-02-07 17:22:30 +00:00
|
|
|
let scripts = [];
|
|
|
|
for (let url in this._scripts) {
|
|
|
|
for (let i = 0; i < this._scripts[url].length; i++) {
|
|
|
|
if (!this._scripts[url][i]) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-09-27 08:30:00 +00:00
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
let script = {
|
|
|
|
url: url,
|
|
|
|
startLine: i,
|
2012-09-27 08:30:00 +00:00
|
|
|
lineCount: this._scripts[url][i].lineCount,
|
|
|
|
source: this.sourceGrip(this._scripts[url][i], this)
|
2012-02-07 17:22:30 +00:00
|
|
|
};
|
|
|
|
scripts.push(script);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let packet = { from: this.actorID,
|
|
|
|
scripts: scripts };
|
|
|
|
return packet;
|
|
|
|
},
|
|
|
|
|
2012-02-10 07:46:10 +00:00
|
|
|
/**
|
|
|
|
* Handle a protocol request to pause the debuggee.
|
|
|
|
*/
|
2012-07-13 10:10:22 +00:00
|
|
|
onInterrupt: function TA_onInterrupt(aRequest) {
|
2012-02-10 07:46:10 +00:00
|
|
|
if (this.state == "exited") {
|
|
|
|
return { type: "exited" };
|
|
|
|
} else if (this.state == "paused") {
|
|
|
|
// TODO: return the actual reason for the existing pause.
|
|
|
|
return { type: "paused", why: { type: "alreadyPaused" } };
|
|
|
|
} else if (this.state != "running") {
|
|
|
|
return { error: "wrongState",
|
|
|
|
message: "Received interrupt request in " + this.state +
|
|
|
|
" state." };
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Put ourselves in the paused state.
|
|
|
|
let packet = this._paused();
|
|
|
|
if (!packet) {
|
|
|
|
return { error: "notInterrupted" };
|
|
|
|
}
|
|
|
|
packet.why = { type: "interrupted" };
|
|
|
|
|
|
|
|
// Send the response to the interrupt request now (rather than
|
|
|
|
// returning it), because we're going to start a nested event loop
|
|
|
|
// here.
|
|
|
|
this.conn.send(packet);
|
|
|
|
|
|
|
|
// Start a nested event loop.
|
|
|
|
this._nest();
|
|
|
|
|
|
|
|
// We already sent a response to this request, don't send one
|
|
|
|
// now.
|
|
|
|
return null;
|
|
|
|
} catch(e) {
|
|
|
|
Cu.reportError(e);
|
|
|
|
return { error: "notInterrupted", message: e.toString() };
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
/**
|
|
|
|
* Return the Debug.Frame for a frame mentioned by the protocol.
|
|
|
|
*/
|
|
|
|
_requestFrame: function TA_requestFrame(aFrameID) {
|
|
|
|
if (!aFrameID) {
|
|
|
|
return this._youngestFrame;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._framePool.has(aFrameID)) {
|
|
|
|
return this._framePool.get(aFrameID).frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
},
|
|
|
|
|
|
|
|
_paused: function TA_paused(aFrame) {
|
2012-03-13 07:13:02 +00:00
|
|
|
// We don't handle nested pauses correctly. Don't try - if we're
|
2012-02-07 17:22:30 +00:00
|
|
|
// paused, just continue running whatever code triggered the pause.
|
2012-03-13 07:13:02 +00:00
|
|
|
// We don't want to actually have nested pauses (although we
|
2012-02-07 17:22:30 +00:00
|
|
|
// have nested event loops). If code runs in the debuggee during
|
|
|
|
// a pause, it should cause the actor to resume (dropping
|
|
|
|
// pause-lifetime actors etc) and then repause when complete.
|
|
|
|
|
|
|
|
if (this.state === "paused") {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2012-03-18 06:50:43 +00:00
|
|
|
// Clear stepping hooks.
|
|
|
|
this.dbg.onEnterFrame = undefined;
|
2012-06-03 13:39:51 +00:00
|
|
|
this.dbg.onExceptionUnwind = undefined;
|
2012-03-18 06:50:43 +00:00
|
|
|
if (aFrame) {
|
|
|
|
aFrame.onStep = undefined;
|
|
|
|
aFrame.onPop = undefined;
|
|
|
|
}
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
this._state = "paused";
|
|
|
|
|
|
|
|
// Save the pause frame (if any) as the youngest frame for
|
|
|
|
// stack viewing.
|
|
|
|
this._youngestFrame = aFrame;
|
|
|
|
|
|
|
|
// Create the actor pool that will hold the pause actor and its
|
|
|
|
// children.
|
|
|
|
dbg_assert(!this._pausePool);
|
|
|
|
this._pausePool = new ActorPool(this.conn);
|
|
|
|
this.conn.addActorPool(this._pausePool);
|
|
|
|
|
|
|
|
// Give children of the pause pool a quick link back to the
|
|
|
|
// thread...
|
|
|
|
this._pausePool.threadActor = this;
|
|
|
|
|
|
|
|
// Create the pause actor itself...
|
|
|
|
dbg_assert(!this._pauseActor);
|
|
|
|
this._pauseActor = new PauseActor(this._pausePool);
|
|
|
|
this._pausePool.addActor(this._pauseActor);
|
|
|
|
|
|
|
|
// Update the list of frames.
|
|
|
|
let poppedFrames = this._updateFrames();
|
|
|
|
|
|
|
|
// Send off the paused packet and spin an event loop.
|
|
|
|
let packet = { from: this.actorID,
|
|
|
|
type: "paused",
|
|
|
|
actor: this._pauseActor.actorID };
|
|
|
|
if (aFrame) {
|
2012-03-13 07:13:02 +00:00
|
|
|
packet.frame = this._createFrameActor(aFrame).form();
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (poppedFrames) {
|
|
|
|
packet.poppedFrames = poppedFrames;
|
|
|
|
}
|
|
|
|
|
|
|
|
return packet;
|
|
|
|
},
|
|
|
|
|
|
|
|
_nest: function TA_nest() {
|
|
|
|
if (this._hooks.preNest) {
|
|
|
|
var nestData = this._hooks.preNest();
|
|
|
|
}
|
|
|
|
|
|
|
|
DebuggerServer.xpcInspector.enterNestedEventLoop();
|
|
|
|
|
|
|
|
dbg_assert(this.state === "running");
|
|
|
|
|
|
|
|
if (this._hooks.postNest) {
|
|
|
|
this._hooks.postNest(nestData)
|
|
|
|
}
|
|
|
|
|
|
|
|
// "continue" resumption value.
|
|
|
|
return undefined;
|
|
|
|
},
|
|
|
|
|
|
|
|
_resumed: function TA_resumed() {
|
|
|
|
this._state = "running";
|
|
|
|
|
|
|
|
// Drop the actors in the pause actor pool.
|
|
|
|
this.conn.removeActorPool(this._pausePool);
|
|
|
|
|
|
|
|
this._pausePool = null;
|
|
|
|
this._pauseActor = null;
|
|
|
|
this._youngestFrame = null;
|
|
|
|
|
|
|
|
return { from: this.actorID, type: "resumed" };
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Expire frame actors for frames that have been popped.
|
|
|
|
*
|
|
|
|
* @returns A list of actor IDs whose frames have been popped.
|
|
|
|
*/
|
|
|
|
_updateFrames: function TA_updateFrames() {
|
|
|
|
let popped = [];
|
|
|
|
|
|
|
|
// Create the actor pool that will hold the still-living frames.
|
|
|
|
let framePool = new ActorPool(this.conn);
|
|
|
|
let frameList = [];
|
|
|
|
|
|
|
|
for each (let frameActor in this._frameActors) {
|
|
|
|
if (frameActor.frame.live) {
|
|
|
|
framePool.addActor(frameActor);
|
|
|
|
frameList.push(frameActor);
|
|
|
|
} else {
|
|
|
|
popped.push(frameActor.actorID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the old frame actor pool, this will expire
|
|
|
|
// any actors that weren't added to the new pool.
|
|
|
|
if (this._framePool) {
|
|
|
|
this.conn.removeActorPool(this._framePool);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._frameActors = frameList;
|
|
|
|
this._framePool = framePool;
|
|
|
|
this.conn.addActorPool(framePool);
|
|
|
|
|
|
|
|
return popped;
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
_createFrameActor: function TA_createFrameActor(aFrame) {
|
2012-02-07 17:22:30 +00:00
|
|
|
if (aFrame.actor) {
|
|
|
|
return aFrame.actor;
|
|
|
|
}
|
|
|
|
|
|
|
|
let actor = new FrameActor(aFrame, this);
|
|
|
|
this._frameActors.push(actor);
|
|
|
|
this._framePool.addActor(actor);
|
|
|
|
aFrame.actor = actor;
|
|
|
|
|
|
|
|
return actor;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2012-03-21 15:49:23 +00:00
|
|
|
* Create and return an environment actor that corresponds to the provided
|
|
|
|
* Debugger.Environment.
|
|
|
|
* @param Debugger.Environment aEnvironment
|
|
|
|
* The lexical environment we want to extract.
|
2012-02-07 17:22:30 +00:00
|
|
|
* @param object aPool
|
|
|
|
* The pool where the newly-created actor will be placed.
|
2012-03-21 15:49:23 +00:00
|
|
|
* @return The EnvironmentActor for aEnvironment or undefined for host
|
|
|
|
* functions or functions scoped to a non-debuggee global.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-03-21 15:49:23 +00:00
|
|
|
createEnvironmentActor:
|
|
|
|
function TA_createEnvironmentActor(aEnvironment, aPool) {
|
|
|
|
if (!aEnvironment) {
|
2012-02-07 17:22:30 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
if (aEnvironment.actor) {
|
|
|
|
return aEnvironment.actor;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
let actor = new EnvironmentActor(aEnvironment, this);
|
2012-02-07 17:22:30 +00:00
|
|
|
this._environmentActors.push(actor);
|
|
|
|
aPool.addActor(actor);
|
2012-03-21 15:49:23 +00:00
|
|
|
aEnvironment.actor = actor;
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
return actor;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a grip for the given debuggee value. If the value is an
|
2012-09-27 08:30:00 +00:00
|
|
|
* object, will create an actor with the given lifetime.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-09-27 08:30:00 +00:00
|
|
|
createValueGrip: function TA_createValueGrip(aValue, aPool=false) {
|
|
|
|
if (!aPool) {
|
|
|
|
aPool = this._pausePool;
|
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
let type = typeof(aValue);
|
2012-08-30 21:10:07 +00:00
|
|
|
|
|
|
|
if (type === "string" && this._stringIsLong(aValue)) {
|
2012-09-27 08:30:00 +00:00
|
|
|
return this.longStringGrip(aValue, aPool);
|
2012-08-30 21:10:07 +00:00
|
|
|
}
|
|
|
|
|
2012-02-07 17:22:30 +00:00
|
|
|
if (type === "boolean" || type === "string" || type === "number") {
|
|
|
|
return aValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aValue === null) {
|
|
|
|
return { type: "null" };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aValue === undefined) {
|
|
|
|
return { type: "undefined" }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof(aValue) === "object") {
|
2012-09-27 08:30:00 +00:00
|
|
|
return this.objectGrip(aValue, aPool);
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dbg_assert(false, "Failed to provide a grip for: " + aValue);
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2012-03-13 07:13:02 +00:00
|
|
|
/**
|
|
|
|
* Return a protocol completion value representing the given
|
|
|
|
* Debugger-provided completion value.
|
|
|
|
*/
|
|
|
|
createProtocolCompletionValue:
|
|
|
|
function TA_createProtocolCompletionValue(aCompletion) {
|
|
|
|
let protoValue = {};
|
|
|
|
if ("return" in aCompletion) {
|
|
|
|
protoValue.return = this.createValueGrip(aCompletion.return);
|
|
|
|
} else if ("yield" in aCompletion) {
|
|
|
|
protoValue.return = this.createValueGrip(aCompletion.yield);
|
|
|
|
} else if ("throw" in aCompletion) {
|
|
|
|
protoValue.throw = this.createValueGrip(aCompletion.throw);
|
|
|
|
} else {
|
|
|
|
protoValue.terminated = true;
|
|
|
|
}
|
|
|
|
return protoValue;
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Create a grip for the given debuggee object.
|
|
|
|
*
|
|
|
|
* @param aValue Debugger.Object
|
|
|
|
* The debuggee object value.
|
|
|
|
* @param aPool ActorPool
|
|
|
|
* The actor pool where the new object actor will be added.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
objectGrip: function TA_objectGrip(aValue, aPool) {
|
|
|
|
if (!aPool.objectActors) {
|
|
|
|
aPool.objectActors = new WeakMap();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aPool.objectActors.has(aValue)) {
|
|
|
|
return aPool.objectActors.get(aValue).grip();
|
|
|
|
}
|
|
|
|
|
|
|
|
let actor = new ObjectActor(aValue, this);
|
|
|
|
aPool.addActor(actor);
|
|
|
|
aPool.objectActors.set(aValue, actor);
|
|
|
|
return actor.grip();
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Create a grip for the given debuggee object with a pause lifetime.
|
|
|
|
*
|
|
|
|
* @param aValue Debugger.Object
|
|
|
|
* The debuggee object value.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
pauseObjectGrip: function TA_pauseObjectGrip(aValue) {
|
|
|
|
if (!this._pausePool) {
|
|
|
|
throw "Object grip requested while not paused.";
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.objectGrip(aValue, this._pausePool);
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
2012-10-12 08:26:49 +00:00
|
|
|
* Extend the lifetime of the provided object actor to thread lifetime.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
2012-10-12 08:26:49 +00:00
|
|
|
* @param aActor object
|
|
|
|
* The object actor.
|
2012-01-23 08:29:15 +00:00
|
|
|
*/
|
2012-10-12 08:26:49 +00:00
|
|
|
threadObjectGrip: function TA_threadObjectGrip(aActor) {
|
|
|
|
if (!this.threadLifetimePool.objectActors) {
|
|
|
|
this.threadLifetimePool.objectActors = new WeakMap();
|
|
|
|
}
|
|
|
|
// We want to reuse the existing actor ID, so we just remove it from the
|
|
|
|
// current pool's weak map and then let pool.addActor do the rest.
|
|
|
|
aActor.registeredPool.objectActors.delete(aActor.obj);
|
|
|
|
this.threadLifetimePool.addActor(aActor);
|
|
|
|
this.threadLifetimePool.objectActors.set(aActor.obj, aActor);
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
2012-08-30 21:10:07 +00:00
|
|
|
/**
|
|
|
|
* Create a grip for the given string.
|
|
|
|
*
|
|
|
|
* @param aString String
|
|
|
|
* The string we are creating a grip for.
|
2012-09-27 08:29:00 +00:00
|
|
|
* @param aPool ActorPool
|
|
|
|
* The actor pool where the new actor will be added.
|
2012-08-30 21:10:07 +00:00
|
|
|
*/
|
2012-09-27 08:29:00 +00:00
|
|
|
longStringGrip: function TA_longStringGrip(aString, aPool) {
|
|
|
|
if (!aPool.longStringActors) {
|
|
|
|
aPool.longStringActors = {};
|
2012-08-30 21:10:07 +00:00
|
|
|
}
|
|
|
|
|
2012-09-27 08:29:00 +00:00
|
|
|
if (aPool.longStringActors.hasOwnProperty(aString)) {
|
|
|
|
return aPool.longStringActors[aString].grip();
|
2012-08-30 21:10:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let actor = new LongStringActor(aString, this);
|
2012-09-27 08:29:00 +00:00
|
|
|
aPool.addActor(actor);
|
|
|
|
aPool.longStringActors[aString] = actor;
|
2012-08-30 21:10:07 +00:00
|
|
|
return actor.grip();
|
|
|
|
},
|
|
|
|
|
2012-09-27 08:29:00 +00:00
|
|
|
/**
|
|
|
|
* Create a long string grip that is scoped to a pause.
|
|
|
|
*
|
|
|
|
* @param aString String
|
|
|
|
* The string we are creating a grip for.
|
|
|
|
*/
|
|
|
|
pauseLongStringGrip: function TA_pauseLongStringGrip (aString) {
|
|
|
|
return this.longStringGrip(aString, this._pausePool);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a long string grip that is scoped to a thread.
|
|
|
|
*
|
|
|
|
* @param aString String
|
|
|
|
* The string we are creating a grip for.
|
|
|
|
*/
|
|
|
|
threadLongStringGrip: function TA_pauseLongStringGrip (aString) {
|
|
|
|
return this.longStringGrip(aString, this._threadLifetimePool);
|
|
|
|
},
|
|
|
|
|
2012-08-30 21:10:07 +00:00
|
|
|
/**
|
|
|
|
* Returns true if the string is long enough to use a LongStringActor instead
|
|
|
|
* of passing the value directly over the protocol.
|
|
|
|
*
|
|
|
|
* @param aString String
|
|
|
|
* The string we are checking the length of.
|
|
|
|
*/
|
|
|
|
_stringIsLong: function TA__stringIsLong(aString) {
|
|
|
|
return aString.length >= DebuggerServer.LONG_STRING_LENGTH;
|
|
|
|
},
|
|
|
|
|
2012-09-27 08:30:00 +00:00
|
|
|
/**
|
|
|
|
* Create a source grip for the given script.
|
|
|
|
*/
|
|
|
|
sourceGrip: function TA_sourceGrip(aScript) {
|
|
|
|
// TODO: Once we have Debugger.Source, this should be replaced with a
|
|
|
|
// weakmap mapping Debugger.Source instances to SourceActor instances.
|
|
|
|
if (!this.threadLifetimePool.sourceActors) {
|
|
|
|
this.threadLifetimePool.sourceActors = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.threadLifetimePool.sourceActors[aScript.url]) {
|
|
|
|
return this.threadLifetimePool.sourceActors[aScript.url].grip();
|
|
|
|
}
|
|
|
|
|
|
|
|
let actor = new SourceActor(aScript, this);
|
|
|
|
this.threadLifetimePool.addActor(actor);
|
|
|
|
this.threadLifetimePool.sourceActors[aScript.url] = actor;
|
|
|
|
return actor.grip();
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
// JS Debugger API hooks.
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A function that the engine calls when a call to a debug event hook,
|
|
|
|
* breakpoint handler, watchpoint handler, or similar function throws some
|
|
|
|
* exception.
|
|
|
|
*
|
|
|
|
* @param aException exception
|
|
|
|
* The exception that was thrown in the debugger code.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
uncaughtExceptionHook: function TA_uncaughtExceptionHook(aException) {
|
|
|
|
dumpn("Got an exception:" + aException);
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* A function that the engine calls when a debugger statement has been
|
|
|
|
* executed in the specified frame.
|
|
|
|
*
|
|
|
|
* @param aFrame Debugger.Frame
|
|
|
|
* The stack frame that contained the debugger statement.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
onDebuggerStatement: function TA_onDebuggerStatement(aFrame) {
|
2012-03-18 06:50:43 +00:00
|
|
|
return this._pauseAndRespond(aFrame, { type: "debuggerStatement" });
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
2012-06-03 13:39:51 +00:00
|
|
|
/**
|
|
|
|
* A function that the engine calls when an exception has been thrown and has
|
|
|
|
* propagated to the specified frame.
|
|
|
|
*
|
|
|
|
* @param aFrame Debugger.Frame
|
|
|
|
* The youngest remaining stack frame.
|
|
|
|
* @param aValue object
|
|
|
|
* The exception that was thrown.
|
|
|
|
*/
|
|
|
|
onExceptionUnwind: function TA_onExceptionUnwind(aFrame, aValue) {
|
|
|
|
try {
|
|
|
|
let packet = this._paused(aFrame);
|
|
|
|
if (!packet) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
packet.why = { type: "exception",
|
|
|
|
exception: this.createValueGrip(aValue) };
|
|
|
|
this.conn.send(packet);
|
|
|
|
return this._nest();
|
|
|
|
} catch(e) {
|
|
|
|
Cu.reportError("Got an exception during TA_onExceptionUnwind: " + e +
|
|
|
|
": " + e.stack);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
2012-03-30 08:25:52 +00:00
|
|
|
* A function that the engine calls when a new script has been loaded into the
|
|
|
|
* scope of the specified debuggee global.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aScript Debugger.Script
|
|
|
|
* The source script that has been loaded into a debuggee compartment.
|
2012-03-30 08:25:52 +00:00
|
|
|
* @param aGlobal Debugger.Object
|
|
|
|
* A Debugger.Object instance whose referent is the global object.
|
2012-01-23 08:29:15 +00:00
|
|
|
*/
|
2012-03-30 08:25:52 +00:00
|
|
|
onNewScript: function TA_onNewScript(aScript, aGlobal) {
|
2012-08-22 08:11:07 +00:00
|
|
|
if (this._addScript(aScript)) {
|
|
|
|
// Notify the client.
|
|
|
|
this.conn.send({
|
|
|
|
from: this.actorID,
|
|
|
|
type: "newScript",
|
|
|
|
url: aScript.url,
|
|
|
|
startLine: aScript.startLine,
|
2012-09-27 08:30:00 +00:00
|
|
|
lineCount: aScript.lineCount,
|
|
|
|
source: this.sourceGrip(aScript, this)
|
2012-08-22 08:11:07 +00:00
|
|
|
});
|
|
|
|
}
|
2012-03-30 08:25:52 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add the provided script to the server cache.
|
|
|
|
*
|
|
|
|
* @param aScript Debugger.Script
|
|
|
|
* The source script that will be stored.
|
2012-08-22 08:11:07 +00:00
|
|
|
* @returns true, if the script was added, false otherwise.
|
2012-03-30 08:25:52 +00:00
|
|
|
*/
|
|
|
|
_addScript: function TA__addScript(aScript) {
|
2012-06-10 23:44:50 +00:00
|
|
|
// Ignore XBL bindings for content debugging.
|
|
|
|
if (aScript.url.indexOf("chrome://") == 0) {
|
2012-08-22 08:11:07 +00:00
|
|
|
return false;
|
2012-06-10 23:44:50 +00:00
|
|
|
}
|
2012-07-15 06:50:41 +00:00
|
|
|
// Ignore about:* pages for content debugging.
|
|
|
|
if (aScript.url.indexOf("about:") == 0) {
|
2012-08-22 08:11:07 +00:00
|
|
|
return false;
|
2012-07-15 06:50:41 +00:00
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
// Use a sparse array for storing the scripts for each URL in order to
|
2012-03-13 07:13:02 +00:00
|
|
|
// optimize retrieval.
|
2012-02-07 17:22:30 +00:00
|
|
|
if (!this._scripts[aScript.url]) {
|
|
|
|
this._scripts[aScript.url] = [];
|
|
|
|
}
|
|
|
|
this._scripts[aScript.url][aScript.startLine] = aScript;
|
2012-06-03 13:39:50 +00:00
|
|
|
|
|
|
|
// Set any stored breakpoints.
|
|
|
|
let existing = this._breakpointStore[aScript.url];
|
|
|
|
if (existing) {
|
2012-07-13 10:10:21 +00:00
|
|
|
let endLine = aScript.startLine + aScript.lineCount - 1;
|
2012-06-12 06:47:08 +00:00
|
|
|
// Iterate over the lines backwards, so that sliding breakpoints don't
|
|
|
|
// affect the loop.
|
|
|
|
for (let line = existing.length - 1; line >= 0; line--) {
|
|
|
|
let bp = existing[line];
|
2012-07-13 10:10:21 +00:00
|
|
|
// Limit search to the line numbers contained in the new script.
|
|
|
|
if (bp && line >= aScript.startLine && line <= endLine) {
|
2012-06-03 13:39:50 +00:00
|
|
|
this._setBreakpoint(bp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-08-22 08:11:07 +00:00
|
|
|
return true;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
ThreadActor.prototype.requestTypes = {
|
|
|
|
"attach": ThreadActor.prototype.onAttach,
|
|
|
|
"detach": ThreadActor.prototype.onDetach,
|
|
|
|
"resume": ThreadActor.prototype.onResume,
|
|
|
|
"clientEvaluate": ThreadActor.prototype.onClientEvaluate,
|
|
|
|
"frames": ThreadActor.prototype.onFrames,
|
2012-02-10 07:46:10 +00:00
|
|
|
"interrupt": ThreadActor.prototype.onInterrupt,
|
2012-02-07 17:22:30 +00:00
|
|
|
"releaseMany": ThreadActor.prototype.onReleaseMany,
|
|
|
|
"setBreakpoint": ThreadActor.prototype.onSetBreakpoint,
|
|
|
|
"scripts": ThreadActor.prototype.onScripts
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a PauseActor.
|
|
|
|
*
|
|
|
|
* PauseActors exist for the lifetime of a given debuggee pause. Used to
|
|
|
|
* scope pause-lifetime grips.
|
|
|
|
*
|
|
|
|
* @param ActorPool aPool
|
|
|
|
* The actor pool created for this pause.
|
|
|
|
*/
|
|
|
|
function PauseActor(aPool)
|
|
|
|
{
|
|
|
|
this.pool = aPool;
|
|
|
|
}
|
|
|
|
|
|
|
|
PauseActor.prototype = {
|
|
|
|
actorPrefix: "pause"
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-08-30 21:10:07 +00:00
|
|
|
/**
|
|
|
|
* A base actor for any actors that should only respond receive messages in the
|
|
|
|
* paused state. Subclasses may expose a `threadActor` which is used to help
|
|
|
|
* determine when we are in a paused state. Subclasses should set their own
|
|
|
|
* "constructor" property if they want better error messages. You should never
|
|
|
|
* instantiate a PauseScopedActor directly, only through subclasses.
|
|
|
|
*/
|
|
|
|
function PauseScopedActor()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A function decorator for creating methods to handle protocol messages that
|
|
|
|
* should only be received while in the paused state.
|
|
|
|
*
|
|
|
|
* @param aMethod Function
|
|
|
|
* The function we are decorating.
|
|
|
|
*/
|
|
|
|
PauseScopedActor.withPaused = function PSA_withPaused(aMethod) {
|
|
|
|
return function () {
|
|
|
|
if (this.isPaused()) {
|
|
|
|
return aMethod.apply(this, arguments);
|
|
|
|
} else {
|
|
|
|
return this._wrongState();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
PauseScopedActor.prototype = {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if we are in the paused state.
|
|
|
|
*/
|
|
|
|
isPaused: function PSA_isPaused() {
|
|
|
|
// When there is not a ThreadActor available (like in the webconsole) we
|
|
|
|
// have to be optimistic and assume that we are paused so that we can
|
|
|
|
// respond to requests.
|
|
|
|
return this.threadActor ? this.threadActor.state === "paused" : true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the wrongState response packet for this actor.
|
|
|
|
*/
|
|
|
|
_wrongState: function PSA_wrongState() {
|
|
|
|
return {
|
|
|
|
error: "wrongState",
|
|
|
|
message: this.constructor.name +
|
|
|
|
" actors can only be accessed while the thread is paused."
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Utility function for updating an object with the properties of another
|
|
|
|
* object.
|
|
|
|
*
|
|
|
|
* @param aTarget Object
|
|
|
|
* The object being updated.
|
|
|
|
* @param aNewAttrs Object
|
|
|
|
* The new attributes being set on the target.
|
|
|
|
*/
|
|
|
|
function update(aTarget, aNewAttrs) {
|
|
|
|
for (let key in aNewAttrs) {
|
2012-09-20 23:44:25 +00:00
|
|
|
let desc = Object.getOwnPropertyDescriptor(aNewAttrs, key);
|
|
|
|
|
|
|
|
if (desc) {
|
|
|
|
Object.defineProperty(aTarget, key, desc);
|
|
|
|
}
|
2012-08-30 21:10:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-09-27 08:30:00 +00:00
|
|
|
/**
|
|
|
|
* A SourceActor provides information about the source of a script.
|
|
|
|
*
|
|
|
|
* @param aScript Debugger.Script
|
|
|
|
* The script whose source we are representing.
|
|
|
|
* @param aThreadActor ThreadActor
|
|
|
|
* The current thread actor.
|
|
|
|
*/
|
|
|
|
function SourceActor(aScript, aThreadActor) {
|
|
|
|
this._threadActor = aThreadActor;
|
|
|
|
this._script = aScript;
|
|
|
|
}
|
|
|
|
|
|
|
|
SourceActor.prototype = {
|
|
|
|
constructor: SourceActor,
|
|
|
|
actorPrefix: "source",
|
|
|
|
|
|
|
|
get threadActor() { return this._threadActor; },
|
|
|
|
|
|
|
|
grip: function SA_grip() {
|
|
|
|
return this.actorID;
|
|
|
|
},
|
|
|
|
|
|
|
|
disconnect: function LSA_disconnect() {
|
|
|
|
if (this.registeredPool && this.registeredPool.sourceActors) {
|
|
|
|
delete this.registeredPool.sourceActors[this.actorID];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handler for the "source" packet.
|
|
|
|
*/
|
|
|
|
onSource: function SA_onSource(aRequest) {
|
|
|
|
this
|
|
|
|
._loadSource()
|
|
|
|
.chainPromise(function(aSource) {
|
|
|
|
return this._threadActor.createValueGrip(
|
|
|
|
aSource, this.threadActor.threadLifetimePool);
|
|
|
|
}.bind(this))
|
|
|
|
.chainPromise(function (aSourceGrip) {
|
|
|
|
return {
|
|
|
|
from: this.actorID,
|
|
|
|
source: aSourceGrip
|
|
|
|
};
|
|
|
|
}.bind(this))
|
|
|
|
.trap(function (aError) {
|
|
|
|
return {
|
|
|
|
"from": this.actorID,
|
|
|
|
"error": "loadSourceError",
|
|
|
|
"message": "Could not load the source for " + this._script.url + "."
|
|
|
|
};
|
|
|
|
}.bind(this))
|
|
|
|
.chainPromise(function (aPacket) {
|
|
|
|
this.conn.send(aPacket);
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert a given string, encoded in a given character set, to unicode.
|
|
|
|
* @param string aString
|
|
|
|
* A string.
|
|
|
|
* @param string aCharset
|
|
|
|
* A character set.
|
|
|
|
* @return string
|
|
|
|
* A unicode string.
|
|
|
|
*/
|
|
|
|
_convertToUnicode: function SS__convertToUnicode(aString, aCharset) {
|
|
|
|
// Decoding primitives.
|
|
|
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
|
|
|
|
|
|
try {
|
|
|
|
converter.charset = aCharset || "UTF-8";
|
|
|
|
return converter.ConvertToUnicode(aString);
|
|
|
|
} catch(e) {
|
|
|
|
return aString;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs a request to load the desired URL and returns a promise.
|
|
|
|
*
|
|
|
|
* @param aURL String
|
|
|
|
* The URL we will request.
|
|
|
|
* @returns Promise
|
|
|
|
*
|
|
|
|
* XXX: It may be better to use nsITraceableChannel to get to the sources
|
|
|
|
* without relying on caching when we can (not for eval, etc.):
|
|
|
|
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
|
|
|
|
*/
|
|
|
|
_loadSource: function SA__loadSource() {
|
|
|
|
let promise = new Promise();
|
|
|
|
let url = this._script.url;
|
|
|
|
let scheme;
|
|
|
|
try {
|
|
|
|
scheme = Services.io.extractScheme(url);
|
|
|
|
} catch (e) {
|
2012-10-03 15:56:45 +00:00
|
|
|
// In the xpcshell tests, the script url is the absolute path of the test
|
|
|
|
// file, which will make a malformed URI error be thrown. Add the file
|
|
|
|
// scheme prefix ourselves.
|
|
|
|
url = "file://" + url;
|
2012-09-27 08:30:00 +00:00
|
|
|
scheme = Services.io.extractScheme(url);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (scheme) {
|
|
|
|
case "file":
|
|
|
|
case "chrome":
|
|
|
|
case "resource":
|
|
|
|
try {
|
|
|
|
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
|
|
|
|
if (!Components.isSuccessCode(aStatus)) {
|
|
|
|
promise.reject(new Error("Request failed"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
|
|
|
|
promise.resolve(this._convertToUnicode(source));
|
|
|
|
aStream.close();
|
|
|
|
}.bind(this));
|
|
|
|
} catch (ex) {
|
|
|
|
promise.reject(new Error("Request failed"));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2012-10-03 15:56:45 +00:00
|
|
|
let channel;
|
|
|
|
try {
|
|
|
|
channel = Services.io.newChannel(url, null, null);
|
|
|
|
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
|
|
|
|
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
|
|
|
|
// newChannel won't be able to handle it.
|
|
|
|
url = "file:///" + url;
|
|
|
|
channel = Services.io.newChannel(url, null, null);
|
|
|
|
}
|
2012-09-27 08:30:00 +00:00
|
|
|
let chunks = [];
|
|
|
|
let streamListener = {
|
|
|
|
onStartRequest: function(aRequest, aContext, aStatusCode) {
|
|
|
|
if (!Components.isSuccessCode(aStatusCode)) {
|
|
|
|
promise.reject("Request failed");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
|
|
|
|
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
|
|
|
},
|
|
|
|
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
|
|
|
if (!Components.isSuccessCode(aStatusCode)) {
|
|
|
|
promise.reject("Request failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
promise.resolve(this._convertToUnicode(chunks.join(""),
|
|
|
|
channel.contentCharset));
|
|
|
|
}.bind(this)
|
|
|
|
};
|
|
|
|
|
|
|
|
channel.loadFlags = channel.LOAD_FROM_CACHE;
|
|
|
|
channel.asyncOpen(streamListener, null);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
SourceActor.prototype.requestTypes = {
|
|
|
|
"source": SourceActor.prototype.onSource
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Creates an actor for the specified object.
|
|
|
|
*
|
|
|
|
* @param aObj Debugger.Object
|
|
|
|
* The debuggee object.
|
|
|
|
* @param aThreadActor ThreadActor
|
|
|
|
* The parent thread actor for this object.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
function ObjectActor(aObj, aThreadActor)
|
|
|
|
{
|
|
|
|
this.obj = aObj;
|
|
|
|
this.threadActor = aThreadActor;
|
|
|
|
}
|
|
|
|
|
2012-08-30 21:10:07 +00:00
|
|
|
ObjectActor.prototype = Object.create(PauseScopedActor.prototype);
|
2012-02-07 17:22:30 +00:00
|
|
|
|
2012-08-30 21:10:07 +00:00
|
|
|
update(ObjectActor.prototype, {
|
|
|
|
constructor: ObjectActor,
|
|
|
|
actorPrefix: "obj",
|
2012-02-07 17:22:30 +00:00
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Returns a grip for this actor for returning in a protocol message.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
grip: function OA_grip() {
|
|
|
|
return { "type": "object",
|
2012-03-13 07:13:02 +00:00
|
|
|
"class": this.obj.class,
|
2012-02-07 17:22:30 +00:00
|
|
|
"actor": this.actorID };
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Releases this actor from the pool.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
release: function OA_release() {
|
|
|
|
this.registeredPool.objectActors.delete(this.obj);
|
2012-10-12 08:26:49 +00:00
|
|
|
this.registeredPool.removeActor(this);
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the names of the properties defined on
|
|
|
|
* the object and not its prototype.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onOwnPropertyNames:
|
|
|
|
PauseScopedActor.withPaused(function OA_onOwnPropertyNames(aRequest) {
|
2012-02-07 17:22:30 +00:00
|
|
|
return { from: this.actorID,
|
|
|
|
ownPropertyNames: this.obj.getOwnPropertyNames() };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the prototype and own properties of
|
|
|
|
* the object.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onPrototypeAndProperties:
|
|
|
|
PauseScopedActor.withPaused(function OA_onPrototypeAndProperties(aRequest) {
|
2012-02-07 17:22:30 +00:00
|
|
|
let ownProperties = {};
|
|
|
|
for each (let name in this.obj.getOwnPropertyNames()) {
|
|
|
|
try {
|
|
|
|
let desc = this.obj.getOwnPropertyDescriptor(name);
|
|
|
|
ownProperties[name] = this._propertyDescriptor(desc);
|
|
|
|
} catch (e if e.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO") {
|
|
|
|
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
|
|
|
|
// allowed.
|
|
|
|
dumpn("Error while getting the property descriptor for " + name +
|
|
|
|
": " + e.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return { from: this.actorID,
|
2012-01-23 08:29:15 +00:00
|
|
|
prototype: this.threadActor.createValueGrip(this.obj.proto),
|
2012-02-07 17:22:30 +00:00
|
|
|
ownProperties: ownProperties };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the prototype of the object.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onPrototype: PauseScopedActor.withPaused(function OA_onPrototype(aRequest) {
|
2012-02-07 17:22:30 +00:00
|
|
|
return { from: this.actorID,
|
2012-01-23 08:29:15 +00:00
|
|
|
prototype: this.threadActor.createValueGrip(this.obj.proto) };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the property descriptor of the
|
|
|
|
* object's specified property.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onProperty: PauseScopedActor.withPaused(function OA_onProperty(aRequest) {
|
2012-02-07 17:22:30 +00:00
|
|
|
if (!aRequest.name) {
|
2012-03-13 07:13:02 +00:00
|
|
|
return { error: "missingParameter",
|
2012-02-07 17:22:30 +00:00
|
|
|
message: "no property name was specified" };
|
|
|
|
}
|
|
|
|
|
|
|
|
let desc = this.obj.getOwnPropertyDescriptor(aRequest.name);
|
|
|
|
return { from: this.actorID,
|
|
|
|
descriptor: this._propertyDescriptor(desc) };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
/**
|
2012-01-23 08:29:15 +00:00
|
|
|
* A helper method that creates a property descriptor for the provided object,
|
|
|
|
* properly formatted for sending in a protocol response.
|
|
|
|
*
|
|
|
|
* @param aObject object
|
|
|
|
* The object that the descriptor is generated for.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
|
|
|
_propertyDescriptor: function OA_propertyDescriptor(aObject) {
|
|
|
|
let descriptor = {};
|
|
|
|
descriptor.configurable = aObject.configurable;
|
|
|
|
descriptor.enumerable = aObject.enumerable;
|
2012-03-21 15:49:23 +00:00
|
|
|
if (aObject.value !== undefined) {
|
2012-02-07 17:22:30 +00:00
|
|
|
descriptor.writable = aObject.writable;
|
2012-01-23 08:29:15 +00:00
|
|
|
descriptor.value = this.threadActor.createValueGrip(aObject.value);
|
2012-02-07 17:22:30 +00:00
|
|
|
} else {
|
2012-01-23 08:29:15 +00:00
|
|
|
descriptor.get = this.threadActor.createValueGrip(aObject.get);
|
|
|
|
descriptor.set = this.threadActor.createValueGrip(aObject.set);
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
return descriptor;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the source code of a function.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onDecompile: PauseScopedActor.withPaused(function OA_onDecompile(aRequest) {
|
2012-03-13 07:13:02 +00:00
|
|
|
if (this.obj.class !== "Function") {
|
|
|
|
return { error: "objectNotFunction",
|
2012-02-07 17:22:30 +00:00
|
|
|
message: "decompile request is only valid for object grips " +
|
|
|
|
"with a 'Function' class." };
|
|
|
|
}
|
|
|
|
|
|
|
|
return { from: this.actorID,
|
|
|
|
decompiledCode: this.obj.decompile(!!aRequest.pretty) };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the lexical scope of a function.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onScope: PauseScopedActor.withPaused(function OA_onScope(aRequest) {
|
2012-03-13 07:13:02 +00:00
|
|
|
if (this.obj.class !== "Function") {
|
|
|
|
return { error: "objectNotFunction",
|
2012-02-07 17:22:30 +00:00
|
|
|
message: "scope request is only valid for object grips with a" +
|
|
|
|
" 'Function' class." };
|
|
|
|
}
|
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
let envActor = this.threadActor.createEnvironmentActor(this.obj.environment,
|
2012-03-13 07:13:02 +00:00
|
|
|
this.registeredPool);
|
|
|
|
if (!envActor) {
|
|
|
|
return { error: "notDebuggee",
|
|
|
|
message: "cannot access the environment of this function." };
|
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
|
2012-05-29 09:08:20 +00:00
|
|
|
// XXX: the following call of env.form() won't work until bug 747514 lands.
|
|
|
|
// We can't get to the frame that defined this function's environment,
|
|
|
|
// neither here, nor during ObjectActor's construction. Luckily, we don't
|
|
|
|
// use the 'scope' request in the debugger frontend.
|
2012-03-13 07:13:02 +00:00
|
|
|
return { name: this.obj.name || null,
|
2012-03-21 15:49:23 +00:00
|
|
|
scope: envActor.form(this.obj) };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to provide the name and parameters of a function.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onNameAndParameters: PauseScopedActor.withPaused(function OA_onNameAndParameters(aRequest) {
|
2012-03-13 07:13:02 +00:00
|
|
|
if (this.obj.class !== "Function") {
|
|
|
|
return { error: "objectNotFunction",
|
|
|
|
message: "nameAndParameters request is only valid for object " +
|
|
|
|
"grips with a 'Function' class." };
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return { name: this.obj.name || null,
|
|
|
|
parameters: this.obj.parameterNames };
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Handle a protocol request to promote a pause-lifetime grip to a
|
|
|
|
* thread-lifetime grip.
|
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onThreadGrip: PauseScopedActor.withPaused(function OA_onThreadGrip(aRequest) {
|
2012-10-12 08:26:49 +00:00
|
|
|
this.threadActor.threadObjectGrip(this);
|
|
|
|
return {};
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
2012-02-07 17:22:30 +00:00
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Handle a protocol request to release a thread-lifetime grip.
|
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
|
|
|
*/
|
2012-08-30 21:10:07 +00:00
|
|
|
onRelease: PauseScopedActor.withPaused(function OA_onRelease(aRequest) {
|
2012-02-07 17:22:30 +00:00
|
|
|
if (this.registeredPool !== this.threadActor.threadLifetimePool) {
|
2012-03-13 07:13:02 +00:00
|
|
|
return { error: "notReleasable",
|
2012-10-12 08:26:49 +00:00
|
|
|
message: "Only thread-lifetime actors can be released." };
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.release();
|
|
|
|
return {};
|
2012-08-30 21:10:07 +00:00
|
|
|
}),
|
|
|
|
});
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
ObjectActor.prototype.requestTypes = {
|
|
|
|
"nameAndParameters": ObjectActor.prototype.onNameAndParameters,
|
|
|
|
"prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
|
|
|
|
"prototype": ObjectActor.prototype.onPrototype,
|
|
|
|
"property": ObjectActor.prototype.onProperty,
|
|
|
|
"ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
|
|
|
|
"scope": ObjectActor.prototype.onScope,
|
|
|
|
"decompile": ObjectActor.prototype.onDecompile,
|
|
|
|
"threadGrip": ObjectActor.prototype.onThreadGrip,
|
|
|
|
"release": ObjectActor.prototype.onRelease,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-08-30 21:10:07 +00:00
|
|
|
/**
|
|
|
|
* Creates an actor for the specied "very long" string. "Very long" is specified
|
|
|
|
* at the server's discretion.
|
|
|
|
*
|
|
|
|
* @param aString String
|
|
|
|
* The string.
|
|
|
|
*/
|
|
|
|
function LongStringActor(aString)
|
|
|
|
{
|
|
|
|
this.string = aString;
|
|
|
|
this.stringLength = aString.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
LongStringActor.prototype = {
|
|
|
|
|
|
|
|
actorPrefix: "longString",
|
|
|
|
|
|
|
|
disconnect: function LSA_disconnect() {
|
|
|
|
// Because longStringActors is not a weak map, we won't automatically leave
|
|
|
|
// it so we need to manually leave on disconnect so that we don't leak
|
|
|
|
// memory.
|
|
|
|
if (this.registeredPool && this.registeredPool.longStringActors) {
|
|
|
|
delete this.registeredPool.longStringActors[this.actorID];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a grip for this actor for returning in a protocol message.
|
|
|
|
*/
|
|
|
|
grip: function LSA_grip() {
|
|
|
|
return {
|
|
|
|
"type": "longString",
|
|
|
|
"initial": this.string.substring(
|
|
|
|
0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
|
|
|
|
"length": this.stringLength,
|
|
|
|
"actor": this.actorID
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a request to extract part of this actor's string.
|
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
|
|
|
*/
|
|
|
|
onSubstring: function LSA_onSubString(aRequest) {
|
|
|
|
return {
|
|
|
|
"from": this.actorID,
|
|
|
|
"substring": this.string.substring(aRequest.start, aRequest.end)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
LongStringActor.prototype.requestTypes = {
|
|
|
|
"substring": LongStringActor.prototype.onSubstring
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Creates an actor for the specified stack frame.
|
|
|
|
*
|
|
|
|
* @param aFrame Debugger.Frame
|
|
|
|
* The debuggee frame.
|
|
|
|
* @param aThreadActor ThreadActor
|
|
|
|
* The parent thread actor for this frame.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
function FrameActor(aFrame, aThreadActor)
|
|
|
|
{
|
|
|
|
this.frame = aFrame;
|
|
|
|
this.threadActor = aThreadActor;
|
|
|
|
}
|
|
|
|
|
|
|
|
FrameActor.prototype = {
|
|
|
|
actorPrefix: "frame",
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A pool that contains frame-lifetime objects, like the environment.
|
|
|
|
*/
|
|
|
|
_frameLifetimePool: null,
|
|
|
|
get frameLifetimePool() {
|
|
|
|
if (!this._frameLifetimePool) {
|
|
|
|
this._frameLifetimePool = new ActorPool(this.conn);
|
|
|
|
this.conn.addActorPool(this._frameLifetimePool);
|
|
|
|
}
|
|
|
|
return this._frameLifetimePool;
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Finalization handler that is called when the actor is being evicted from
|
|
|
|
* the pool.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
disconnect: function FA_disconnect() {
|
|
|
|
this.conn.removeActorPool(this._frameLifetimePool);
|
|
|
|
this._frameLifetimePool = null;
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
2012-03-13 07:13:02 +00:00
|
|
|
* Returns a frame form for use in a protocol message.
|
2012-01-23 08:29:15 +00:00
|
|
|
*/
|
2012-03-13 07:13:02 +00:00
|
|
|
form: function FA_form() {
|
|
|
|
let form = { actor: this.actorID,
|
2012-02-07 17:22:30 +00:00
|
|
|
type: this.frame.type };
|
|
|
|
if (this.frame.type === "call") {
|
2012-03-13 07:13:02 +00:00
|
|
|
form.callee = this.threadActor.createValueGrip(this.frame.callee);
|
2012-05-29 09:08:20 +00:00
|
|
|
form.calleeName = getFunctionName(this.frame.callee);
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
let envActor = this.threadActor
|
2012-03-21 15:49:23 +00:00
|
|
|
.createEnvironmentActor(this.frame.environment,
|
2012-01-23 08:29:15 +00:00
|
|
|
this.frameLifetimePool);
|
2012-03-21 15:49:23 +00:00
|
|
|
form.environment = envActor ? envActor.form(this.frame) : envActor;
|
2012-03-13 07:13:02 +00:00
|
|
|
form.this = this.threadActor.createValueGrip(this.frame.this);
|
|
|
|
form.arguments = this._args();
|
2012-02-11 09:44:20 +00:00
|
|
|
if (this.frame.script) {
|
2012-03-13 07:13:02 +00:00
|
|
|
form.where = { url: this.frame.script.url,
|
2012-02-11 09:44:20 +00:00
|
|
|
line: this.frame.script.getOffsetLine(this.frame.offset) };
|
|
|
|
}
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
if (!this.frame.older) {
|
2012-03-13 07:13:02 +00:00
|
|
|
form.oldest = true;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
2012-03-13 07:13:02 +00:00
|
|
|
return form;
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
_args: function FA__args() {
|
2012-03-13 07:13:02 +00:00
|
|
|
if (!this.frame.arguments) {
|
2012-02-07 17:22:30 +00:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
return [this.threadActor.createValueGrip(arg)
|
2012-03-13 07:13:02 +00:00
|
|
|
for each (arg in this.frame.arguments)];
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Handle a protocol request to pop this frame from the stack.
|
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
onPop: function FA_onPop(aRequest) {
|
2012-03-18 06:50:43 +00:00
|
|
|
// TODO: remove this when Debugger.Frame.prototype.pop is implemented
|
|
|
|
if (typeof this.frame.pop != "function") {
|
|
|
|
return { error: "notImplemented",
|
|
|
|
message: "Popping frames is not yet implemented." };
|
|
|
|
}
|
|
|
|
|
|
|
|
while (this.frame != this.threadActor.dbg.getNewestFrame()) {
|
|
|
|
this.threadActor.dbg.getNewestFrame().pop();
|
|
|
|
}
|
|
|
|
this.frame.pop(aRequest.completionValue);
|
|
|
|
|
|
|
|
// TODO: return the watches property when frame pop watch actors are
|
|
|
|
// implemented.
|
|
|
|
return { from: this.actorID };
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
FrameActor.prototype.requestTypes = {
|
|
|
|
"pop": FrameActor.prototype.onPop,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a BreakpointActor. BreakpointActors exist for the lifetime of their
|
|
|
|
* containing thread and are responsible for deleting breakpoints, handling
|
|
|
|
* breakpoint hits and associating breakpoints with scripts.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
2012-02-07 17:22:30 +00:00
|
|
|
* @param ThreadActor aThreadActor
|
|
|
|
* The parent thread actor that contains this breakpoint.
|
2012-06-03 13:39:50 +00:00
|
|
|
* @param object aLocation
|
|
|
|
* The location of the breakpoint as specified in the protocol.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-06-03 13:39:50 +00:00
|
|
|
function BreakpointActor(aThreadActor, aLocation)
|
2012-02-07 17:22:30 +00:00
|
|
|
{
|
2012-06-03 13:39:50 +00:00
|
|
|
this.scripts = [];
|
2012-02-07 17:22:30 +00:00
|
|
|
this.threadActor = aThreadActor;
|
2012-06-03 13:39:50 +00:00
|
|
|
this.location = aLocation;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BreakpointActor.prototype = {
|
|
|
|
actorPrefix: "breakpoint",
|
|
|
|
|
2012-06-03 13:39:50 +00:00
|
|
|
/**
|
|
|
|
* Called when this same breakpoint is added to another Debugger.Script
|
|
|
|
* instance, in the case of a page reload.
|
|
|
|
*
|
|
|
|
* @param aScript Debugger.Script
|
|
|
|
* The new source script on which the breakpoint has been set.
|
|
|
|
* @param ThreadActor aThreadActor
|
|
|
|
* The parent thread actor that contains this breakpoint.
|
|
|
|
*/
|
|
|
|
addScript: function BA_addScript(aScript, aThreadActor) {
|
|
|
|
this.threadActor = aThreadActor;
|
|
|
|
this.scripts.push(aScript);
|
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* A function that the engine calls when a breakpoint has been hit.
|
|
|
|
*
|
|
|
|
* @param aFrame Debugger.Frame
|
|
|
|
* The stack frame that contained the breakpoint.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
hit: function BA_hit(aFrame) {
|
2012-03-18 06:50:43 +00:00
|
|
|
// TODO: add the rest of the breakpoints on that line (bug 676602).
|
|
|
|
let reason = { type: "breakpoint", actors: [ this.actorID ] };
|
|
|
|
return this.threadActor._pauseAndRespond(aFrame, reason);
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
|
|
|
* Handle a protocol request to remove this breakpoint.
|
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
|
|
|
*/
|
2012-02-07 17:22:30 +00:00
|
|
|
onDelete: function BA_onDelete(aRequest) {
|
2012-06-03 13:39:50 +00:00
|
|
|
// Remove from the breakpoint store.
|
|
|
|
let scriptBreakpoints = this.threadActor._breakpointStore[this.location.url];
|
|
|
|
delete scriptBreakpoints[this.location.line];
|
|
|
|
// Remove the actual breakpoint.
|
2012-10-12 08:26:49 +00:00
|
|
|
this.threadActor._hooks.removeFromBreakpointPool(this);
|
2012-06-03 13:39:50 +00:00
|
|
|
for (let script of this.scripts) {
|
|
|
|
script.clearBreakpoint(this);
|
|
|
|
}
|
|
|
|
this.scripts = null;
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
return { from: this.actorID };
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
BreakpointActor.prototype.requestTypes = {
|
|
|
|
"delete": BreakpointActor.prototype.onDelete
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an EnvironmentActor. EnvironmentActors are responsible for listing
|
|
|
|
* the bindings introduced by a lexical environment and assigning new values to
|
|
|
|
* those identifier bindings.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
2012-03-21 15:49:23 +00:00
|
|
|
* @param Debugger.Environment aEnvironment
|
|
|
|
* The lexical environment that will be used to create the actor.
|
2012-02-07 17:22:30 +00:00
|
|
|
* @param ThreadActor aThreadActor
|
|
|
|
* The parent thread actor that contains this environment.
|
|
|
|
*/
|
2012-03-21 15:49:23 +00:00
|
|
|
function EnvironmentActor(aEnvironment, aThreadActor)
|
2012-02-07 17:22:30 +00:00
|
|
|
{
|
2012-03-21 15:49:23 +00:00
|
|
|
this.obj = aEnvironment;
|
2012-02-07 17:22:30 +00:00
|
|
|
this.threadActor = aThreadActor;
|
|
|
|
}
|
|
|
|
|
|
|
|
EnvironmentActor.prototype = {
|
|
|
|
actorPrefix: "environment",
|
|
|
|
|
2012-01-23 08:29:15 +00:00
|
|
|
/**
|
2012-03-21 15:49:23 +00:00
|
|
|
* Returns an environment form for use in a protocol message. Note that the
|
2012-05-29 09:08:20 +00:00
|
|
|
* requirement of passing the frame as a parameter is only temporary, since
|
|
|
|
* when bug 747514 lands, the environment will have a callee property that
|
|
|
|
* will contain it.
|
2012-03-21 15:49:23 +00:00
|
|
|
*
|
2012-05-29 09:08:20 +00:00
|
|
|
* @param Debugger.Frame aObject
|
|
|
|
* The stack frame object whose environment bindings are being
|
|
|
|
* generated.
|
2012-01-23 08:29:15 +00:00
|
|
|
*/
|
2012-03-21 15:49:23 +00:00
|
|
|
form: function EA_form(aObject) {
|
2012-02-07 17:22:30 +00:00
|
|
|
// Debugger.Frame might be dead by the time we get here, which will cause
|
|
|
|
// accessing its properties to throw.
|
2012-03-21 15:49:23 +00:00
|
|
|
if (!aObject.live) {
|
2012-02-07 17:22:30 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
let parent;
|
2012-03-21 15:49:23 +00:00
|
|
|
if (this.obj.parent) {
|
|
|
|
let thread = this.threadActor;
|
2012-05-29 09:08:20 +00:00
|
|
|
parent = thread.createEnvironmentActor(this.obj.parent,
|
2012-03-21 15:49:23 +00:00
|
|
|
this.registeredPool);
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-05-29 09:08:20 +00:00
|
|
|
// Deduce the frame that created the parent scope in order to pass it to
|
|
|
|
// parent.form(). TODO: this can be removed after bug 747514 is done.
|
|
|
|
let parentFrame = aObject;
|
|
|
|
if (this.obj.type == "declarative" && aObject.older) {
|
|
|
|
parentFrame = aObject.older;
|
|
|
|
}
|
2012-03-13 07:13:02 +00:00
|
|
|
let form = { actor: this.actorID,
|
2012-05-29 09:08:20 +00:00
|
|
|
parent: parent ? parent.form(parentFrame) : parent };
|
|
|
|
|
|
|
|
if (this.obj.type == "with") {
|
|
|
|
form.type = "with";
|
|
|
|
form.object = this.threadActor.createValueGrip(this.obj.object);
|
|
|
|
} else if (this.obj.type == "object") {
|
|
|
|
form.type = "object";
|
|
|
|
form.object = this.threadActor.createValueGrip(this.obj.object);
|
|
|
|
} else { // this.obj.type == "declarative"
|
|
|
|
if (aObject.callee) {
|
2012-03-13 07:13:02 +00:00
|
|
|
form.type = "function";
|
2012-05-29 09:08:20 +00:00
|
|
|
form.function = this.threadActor.createValueGrip(aObject.callee);
|
|
|
|
form.functionName = getFunctionName(aObject.callee);
|
2012-02-07 17:22:30 +00:00
|
|
|
} else {
|
2012-03-13 07:13:02 +00:00
|
|
|
form.type = "block";
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-03-21 15:49:23 +00:00
|
|
|
form.bindings = this._bindings(aObject);
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
2012-03-13 07:13:02 +00:00
|
|
|
return form;
|
2012-02-07 17:22:30 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the identifier bindings object as required by the remote protocol
|
2012-05-29 09:08:20 +00:00
|
|
|
* specification. Note that the requirement of passing the frame as a
|
|
|
|
* parameter is only temporary, since when bug 747514 lands, the environment
|
|
|
|
* will have a callee property that will contain it.
|
2012-03-21 15:49:23 +00:00
|
|
|
*
|
2012-05-29 09:08:20 +00:00
|
|
|
* @param Debugger.Frame aObject [optional]
|
|
|
|
* The stack frame whose environment bindings are being generated. When
|
|
|
|
* left unspecified, the bindings do not contain an 'arguments'
|
|
|
|
* property.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
2012-03-21 15:49:23 +00:00
|
|
|
_bindings: function EA_bindings(aObject) {
|
2012-03-13 07:13:02 +00:00
|
|
|
let bindings = { arguments: [], variables: {} };
|
2012-02-07 17:22:30 +00:00
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
// TODO: this part should be removed in favor of the commented-out part
|
|
|
|
// below when getVariableDescriptor lands (bug 725815).
|
|
|
|
if (typeof this.obj.getVariable != "function") {
|
|
|
|
//if (typeof this.obj.getVariableDescriptor != "function") {
|
2012-02-07 17:22:30 +00:00
|
|
|
return bindings;
|
|
|
|
}
|
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
let parameterNames;
|
|
|
|
if (aObject && aObject.callee) {
|
|
|
|
parameterNames = aObject.callee.parameterNames;
|
|
|
|
}
|
|
|
|
for each (let name in parameterNames) {
|
2012-03-13 07:13:02 +00:00
|
|
|
let arg = {};
|
2012-03-21 15:49:23 +00:00
|
|
|
// TODO: this part should be removed in favor of the commented-out part
|
|
|
|
// below when getVariableDescriptor lands (bug 725815).
|
|
|
|
let desc = {
|
|
|
|
value: this.obj.getVariable(name),
|
|
|
|
configurable: false,
|
|
|
|
writable: true,
|
|
|
|
enumerable: true
|
|
|
|
};
|
|
|
|
|
|
|
|
// let desc = this.obj.getVariableDescriptor(name);
|
2012-03-13 07:13:02 +00:00
|
|
|
let descForm = {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: desc.configurable
|
|
|
|
};
|
|
|
|
if ("value" in desc) {
|
|
|
|
descForm.value = this.threadActor.createValueGrip(desc.value);
|
|
|
|
descForm.writable = desc.writable;
|
|
|
|
} else {
|
|
|
|
descForm.get = this.threadActor.createValueGrip(desc.get);
|
|
|
|
descForm.set = this.threadActor.createValueGrip(desc.set);
|
|
|
|
}
|
|
|
|
arg[name] = descForm;
|
|
|
|
bindings.arguments.push(arg);
|
|
|
|
}
|
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
for each (let name in this.obj.names()) {
|
2012-03-13 07:13:02 +00:00
|
|
|
if (bindings.arguments.some(function exists(element) {
|
|
|
|
return !!element[name];
|
|
|
|
})) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-03-21 15:49:23 +00:00
|
|
|
// TODO: this part should be removed in favor of the commented-out part
|
|
|
|
// below when getVariableDescriptor lands.
|
|
|
|
let desc = {
|
|
|
|
configurable: false,
|
|
|
|
writable: true,
|
|
|
|
enumerable: true
|
|
|
|
};
|
2012-06-10 23:44:50 +00:00
|
|
|
try {
|
|
|
|
desc.value = this.obj.getVariable(name);
|
|
|
|
} catch (e) {
|
|
|
|
// Avoid "Debugger scope is not live" errors for |arguments|, introduced
|
|
|
|
// in bug 746601.
|
|
|
|
if (name != "arguments") {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2012-03-21 15:49:23 +00:00
|
|
|
//let desc = this.obj.getVariableDescriptor(name);
|
2012-03-13 07:13:02 +00:00
|
|
|
let descForm = {
|
|
|
|
enumerable: true,
|
|
|
|
configurable: desc.configurable
|
|
|
|
};
|
|
|
|
if ("value" in desc) {
|
|
|
|
descForm.value = this.threadActor.createValueGrip(desc.value);
|
|
|
|
descForm.writable = desc.writable;
|
2012-02-07 17:22:30 +00:00
|
|
|
} else {
|
2012-03-13 07:13:02 +00:00
|
|
|
descForm.get = this.threadActor.createValueGrip(desc.get);
|
|
|
|
descForm.set = this.threadActor.createValueGrip(desc.set);
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
2012-03-13 07:13:02 +00:00
|
|
|
bindings.variables[name] = descForm;
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return bindings;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to change the value of a variable bound in this
|
|
|
|
* lexical environment.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
|
|
|
onAssign: function EA_onAssign(aRequest) {
|
2012-05-24 11:23:53 +00:00
|
|
|
// TODO: enable the commented-out part when getVariableDescriptor lands
|
|
|
|
// (bug 725815).
|
|
|
|
/*let desc = this.obj.getVariableDescriptor(aRequest.name);
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
if (!desc.writable) {
|
|
|
|
return { error: "immutableBinding",
|
|
|
|
message: "Changing the value of an immutable binding is not " +
|
|
|
|
"allowed" };
|
2012-05-24 11:23:53 +00:00
|
|
|
}*/
|
2012-02-07 17:22:30 +00:00
|
|
|
|
|
|
|
try {
|
2012-03-21 15:49:23 +00:00
|
|
|
this.obj.setVariable(aRequest.name, aRequest.value);
|
2012-02-07 17:22:30 +00:00
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof Debugger.DebuggeeWouldRun) {
|
2012-03-13 07:13:02 +00:00
|
|
|
return { error: "threadWouldRun",
|
|
|
|
cause: e.cause ? e.cause : "setter",
|
|
|
|
message: "Assigning a value would cause the debuggee to run" };
|
2012-02-07 17:22:30 +00:00
|
|
|
}
|
|
|
|
// This should never happen, so let it complain loudly if it does.
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
return { from: this.actorID };
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle a protocol request to fully enumerate the bindings introduced by the
|
|
|
|
* lexical environment.
|
2012-01-23 08:29:15 +00:00
|
|
|
*
|
|
|
|
* @param aRequest object
|
|
|
|
* The protocol request object.
|
2012-02-07 17:22:30 +00:00
|
|
|
*/
|
|
|
|
onBindings: function EA_onBindings(aRequest) {
|
|
|
|
return { from: this.actorID,
|
|
|
|
bindings: this._bindings() };
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
EnvironmentActor.prototype.requestTypes = {
|
|
|
|
"assign": EnvironmentActor.prototype.onAssign,
|
|
|
|
"bindings": EnvironmentActor.prototype.onBindings
|
|
|
|
};
|
2012-05-29 09:08:20 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function to deduce the name of the provided function.
|
|
|
|
*
|
|
|
|
* @param Debugger.Object aFunction
|
|
|
|
* The function whose name will be returned.
|
|
|
|
*/
|
|
|
|
function getFunctionName(aFunction) {
|
|
|
|
let name;
|
|
|
|
if (aFunction.name) {
|
|
|
|
name = aFunction.name;
|
|
|
|
} else {
|
2012-09-10 08:48:52 +00:00
|
|
|
// Check if the developer has added a de-facto standard displayName
|
|
|
|
// property for us to use.
|
2012-05-29 09:08:20 +00:00
|
|
|
let desc = aFunction.getOwnPropertyDescriptor("displayName");
|
|
|
|
if (desc && desc.value && typeof desc.value == "string") {
|
|
|
|
name = desc.value;
|
2012-09-18 07:30:02 +00:00
|
|
|
} else {
|
2012-09-10 08:48:52 +00:00
|
|
|
// Otherwise use SpiderMonkey's inferred name.
|
|
|
|
name = aFunction.displayName;
|
2012-05-29 09:08:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|