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/. */
12 } = require("resource://devtools/server/actors/utils/logEvent.js");
15 * Set breakpoints on all the given entry points with the given
16 * BreakpointActor as the handler.
18 * @param BreakpointActor actor
19 * The actor handling the breakpoint hits.
20 * @param Array entryPoints
21 * An array of objects of the form `{ script, offsets }`.
23 function setBreakpointAtEntryPoints(actor, entryPoints) {
24 for (const { script, offsets } of entryPoints) {
25 actor.addScript(script, offsets);
29 exports.setBreakpointAtEntryPoints = setBreakpointAtEntryPoints;
32 * BreakpointActors are instantiated for each breakpoint that has been installed
33 * by the client. They are not true actors and do not communicate with the
34 * client directly, but encapsulate the DebuggerScript locations where the
35 * breakpoint is installed.
37 class BreakpointActor {
38 constructor(threadActor, location) {
39 // A map from Debugger.Script instances to the offsets which the breakpoint
40 // has been set for in that script.
41 this.scripts = new Map();
43 this.threadActor = threadActor;
44 this.location = location;
49 const oldOptions = this.options;
50 this.options = options;
52 for (const [script, offsets] of this.scripts) {
53 this._newOffsetsOrOptions(script, offsets, oldOptions);
63 return this.scripts.has(script);
67 * Called when this same breakpoint is added to another Debugger.Script
70 * @param script Debugger.Script
71 * The new source script on which the breakpoint has been set.
72 * @param offsets Array
73 * Any offsets in the script the breakpoint is associated with.
75 addScript(script, offsets) {
76 this.scripts.set(script, offsets.concat(this.scripts.get(offsets) || []));
77 this._newOffsetsOrOptions(script, offsets, null);
81 * Remove the breakpoints from associated scripts and clear the script cache.
84 for (const [script] of this.scripts) {
85 script.clearBreakpoint(this);
91 * Called on changes to this breakpoint's script offsets or options.
93 _newOffsetsOrOptions(script, offsets) {
94 // Clear any existing handler first in case this is called multiple times
95 // after options change.
96 for (const offset of offsets) {
97 script.clearBreakpoint(this, offset);
100 // In all other cases, this is used as a script breakpoint handler.
101 for (const offset of offsets) {
102 script.setBreakpoint(offset, this);
107 * Check if this breakpoint has a condition that doesn't error and
108 * evaluates to true in frame.
110 * @param frame Debugger.Frame
111 * The frame to evaluate the condition in
113 * - result: boolean|undefined
114 * True when the conditional breakpoint should trigger a pause,
115 * false otherwise. If the condition evaluation failed/killed,
116 * `result` will be `undefined`.
118 * If the condition throws, this is the thrown message.
120 checkCondition(frame, condition) {
121 // Ensure disabling breakpoint while evaluating the condition.
122 // All but exception breakpoint to report any exception when running the condition.
123 this.threadActor.insideClientEvaluation = {
125 reportExceptionsWhenBreaksAreDisabled: true,
129 // Temporarily enable pause on exception when evaluating the condition.
130 const hadToEnablePauseOnException =
131 !this.threadActor.isPauseOnExceptionsEnabled();
133 if (hadToEnablePauseOnException) {
134 this.threadActor.setPauseOnExceptions(true);
136 completion = frame.eval(condition, { hideFromDebugger: true });
138 this.threadActor.insideClientEvaluation = null;
139 if (hadToEnablePauseOnException) {
140 this.threadActor.setPauseOnExceptions(false);
144 if (completion.throw) {
145 // The evaluation failed and threw
148 message: getThrownMessage(completion),
150 } else if (completion.yield) {
151 assert(false, "Shouldn't ever get yield completions from an eval");
153 return { result: !!completion.return };
156 // The evaluation was killed (possibly by the slow script dialog)
157 return { result: undefined };
161 * A function that the engine calls when a breakpoint has been hit.
163 * @param frame Debugger.Frame
164 * The stack frame that contained the breakpoint.
166 // eslint-disable-next-line complexity
168 if (this.threadActor.shouldSkipAnyBreakpoint) {
172 // Don't pause if we are currently stepping (in or over) or the frame is
174 const location = this.threadActor.sourcesManager.getFrameLocation(frame);
175 if (this.threadActor.sourcesManager.isFrameBlackBoxed(frame)) {
179 // If we're trying to pop this frame, and we see a breakpoint at
180 // the spot at which popping started, ignore it. See bug 970469.
181 const locationAtFinish = frame.onPop?.location;
184 locationAtFinish.line === location.line &&
185 locationAtFinish.column === location.column
190 if (!this.threadActor.hasMoved(frame, "breakpoint")) {
194 const reason = { type: "breakpoint", actors: [this.actorID] };
195 const { condition, logValue } = this.options || {};
198 const { result, message } = this.checkCondition(frame, condition);
200 // Don't pause if the result is falsey
206 reason.type = "breakpointConditionThrown";
207 reason.message = message;
213 threadActor: this.threadActor,
216 expression: `[${logValue}]`,
220 return this.threadActor._pauseAndRespond(frame, reason);
224 // Remove from the breakpoint store.
225 this.threadActor.breakpointActorMap.deleteActor(this.location);
226 // Remove the actual breakpoint from the associated scripts.
227 this.removeScripts();
232 exports.BreakpointActor = BreakpointActor;