Bug 1511422 - The console separator should be below the message when we're paused after the log. r=bhackett
authorJason Laster <jlaster@mozilla.com>
Fri, 30 Nov 2018 14:22:20 -0500
changeset 449159 0dc7783a6cd9f60f7ac9757e62037a9c1fbb0839
parent 449158 0d11eb20a6af24bb4d332cbbc7bacc4d6cebcc36
child 449160 c120724df998b9590951b54fad6049595ecda0cb
push id35145
push useraciure@mozilla.com
push dateSun, 02 Dec 2018 09:46:48 +0000
treeherdermozilla-central@8e021c409c6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1511422
milestone65.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 1511422 - The console separator should be below the message when we're paused after the log. r=bhackett Tags: Differential Revision: https://phabricator.services.mozilla.com/D13581
devtools/client/debugger/new/test/mochitest/browser.ini
devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-01.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-02.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-03.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-04.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-05.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js
devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-02.js
devtools/client/debugger/new/test/mochitest/examples/doc_rr_logs.html
devtools/client/debugger/new/test/mochitest/head.js
devtools/client/themes/toolbox.css
devtools/client/themes/webconsole.css
devtools/client/webconsole/components/Message.js
devtools/client/webconsole/components/MessageContainer.js
devtools/client/webconsole/components/message-types/ConsoleApiCall.js
devtools/client/webconsole/webconsole-frame.js
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -645,16 +645,17 @@ support-files =
   examples/frames.js
   examples/pause-points.js
   examples/script-mutate.js
   examples/script-switching-02.js
   examples/script-switching-01.js
   examples/times2.js
   examples/doc_rr_basic.html
   examples/doc_rr_continuous.html
+  examples/doc_rr_logs.html
   examples/doc_rr_recovery.html
   examples/doc_rr_error.html
 
 [browser_dbg-asm.js]
 [browser_dbg-async-stepping.js]
 [browser_dbg-sourcemapped-breakpoint-console.js]
 skip-if = (os == "win" && ccov) # Bug 1453549
 [browser_dbg-xhr-breakpoints.js]
@@ -778,8 +779,11 @@ skip-if = true # See bug 1481009
 [browser_dbg_rr_replay-01.js]
 skip-if = os != "mac" || debug || !nightly_build
 [browser_dbg_rr_replay-02.js]
 skip-if = os != "mac" || debug || !nightly_build
 [browser_dbg_rr_replay-03.js]
 skip-if = os != "mac" || debug || !nightly_build
 [browser_dbg_rr_console_warp-01.js]
 skip-if = os != "mac" || debug || !nightly_build
+[browser_dbg_rr_console_warp-02.js]
+skip-if = os != "mac" || debug || !nightly_build
+
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
@@ -1,31 +1,8 @@
-// Return a promise with a reference to jsterm, opening the split
-// console if necessary.  This cleans up the split console pref so
-// it won't pollute other tests.
-function getSplitConsole(dbg) {
-  const { toolbox, win } = dbg;
-
-  if (!win) {
-    win = toolbox.win;
-  }
-
-  if (!toolbox.splitConsole) {
-    pressKey(dbg, "Escape");
-  }
-
-  return new Promise(resolve => {
-    toolbox.getPanelWhenReady("webconsole").then(() => {
-      ok(toolbox.splitConsole, "Split console is shown.");
-      let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
-      resolve(jsterm);
-    });
-  });
-}
-
 add_task(async function() {
   Services.prefs.setBoolPref("devtools.toolbox.splitconsoleEnabled", true);
   const dbg = await initDebugger("doc-script-switching.html", "switching-01");
 
   await selectSource(dbg, "switching-01");
 
   // open the console
   await getSplitConsole(dbg);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-01.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-01.js
@@ -2,42 +2,41 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test basic breakpoint functionality in web replay.
 async function test() {
   waitForExplicitFinish();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
-  await once(Services.ppmm, "RecordingFinished");
+  const dbg = await attatchRecordingDebugger(
+    "doc_rr_basic.html",
+    { waitForRecording: true }
+  );
+  const {threadClient, tab, toolbox} = dbg;
 
-  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
-  await setBreakpoint(client, "doc_rr_basic.html", 21);
+  await setBreakpoint(threadClient, "doc_rr_basic.html", 21);
 
   // Visit a lot of breakpoints so that we are sure we have crossed major
   // checkpoint boundaries.
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 10);
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 9);
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 8);
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 7);
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 6);
-  await resumeToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 7);
-  await resumeToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 8);
-  await resumeToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 9);
-  await resumeToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 10);
+  await rewindToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 10);
+  await rewindToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 9);
+  await rewindToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 8);
+  await rewindToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 7);
+  await rewindToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 6);
+  await resumeToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 7);
+  await resumeToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 8);
+  await resumeToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 9);
+  await resumeToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 10);
 
   await toolbox.closeToolbox();
   await gBrowser.removeTab(tab);
   finish();
 }
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-02.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-02.js
@@ -2,28 +2,24 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test unhandled divergence while evaluating at a breakpoint with Web Replay.
 async function test() {
   waitForExplicitFinish();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
-  await once(Services.ppmm, "RecordingFinished");
+  const dbg = await attatchRecordingDebugger("doc_rr_basic.html", { waitForRecording: true });
+  const {threadClient, tab, toolbox} = dbg;
 
-  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
-  await setBreakpoint(client, "doc_rr_basic.html", 21);
-  await rewindToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 10);
-  await checkEvaluateInTopFrameThrows(client, "window.alert(3)");
-  await checkEvaluateInTopFrame(client, "number", 10);
-  await checkEvaluateInTopFrameThrows(client, "window.alert(3)");
-  await checkEvaluateInTopFrame(client, "number", 10);
-  await checkEvaluateInTopFrame(client, "testStepping2()", undefined);
+  await setBreakpoint(threadClient, "doc_rr_basic.html", 21);
+  await rewindToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 10);
+  await checkEvaluateInTopFrameThrows(threadClient, "window.alert(3)");
+  await checkEvaluateInTopFrame(threadClient, "number", 10);
+  await checkEvaluateInTopFrameThrows(threadClient, "window.alert(3)");
+  await checkEvaluateInTopFrame(threadClient, "number", 10);
+  await checkEvaluateInTopFrame(threadClient, "testStepping2()", undefined);
 
   await toolbox.destroy();
   await gBrowser.removeTab(tab);
   finish();
 }
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-03.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-03.js
@@ -2,28 +2,26 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test some issues when stepping around after hitting a breakpoint while recording.
 async function test() {
   waitForExplicitFinish();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
+  const dbg = await attatchRecordingDebugger("doc_rr_continuous.html");
+  const {threadClient, tab, toolbox} = dbg;
 
-  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
-  await setBreakpoint(client, "doc_rr_continuous.html", 19);
-  await resumeToLine(client, 19);
-  await reverseStepOverToLine(client, 18);
-  await checkEvaluateInTopFrame(client, "SpecialPowers.Cu.recordReplayDirective(/* AlwaysTakeTemporarySnapshots */ 3)", undefined);
-  await stepInToLine(client, 22);
-  await setBreakpoint(client, "doc_rr_continuous.html", 24);
-  await resumeToLine(client, 24);
-  await setBreakpoint(client, "doc_rr_continuous.html", 22);
-  await rewindToLine(client, 22);
+  await threadClient.interrupt();
+  await setBreakpoint(threadClient, "doc_rr_continuous.html", 19);
+  await resumeToLine(threadClient, 19);
+  await reverseStepOverToLine(threadClient, 18);
+  await checkEvaluateInTopFrame(threadClient, "SpecialPowers.Cu.recordReplayDirective(/* AlwaysTakeTemporarySnapshots */ 3)", undefined);
+  await stepInToLine(threadClient, 22);
+  await setBreakpoint(threadClient, "doc_rr_continuous.html", 24);
+  await resumeToLine(threadClient, 24);
+  await setBreakpoint(threadClient, "doc_rr_continuous.html", 22);
+  await rewindToLine(threadClient, 22);
 
   await toolbox.destroy();
   await gBrowser.removeTab(tab);
   finish();
 }
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-04.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-04.js
@@ -3,34 +3,31 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test navigating back to earlier breakpoints while recording, then resuming
 // recording.
 async function test() {
   waitForExplicitFinish();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_continuous.html", "current");
+  const dbg = await attatchRecordingDebugger("doc_rr_continuous.html");
+  const {threadClient, tab, toolbox} = dbg;
 
-  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
-  await setBreakpoint(client, "doc_rr_continuous.html", 14);
-  await resumeToLine(client, 14);
-  let value = await evaluateInTopFrame(client, "number");
-  await resumeToLine(client, 14);
-  await checkEvaluateInTopFrame(client, "number", value + 1);
-  await rewindToLine(client, 14);
-  await checkEvaluateInTopFrame(client, "number", value);
-  await resumeToLine(client, 14);
-  await checkEvaluateInTopFrame(client, "number", value + 1);
-  await resumeToLine(client, 14);
-  await checkEvaluateInTopFrame(client, "number", value + 2);
-  await resumeToLine(client, 14);
-  await checkEvaluateInTopFrame(client, "number", value + 3);
-  await rewindToLine(client, 14);
-  await checkEvaluateInTopFrame(client, "number", value + 2);
+  await setBreakpoint(threadClient, "doc_rr_continuous.html", 14);
+  await resumeToLine(threadClient, 14);
+  let value = await evaluateInTopFrame(threadClient, "number");
+  await resumeToLine(threadClient, 14);
+  await checkEvaluateInTopFrame(threadClient, "number", value + 1);
+  await rewindToLine(threadClient, 14);
+  await checkEvaluateInTopFrame(threadClient, "number", value);
+  await resumeToLine(threadClient, 14);
+  await checkEvaluateInTopFrame(threadClient, "number", value + 1);
+  await resumeToLine(threadClient, 14);
+  await checkEvaluateInTopFrame(threadClient, "number", value + 2);
+  await resumeToLine(threadClient, 14);
+  await checkEvaluateInTopFrame(threadClient, "number", value + 3);
+  await rewindToLine(threadClient, 14);
+  await checkEvaluateInTopFrame(threadClient, "number", value + 2);
 
   await toolbox.destroy();
   await gBrowser.removeTab(tab);
   finish();
 }
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-05.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_breakpoints-05.js
@@ -3,29 +3,28 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test hitting breakpoints when rewinding past the point where the breakpoint
 // script was created.
 async function test() {
   waitForExplicitFinish();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_basic.html", "current");
-  await once(Services.ppmm, "RecordingFinished");
+  const dbg = await attatchRecordingDebugger(
+    "doc_rr_basic.html", 
+    { waitForRecording: true }
+  );
 
-  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
+  const {threadClient, tab, toolbox} = dbg;
 
   // Rewind to the beginning of the recording.
-  await rewindToLine(client, undefined);
+  await rewindToLine(threadClient, undefined);
 
-  await setBreakpoint(client, "doc_rr_basic.html", 21);
-  await resumeToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 1);
-  await resumeToLine(client, 21);
-  await checkEvaluateInTopFrame(client, "number", 2);
+  await setBreakpoint(threadClient, "doc_rr_basic.html", 21);
+  await resumeToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 1);
+  await resumeToLine(threadClient, 21);
+  await checkEvaluateInTopFrame(threadClient, "number", 2);
 
   await toolbox.destroy();
   await gBrowser.removeTab(tab);
   finish();
 }
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-01.js
@@ -1,100 +1,53 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-var { HUDService } = require("devtools/client/webconsole/hudservice");
-
 // This functionality was copied from devtools/client/webconsole/test/mochitest/head.js,
 // since this test straddles both the web console and the debugger. I couldn't
 // figure out how to load that script directly here.
-
-function findMessages(hud, text, selector = ".message") {
-  const messages = hud.ui.outputNode.querySelectorAll(selector);
-  const elements = Array.prototype.filter.call(
-    messages,
-    (el) => el.textContent.includes(text)
-  );
-  return elements;
-}
-
-function waitForThreadEvents(console, eventName) {
+function waitForThreadEvents(threadClient, eventName) {
   info(`Waiting for thread event '${eventName}' to fire.`);
-  const thread = console.threadClient;
 
   return new Promise(function(resolve, reject) {
-    thread.addListener(eventName, function onEvent(eventName, ...args) {
+    threadClient.addListener(eventName, function onEvent(eventName, ...args) {
       info(`Thread event '${eventName}' fired.`);
-      thread.removeListener(eventName, onEvent);
+      threadClient.removeListener(eventName, onEvent);
       resolve.apply(resolve, args);
     });
   });
 }
 
-async function openContextMenu(hud, element) {
-  const onConsoleMenuOpened = hud.ui.consoleOutput.once("menu-open");
-  synthesizeContextMenuEvent(element);
-  await onConsoleMenuOpened;
-  const doc = hud.ui.consoleOutput.owner.chromeWindow.document;
-  return doc.getElementById("webconsole-menu");
-}
-
-function hideContextMenu(hud) {
-  const doc = hud.ui.consoleOutput.owner.chromeWindow.document;
-  const popup = doc.getElementById("webconsole-menu");
-  if (!popup) {
-    return Promise.resolve();
-  }
-
-  const onPopupHidden = once(popup, "popuphidden");
-  popup.hidePopup();
-  return onPopupHidden;
-}
 
 // Test basic console time warping functionality in web replay.
 async function test() {
   waitForExplicitFinish();
 
-  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
-  gBrowser.selectedTab = tab;
-  openTrustedLinkIn(EXAMPLE_URL + "doc_rr_error.html", "current");
-  await once(Services.ppmm, "RecordingFinished");
-
-  let console = await openToolboxForTab(tab, "webconsole");
-  let hud = console.getCurrentPanel().hud;
-  let messages = findMessages(hud, "Number 5");
-  ok(messages.length == 1, "Found one message");
-  let message = messages.pop();
+  const dbg = await attatchRecordingDebugger(
+    "doc_rr_error.html", 
+    { waitForRecording: true }
+  );
 
-  let menuPopup = await openContextMenu(hud, message);
-  let timeWarpItem = menuPopup.querySelector("#console-menu-time-warp");
-  ok(timeWarpItem, "Time warp menu item is available");
-  timeWarpItem.click();
-  await hideContextMenu(hud);
-
-  await once(Services.ppmm, "TimeWarpFinished");
+  const {tab, toolbox, threadClient} = dbg;
+  const console = await getSplitConsole(dbg);
+  const hud = console.hud;
 
-  await waitForThreadEvents(console, 'paused')
-  messages = findMessages(hud, "", ".paused");
-  ok(messages.length == 1, "Found one paused message");
+  await warpToMessage(hud, threadClient, "Number 5");
+  await threadClient.interrupt();
 
-  let toolbox = await attachDebugger(tab), client = toolbox.threadClient;
-  await client.interrupt();
-
-  await checkEvaluateInTopFrame(client, "number", 5);
+  await checkEvaluateInTopFrame(threadClient, "number", 5);
 
   // Initially we are paused inside the 'new Error()' call on line 19. The
   // first reverse step takes us to the start of that line.
-  await reverseStepOverToLine(client, 19);
-
-  await reverseStepOverToLine(client, 18);
-  await setBreakpoint(client, "doc_rr_error.html", 12);
-  await rewindToLine(client, 12);
-  await checkEvaluateInTopFrame(client, "number", 4);
-  await resumeToLine(client, 12);
-  await checkEvaluateInTopFrame(client, "number", 5);
+  await reverseStepOverToLine(threadClient, 19);
+  await reverseStepOverToLine(threadClient, 18);
+  await setBreakpoint(threadClient, "doc_rr_error.html", 12);
+  await rewindToLine(threadClient, 12);
+  await checkEvaluateInTopFrame(threadClient, "number", 4);
+  await resumeToLine(threadClient, 12);
+  await checkEvaluateInTopFrame(threadClient, "number", 5);
 
   await toolbox.destroy();
   await gBrowser.removeTab(tab);
   finish();
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_rr_console_warp-02.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+// This functionality was copied from devtools/client/webconsole/test/mochitest/head.js,
+// since this test straddles both the web console and the debugger. I couldn't
+// figure out how to load that script directly here.
+function waitForThreadEvents(threadClient, eventName) {
+  info(`Waiting for thread event '${eventName}' to fire.`);
+
+  return new Promise(function(resolve, reject) {
+    threadClient.addListener(eventName, function onEvent(eventName, ...args) {
+      info(`Thread event '${eventName}' fired.`);
+      threadClient.removeListener(eventName, onEvent);
+      resolve.apply(resolve, args);
+    });
+  });
+}
+
+
+// Test basic console time warping functionality in web replay.
+async function test() {
+  waitForExplicitFinish();
+
+  const dbg = await attatchRecordingDebugger(
+    "doc_rr_logs.html", 
+    { waitForRecording: true }
+  );
+
+  const {tab, toolbox, threadClient} = dbg;
+  const console = await getSplitConsole(dbg);
+  const hud = console.hud;
+
+  let message = await warpToMessage(hud, threadClient, "number: 1");
+  ok(!message.classList.contains("paused-before"), "paused before message is not shown");
+
+  await stepOverToLine(threadClient, 18);
+  await reverseStepOverToLine(threadClient, 17);
+
+  message = findMessage(hud, "number: 1")
+  ok(message.classList.contains("paused-before"), "paused before message is shown");
+
+  await toolbox.destroy();
+  await gBrowser.removeTab(tab);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc_rr_logs.html
@@ -0,0 +1,28 @@
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="utf-8"/>
+</head>
+
+<body>
+<div id="maindiv" style="padding-top:50px">Hello World!</div>
+</body>
+<script>
+const cpmm = SpecialPowers.Services.cpmm;
+function recordingFinished() {
+  cpmm.sendAsyncMessage("RecordingFinished");
+}
+var number = 0;
+function f() {
+  number++;
+  console.log({ number }); 
+  number++;
+  console.log({ number }); 
+  number++;
+  console.log({ number }); 
+  window.setTimeout(recordingFinished);
+}
+window.setTimeout(f, 1);
+// Simulate a longer recording by marking major checkpoints whenever possible.
+SpecialPowers.Cu.recordReplayDirective(/* AlwaysMarkMajorCheckpoints */ 4);
+</script>
+</html>
--- a/devtools/client/debugger/new/test/mochitest/head.js
+++ b/devtools/client/debugger/new/test/mochitest/head.js
@@ -41,16 +41,27 @@ Services.scriptloader.loadSubScript(
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/debugger/new/test/mochitest/helpers.js",
   this
 );
 
 const EXAMPLE_URL =
   "http://example.com/browser/devtools/client/debugger/new/test/mochitest/examples/";
 
+
+async function waitUntilPredicate(predicate) {
+  let result;
+  await waitUntil(() => {
+    result = predicate();
+    return result;
+  })
+
+  return result;
+}
+
 // NOTE: still experimental, the screenshots might not be exactly correct
 async function takeScreenshot(dbg) {
   let canvas = dbg.win.document.createElementNS(
     "http://www.w3.org/1999/xhtml",
     "html:canvas"
   );
   let context = canvas.getContext("2d");
   canvas.width = dbg.win.innerWidth;
@@ -63,16 +74,52 @@ async function takeScreenshot(dbg) {
 // Attach a debugger to a tab, returning a promise that resolves with the
 // debugger's toolbox.
 async function attachDebugger(tab) {
   let target = await TargetFactory.forTab(tab);
   let toolbox = await gDevTools.showToolbox(target, "jsdebugger");
   return toolbox;
 }
 
+async function attatchRecordingDebugger(url, { waitForRecording } = { waitForRecording: false }) {
+  let tab = BrowserTestUtils.addTab(gBrowser, null, { recordExecution: "*" });
+  gBrowser.selectedTab = tab;
+  openTrustedLinkIn(EXAMPLE_URL + url, "current");
+  
+  if (waitForRecording) {
+    await once(Services.ppmm, "RecordingFinished");
+  }
+  const toolbox = await attachDebugger(tab);
+  const dbg = createDebuggerContext(toolbox)
+  const threadClient = dbg.toolbox.threadClient;
+
+  await threadClient.interrupt();
+  return {...dbg, tab, threadClient};
+}
+
+
+// Return a promise with a reference to jsterm, opening the split
+// console if necessary.  This cleans up the split console pref so
+// it won't pollute other tests.
+async function getSplitConsole(dbg) {
+  const { toolbox, win } = dbg;
+
+  if (!win) {
+    win = toolbox.win;
+  }
+
+  if (!toolbox.splitConsole) {
+    pressKey(dbg, "Escape");
+  }
+
+  await toolbox.openSplitConsole();
+  return toolbox.getPanel("webconsole");
+}
+
+
 // Return a promise that resolves when a breakpoint has been set.
 async function setBreakpoint(threadClient, expectedFile, lineno) {
   let {sources} = await threadClient.getSources();
   ok(sources.length == 1, "Got one source");
   ok(RegExp(expectedFile).test(sources[0].url), "Source is " + expectedFile);
   let sourceClient = threadClient.source(sources[0]);
   await sourceClient.setBreakpoint({ line: lineno });
 }
@@ -138,8 +185,78 @@ async function checkEvaluateInTopFrameTh
 }
 
 // Return a pathname that can be used for a new recording file.
 function newRecordingFile() {
   ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
   return OS.Path.join(OS.Constants.Path.tmpDir,
                       "MochitestRecording" + Math.round(Math.random() * 1000000000));
 }
+
+
+async function warpToMessage(hud, threadClient, text) {
+  let messages = await waitForMessages(hud, text);
+  ok(messages.length == 1, "Found one message");
+  let message = messages.pop();
+
+  let menuPopup = await openConsoleContextMenu(hud, message);
+  console.log(`.>> menu`, menuPopup);
+
+
+  let timeWarpItem = menuPopup.querySelector("#console-menu-time-warp");
+  ok(timeWarpItem, "Time warp menu item is available");
+
+  timeWarpItem.click();
+
+  await Promise.all([
+    hideConsoleContextMenu(hud),
+    once(Services.ppmm, "TimeWarpFinished"),
+    waitForThreadEvents(threadClient, 'paused')
+  ]);
+
+  messages = findMessages(hud, "", ".paused");
+  ok(messages.length == 1, "Found one paused message");
+
+  return message;
+}
+
+
+function findMessage(hud, text, selector = ".message") {
+  return findMessages(hud, text, selector)[0]
+}
+
+function findMessages(hud, text, selector = ".message") {
+  const messages = hud.ui.outputNode.querySelectorAll(selector);
+  const elements = Array.prototype.filter.call(
+    messages,
+    (el) => el.textContent.includes(text)
+  );
+
+  if (elements.length == 0) {
+    return null;
+  }
+
+  return elements;
+}
+
+function waitForMessages(hud, text, selector = ".message") {
+  return waitUntilPredicate(() => findMessages(hud, text, selector))
+}
+
+async function openConsoleContextMenu(hud, element) {
+  const onConsoleMenuOpened = hud.ui.consoleOutput.once("menu-open");
+  synthesizeContextMenuEvent(element);
+  await onConsoleMenuOpened;
+  const doc = hud.ui.consoleOutput.owner.chromeWindow.document;
+  return doc.getElementById("webconsole-menu");
+}
+
+function hideConsoleContextMenu(hud) {
+  const doc = hud.ui.consoleOutput.owner.chromeWindow.document;
+  const popup = doc.getElementById("webconsole-menu");
+  if (!popup) {
+    return Promise.resolve();
+  }
+
+  const onPopupHidden = once(popup, "popuphidden");
+  popup.hidePopup();
+  return onPopupHidden;
+}
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -483,41 +483,45 @@
   border: 1px solid #bfc9d2;
 }
 
 .webreplay-player .progress {
   position: absolute;
   width: 100%;
   height: 100%;
   background: var(--progress-playing-background);
+  transition-duration: 200ms;
+
 }
 
 .webreplay-player #overlay:not(.recording) .progress::after {
   background: var(--purple-50);
   width: 1px;
   height: 100%;
   right: 0;
   opacity: 0.4;
   display: block;
   content: "";
   position: absolute;
 }
 
 .webreplay-player .recording .progress {
   background: var(--progress-recording-background);
+  transition-duration: 200ms;
 }
 
 .webreplay-player .message {
   position: absolute;
   height: 100%;
   width: 7px;
   height: 7px;
   border-radius: 4.5px;
   top: calc(50% - 3.5px);
   background: var(--blue-40);
+  transition-duration: 100ms;
 }
 
 .webreplay-player .message.overlayed {
   border: 1px solid var(--progress-playing-background);
   top: 5.5px;
 }
 
 .webreplay-player .message.overlayed.future {
@@ -608,16 +612,17 @@
 .webreplay-player .progress-line {
   width: 0%;
   height: 1px;
   background: var(--blue-40);
   position: absolute;
   left: 0;
   right: 10px;
   top: 50%;
+  transition-duration: 200ms;
 }
 
 .webreplay-player .progress-line.end {
   opacity: 0.3;
 }
 
 .webreplay-player .recording .progress-line {
   background: #d0021b;
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -117,17 +117,23 @@ a {
   height: 1px;
   bottom: 0px;
   left: -3px;
   display: block;
   content: "";
   position: absolute;
 }
 
-.message.paused ~ .message:not(.command):not(.result) .message-body-wrapper {
+.message.paused.paused-before::before {
+  top: 0px;
+  bottom: inherit;
+}
+
+.message.paused ~ .message:not(.command):not(.result) .message-body-wrapper,
+.message.paused.paused-before .message-body-wrapper {
   opacity: 0.5;
 }
 
 .message.startGroup,
 .message.startGroupCollapsed {
   --console-output-indent-border-color: transparent;
 }
 
--- a/devtools/client/webconsole/components/Message.js
+++ b/devtools/client/webconsole/components/Message.js
@@ -42,16 +42,19 @@ class Message extends Component {
       repeat: PropTypes.any,
       frame: PropTypes.any,
       attachment: PropTypes.any,
       stacktrace: PropTypes.any,
       messageId: PropTypes.string,
       executionPoint: PropTypes.shape({
         progress: PropTypes.number,
       }),
+      pausedExecutionPoint: PropTypes.shape({
+        progress: PropTypes.number,
+      }),
       scrollToMessage: PropTypes.bool,
       exceptionDocURL: PropTypes.string,
       request: PropTypes.object,
       dispatch: PropTypes.func,
       timeStamp: PropTypes.number,
       timestampsVisible: PropTypes.bool.isRequired,
       serviceContainer: PropTypes.shape({
         emitNewMessage: PropTypes.func.isRequired,
@@ -157,26 +160,33 @@ class Message extends Component {
       messageBody,
       frame,
       stacktrace,
       serviceContainer,
       exceptionDocURL,
       timeStamp = Date.now(),
       timestampsVisible,
       executionPoint,
+      pausedExecutionPoint,
       notes,
     } = this.props;
 
     topLevelClasses.push("message", source, type, level);
     if (open) {
       topLevelClasses.push("open");
     }
 
     if (isPaused) {
       topLevelClasses.push("paused");
+
+      if (pausedExecutionPoint
+        && executionPoint
+        && pausedExecutionPoint.progress < executionPoint.progress) {
+        topLevelClasses.push("paused-before");
+      }
     }
 
     let timestampEl;
     if (timestampsVisible === true) {
       timestampEl = dom.span({
         className: "timestamp devtools-monospace",
       }, l10n.timestampString(timeStamp));
     }
--- a/devtools/client/webconsole/components/MessageContainer.js
+++ b/devtools/client/webconsole/components/MessageContainer.js
@@ -31,16 +31,17 @@ class MessageContainer extends Component
       open: PropTypes.bool.isRequired,
       serviceContainer: PropTypes.object.isRequired,
       tableData: PropTypes.object,
       timestampsVisible: PropTypes.bool.isRequired,
       repeat: PropTypes.number,
       networkMessageUpdate: PropTypes.object,
       getMessage: PropTypes.func.isRequired,
       isPaused: PropTypes.bool.isRequired,
+      pausedExecutionPoint: PropTypes.any,
     };
   }
 
   static get defaultProps() {
     return {
       open: false,
     };
   }
@@ -49,23 +50,26 @@ class MessageContainer extends Component
     const repeatChanged = this.props.repeat !== nextProps.repeat;
     const openChanged = this.props.open !== nextProps.open;
     const tableDataChanged = this.props.tableData !== nextProps.tableData;
     const timestampVisibleChanged =
       this.props.timestampsVisible !== nextProps.timestampsVisible;
     const networkMessageUpdateChanged =
       this.props.networkMessageUpdate !== nextProps.networkMessageUpdate;
     const pausedChanged = this.props.isPaused !== nextProps.isPaused;
+    const executionPointChanged =
+      this.props.pausedExecutionPoint !== nextProps.pausedExecutionPoint;
 
     return repeatChanged
       || openChanged
       || tableDataChanged
       || timestampVisibleChanged
       || networkMessageUpdateChanged
-      || pausedChanged;
+      || pausedChanged
+      || executionPointChanged;
   }
 
   render() {
     const message = this.props.getMessage();
 
     const MessageComponent = getMessageComponent(message);
     return MessageComponent(Object.assign({message}, this.props));
   }
--- a/devtools/client/webconsole/components/message-types/ConsoleApiCall.js
+++ b/devtools/client/webconsole/components/message-types/ConsoleApiCall.js
@@ -34,16 +34,17 @@ function ConsoleApiCall(props) {
   const {
     dispatch,
     message,
     open,
     tableData,
     serviceContainer,
     timestampsVisible,
     repeat,
+    pausedExecutionPoint,
     isPaused,
   } = props;
   const {
     id: messageId,
     executionPoint,
     indent,
     source,
     type,
@@ -111,16 +112,17 @@ function ConsoleApiCall(props) {
 
   const collapsible = isGroupType(type)
     || (type === "error" && Array.isArray(stacktrace));
   const topLevelClasses = ["cm-s-mozilla"];
 
   return Message({
     messageId,
     executionPoint,
+    pausedExecutionPoint,
     isPaused,
     open,
     collapsible,
     collapseTitle,
     source,
     type,
     level,
     topLevelClasses,
--- a/devtools/client/webconsole/webconsole-frame.js
+++ b/devtools/client/webconsole/webconsole-frame.js
@@ -74,17 +74,16 @@ WebConsoleFrame.prototype = {
    * Initialize the WebConsoleFrame instance.
    * @return object
    *         A promise object that resolves once the frame is ready to use.
    */
   async init() {
     this._initUI();
     await this._initConnection();
     await this.consoleOutput.init();
-
     // Toggle the timestamp on preference change
     Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this._onToolboxPrefChanged();
 
     const id = WebConsoleUtils.supportsString(this.hudId);
     if (Services.obs) {
       Services.obs.notifyObservers(id, "web-console-created");
     }