summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/breakpoint.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/breakpoint.js')
-rw-r--r--devtools/server/actors/breakpoint.js189
1 files changed, 189 insertions, 0 deletions
diff --git a/devtools/server/actors/breakpoint.js b/devtools/server/actors/breakpoint.js
new file mode 100644
index 000000000..547dcd0f1
--- /dev/null
+++ b/devtools/server/actors/breakpoint.js
@@ -0,0 +1,189 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 { ActorClassWithSpec } = require("devtools/shared/protocol");
+const { breakpointSpec } = require("devtools/shared/specs/breakpoint");
+
+/**
+ * Set breakpoints on all the given entry points with the given
+ * BreakpointActor as the handler.
+ *
+ * @param BreakpointActor actor
+ * The actor handling the breakpoint hits.
+ * @param Array entryPoints
+ * An array of objects of the form `{ script, offsets }`.
+ */
+function setBreakpointAtEntryPoints(actor, entryPoints) {
+ for (let { script, offsets } of entryPoints) {
+ actor.addScript(script);
+ for (let offset of offsets) {
+ script.setBreakpoint(offset, actor);
+ }
+ }
+}
+
+exports.setBreakpointAtEntryPoints = setBreakpointAtEntryPoints;
+
+/**
+ * BreakpointActors exist for the lifetime of their containing thread and are
+ * responsible for deleting breakpoints, handling breakpoint hits and
+ * associating breakpoints with scripts.
+ */
+let BreakpointActor = ActorClassWithSpec(breakpointSpec, {
+ /**
+ * Create a Breakpoint actor.
+ *
+ * @param ThreadActor threadActor
+ * The parent thread actor that contains this breakpoint.
+ * @param OriginalLocation originalLocation
+ * The original location of the breakpoint.
+ */
+ initialize: function (threadActor, originalLocation) {
+ // The set of Debugger.Script instances that this breakpoint has been set
+ // upon.
+ this.scripts = new Set();
+
+ this.threadActor = threadActor;
+ this.originalLocation = originalLocation;
+ this.condition = null;
+ this.isPending = true;
+ },
+
+ disconnect: function () {
+ this.removeScripts();
+ },
+
+ hasScript: function (script) {
+ return this.scripts.has(script);
+ },
+
+ /**
+ * Called when this same breakpoint is added to another Debugger.Script
+ * instance.
+ *
+ * @param script Debugger.Script
+ * The new source script on which the breakpoint has been set.
+ */
+ addScript: function (script) {
+ this.scripts.add(script);
+ this.isPending = false;
+ },
+
+ /**
+ * Remove the breakpoints from associated scripts and clear the script cache.
+ */
+ removeScripts: function () {
+ for (let script of this.scripts) {
+ script.clearBreakpoint(this);
+ }
+ this.scripts.clear();
+ },
+
+ /**
+ * Check if this breakpoint has a condition that doesn't error and
+ * evaluates to true in frame.
+ *
+ * @param frame Debugger.Frame
+ * The frame to evaluate the condition in
+ * @returns Object
+ * - result: boolean|undefined
+ * True when the conditional breakpoint should trigger a pause,
+ * false otherwise. If the condition evaluation failed/killed,
+ * `result` will be `undefined`.
+ * - message: string
+ * If the condition throws, this is the thrown message.
+ */
+ checkCondition: function (frame) {
+ let completion = frame.eval(this.condition);
+ if (completion) {
+ if (completion.throw) {
+ // The evaluation failed and threw
+ let message = "Unknown exception";
+ try {
+ if (completion.throw.getOwnPropertyDescriptor) {
+ message = completion.throw.getOwnPropertyDescriptor("message")
+ .value;
+ } else if (completion.toString) {
+ message = completion.toString();
+ }
+ } catch (ex) {}
+ return {
+ result: true,
+ message: message
+ };
+ } else if (completion.yield) {
+ assert(false, "Shouldn't ever get yield completions from an eval");
+ } else {
+ return { result: completion.return ? true : false };
+ }
+ } else {
+ // The evaluation was killed (possibly by the slow script dialog)
+ return { result: undefined };
+ }
+ },
+
+ /**
+ * A function that the engine calls when a breakpoint has been hit.
+ *
+ * @param frame Debugger.Frame
+ * The stack frame that contained the breakpoint.
+ */
+ hit: function (frame) {
+ // Don't pause if we are currently stepping (in or over) or the frame is
+ // black-boxed.
+ let generatedLocation = this.threadActor.sources.getFrameLocation(frame);
+ let { originalSourceActor } = this.threadActor.unsafeSynchronize(
+ this.threadActor.sources.getOriginalLocation(generatedLocation));
+ let url = originalSourceActor.url;
+
+ if (this.threadActor.sources.isBlackBoxed(url)
+ || frame.onStep) {
+ return undefined;
+ }
+
+ let reason = {};
+
+ if (this.threadActor._hiddenBreakpoints.has(this.actorID)) {
+ reason.type = "pauseOnDOMEvents";
+ } else if (!this.condition) {
+ reason.type = "breakpoint";
+ // TODO: add the rest of the breakpoints on that line (bug 676602).
+ reason.actors = [ this.actorID ];
+ } else {
+ let { result, message } = this.checkCondition(frame);
+
+ if (result) {
+ if (!message) {
+ reason.type = "breakpoint";
+ } else {
+ reason.type = "breakpointConditionThrown";
+ reason.message = message;
+ }
+ reason.actors = [ this.actorID ];
+ } else {
+ return undefined;
+ }
+ }
+ return this.threadActor._pauseAndRespond(frame, reason);
+ },
+
+ /**
+ * Handle a protocol request to remove this breakpoint.
+ */
+ delete: function () {
+ // Remove from the breakpoint store.
+ if (this.originalLocation) {
+ this.threadActor.breakpointActorMap.deleteActor(this.originalLocation);
+ }
+ this.threadActor.threadLifetimePool.removeActor(this);
+ // Remove the actual breakpoint from the associated scripts.
+ this.removeScripts();
+ }
+});
+
+exports.BreakpointActor = BreakpointActor;