From 1d75bb2c8a9d98e83ba8338f426b5497fcc528c6 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Wed, 12 Feb 2020 04:02:17 +0000 Subject: [PATCH] Bug 1610416 - Expose SavedFrame frames via debugger server. r=jlast Differential Revision: https://phabricator.services.mozilla.com/D61517 --HG-- extra : moz-landing-system : lando --- devtools/server/actors/frame.js | 82 ++++++++++++++++++++++++++++++-- devtools/server/actors/thread.js | 52 +++++++++++++++----- 2 files changed, 119 insertions(+), 15 deletions(-) diff --git a/devtools/server/actors/frame.js b/devtools/server/actors/frame.js index 969741e2b7a7..a9f77fb5720a 100644 --- a/devtools/server/actors/frame.js +++ b/devtools/server/actors/frame.js @@ -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; diff --git a/devtools/server/actors/thread.js b/devtools/server/actors/thread.js index e31447834c9a..cb816c84d060 100644 --- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -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; },