summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/frame.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/frame.js')
-rw-r--r--devtools/server/actors/frame.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/devtools/server/actors/frame.js b/devtools/server/actors/frame.js
new file mode 100644
index 0000000000..be4f5e3eb3
--- /dev/null
+++ b/devtools/server/actors/frame.js
@@ -0,0 +1,225 @@
+/* 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/. */
+
+"use strict";
+
+const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
+const { Pool } = require("resource://devtools/shared/protocol/Pool.js");
+const { frameSpec } = require("resource://devtools/shared/specs/frame.js");
+
+const Debugger = require("Debugger");
+const { assert } = require("resource://devtools/shared/DevToolsUtils.js");
+const {
+ createValueGrip,
+} = require("resource://devtools/server/actors/object/utils.js");
+
+function formatDisplayName(frame) {
+ if (frame.type === "call") {
+ const callee = frame.callee;
+ return callee.name || callee.userDisplayName || callee.displayName;
+ }
+
+ 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.sourcesManager.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.
+ */
+class FrameActor extends Actor {
+ /**
+ * Creates the Frame actor.
+ *
+ * @param frame Debugger.Frame|SavedFrame
+ * The debuggee frame.
+ * @param threadActor ThreadActor
+ * The parent thread actor for this frame.
+ */
+ constructor(frame, threadActor, depth) {
+ super(threadActor.conn, frameSpec);
+
+ this.frame = frame;
+ this.threadActor = threadActor;
+ this.depth = depth;
+ }
+
+ /**
+ * A pool that contains frame-lifetime objects, like the environment.
+ */
+ _frameLifetimePool = null;
+ get frameLifetimePool() {
+ if (!this._frameLifetimePool) {
+ this._frameLifetimePool = new Pool(this.conn, "frame");
+ }
+ return this._frameLifetimePool;
+ }
+
+ /**
+ * Finalization handler that is called when the actor is being evicted from
+ * the pool.
+ */
+ destroy() {
+ if (this._frameLifetimePool) {
+ this._frameLifetimePool.destroy();
+ this._frameLifetimePool = null;
+ }
+ super.destroy();
+ }
+
+ getEnvironment() {
+ try {
+ if (!this.frame.environment) {
+ return {};
+ }
+ } catch (e) {
+ // |this.frame| might not be live. FIXME Bug 1477030 we shouldn't be
+ // using frames we derived from a point where we are not currently
+ // paused at.
+ return {};
+ }
+
+ const envActor = this.threadActor.createEnvironmentActor(
+ this.frame.environment,
+ this.frameLifetimePool
+ );
+
+ return envActor.form();
+ }
+
+ /**
+ * Returns a frame form for use in a protocol message.
+ */
+ form() {
+ // 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",
+ state: this.frame.onStack ? "on-stack" : "suspended",
+ };
+
+ if (this.depth) {
+ form.depth = this.depth;
+ }
+
+ if (this.frame.type != "wasmcall") {
+ form.this = createValueGrip(
+ this.frame.this,
+ threadActor._pausePool,
+ threadActor.objectGrip
+ );
+ }
+
+ form.displayName = formatDisplayName(this.frame);
+ form.arguments = this._args();
+
+ if (this.frame.script) {
+ const location = this.threadActor.sourcesManager.getFrameLocation(
+ this.frame
+ );
+ form.where = {
+ actor: location.sourceActor.actorID,
+ line: location.line,
+ column: location.column,
+ };
+ }
+
+ if (!this.frame.older) {
+ form.oldest = true;
+ }
+
+ return form;
+ }
+
+ _args() {
+ if (!this.frame.onStack || !this.frame.arguments) {
+ return [];
+ }
+
+ return this.frame.arguments.map(arg =>
+ createValueGrip(
+ arg,
+ this.threadActor._pausePool,
+ this.threadActor.objectGrip
+ )
+ );
+ }
+}
+
+exports.FrameActor = FrameActor;
+exports.formatDisplayName = formatDisplayName;
+exports.getSavedFrameParent = getSavedFrameParent;
+exports.isValidSavedFrame = isValidSavedFrame;