diff options
Diffstat (limited to 'devtools/server/actors/resources/thread-states.js')
-rw-r--r-- | devtools/server/actors/resources/thread-states.js | 136 |
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; |