thread-states.js (5278B)
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/. */ 4 5 "use strict"; 6 7 const Targets = require("resource://devtools/server/actors/targets/index.js"); 8 9 const { 10 PAUSE_REASONS, 11 STATES: THREAD_STATES, 12 } = require("resource://devtools/server/actors/thread.js"); 13 14 // Possible values of breakpoint's resource's `state` attribute 15 const STATES = { 16 PAUSED: "paused", 17 RESUMED: "resumed", 18 }; 19 20 /** 21 * Emit THREAD_STATE resources, which is emitted each time the target's thread pauses or resumes. 22 * So that there is two distinct values for this resource: pauses and resumes. 23 * These values are distinguished by `state` attribute which can be either "paused" or "resumed". 24 * 25 * Resume events, won't expose any other attribute other than `resourceType` and `state`. 26 * 27 * Pause events will expose the following attributes: 28 * - why {Object}: Description of why the thread pauses. See ThreadActor's PAUSE_REASONS definition for more information. 29 * - frame {Object}: Description of the frame where we just paused. This is a FrameActor's form. 30 */ 31 class BreakpointWatcher { 32 constructor() { 33 this.onPaused = this.onPaused.bind(this); 34 this.onResumed = this.onResumed.bind(this); 35 } 36 37 /** 38 * Start watching for state changes of the thread actor. 39 * This will notify whenever the thread actor pause and resume. 40 * 41 * @param TargetActor targetActor 42 * The target actor from which we should observe breakpoints 43 * @param Object options 44 * Dictionary object with following attributes: 45 * - onAvailable: mandatory function 46 * This will be called for each resource. 47 */ 48 async watch(targetActor, { onAvailable }) { 49 // The Browser Toolbox uses the Content Process target's Thread actor to debug all scripts 50 // running into a given process. This includes WindowGlobal scripts. 51 // Because of this, and in such configuration, we have to ignore the WindowGlobal targets. 52 if ( 53 targetActor.sessionContext.type == "all" && 54 !targetActor.sessionContext.enableWindowGlobalThreadActors && 55 targetActor.targetType === Targets.TYPES.FRAME && 56 targetActor.typeName != "parentProcessTarget" 57 ) { 58 return; 59 } 60 61 const { threadActor } = targetActor; 62 this.threadActor = threadActor; 63 this.onAvailable = onAvailable; 64 65 // If this watcher is created during target creation, attach the thread actor automatically. 66 // Otherwise it would not pause on anything (especially debugger statements). 67 // However, do not attach the thread actor for Workers. They use a codepath 68 // which releases the worker on `attach`. For them, the client will call `attach`. (bug 1691986) 69 const isTargetCreation = this.threadActor.state == THREAD_STATES.DETACHED; 70 if (isTargetCreation && !targetActor.targetType.endsWith("worker")) { 71 await this.threadActor.attach({}); 72 } 73 74 this.isInterrupted = false; 75 76 threadActor.on("paused", this.onPaused); 77 threadActor.on("resumed", this.onResumed); 78 79 // For top-level targets, the thread actor may have been attached by the frontend 80 // on toolbox opening, and we start observing for thread state updates much later. 81 // In which case, the thread actor may already be paused and we handle this here. 82 // It will also occurs for all other targets once bug 1681698 lands, 83 // as the thread actor will be initialized before the target starts loading. 84 // And it will occur for all targets once bug 1686748 lands. 85 // 86 // Note that we have to check if we have a "lastPausedPacket", 87 // because the thread Actor is immediately set as being paused, 88 // but the pause packet is built asynchronously and available slightly later. 89 // If the "lastPausedPacket" is null, while the thread actor is paused, 90 // it is fine to ignore as the "paused" event will be fire later. 91 if (threadActor.isPaused() && threadActor.lastPausedPacket()) { 92 this.onPaused(threadActor.lastPausedPacket()); 93 } 94 } 95 96 /** 97 * Stop watching for breakpoints 98 */ 99 destroy() { 100 if (!this.threadActor) { 101 return; 102 } 103 this.threadActor.off("paused", this.onPaused); 104 this.threadActor.off("resumed", this.onResumed); 105 } 106 107 onPaused(packet) { 108 // If paused by an explicit interrupt, which are generated by the 109 // slow script dialog and internal events such as setting 110 // breakpoints, ignore the event. 111 const { why } = packet; 112 if (why.type === PAUSE_REASONS.INTERRUPTED && !why.onNext) { 113 this.isInterrupted = true; 114 return; 115 } 116 117 // Ignore attached events because they are not useful to the user. 118 if (why.type == PAUSE_REASONS.ALREADY_PAUSED) { 119 return; 120 } 121 122 this.onAvailable([ 123 { 124 state: STATES.PAUSED, 125 why, 126 frame: packet.frame.form(), 127 }, 128 ]); 129 } 130 131 onResumed() { 132 // NOTE: resumed events are suppressed while interrupted 133 // to prevent unintentional behavior. 134 if (this.isInterrupted) { 135 this.isInterrupted = false; 136 return; 137 } 138 139 this.onAvailable([ 140 { 141 state: STATES.RESUMED, 142 }, 143 ]); 144 } 145 } 146 147 module.exports = BreakpointWatcher;