Bug 1610416 - Expose SavedFrame frames via debugger server. r=jlast

Differential Revision: https://phabricator.services.mozilla.com/D61517

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Logan Smyth 2020-02-12 04:02:17 +00:00
parent ff0625bca4
commit 1d75bb2c8a
2 changed files with 119 additions and 15 deletions

View File

@ -4,6 +4,9 @@
"use strict";
const { Cu } = require("chrome");
const Debugger = require("Debugger");
const { assert } = require("devtools/shared/DevToolsUtils");
const { ActorPool } = require("devtools/server/actors/common");
const { createValueGrip } = require("devtools/server/actors/object/utils");
const { ActorClassWithSpec } = require("devtools/shared/protocol");
@ -18,6 +21,49 @@ function formatDisplayName(frame) {
return `(${frame.type})`;
}
function isDeadSavedFrame(savedFrame) {
return Cu && Cu.isDeadWrapper(savedFrame);
}
function isValidSavedFrame(threadActor, savedFrame) {
return (
!isDeadSavedFrame(savedFrame) &&
// If the frame's source is unknown to the debugger, then we ignore it
// since the frame likely does not belong to a realm that is marked
// as a debuggee.
// This check will also fail if the frame would have been known but was
// GCed before the debugger was opened on the page.
// TODO: Use SavedFrame's security principal to limit non-debuggee frames
// and pass all unknown frames to the debugger as a URL with no sourceID.
getSavedFrameSource(threadActor, savedFrame)
);
}
function getSavedFrameSource(threadActor, savedFrame) {
return threadActor.sources.getSourceActorByInternalSourceId(
savedFrame.sourceId
);
}
function getSavedFrameParent(threadActor, savedFrame) {
if (isDeadSavedFrame(savedFrame)) {
return null;
}
while (true) {
savedFrame = savedFrame.parent || savedFrame.asyncParent;
// If the saved frame is a dead wrapper, we don't have any way to keep
// stepping through parent frames.
if (!savedFrame || isDeadSavedFrame(savedFrame)) {
savedFrame = null;
break;
}
if (isValidSavedFrame(threadActor, savedFrame)) {
break;
}
}
return savedFrame;
}
/**
* An actor for a specified stack frame.
*/
@ -25,7 +71,7 @@ const FrameActor = ActorClassWithSpec(frameSpec, {
/**
* Creates the Frame actor.
*
* @param frame Debugger.Frame
* @param frame Debugger.Frame|SavedFrame
* The debuggee frame.
* @param threadActor ThreadActor
* The parent thread actor for this frame.
@ -81,13 +127,41 @@ const FrameActor = ActorClassWithSpec(frameSpec, {
* Returns a frame form for use in a protocol message.
*/
form: function() {
// SavedFrame actors have their own frame handling.
if (!(this.frame instanceof Debugger.Frame)) {
// The Frame actor shouldn't be used after evaluation is resumed, so
// there shouldn't be an easy way for the saved frame to be referenced
// once it has died.
assert(!isDeadSavedFrame(this.frame));
const obj = {
actor: this.actorID,
// TODO: Bug 1610418 - Consider updating SavedFrame to have a type.
type: "dead",
asyncCause: this.frame.asyncCause,
state: "dead",
displayName: this.frame.functionDisplayName,
arguments: [],
where: {
// The frame's source should always be known because
// getSavedFrameParent will skip over frames with unknown sources.
actor: getSavedFrameSource(this.threadActor, this.frame).actorID,
line: this.frame.line,
// SavedFrame objects have a 1-based column number, but this API and
// Debugger API objects use a 0-based column value.
column: this.frame.column - 1,
},
oldest: !getSavedFrameParent(this.threadActor, this.frame),
};
return obj;
}
const threadActor = this.threadActor;
const form = {
actor: this.actorID,
type: this.frame.type,
asyncCause: this.frame.onStack ? null : "await",
// This should expand with "dead" when we support SavedFrames.
state: this.frame.onStack ? "on-stack" : "suspended",
};
@ -139,3 +213,5 @@ const FrameActor = ActorClassWithSpec(frameSpec, {
exports.FrameActor = FrameActor;
exports.formatDisplayName = formatDisplayName;
exports.getSavedFrameParent = getSavedFrameParent;
exports.isValidSavedFrame = isValidSavedFrame;

View File

@ -11,6 +11,7 @@ const { ActorPool } = require("devtools/server/actors/common");
const { createValueGrip } = require("devtools/server/actors/object/utils");
const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const Debugger = require("Debugger");
const { assert, dumpn, reportException } = DevToolsUtils;
const { threadSpec } = require("devtools/shared/specs/thread");
const {
@ -54,6 +55,18 @@ loader.lazyRequireGetter(
"devtools/server/actors/frame",
true
);
loader.lazyRequireGetter(
this,
"getSavedFrameParent",
"devtools/server/actors/frame",
true
);
loader.lazyRequireGetter(
this,
"isValidSavedFrame",
"devtools/server/actors/frame",
true
);
loader.lazyRequireGetter(
this,
"HighlighterEnvironment",
@ -106,6 +119,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
this._activeEventPause = null;
this._pauseOverlay = null;
this._priorPause = null;
this._frameActorMap = new WeakMap();
this._watchpointsMap = new WatchpointMap(this);
@ -1218,8 +1232,18 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
const currentFrame = frame;
frame = null;
if (currentFrame.older) {
if (!(currentFrame instanceof Debugger.Frame)) {
frame = getSavedFrameParent(this, currentFrame);
} else if (currentFrame.older) {
frame = currentFrame.older;
} else if (
this._options.shouldIncludeSavedFrames &&
currentFrame.olderSavedFrame
) {
frame = currentFrame.olderSavedFrame;
if (frame && !isValidSavedFrame(this, frame)) {
frame = null;
}
} else if (
this._options.shouldIncludeAsyncLiveFrames &&
currentFrame.asyncPromise
@ -1262,9 +1286,14 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
// Return count frames, or all remaining frames if count is not defined.
const frames = [];
for (; frame && (!count || i < start + count); i++, walkToParentFrame()) {
const sourceActor = this.sources.createSourceActor(frame.script.source);
if (!sourceActor) {
continue;
// SavedFrame instances don't have direct Debugger.Source object. If
// there is an active Debugger.Source that represents the SaveFrame's
// source, it will have already been created in the server.
if (frame instanceof Debugger.Frame) {
const sourceActor = this.sources.createSourceActor(frame.script.source);
if (!sourceActor) {
continue;
}
}
const frameActor = this._createFrameActor(frame, i);
frames.push(frameActor);
@ -1494,15 +1523,14 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
},
_createFrameActor: function(frame, depth) {
if (frame.actor) {
return frame.actor;
let actor = this._frameActorMap.get(frame);
if (!actor) {
actor = new FrameActor(frame, this, depth);
this._frameActors.push(actor);
this._framesPool.addActor(actor);
this._frameActorMap.set(frame, actor);
}
const actor = new FrameActor(frame, this, depth);
this._frameActors.push(actor);
this._framesPool.addActor(actor);
frame.actor = actor;
return actor;
},