Bug 1484818 - Fix record/replay problems with protocol.js ThreadClient, r=yulia.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 20 Jun 2019 06:31:55 -1000
changeset 542832 ea80e75052563cf655a3ff4c4e050474e06c5203
parent 542831 affad0f5479bee8f2e29bdc47283bae38abdd353
child 542833 7bb92957787da8256bbb345662c6b8b1d8e5fbba
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyulia
bugs1484818
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1484818 - Fix record/replay problems with protocol.js ThreadClient, r=yulia.
devtools/client/inspector/inspector.js
devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-05.js
devtools/client/webreplay/mochitest/browser_rr_inspector-01.js
devtools/client/webreplay/mochitest/browser_rr_inspector-02.js
devtools/client/webreplay/mochitest/browser_rr_inspector-03.js
devtools/server/actors/thread.js
devtools/shared/specs/thread.js
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -131,17 +131,18 @@ function Inspector(toolbox) {
   this.onPanelWindowResize = this.onPanelWindowResize.bind(this);
   this.onShowBoxModelHighlighterForNode =
     this.onShowBoxModelHighlighterForNode.bind(this);
   this.onSidebarHidden = this.onSidebarHidden.bind(this);
   this.onSidebarResized = this.onSidebarResized.bind(this);
   this.onSidebarSelect = this.onSidebarSelect.bind(this);
   this.onSidebarShown = this.onSidebarShown.bind(this);
   this.onSidebarToggle = this.onSidebarToggle.bind(this);
-  this.handleThreadState = this.handleThreadState.bind(this);
+  this.handleThreadPaused = this.handleThreadPaused.bind(this);
+  this.handleThreadResumed = this.handleThreadResumed.bind(this);
 
   this._target.on("will-navigate", this._onBeforeNavigate);
 }
 
 Inspector.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
@@ -152,18 +153,18 @@ Inspector.prototype = {
     // When replaying, we need to listen to changes in the target's pause state.
     if (this._target.isReplayEnabled()) {
       let dbg = this._toolbox.getPanel("jsdebugger");
       if (!dbg) {
         dbg = await this._toolbox.loadTool("jsdebugger");
       }
       this._replayResumed = !dbg.isPaused();
 
-      this._target.threadClient.on("paused", this.handleThreadState);
-      this._target.threadClient.on("resumed", this.handleThreadState);
+      this._target.threadClient.on("paused", this.handleThreadPaused);
+      this._target.threadClient.on("resumed", this.handleThreadResumed);
     }
 
     await Promise.all([
       this._getCssProperties(),
       this._getPageStyle(),
       this._getDefaultSelection(),
       this._getAccessibilityFront(),
       this._getChangesFront(),
@@ -1133,20 +1134,28 @@ Inspector.prototype = {
       this.setupToolbar();
     };
     this._pendingSelection = onNodeSelected;
     this._getDefaultNodeForSelection()
         .then(onNodeSelected, this._handleRejectionIfNotDestroyed);
   },
 
   /**
-   * When replaying, reset the inspector whenever the target paused or unpauses.
+   * When replaying, reset the inspector whenever the target pauses.
    */
-  handleThreadState(packet) {
-    this._replayResumed = packet.type != "paused";
+  handleThreadPaused() {
+    this._replayResumed = false;
+    this.onNewRoot();
+  },
+
+  /**
+   * When replaying, reset the inspector whenever the target resumes.
+   */
+  handleThreadResumed() {
+    this._replayResumed = true;
     this.onNewRoot();
   },
 
   /**
    * Handler for "markuploaded" event fired on a new root mutation and after the markup
    * view is initialized. Expands the current selected node and restores the saved
    * highlighter state.
    */
@@ -1374,18 +1383,18 @@ Inspector.prototype = {
   /**
    * Destroy the inspector.
    */
   destroy: function() {
     if (this._panelDestroyer) {
       return this._panelDestroyer;
     }
 
-    this._target.threadClient.off("paused", this.handleThreadState);
-    this._target.threadClient.off("resumed", this.handleThreadState);
+    this._target.threadClient.off("paused", this.handleThreadPaused);
+    this._target.threadClient.off("resumed", this.handleThreadResumed);
 
     if (this.walker) {
       this.walker.off("new-root", this.onNewRoot);
       this.pageStyle = null;
     }
 
     this.cancelUpdate();
 
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-05.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_breakpoints-05.js
@@ -11,16 +11,18 @@
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_rr_basic.html",
     { waitForRecording: true }
   );
 
   const {threadClient, tab, toolbox, target} = dbg;
 
+  await threadClient.interrupt();
+
   // Rewind to the beginning of the recording.
   await rewindToLine(threadClient, undefined);
 
   const bp = await setBreakpoint(threadClient, "doc_rr_basic.html", 21);
   await resumeToLine(threadClient, 21);
   await checkEvaluateInTopFrame(target, "number", 1);
   await resumeToLine(threadClient, 21);
   await checkEvaluateInTopFrame(target, "number", 2);
--- a/devtools/client/webreplay/mochitest/browser_rr_inspector-01.js
+++ b/devtools/client/webreplay/mochitest/browser_rr_inspector-01.js
@@ -18,16 +18,18 @@ function getContainerForNodeFront(nodeFr
 // Test basic inspector functionality in web replay: the inspector is able to
 // show contents when paused according to the child's current position.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_inspector_basic.html",
     { waitForRecording: true }
   );
   const {threadClient, tab, toolbox} = dbg;
+
+  await threadClient.interrupt();
   await threadClient.resume();
 
   const {inspector} = await openInspector();
 
   let nodeFront = await getNodeFront("#maindiv", inspector);
   let container = getContainerForNodeFront(nodeFront, inspector);
   ok(!container, "No node container while unpaused");
 
--- a/devtools/client/webreplay/mochitest/browser_rr_inspector-02.js
+++ b/devtools/client/webreplay/mochitest/browser_rr_inspector-02.js
@@ -12,16 +12,18 @@ Services.scriptloader.loadSubScript(
 
 // Test that the element highlighter works when paused and replaying.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_inspector_basic.html",
     { waitForRecording: true }
   );
   const {threadClient, tab, toolbox} = dbg;
+
+  await threadClient.interrupt();
   await threadClient.resume();
 
   await threadClient.interrupt();
   const bp = await setBreakpoint(threadClient, "doc_inspector_basic.html", 9);
   await rewindToLine(threadClient, 9);
 
   const {inspector, testActor} = await openInspector();
 
--- a/devtools/client/webreplay/mochitest/browser_rr_inspector-03.js
+++ b/devtools/client/webreplay/mochitest/browser_rr_inspector-03.js
@@ -28,16 +28,18 @@ function getComputedViewProperty(view, n
 
 // Test that styles for elements can be viewed when using web replay.
 add_task(async function() {
   const dbg = await attachRecordingDebugger(
     "doc_inspector_styles.html",
     { waitForRecording: true }
   );
   const {threadClient, tab, toolbox} = dbg;
+
+  await threadClient.interrupt();
   await threadClient.resume();
 
   await threadClient.interrupt();
 
   const {inspector, view} = await openComputedView();
   await checkBackgroundColor("body", "rgb(0, 128, 0)");
   await checkBackgroundColor("#maindiv", "rgb(0, 0, 255)");
 
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -1089,29 +1089,17 @@ const ThreadActor = ActorClassWithSpec(t
 
     try {
       if (resumeLimit) {
         await this._handleResumeLimit({resumeLimit, rewind});
       } else {
         this._clearSteppingHooks();
       }
 
-      // When replaying execution in a separate process we need to explicitly
-      // notify that process when to resume execution.
-      if (this.dbg.replaying) {
-        if (resumeLimit && resumeLimit.type == "warp") {
-          this.dbg.replayTimeWarp(resumeLimit.target);
-        } else if (rewind) {
-          this.dbg.replayResumeBackward();
-        } else {
-          this.dbg.replayResumeForward();
-        }
-      }
-
-      this.doResume();
+      this.doResume({ resumeLimit, rewind });
       return {};
     } catch (error) {
       return error instanceof Error
         ? {
             error: "unknownError",
             message: DevToolsUtils.safeErrorString(error) }
         // It is a known error, and the promise was rejected with an error
         // packet.
@@ -1119,17 +1107,29 @@ const ThreadActor = ActorClassWithSpec(t
     }
   },
 
   /**
    * Only resume and notify necessary observers. This should be used in cases
    * when we do not want to notify the front end of a resume, for example when
    * we are shutting down.
    */
-  doResume() {
+  doResume({ resumeLimit, rewind } = {}) {
+    // When replaying execution in a separate process we need to explicitly
+    // notify that process when to resume execution.
+    if (this.dbg.replaying) {
+      if (resumeLimit && resumeLimit.type == "warp") {
+        this.dbg.replayTimeWarp(resumeLimit.target);
+      } else if (rewind) {
+        this.dbg.replayResumeBackward();
+      } else {
+        this.dbg.replayResumeForward();
+      }
+    }
+
     this.maybePauseOnExceptions();
     this._state = "running";
 
     // Drop the actors in the pause actor pool.
     this.conn.removeActorPool(this._pausePool);
 
     this._pausePool = null;
     this._pauseActor = null;
--- a/devtools/shared/specs/thread.js
+++ b/devtools/shared/specs/thread.js
@@ -19,16 +19,18 @@ const threadSpec = generateActorSpec({
 
   events: {
     paused: {
       actor: Option(0, "nullable:string"),
       frame: Option(0, "nullable:json"),
       why: Option(0, "nullable:json"),
       poppedFrames: Option(0, "nullable:json"),
       error: Option(0, "nullable:json"),
+      recordingEndpoint: Option(0, "nullable:json"),
+      executionPoint: Option(0, "nullable:json"),
     },
     resumed: {},
     detached: {},
     willInterrupt: {},
     newSource: {
       source: Option(0, "json"),
     },
     progress: {