261 lines
6.4 KiB
JavaScript
261 lines
6.4 KiB
JavaScript
/* 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 { ThreadStateTypes } = require("resource://devtools/client/constants.js");
|
|
const {
|
|
FrontClassWithSpec,
|
|
registerFront,
|
|
} = require("resource://devtools/shared/protocol.js");
|
|
|
|
const { threadSpec } = require("resource://devtools/shared/specs/thread.js");
|
|
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"ObjectFront",
|
|
"resource://devtools/client/fronts/object.js",
|
|
true
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"FrameFront",
|
|
"resource://devtools/client/fronts/frame.js"
|
|
);
|
|
loader.lazyRequireGetter(
|
|
this,
|
|
"SourceFront",
|
|
"resource://devtools/client/fronts/source.js",
|
|
true
|
|
);
|
|
|
|
/**
|
|
* Creates a thread front for the remote debugging protocol server. This client
|
|
* is a front to the thread actor created in the server side, hiding the
|
|
* protocol details in a traditional JavaScript API.
|
|
*
|
|
* @param client DevToolsClient
|
|
* @param actor string
|
|
* The actor ID for this thread.
|
|
*/
|
|
class ThreadFront extends FrontClassWithSpec(threadSpec) {
|
|
constructor(client, targetFront, parentFront) {
|
|
super(client, targetFront, parentFront);
|
|
this.client = client;
|
|
this._threadGrips = {};
|
|
// Note that this isn't matching ThreadActor state field.
|
|
// ThreadFront is only using two values: paused or attached.
|
|
this._state = "attached";
|
|
|
|
this._beforePaused = this._beforePaused.bind(this);
|
|
this._beforeResumed = this._beforeResumed.bind(this);
|
|
this.before("paused", this._beforePaused);
|
|
this.before("resumed", this._beforeResumed);
|
|
this.targetFront.on("will-navigate", this._onWillNavigate.bind(this));
|
|
// Attribute name from which to retrieve the actorID out of the target actor's form
|
|
this.formAttributeName = "threadActor";
|
|
}
|
|
|
|
get state() {
|
|
return this._state;
|
|
}
|
|
|
|
get paused() {
|
|
return this._state === "paused";
|
|
}
|
|
|
|
get actor() {
|
|
return this.actorID;
|
|
}
|
|
|
|
_assertPaused(command) {
|
|
if (!this.paused) {
|
|
throw Error(
|
|
command + " command sent while not paused. Currently " + this._state
|
|
);
|
|
}
|
|
}
|
|
|
|
getFrames(start, count) {
|
|
return super.frames(start, count);
|
|
}
|
|
|
|
/**
|
|
* Resume a paused thread. If the optional limit parameter is present, then
|
|
* the thread will also pause when that limit is reached.
|
|
*
|
|
* @param [optional] object limit
|
|
* An object with a type property set to the appropriate limit (next,
|
|
* step, or finish) per the remote debugging protocol specification.
|
|
* Use null to specify no limit.
|
|
*/
|
|
async _doResume(resumeLimit, frameActorID) {
|
|
this._assertPaused("resume");
|
|
|
|
// Put the client in a tentative "resuming" state so we can prevent
|
|
// further requests that should only be sent in the paused state.
|
|
this._previousState = this._state;
|
|
this._state = "resuming";
|
|
try {
|
|
await super.resume(resumeLimit, frameActorID);
|
|
} catch (e) {
|
|
if (this._state == "resuming") {
|
|
// There was an error resuming, update the state to the new one
|
|
// reported by the server, if given (only on wrongState), otherwise
|
|
// reset back to the previous state.
|
|
if (e.state) {
|
|
this._state = ThreadStateTypes[e.state];
|
|
} else {
|
|
this._state = this._previousState;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete this._previousState;
|
|
}
|
|
|
|
/**
|
|
* Resume a paused thread.
|
|
*/
|
|
resume() {
|
|
return this._doResume(null);
|
|
}
|
|
|
|
/**
|
|
* Resume then pause without stepping.
|
|
*
|
|
*/
|
|
resumeThenPause() {
|
|
return this._doResume({ type: "break" });
|
|
}
|
|
|
|
/**
|
|
* Step over a function call.
|
|
*/
|
|
stepOver(frameActorID) {
|
|
return this._doResume({ type: "next" }, frameActorID);
|
|
}
|
|
|
|
/**
|
|
* Step into a function call.
|
|
*/
|
|
stepIn(frameActorID) {
|
|
return this._doResume({ type: "step" }, frameActorID);
|
|
}
|
|
|
|
/**
|
|
* Step out of a function call.
|
|
*/
|
|
stepOut(frameActorID) {
|
|
return this._doResume({ type: "finish" }, frameActorID);
|
|
}
|
|
|
|
/**
|
|
* Restart selected frame.
|
|
*/
|
|
restart(frameActorID) {
|
|
return this._doResume({ type: "restart" }, frameActorID);
|
|
}
|
|
|
|
/**
|
|
* Immediately interrupt a running thread.
|
|
*/
|
|
interrupt() {
|
|
return this._doInterrupt(null);
|
|
}
|
|
|
|
/**
|
|
* Pause execution right before the next JavaScript bytecode is executed.
|
|
*/
|
|
breakOnNext() {
|
|
return this._doInterrupt("onNext");
|
|
}
|
|
|
|
/**
|
|
* Interrupt a running thread.
|
|
*/
|
|
_doInterrupt(when) {
|
|
return super.interrupt(when);
|
|
}
|
|
|
|
/**
|
|
* Request the loaded sources for the current thread.
|
|
*/
|
|
async getSources() {
|
|
let sources = [];
|
|
try {
|
|
sources = await super.sources();
|
|
} catch (e) {
|
|
// we may have closed the connection
|
|
console.log(`getSources failed. Connection may have closed: ${e}`);
|
|
}
|
|
return { sources };
|
|
}
|
|
|
|
/**
|
|
* This is only used by tests, which should be migrated to DevToolsClient.createObjectFront
|
|
*/
|
|
pauseGrip(grip) {
|
|
return new ObjectFront(this.conn, this.targetFront, this, grip);
|
|
}
|
|
|
|
/**
|
|
* Clear and invalidate all the grip fronts from the given cache.
|
|
*
|
|
* @param gripCacheName
|
|
* The property name of the grip cache we want to clear.
|
|
*/
|
|
_clearObjectFronts(gripCacheName) {
|
|
for (const id in this[gripCacheName]) {
|
|
this[gripCacheName][id].valid = false;
|
|
}
|
|
this[gripCacheName] = {};
|
|
}
|
|
|
|
_beforePaused(packet) {
|
|
this._state = "paused";
|
|
this._onThreadState(packet);
|
|
}
|
|
|
|
_beforeResumed() {
|
|
this._state = "attached";
|
|
this._onThreadState(null);
|
|
this.unmanageChildren(FrameFront);
|
|
}
|
|
|
|
_onWillNavigate() {
|
|
this.unmanageChildren(SourceFront);
|
|
}
|
|
|
|
/**
|
|
* Handle thread state change by doing necessary cleanup
|
|
*/
|
|
_onThreadState(packet) {
|
|
// The debugger UI may not be initialized yet so we want to keep
|
|
// the packet around so it knows what to pause state to display
|
|
// when it's initialized
|
|
this._lastPausePacket = packet;
|
|
}
|
|
|
|
getLastPausePacket() {
|
|
return this._lastPausePacket;
|
|
}
|
|
|
|
/**
|
|
* Return an instance of SourceFront for the given source actor form.
|
|
*/
|
|
source(form) {
|
|
if (form.actor in this._threadGrips) {
|
|
return this._threadGrips[form.actor];
|
|
}
|
|
|
|
const sourceFront = new SourceFront(this.client, form);
|
|
this.manage(sourceFront);
|
|
this._threadGrips[form.actor] = sourceFront;
|
|
return sourceFront;
|
|
}
|
|
}
|
|
|
|
exports.ThreadFront = ThreadFront;
|
|
registerFront(ThreadFront);
|