summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/resources/thread-states.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/resources/thread-states.js')
-rw-r--r--devtools/server/actors/resources/thread-states.js136
1 files changed, 136 insertions, 0 deletions
diff --git a/devtools/server/actors/resources/thread-states.js b/devtools/server/actors/resources/thread-states.js
new file mode 100644
index 0000000000..9ac79088d2
--- /dev/null
+++ b/devtools/server/actors/resources/thread-states.js
@@ -0,0 +1,136 @@
+/* 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 {
+ TYPES: { THREAD_STATE },
+} = require("resource://devtools/server/actors/resources/index.js");
+
+const {
+ PAUSE_REASONS,
+ STATES: THREAD_STATES,
+} = require("resource://devtools/server/actors/thread.js");
+
+// Possible values of breakpoint's resource's `state` attribute
+const STATES = {
+ PAUSED: "paused",
+ RESUMED: "resumed",
+};
+
+/**
+ * Emit THREAD_STATE resources, which is emitted each time the target's thread pauses or resumes.
+ * So that there is two distinct values for this resource: pauses and resumes.
+ * These values are distinguished by `state` attribute which can be either "paused" or "resumed".
+ *
+ * Resume events, won't expose any other attribute other than `resourceType` and `state`.
+ *
+ * Pause events will expose the following attributes:
+ * - why {Object}: Description of why the thread pauses. See ThreadActor's PAUSE_REASONS definition for more information.
+ * - frame {Object}: Description of the frame where we just paused. This is a FrameActor's form.
+ */
+class BreakpointWatcher {
+ constructor() {
+ this.onPaused = this.onPaused.bind(this);
+ this.onResumed = this.onResumed.bind(this);
+ }
+
+ /**
+ * Start watching for state changes of the thread actor.
+ * This will notify whenever the thread actor pause and resume.
+ *
+ * @param TargetActor targetActor
+ * The target actor from which we should observe breakpoints
+ * @param Object options
+ * Dictionary object with following attributes:
+ * - onAvailable: mandatory function
+ * This will be called for each resource.
+ */
+ async watch(targetActor, { onAvailable }) {
+ const { threadActor } = targetActor;
+ this.threadActor = threadActor;
+ this.onAvailable = onAvailable;
+
+ // If this watcher is created during target creation, attach the thread actor automatically.
+ // Otherwise it would not pause on anything (especially debugger statements).
+ // However, do not attach the thread actor for Workers. They use a codepath
+ // which releases the worker on `attach`. For them, the client will call `attach`. (bug 1691986)
+ const isTargetCreation = this.threadActor.state == THREAD_STATES.DETACHED;
+ if (isTargetCreation && !targetActor.targetType.endsWith("worker")) {
+ await this.threadActor.attach({});
+ }
+
+ this.isInterrupted = false;
+
+ threadActor.on("paused", this.onPaused);
+ threadActor.on("resumed", this.onResumed);
+
+ // For top-level targets, the thread actor may have been attached by the frontend
+ // on toolbox opening, and we start observing for thread state updates much later.
+ // In which case, the thread actor may already be paused and we handle this here.
+ // It will also occurs for all other targets once bug 1681698 lands,
+ // as the thread actor will be initialized before the target starts loading.
+ // And it will occur for all targets once bug 1686748 lands.
+ //
+ // Note that we have to check if we have a "lastPausedPacket",
+ // because the thread Actor is immediately set as being paused,
+ // but the pause packet is built asynchronously and available slightly later.
+ // If the "lastPausedPacket" is null, while the thread actor is paused,
+ // it is fine to ignore as the "paused" event will be fire later.
+ if (threadActor.isPaused() && threadActor.lastPausedPacket()) {
+ this.onPaused(threadActor.lastPausedPacket());
+ }
+ }
+
+ /**
+ * Stop watching for breakpoints
+ */
+ destroy() {
+ this.threadActor.off("paused", this.onPaused);
+ this.threadActor.off("resumed", this.onResumed);
+ }
+
+ onPaused(packet) {
+ // If paused by an explicit interrupt, which are generated by the
+ // slow script dialog and internal events such as setting
+ // breakpoints, ignore the event.
+ const { why } = packet;
+ if (why.type === PAUSE_REASONS.INTERRUPTED && !why.onNext) {
+ this.isInterrupted = true;
+ return;
+ }
+
+ // Ignore attached events because they are not useful to the user.
+ if (why.type == PAUSE_REASONS.ALREADY_PAUSED) {
+ return;
+ }
+
+ this.onAvailable([
+ {
+ resourceType: THREAD_STATE,
+ state: STATES.PAUSED,
+ why,
+ frame: packet.frame.form(),
+ },
+ ]);
+ }
+
+ onResumed(packet) {
+ // NOTE: resumed events are suppressed while interrupted
+ // to prevent unintentional behavior.
+ if (this.isInterrupted) {
+ this.isInterrupted = false;
+ return;
+ }
+
+ this.onAvailable([
+ {
+ resourceType: THREAD_STATE,
+ state: STATES.RESUMED,
+ },
+ ]);
+ }
+}
+
+module.exports = BreakpointWatcher;