Bug 1354679 - Show the paused debugger overlay when script gets paused; r=ochameau r=pbro
MozReview-Commit-ID: JZkXtGJ2YIx
--- a/devtools/client/framework/attach-thread.js
+++ b/devtools/client/framework/attach-thread.js
@@ -24,16 +24,17 @@ function handleThreadState(toolbox, even
if (event === "paused") {
toolbox.highlightTool("jsdebugger");
if (packet.why.type === "debuggerStatement" ||
packet.why.type === "breakpoint" ||
packet.why.type === "exception") {
toolbox.raise();
toolbox.selectTool("jsdebugger");
+ toolbox._threadClient.clientPaused();
}
} else if (event === "resumed") {
toolbox.unhighlightTool("jsdebugger");
}
}
function attachThread(toolbox) {
let deferred = defer();
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -1223,16 +1223,24 @@ const ThreadActor = ActorClassWithSpec(t
_getNextStepFrame: function (frame) {
let stepFrame = frame.reportedPop ? frame.older : frame;
if (!stepFrame || !stepFrame.script) {
stepFrame = null;
}
return stepFrame;
},
+ /**
+ * Client informs the server that we have a user significant pause.
+ */
+ onClientPaused: function () {
+ this.emit("clientPaused");
+ return { type: "clientPaused" };
+ },
+
onClientEvaluate: function (request) {
if (this.state !== "paused") {
return { error: "wrongState",
message: "Debuggee must be paused to evaluate code." };
}
let frame = this._requestFrame(request.frame);
if (!frame) {
@@ -1240,16 +1248,17 @@ const ThreadActor = ActorClassWithSpec(t
message: "Evaluation frame not found" };
}
if (!frame.environment) {
return { error: "notDebuggee",
message: "cannot access the environment of this frame." };
}
+ // TODO: this is not being used, paused takes no arguements
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
@@ -1537,17 +1546,16 @@ const ThreadActor = ActorClassWithSpec(t
// We don't want to actually have nested pauses (although we
// 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;
}
- this.emit("pausing", this.state);
// Clear stepping hooks.
this.dbg.onEnterFrame = undefined;
this.dbg.onExceptionUnwind = undefined;
if (frame) {
frame.onStep = undefined;
frame.onPop = undefined;
}
@@ -1596,24 +1604,24 @@ const ThreadActor = ActorClassWithSpec(t
if (poppedFrames) {
packet.poppedFrames = poppedFrames;
}
return packet;
},
_resumed: function () {
+ this.emit('resumed');
this._state = "running";
// Drop the actors in the pause actor pool.
this.conn.removeActorPool(this._pausePool);
this._pausePool = null;
this._pauseActor = null;
- this.emit("resumed");
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.
@@ -2059,16 +2067,17 @@ const ThreadActor = ActorClassWithSpec(t
});
Object.assign(ThreadActor.prototype.requestTypes, {
"attach": ThreadActor.prototype.onAttach,
"detach": ThreadActor.prototype.onDetach,
"reconfigure": ThreadActor.prototype.onReconfigure,
"resume": ThreadActor.prototype.onResume,
"clientEvaluate": ThreadActor.prototype.onClientEvaluate,
+ "clientPaused": ThreadActor.prototype.onClientPaused,
"frames": ThreadActor.prototype.onFrames,
"interrupt": ThreadActor.prototype.onInterrupt,
"eventListeners": ThreadActor.prototype.onEventListeners,
"releaseMany": ThreadActor.prototype.onReleaseMany,
"sources": ThreadActor.prototype.onSources,
"threadGrips": ThreadActor.prototype.onThreadGrips,
"prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
});
@@ -2388,22 +2397,17 @@ exports.unwrapDebuggerObjectGlobal = wra
}
};
// Manage a collection of PausedDebuggerOverlays and their HighlighterEnvironments indexed
// by TabActors (export it so it the paused state can be tested).
const pausedStateOverlays = exports.pausedStateOverlays = new Map();
function attachPauseStateListeners(threadActor) {
- EventEmitter.on(threadActor, "pausing", (pausingState) => {
- if (pausingState !== "attached") {
- console.log(pausingState);
- return showPausedStateOverlay(threadActor._parent);
- }
- });
+ EventEmitter.on(threadActor, "clientPaused", () => showPausedStateOverlay(threadActor._parent));
EventEmitter.on(threadActor, "resumed",() => hidePausedStateOverlay(threadActor._parent));
EventEmitter.on(threadActor, "exiting",() => destroyPausedStateOverlay(threadActor._parent));
}
/**
* Get the instance of the PausedDebuggerOverlay for a TabActor.
*
* @param {TabActor} tabActor
--- a/devtools/server/tests/unit/test_pausedOverlay.js
+++ b/devtools/server/tests/unit/test_pausedOverlay.js
@@ -43,75 +43,32 @@ add_task(async function() {
gThreadClient = threadClient;
gTabClient = tabClient;
// Inject a mock overlay here. We need the TabActor reference in order to inject the
// mock into the pausedStateOverlays map (go over the transport boundary to do this).
pausedStateOverlays.set(
gClient._transport._serverConnection.getActor(gTabClient._actor), mockOverlay);
- yield test_debugger_statement();
- yield test_exception();
- yield test_breakpoint();
+ yield test_client_paused();
yield gClient.close();
do_test_finished();
});
});
-function* test_debugger_statement() {
+function* test_client_paused() {
ok(!isOverlayVisible, "The overlay is hidden at first");
+ // this is triggering a pause, this may be triggered in any way
+ // such as breakpoint, exception, etc
yield executeOnNextTickAndWaitForPause(() => {
Cu.evalInSandbox("debugger;", gDebuggee);
}, gClient);
+ // we only show the paused overlay if the client determines this is an
+ // important pause for the user
+ yield gThreadClient.clientPaused();
- ok(isOverlayVisible, "The overlay is visible on debugger statements");
+ ok(isOverlayVisible, "The overlay is visible on client paused");
yield gThreadClient.resume();
ok(!isOverlayVisible, "The overlay is hidden on resume");
}
-
-function* test_exception() {
- yield gThreadClient.pauseOnExceptions(true, false);
-
- ok(!isOverlayVisible, "The overlay is hidden at first");
-
- yield executeOnNextTickAndWaitForPause(() => {
- Cu.evalInSandbox("try { throw new Error('test error'); } catch (e) {}", gDebuggee);
- }, gClient);
-
- ok(isOverlayVisible,
- "The overlay is visible when break on exceptions is set and there is an error");
-
- yield gThreadClient.resume();
- ok(!isOverlayVisible, "The overlay is hidden on resume");
-
- yield gThreadClient.pauseOnExceptions(false);
-}
-
-function* test_breakpoint() {
- ok(!isOverlayVisible, "The overlay is hidden at first");
-
- executeSoon(() => {
- Cu.evalInSandbox(
- `var line0 = new Error().lineNumber;
- function breakInHere() {
- return 2;
- }`,
- gDebuggee
- );
- });
-
- let { sources } = yield gThreadClient.getSources();
- let source = gThreadClient.source(sources[sources.length - 1]);
-
- yield setBreakpoint(source, { line: gDebuggee.line0 + 2 });
-
- yield executeOnNextTickAndWaitForPause(() => {
- gDebuggee.breakInHere();
- }, gClient);
-
- ok(isOverlayVisible, "The overlay is visible when a breakpoint is found");
-
- yield gThreadClient.resume();
- ok(!isOverlayVisible, "The overlay is hidden on resume");
-}
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -270,16 +270,24 @@ ThreadClient.prototype = {
onResponse(response);
return response;
}
return this.resume(onResponse);
});
},
/**
+ * Notify the server that the client needs a pause overlay to be shown.
+ *
+ */
+ clientPaused: DebuggerClient.requester({
+ type: "clientPaused"
+ }),
+
+ /**
* Send a clientEvaluate packet to the debuggee. Response
* will be a resume packet.
*
* @param string frame
* The actor ID of the frame where the evaluation should take place.
* @param string expression
* The expression that will be evaluated in the scope of the frame
* above.