summaryrefslogtreecommitdiffstats
path: root/devtools/client/fronts/thread.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/fronts/thread.js')
-rw-r--r--devtools/client/fronts/thread.js285
1 files changed, 285 insertions, 0 deletions
diff --git a/devtools/client/fronts/thread.js b/devtools/client/fronts/thread.js
new file mode 100644
index 0000000000..f5d05aa868
--- /dev/null
+++ b/devtools/client/fronts/thread.js
@@ -0,0 +1,285 @@
+/* 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._pauseGrips = {};
+ 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 };
+ }
+
+ /**
+ * Return a ObjectFront object for the given object grip.
+ *
+ * @param grip object
+ * A pause-lifetime object grip returned by the protocol.
+ */
+ pauseGrip(grip) {
+ if (grip.actor in this._pauseGrips) {
+ return this._pauseGrips[grip.actor];
+ }
+
+ const objectFront = new ObjectFront(
+ this.conn,
+ this.targetFront,
+ this,
+ grip
+ );
+ this._pauseGrips[grip.actor] = objectFront;
+ return objectFront;
+ }
+
+ /**
+ * 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] = {};
+ }
+
+ /**
+ * Invalidate pause-lifetime grip clients and clear the list of current grip
+ * clients.
+ */
+ _clearPauseGrips() {
+ this._clearObjectFronts("_pauseGrips");
+ }
+
+ _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;
+ this._clearPauseGrips();
+ }
+
+ 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);