thread.js (6582B)
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 { ThreadStateTypes } = require("resource://devtools/client/constants.js"); 8 const { 9 FrontClassWithSpec, 10 registerFront, 11 } = require("resource://devtools/shared/protocol.js"); 12 13 const { threadSpec } = require("resource://devtools/shared/specs/thread.js"); 14 15 loader.lazyRequireGetter( 16 this, 17 "ObjectFront", 18 "resource://devtools/client/fronts/object.js", 19 true 20 ); 21 loader.lazyRequireGetter( 22 this, 23 "FrameFront", 24 "resource://devtools/client/fronts/frame.js" 25 ); 26 loader.lazyRequireGetter( 27 this, 28 "SourceFront", 29 "resource://devtools/client/fronts/source.js", 30 true 31 ); 32 33 /** 34 * Creates a thread front for the remote debugging protocol server. This client 35 * is a front to the thread actor created in the server side, hiding the 36 * protocol details in a traditional JavaScript API. 37 * 38 * @param client DevToolsClient 39 * @param actor string 40 * The actor ID for this thread. 41 */ 42 class ThreadFront extends FrontClassWithSpec(threadSpec) { 43 constructor(client, targetFront, parentFront) { 44 super(client, targetFront, parentFront); 45 this.client = client; 46 this._threadGrips = {}; 47 // Note that this isn't matching ThreadActor state field. 48 // ThreadFront is only using two values: paused or attached. 49 this._state = "attached"; 50 51 this._beforePaused = this._beforePaused.bind(this); 52 this._beforeResumed = this._beforeResumed.bind(this); 53 this.before("paused", this._beforePaused); 54 this.before("resumed", this._beforeResumed); 55 this.targetFront.on("will-navigate", this._onWillNavigate.bind(this)); 56 // Attribute name from which to retrieve the actorID out of the target actor's form 57 this.formAttributeName = "threadActor"; 58 } 59 60 get state() { 61 return this._state; 62 } 63 64 get paused() { 65 return this._state === "paused"; 66 } 67 68 get actor() { 69 return this.actorID; 70 } 71 72 _assertPaused(command) { 73 if (!this.paused) { 74 throw Error( 75 command + " command sent while not paused. Currently " + this._state 76 ); 77 } 78 } 79 80 getFrames(start, count) { 81 return super.frames(start, count); 82 } 83 84 /** 85 * Resume a paused thread. If the optional limit parameter is present, then 86 * the thread will also pause when that limit is reached. 87 * 88 * @param [optional] object limit 89 * An object with a type property set to the appropriate limit (next, 90 * step, or finish) per the remote debugging protocol specification. 91 * Use null to specify no limit. 92 */ 93 async _doResume(resumeLimit, frameActorID) { 94 this._assertPaused("resume"); 95 96 // Put the client in a tentative "resuming" state so we can prevent 97 // further requests that should only be sent in the paused state. 98 this._previousState = this._state; 99 this._state = "resuming"; 100 try { 101 await super.resume(resumeLimit, frameActorID); 102 } catch (e) { 103 if (this._state == "resuming") { 104 // There was an error resuming, update the state to the new one 105 // reported by the server, if given (only on wrongState), otherwise 106 // reset back to the previous state. 107 if (e.state) { 108 this._state = ThreadStateTypes[e.state]; 109 } else { 110 this._state = this._previousState; 111 } 112 } 113 } 114 115 delete this._previousState; 116 } 117 118 /** 119 * Resume a paused thread. 120 */ 121 resume() { 122 return this._doResume(null); 123 } 124 125 /** 126 * Resume then pause without stepping. 127 * 128 */ 129 resumeThenPause() { 130 return this._doResume({ type: "break" }); 131 } 132 133 /** 134 * Step over a function call. 135 */ 136 stepOver(frameActorID) { 137 return this._doResume({ type: "next" }, frameActorID); 138 } 139 140 /** 141 * Step into a function call. 142 */ 143 stepIn(frameActorID) { 144 return this._doResume({ type: "step" }, frameActorID); 145 } 146 147 /** 148 * Step out of a function call. 149 */ 150 stepOut(frameActorID) { 151 return this._doResume({ type: "finish" }, frameActorID); 152 } 153 154 /** 155 * Restart selected frame. 156 */ 157 restart(frameActorID) { 158 return this._doResume({ type: "restart" }, frameActorID); 159 } 160 161 /** 162 * Immediately interrupt a running thread. 163 */ 164 interrupt() { 165 return this._doInterrupt(null); 166 } 167 168 /** 169 * Pause execution right before the next JavaScript bytecode is executed. 170 */ 171 breakOnNext() { 172 return this._doInterrupt("onNext"); 173 } 174 175 /** 176 * Interrupt a running thread. 177 */ 178 _doInterrupt(when) { 179 return super.interrupt(when); 180 } 181 182 /** 183 * Request the loaded sources for the current thread. 184 */ 185 async getSources() { 186 let sources = []; 187 try { 188 sources = await super.sources(); 189 } catch (e) { 190 // we may have closed the connection 191 console.log(`getSources failed. Connection may have closed: ${e}`); 192 } 193 return { sources }; 194 } 195 196 /** 197 * This is only used by tests, which should be migrated to DevToolsClient.createObjectFront 198 */ 199 pauseGrip(grip) { 200 return new ObjectFront(this.conn, this.targetFront, this, grip); 201 } 202 203 /** 204 * Clear and invalidate all the grip fronts from the given cache. 205 * 206 * @param gripCacheName 207 * The property name of the grip cache we want to clear. 208 */ 209 _clearObjectFronts(gripCacheName) { 210 for (const id in this[gripCacheName]) { 211 this[gripCacheName][id].valid = false; 212 } 213 this[gripCacheName] = {}; 214 } 215 216 _beforePaused(packet) { 217 this._state = "paused"; 218 this._onThreadState(packet); 219 } 220 221 _beforeResumed() { 222 this._state = "attached"; 223 this._onThreadState(null); 224 this.unmanageChildren(FrameFront); 225 } 226 227 _onWillNavigate() { 228 this.unmanageChildren(SourceFront); 229 } 230 231 /** 232 * Handle thread state change by doing necessary cleanup 233 */ 234 _onThreadState(packet) { 235 // The debugger UI may not be initialized yet so we want to keep 236 // the packet around so it knows what to pause state to display 237 // when it's initialized 238 this._lastPausePacket = packet; 239 } 240 241 getLastPausePacket() { 242 return this._lastPausePacket; 243 } 244 245 /** 246 * Return an instance of SourceFront for the given source actor form. 247 */ 248 source(form) { 249 if (form.actor in this._threadGrips) { 250 return this._threadGrips[form.actor]; 251 } 252 253 const sourceFront = new SourceFront(this.client, form); 254 this.manage(sourceFront); 255 this._threadGrips[form.actor] = sourceFront; 256 return sourceFront; 257 } 258 } 259 260 exports.ThreadFront = ThreadFront; 261 registerFront(ThreadFront);