Bug 1913305 - Add test. r=mtigley
[gecko.git] / devtools / server / actors / frame.js
blobbe4f5e3eb390b735a89b713e541253fe9e482cef
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const { Actor } = require("resource://devtools/shared/protocol/Actor.js");
8 const { Pool } = require("resource://devtools/shared/protocol/Pool.js");
9 const { frameSpec } = require("resource://devtools/shared/specs/frame.js");
11 const Debugger = require("Debugger");
12 const { assert } = require("resource://devtools/shared/DevToolsUtils.js");
13 const {
14   createValueGrip,
15 } = require("resource://devtools/server/actors/object/utils.js");
17 function formatDisplayName(frame) {
18   if (frame.type === "call") {
19     const callee = frame.callee;
20     return callee.name || callee.userDisplayName || callee.displayName;
21   }
23   return `(${frame.type})`;
26 function isDeadSavedFrame(savedFrame) {
27   return Cu && Cu.isDeadWrapper(savedFrame);
29 function isValidSavedFrame(threadActor, savedFrame) {
30   return (
31     !isDeadSavedFrame(savedFrame) &&
32     // If the frame's source is unknown to the debugger, then we ignore it
33     // since the frame likely does not belong to a realm that is marked
34     // as a debuggee.
35     // This check will also fail if the frame would have been known but was
36     // GCed before the debugger was opened on the page.
37     // TODO: Use SavedFrame's security principal to limit non-debuggee frames
38     // and pass all unknown frames to the debugger as a URL with no sourceID.
39     getSavedFrameSource(threadActor, savedFrame)
40   );
42 function getSavedFrameSource(threadActor, savedFrame) {
43   return threadActor.sourcesManager.getSourceActorByInternalSourceId(
44     savedFrame.sourceId
45   );
47 function getSavedFrameParent(threadActor, savedFrame) {
48   if (isDeadSavedFrame(savedFrame)) {
49     return null;
50   }
52   while (true) {
53     savedFrame = savedFrame.parent || savedFrame.asyncParent;
55     // If the saved frame is a dead wrapper, we don't have any way to keep
56     // stepping through parent frames.
57     if (!savedFrame || isDeadSavedFrame(savedFrame)) {
58       savedFrame = null;
59       break;
60     }
62     if (isValidSavedFrame(threadActor, savedFrame)) {
63       break;
64     }
65   }
66   return savedFrame;
69 /**
70  * An actor for a specified stack frame.
71  */
72 class FrameActor extends Actor {
73   /**
74    * Creates the Frame actor.
75    *
76    * @param frame Debugger.Frame|SavedFrame
77    *        The debuggee frame.
78    * @param threadActor ThreadActor
79    *        The parent thread actor for this frame.
80    */
81   constructor(frame, threadActor, depth) {
82     super(threadActor.conn, frameSpec);
84     this.frame = frame;
85     this.threadActor = threadActor;
86     this.depth = depth;
87   }
89   /**
90    * A pool that contains frame-lifetime objects, like the environment.
91    */
92   _frameLifetimePool = null;
93   get frameLifetimePool() {
94     if (!this._frameLifetimePool) {
95       this._frameLifetimePool = new Pool(this.conn, "frame");
96     }
97     return this._frameLifetimePool;
98   }
100   /**
101    * Finalization handler that is called when the actor is being evicted from
102    * the pool.
103    */
104   destroy() {
105     if (this._frameLifetimePool) {
106       this._frameLifetimePool.destroy();
107       this._frameLifetimePool = null;
108     }
109     super.destroy();
110   }
112   getEnvironment() {
113     try {
114       if (!this.frame.environment) {
115         return {};
116       }
117     } catch (e) {
118       // |this.frame| might not be live. FIXME Bug 1477030 we shouldn't be
119       // using frames we derived from a point where we are not currently
120       // paused at.
121       return {};
122     }
124     const envActor = this.threadActor.createEnvironmentActor(
125       this.frame.environment,
126       this.frameLifetimePool
127     );
129     return envActor.form();
130   }
132   /**
133    * Returns a frame form for use in a protocol message.
134    */
135   form() {
136     // SavedFrame actors have their own frame handling.
137     if (!(this.frame instanceof Debugger.Frame)) {
138       // The Frame actor shouldn't be used after evaluation is resumed, so
139       // there shouldn't be an easy way for the saved frame to be referenced
140       // once it has died.
141       assert(!isDeadSavedFrame(this.frame));
143       const obj = {
144         actor: this.actorID,
145         // TODO: Bug 1610418 - Consider updating SavedFrame to have a type.
146         type: "dead",
147         asyncCause: this.frame.asyncCause,
148         state: "dead",
149         displayName: this.frame.functionDisplayName,
150         arguments: [],
151         where: {
152           // The frame's source should always be known because
153           // getSavedFrameParent will skip over frames with unknown sources.
154           actor: getSavedFrameSource(this.threadActor, this.frame).actorID,
155           line: this.frame.line,
156           // SavedFrame objects have a 1-based column number, but this API and
157           // Debugger API objects use a 0-based column value.
158           column: this.frame.column - 1,
159         },
160         oldest: !getSavedFrameParent(this.threadActor, this.frame),
161       };
163       return obj;
164     }
166     const threadActor = this.threadActor;
167     const form = {
168       actor: this.actorID,
169       type: this.frame.type,
170       asyncCause: this.frame.onStack ? null : "await",
171       state: this.frame.onStack ? "on-stack" : "suspended",
172     };
174     if (this.depth) {
175       form.depth = this.depth;
176     }
178     if (this.frame.type != "wasmcall") {
179       form.this = createValueGrip(
180         this.frame.this,
181         threadActor._pausePool,
182         threadActor.objectGrip
183       );
184     }
186     form.displayName = formatDisplayName(this.frame);
187     form.arguments = this._args();
189     if (this.frame.script) {
190       const location = this.threadActor.sourcesManager.getFrameLocation(
191         this.frame
192       );
193       form.where = {
194         actor: location.sourceActor.actorID,
195         line: location.line,
196         column: location.column,
197       };
198     }
200     if (!this.frame.older) {
201       form.oldest = true;
202     }
204     return form;
205   }
207   _args() {
208     if (!this.frame.onStack || !this.frame.arguments) {
209       return [];
210     }
212     return this.frame.arguments.map(arg =>
213       createValueGrip(
214         arg,
215         this.threadActor._pausePool,
216         this.threadActor.objectGrip
217       )
218     );
219   }
222 exports.FrameActor = FrameActor;
223 exports.formatDisplayName = formatDisplayName;
224 exports.getSavedFrameParent = getSavedFrameParent;
225 exports.isValidSavedFrame = isValidSavedFrame;