зеркало из https://github.com/mozilla/gecko-dev.git
Bug 917963 - Implement break on first script statement. r=davidwalsh
Differential Revision: https://phabricator.services.mozilla.com/D85198
This commit is contained in:
Родитель
db7d4269ad
Коммит
06838cf425
|
@ -20,6 +20,7 @@ add_task(async function() {
|
|||
"event.xhr.load",
|
||||
"timer.timeout.set",
|
||||
"timer.timeout.fire",
|
||||
"script.source.firstStatement",
|
||||
]);
|
||||
|
||||
invokeInTab("clickHandler");
|
||||
|
@ -41,6 +42,11 @@ add_task(async function() {
|
|||
assertPauseLocation(dbg, 28);
|
||||
await resume(dbg);
|
||||
|
||||
invokeInTab("evalHandler");
|
||||
await waitForPaused(dbg);
|
||||
assertPauseLocation(dbg, 2, "http://example.com/eval-test.js");
|
||||
await resume(dbg);
|
||||
|
||||
// Test that we don't pause on event breakpoints when source is blackboxed.
|
||||
await clickElement(dbg, "blackbox");
|
||||
await waitForDispatch(dbg, "BLACKBOX");
|
||||
|
@ -59,10 +65,10 @@ add_task(async function() {
|
|||
await waitForDispatch(dbg, "BLACKBOX");
|
||||
});
|
||||
|
||||
function assertPauseLocation(dbg, line) {
|
||||
function assertPauseLocation(dbg, line, url = "event-breakpoints.js") {
|
||||
const { location } = dbg.selectors.getVisibleSelectedFrame();
|
||||
|
||||
const source = findSource(dbg, "event-breakpoints.js");
|
||||
const source = findSource(dbg, url);
|
||||
|
||||
is(location.sourceId, source.id, `correct sourceId`);
|
||||
is(location.line, line, `correct line`);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<button id="click-button">Run Click Handler</button>
|
||||
<button id="xhr-button">Run XHR Handler</button>
|
||||
<button id="timer-button">Run Timer Handler</button>
|
||||
<button id="eval-button">Run Eval</button>
|
||||
<div id="click-target" style="margin: 50px; background-color: green;">
|
||||
Click Target
|
||||
</div>
|
||||
|
|
|
@ -29,3 +29,11 @@ function timerHandler() {
|
|||
}, 50);
|
||||
console.log("timer set");
|
||||
}
|
||||
|
||||
document.getElementById("eval-button").onmousedown = evalHandler;
|
||||
function evalHandler() {
|
||||
eval(`
|
||||
console.log("eval ran");
|
||||
//# sourceURL=http://example.com/eval-test.js
|
||||
`);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ const { threadSpec } = require("devtools/shared/specs/thread");
|
|||
const {
|
||||
getAvailableEventBreakpoints,
|
||||
eventBreakpointForNotification,
|
||||
eventsRequireNotifications,
|
||||
firstStatementBreakpointId,
|
||||
makeEventBreakpointMessage,
|
||||
} = require("devtools/server/actors/utils/event-breakpoints");
|
||||
const {
|
||||
|
@ -215,6 +217,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
|
|||
this._parent.on("will-navigate", this._onWillNavigate);
|
||||
this._parent.on("navigate", this._onNavigate);
|
||||
|
||||
this._firstStatementBreakpoint = null;
|
||||
this._debuggerNotificationObserver = new DebuggerNotificationObserver();
|
||||
|
||||
if (Services.obs) {
|
||||
|
@ -617,18 +620,92 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
|
|||
setActiveEventBreakpoints: function(ids) {
|
||||
this._activeEventBreakpoints = new Set(ids);
|
||||
|
||||
if (this._activeEventBreakpoints.size === 0) {
|
||||
this._debuggerNotificationObserver.removeListener(
|
||||
this._eventBreakpointListener
|
||||
);
|
||||
} else {
|
||||
if (eventsRequireNotifications(ids)) {
|
||||
this._debuggerNotificationObserver.addListener(
|
||||
this._eventBreakpointListener
|
||||
);
|
||||
} else {
|
||||
this._debuggerNotificationObserver.removeListener(
|
||||
this._eventBreakpointListener
|
||||
);
|
||||
}
|
||||
|
||||
if (this._activeEventBreakpoints.has(firstStatementBreakpointId())) {
|
||||
this._ensureFirstStatementBreakpointInitialized();
|
||||
|
||||
this._firstStatementBreakpoint.hit = frame =>
|
||||
this._pauseAndRespondEventBreakpoint(
|
||||
frame,
|
||||
firstStatementBreakpointId()
|
||||
);
|
||||
} else if (this._firstStatementBreakpoint) {
|
||||
// Disabling the breakpoint disables the feature as much as we need it
|
||||
// to. We do not bother removing breakpoints from the scripts themselves
|
||||
// here because the breakpoints will be a no-op if `hit` is `null`, and
|
||||
// if we wanted to remove them, we'd need a way to iterate through them
|
||||
// all, which would require us to hold strong references to them, which
|
||||
// just isn't needed. Plus, if the user disables and then re-enables the
|
||||
// feature again later, the breakpoints will still be there to work.
|
||||
this._firstStatementBreakpoint.hit = null;
|
||||
}
|
||||
},
|
||||
|
||||
_ensureFirstStatementBreakpointInitialized() {
|
||||
if (this._firstStatementBreakpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._firstStatementBreakpoint = { hit: null };
|
||||
for (const script of this.dbg.findScripts()) {
|
||||
this._maybeTrackFirstStatementBreakpoint(script);
|
||||
}
|
||||
},
|
||||
|
||||
_maybeTrackFirstStatementBreakpointForNewGlobal(global) {
|
||||
if (this._firstStatementBreakpoint) {
|
||||
for (const script of this.dbg.findScripts({ global })) {
|
||||
this._maybeTrackFirstStatementBreakpoint(script);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_maybeTrackFirstStatementBreakpoint(script) {
|
||||
if (
|
||||
// If the feature is not enabled yet, there is nothing to do.
|
||||
!this._firstStatementBreakpoint ||
|
||||
// WASM files don't have a first statement.
|
||||
script.format !== "js" ||
|
||||
// All "top-level" scripts are non-functions, whether that's because
|
||||
// the script is a module, a global script, or an eval or what.
|
||||
script.isFunction
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bps = script.getPossibleBreakpoints();
|
||||
|
||||
// Scripts aren't guaranteed to have a step start if for instance the
|
||||
// file contains only function declarations, so in that case we try to
|
||||
// fall back to whatever we can find.
|
||||
let meta = bps.find(bp => bp.isStepStart) || bps[0];
|
||||
if (!meta) {
|
||||
// We've tried to avoid using `getAllColumnOffsets()` because the set of
|
||||
// locations included in this list is very under-defined, but for this
|
||||
// usecase it's not the end of the world. Maybe one day we could have an
|
||||
// "onEnterFrame" that was scoped to a specific script to avoid this.
|
||||
meta = script.getAllColumnOffsets()[0];
|
||||
}
|
||||
|
||||
if (!meta) {
|
||||
// Not certain that this is actually possible, but including for sanity
|
||||
// so that we don't throw unexpectedly.
|
||||
return;
|
||||
}
|
||||
script.setBreakpoint(meta.offset, this._firstStatementBreakpoint);
|
||||
},
|
||||
|
||||
_onNewDebuggee(global) {
|
||||
this._maybeTrackFirstStatementBreakpointForNewGlobal(global);
|
||||
try {
|
||||
this._debuggerNotificationObserver.connect(global.unsafeDereference());
|
||||
} catch (e) {}
|
||||
|
@ -1949,16 +2026,15 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
|
|||
},
|
||||
|
||||
/**
|
||||
* A function that the engine calls when a new script has been loaded into the
|
||||
* scope of the specified debuggee global.
|
||||
* A function that the engine calls when a new script has been loaded.
|
||||
*
|
||||
* @param script Debugger.Script
|
||||
* The source script that has been loaded into a debuggee compartment.
|
||||
* @param global Debugger.Object
|
||||
* A Debugger.Object instance whose referent is the global object.
|
||||
*/
|
||||
onNewScript: function(script, global) {
|
||||
onNewScript: function(script) {
|
||||
this._addSource(script.source);
|
||||
|
||||
this._maybeTrackFirstStatementBreakpoint(script);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,6 +79,13 @@ function animationEvent(operation, name, notificationType) {
|
|||
};
|
||||
}
|
||||
|
||||
const SCRIPT_FIRST_STATEMENT_BREAKPOINT = {
|
||||
id: "script.source.firstStatement",
|
||||
type: "script",
|
||||
name: "Script First Statement",
|
||||
message: "Script First Statement",
|
||||
};
|
||||
|
||||
const AVAILABLE_BREAKPOINTS = [
|
||||
{
|
||||
name: "Animation",
|
||||
|
@ -251,6 +258,10 @@ const AVAILABLE_BREAKPOINTS = [
|
|||
generalEvent("pointer", "lostpointercapture"),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Script",
|
||||
items: [SCRIPT_FIRST_STATEMENT_BREAKPOINT],
|
||||
},
|
||||
{
|
||||
name: "Timer",
|
||||
items: [
|
||||
|
@ -365,6 +376,8 @@ for (const eventBP of FLAT_EVENTS) {
|
|||
}
|
||||
byEventType[eventType] = eventBP.id;
|
||||
}
|
||||
} else if (eventBP.type === "script") {
|
||||
// Nothing to do.
|
||||
} else {
|
||||
throw new Error("Unknown type: " + eventBP.type);
|
||||
}
|
||||
|
@ -430,6 +443,25 @@ function makeEventBreakpointMessage(id) {
|
|||
return EVENTS_BY_ID[id].message;
|
||||
}
|
||||
|
||||
exports.firstStatementBreakpointId = firstStatementBreakpointId;
|
||||
function firstStatementBreakpointId() {
|
||||
return SCRIPT_FIRST_STATEMENT_BREAKPOINT.id;
|
||||
}
|
||||
|
||||
exports.eventsRequireNotifications = eventsRequireNotifications;
|
||||
function eventsRequireNotifications(ids) {
|
||||
for (const id of ids) {
|
||||
const eventBreakpoint = EVENTS_BY_ID[id];
|
||||
|
||||
// Script events are implemented directly in the server and do not require
|
||||
// notifications from Gecko, so there is no need to watch for them.
|
||||
if (eventBreakpoint && eventBreakpoint.type !== "script") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
exports.getAvailableEventBreakpoints = getAvailableEventBreakpoints;
|
||||
function getAvailableEventBreakpoints() {
|
||||
const available = [];
|
||||
|
|
Загрузка…
Ссылка в новой задаче