Source code

Revision control

Copy as Markdown

Other Tools

/* 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 },
const {
PAUSE_REASONS,
STATES: THREAD_STATES,
// 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 }) {
// When debugging the whole browser (via the Browser Toolbox), we instantiate both content process and window global (FRAME) targets.
// But the debugger will only use the content process target's thread actor.
// Thread actor, Sources and Breakpoints have to be only managed for the content process target,
// and we should explicitly ignore the window global target.
if (
targetActor.sessionContext.type == "all" &&
targetActor.targetType === Targets.TYPES.FRAME &&
targetActor.typeName != "parentProcessTarget"
) {
return;
}
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() {
if (!this.threadActor) {
return;
}
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() {
// 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;