Bug 1639425 - Add forward and back buttons to remote debugging r=daisuke,flod,jdescottes
authorDavid Walsh <dwalsh@mozilla.com>
Wed, 03 Jun 2020 22:43:56 +0000
changeset 533817 793023d3cb5f4c275ca8016f79d8b51fc8bf78df
parent 533816 d39b5b23b9e7fc06f33ff4511a48fee3e117ea83
child 533818 aaf96d2f132908e92c07fd77eff9e0047645bb23
push id37478
push userabutkovits@mozilla.com
push dateThu, 04 Jun 2020 09:29:07 +0000
treeherdermozilla-central@e87e4800d332 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaisuke, flod, jdescottes
bugs1639425
milestone79.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 1639425 - Add forward and back buttons to remote debugging r=daisuke,flod,jdescottes Differential Revision: https://phabricator.services.mozilla.com/D76203
devtools/client/aboutdebugging/test/browser/browser.ini
devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_navigate_back_forward.js
devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_navigate_to_url.js
devtools/client/aboutdebugging/test/browser/head.js
devtools/client/framework/components/DebugTargetInfo.js
devtools/client/framework/test/node/components/__snapshots__/debug-target-info.test.js.snap
devtools/client/framework/test/node/components/debug-target-info.test.js
devtools/client/locales/en-US/toolbox.properties
devtools/client/themes/toolbox.css
devtools/server/actors/targets/browsing-context.js
devtools/shared/specs/targets/browsing-context.js
--- a/devtools/client/aboutdebugging/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser/browser.ini
@@ -56,16 +56,17 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_debug-target-pane_collapsibilities_preference.js]
 [browser_aboutdebugging_debug-target-pane_empty.js]
 [browser_aboutdebugging_debug-target-pane_usb_runtime.js]
 [browser_aboutdebugging_devtools.js]
 [browser_aboutdebugging_devtoolstoolbox_contextmenu.js]
 [browser_aboutdebugging_devtoolstoolbox_contextmenu_markupview.js]
 [browser_aboutdebugging_devtoolstoolbox_focus.js]
 [browser_aboutdebugging_devtoolstoolbox_menubar.js]
+[browser_aboutdebugging_devtoolstoolbox_navigate_back_forward.js]
 [browser_aboutdebugging_devtoolstoolbox_navigate_reload_button.js]
 [browser_aboutdebugging_devtoolstoolbox_navigate_to_url.js]
 [browser_aboutdebugging_devtoolstoolbox_performance.js]
 skip-if = os == 'linux' && e10s && (asan || debug) # Same skip-if as old perf panel test suite. Bug 1254821
 [browser_aboutdebugging_devtoolstoolbox_reload.js]
 skip-if = verify || ccov || (os == 'linux' && debug) #bug 1544828, test loads the toolbox 2 times for each panel, might timeout or OOM
 [browser_aboutdebugging_devtoolstoolbox_shortcuts.js]
 skip-if = ccov || (os == 'linux' && bits == 64) # Bug 1521349, Bug 1548015, Bug 1544828
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_navigate_back_forward.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGINAL_URL = "about:home";
+const OTHER_URL = "about:blank";
+
+async function waitForUrl(url, toolbox, browserTab, win) {
+  return Promise.all([
+    waitUntil(
+      () =>
+        toolbox.target.url === url &&
+        browserTab.linkedBrowser.currentURI.spec === url
+    ),
+    toolbox.target.once("navigate"),
+    toolbox.target.client.waitForRequestsToSettle(),
+    waitForRequestsToSettle(win.AboutDebugging.store),
+  ]);
+}
+
+// Test that ensures the remote page can go forward and back via UI buttons
+add_task(async function() {
+  const browserTab = await addTab(ORIGINAL_URL);
+
+  const { document, tab, window } = await openAboutDebugging();
+
+  // go to This Firefox and inspect the new tab
+  info("Inspecting a new tab in This Firefox");
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+  const devToolsToolbox = await openAboutDevtoolsToolbox(
+    document,
+    tab,
+    window,
+    ORIGINAL_URL
+  );
+  const { devtoolsDocument, devtoolsWindow } = devToolsToolbox;
+  const toolbox = getToolbox(devtoolsWindow);
+
+  info("Navigating to another URL");
+  const urlInput = devtoolsDocument.querySelector(".devtools-textinput");
+  await synthesizeUrlKeyInput(devToolsToolbox, urlInput, OTHER_URL);
+  await waitForUrl(OTHER_URL, toolbox, browserTab, window);
+
+  info("Clicking back button");
+  devtoolsDocument.querySelector(".qa-back-button").click();
+  await waitForUrl(ORIGINAL_URL, toolbox, browserTab, window);
+
+  info("Clicking the forward button");
+  devtoolsDocument.querySelector(".qa-forward-button").click();
+  await waitForUrl(OTHER_URL, toolbox, browserTab, window);
+
+  ok(true, "Clicking back and forward works!");
+});
--- a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_navigate_to_url.js
+++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_devtoolstoolbox_navigate_to_url.js
@@ -45,39 +45,8 @@ add_task(async function() {
   info("Remove the background tab");
   await removeTab(debug_tab);
   await waitUntil(() => !findDebugTargetByText("NEW_TAB_TITLE", document));
   await waitForRequestsToSettle(window.AboutDebugging.store);
 
   info("Remove the about:debugging tab.");
   await removeTab(tab);
 });
-
-/**
- * Synthesizes key input inside the DebugTargetInfo's URL component.
- *
- * @param {DevToolsToolbox} toolbox
- *        The DevToolsToolbox debugging the target.
- * @param {HTMLElement} inputEl
- *        The <input> element to submit the URL with.
- * @param {String}  url
- *        The URL to navigate to.
- */
-async function synthesizeUrlKeyInput(toolbox, inputEl, url) {
-  const { devtoolsDocument, devtoolsWindow } = toolbox;
-
-  info("Wait for URL input to be focused.");
-  const onInputFocused = waitUntil(
-    () => devtoolsDocument.activeElement === inputEl
-  );
-  inputEl.focus();
-  await onInputFocused;
-
-  info("Synthesize entering URL into text field");
-  const onInputChange = waitUntil(() => inputEl.value === url);
-  for (const key of NEW_TAB_URL.split("")) {
-    EventUtils.synthesizeKey(key, {}, devtoolsWindow);
-  }
-  await onInputChange;
-
-  info("Submit URL to navigate to");
-  EventUtils.synthesizeKey("KEY_Enter");
-}
--- a/devtools/client/aboutdebugging/test/browser/head.js
+++ b/devtools/client/aboutdebugging/test/browser/head.js
@@ -411,8 +411,38 @@ async function updateSelectedTab(browser
   // Update the selected tab.
   browser.selectedTab = tab;
 
   if (onTabsSuccess) {
     info("Wait for the tablist update after updating the selected tab");
     await onTabsSuccess;
   }
 }
+
+/**
+ * Synthesizes key input inside the DebugTargetInfo's URL component.
+ *
+ * @param {DevToolsToolbox} toolbox
+ *        The DevToolsToolbox debugging the target.
+ * @param {HTMLElement} inputEl
+ *        The <input> element to submit the URL with.
+ * @param {String}  url
+ *        The URL to navigate to.
+ */
+async function synthesizeUrlKeyInput(toolbox, inputEl, url) {
+  const { devtoolsDocument, devtoolsWindow } = toolbox;
+  info("Wait for URL input to be focused.");
+  const onInputFocused = waitUntil(
+    () => devtoolsDocument.activeElement === inputEl
+  );
+  inputEl.focus();
+  await onInputFocused;
+
+  info("Synthesize entering URL into text field");
+  const onInputChange = waitUntil(() => inputEl.value === url);
+  for (const key of url.split("")) {
+    EventUtils.synthesizeKey(key, {}, devtoolsWindow);
+  }
+  await onInputChange;
+
+  info("Submit URL to navigate to");
+  EventUtils.synthesizeKey("KEY_Enter");
+}
--- a/devtools/client/framework/components/DebugTargetInfo.js
+++ b/devtools/client/framework/components/DebugTargetInfo.js
@@ -262,45 +262,82 @@ class DebugTargetInfo extends PureCompon
         className: "devtools-textinput debug-target-url-input",
         onChange: this.onChange,
         onFocus: this.onFocus,
         defaultValue: url,
       })
     );
   }
 
-  renderReloadButton() {
-    const { L10N, debugTargetData } = this.props;
+  renderNavigationButton(detail) {
+    const { L10N } = this.props;
+
+    return dom.button(
+      {
+        className: `iconized-label navigation-button ${detail.className}`,
+        onClick: detail.onClick,
+      },
+      dom.img({
+        src: detail.icon,
+        alt: L10N.getStr(detail.l10nId),
+      })
+    );
+  }
+
+  renderNavigation() {
+    const { debugTargetData } = this.props;
     const { targetType } = debugTargetData;
 
     if (targetType !== DEBUG_TARGET_TYPES.TAB) {
       return null;
     }
 
-    return dom.button(
+    const items = [];
+
+    if (this.props.toolbox.target.traits.navigation) {
+      items.push(
+        this.renderNavigationButton({
+          className: "qa-back-button",
+          icon: "chrome://browser/skin/back.svg",
+          l10nId: "toolbox.debugTargetInfo.back",
+          onClick: () => this.props.toolbox.target.goBack(),
+        }),
+        this.renderNavigationButton({
+          className: "qa-forward-button",
+          icon: "chrome://browser/skin/forward.svg",
+          l10nId: "toolbox.debugTargetInfo.forward",
+          onClick: () => this.props.toolbox.target.goForward(),
+        })
+      );
+    }
+
+    items.push(
+      this.renderNavigationButton({
+        className: "qa-reload-button",
+        icon: "chrome://browser/skin/reload.svg",
+        l10nId: "toolbox.debugTargetInfo.reload",
+        onClick: () => this.props.toolbox.target.reload(),
+      })
+    );
+
+    return dom.div(
       {
-        className: "iconized-label reload-button qa-reload-button",
-        onClick: () => {
-          this.props.toolbox.target.reload();
-        },
+        className: "debug-target-navigation",
       },
-      dom.img({
-        src: "chrome://browser/skin/reload.svg",
-        alt: L10N.getStr("toolbox.debugTargetInfo.reload"),
-      })
+      ...items
     );
   }
 
   render() {
     return dom.header(
       {
         className: "debug-target-info qa-debug-target-info",
       },
       this.shallRenderConnection() ? this.renderConnection() : null,
       this.renderRuntime(),
       this.renderTargetTitle(),
-      this.renderReloadButton(),
+      this.renderNavigation(),
       this.renderTargetURI()
     );
   }
 }
 
 module.exports = DebugTargetInfo;
--- a/devtools/client/framework/test/node/components/__snapshots__/debug-target-info.test.js.snap
+++ b/devtools/client/framework/test/node/components/__snapshots__/debug-target-info.test.js.snap
@@ -39,25 +39,47 @@ exports[`DebugTargetInfo component Conne
       src="chrome://devtools/skin/images/globe.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
   </span>
-  <button
-    className="iconized-label reload-button qa-reload-button"
-    onClick={[Function]}
+  <div
+    className="debug-target-navigation"
   >
-    <img
-      alt="toolbox.debugTargetInfo.reload"
-      src="chrome://browser/skin/reload.svg"
-    />
-  </button>
+    <button
+      className="iconized-label navigation-button qa-back-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.back"
+        src="chrome://browser/skin/back.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-forward-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.forward"
+        src="chrome://browser/skin/forward.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-reload-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.reload"
+        src="chrome://browser/skin/reload.svg"
+      />
+    </button>
+  </div>
   <span
     className="debug-target-url"
   >
     <form
       className="debug-target-url-form"
       onSubmit={[Function]}
     >
       <input
@@ -166,25 +188,47 @@ exports[`DebugTargetInfo component Targe
       src="chrome://devtools/skin/images/globe.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
   </span>
-  <button
-    className="iconized-label reload-button qa-reload-button"
-    onClick={[Function]}
+  <div
+    className="debug-target-navigation"
   >
-    <img
-      alt="toolbox.debugTargetInfo.reload"
-      src="chrome://browser/skin/reload.svg"
-    />
-  </button>
+    <button
+      className="iconized-label navigation-button qa-back-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.back"
+        src="chrome://browser/skin/back.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-forward-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.forward"
+        src="chrome://browser/skin/forward.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-reload-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.reload"
+        src="chrome://browser/skin/reload.svg"
+      />
+    </button>
+  </div>
   <span
     className="debug-target-url"
   >
     <form
       className="debug-target-url-form"
       onSubmit={[Function]}
     >
       <input
@@ -338,25 +382,47 @@ exports[`DebugTargetInfo component Targe
       src="chrome://devtools/skin/images/globe.svg"
     />
     <b
       className="devtools-ellipsis-text qa-target-title"
     >
       Test Tab Name
     </b>
   </span>
-  <button
-    className="iconized-label reload-button qa-reload-button"
-    onClick={[Function]}
+  <div
+    className="debug-target-navigation"
   >
-    <img
-      alt="toolbox.debugTargetInfo.reload"
-      src="chrome://browser/skin/reload.svg"
-    />
-  </button>
+    <button
+      className="iconized-label navigation-button qa-back-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.back"
+        src="chrome://browser/skin/back.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-forward-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.forward"
+        src="chrome://browser/skin/forward.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-reload-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.reload"
+        src="chrome://browser/skin/reload.svg"
+      />
+    </button>
+  </div>
   <span
     className="debug-target-url"
   >
     <form
       className="debug-target-url-form"
       onSubmit={[Function]}
     >
       <input
@@ -393,25 +459,47 @@ exports[`DebugTargetInfo component Targe
   <span
     className="iconized-label debug-target-title"
   >
     <img
       alt="toolbox.debugTargetInfo.targetType.tab"
       src="chrome://devtools/skin/images/globe.svg"
     />
   </span>
-  <button
-    className="iconized-label reload-button qa-reload-button"
-    onClick={[Function]}
+  <div
+    className="debug-target-navigation"
   >
-    <img
-      alt="toolbox.debugTargetInfo.reload"
-      src="chrome://browser/skin/reload.svg"
-    />
-  </button>
+    <button
+      className="iconized-label navigation-button qa-back-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.back"
+        src="chrome://browser/skin/back.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-forward-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.forward"
+        src="chrome://browser/skin/forward.svg"
+      />
+    </button>
+    <button
+      className="iconized-label navigation-button qa-reload-button"
+      onClick={[Function]}
+    >
+      <img
+        alt="toolbox.debugTargetInfo.reload"
+        src="chrome://browser/skin/reload.svg"
+      />
+    </button>
+  </div>
   <span
     className="debug-target-url"
   >
     <form
       className="debug-target-url-form"
       onSubmit={[Function]}
     >
       <input
--- a/devtools/client/framework/test/node/components/debug-target-info.test.js
+++ b/devtools/client/framework/test/node/components/debug-target-info.test.js
@@ -30,23 +30,29 @@ const findByClassName = (testInstance, c
     return node.props.className && node.props.className.includes(className);
   });
 };
 
 const TEST_TOOLBOX = {
   target: {
     name: "Test Tab Name",
     url: "http://some.target/url",
+    traits: {
+      navigation: true,
+    },
   },
   doc: {},
 };
 
 const TEST_TOOLBOX_NO_NAME = {
   target: {
     url: "http://some.target/without/a/name",
+    traits: {
+      navigation: true,
+    },
   },
   doc: {},
 };
 
 const USB_DEVICE_DESCRIPTION = {
   deviceName: "usbDeviceName",
   icon: "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg",
   name: "usbRuntimeBrandName",
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -231,16 +231,24 @@ toolbox.debugTargetInfo.tabTitleError=To
 # runtime being inspected was made.
 toolbox.debugTargetInfo.connection.usb=USB
 toolbox.debugTargetInfo.connection.network=Network
 
 # LOCALIZATION NOTE (toolbox.debugTargetInfo.reload):
 # Used as the reload button tooltip
 toolbox.debugTargetInfo.reload=Reload
 
+# LOCALIZATION NOTE (toolbox.debugTargetInfo.forward):
+# Used as the navigation's "forward" button tooltip
+toolbox.debugTargetInfo.forward=Forward
+
+# LOCALIZATION NOTE (toolbox.debugTargetInfo.back):
+# Used as the navigation's "back" button tooltip
+toolbox.debugTargetInfo.back=Back
+
 # LOCALIZATION NOTE (toolbox.debugTargetInfo.targetType.*): This is displayed as the
 # alt attribute for an icon in the toolbox header in about:devtools-toolbox,
 # to indicate what is the type of the debug target being inspected.
 toolbox.debugTargetInfo.targetType.extension=Extension
 toolbox.debugTargetInfo.targetType.process=Process
 toolbox.debugTargetInfo.targetType.tab=Tab
 toolbox.debugTargetInfo.targetType.worker=Worker
 
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -23,21 +23,28 @@
  *  Debug Target labels with icon layout
  *  +------+------------------------+---------------+
  *  | icon | label text (bold)      | optional text |
  *  | 20px |     max-content        | max-content   |
  *  +------+------------------------+---------------+
  */
 .debug-target-info .iconized-label {
   display: grid;
-  grid-template-columns: 20px auto auto;
-  grid-column-gap: 8px;
   align-items: center;
+  white-space: nowrap;
+  grid-column-gap: 8px;
+  grid-template-columns: 20px auto auto;
   padding: 0 var(--padding-inline-end-size);
+}
+
+.debug-target-navigation {
+  display: flex;
+  align-items: center;
   white-space: nowrap;
+  padding: 0 4px;
 }
 
 .debug-target-info .iconized-label:not(:last-child) {
   border-inline-end: var(--border-inline-end-width) solid var(--theme-splitter-color);
 }
 
 .debug-target-info .iconized-label img {
   width: 20px;
@@ -54,41 +61,41 @@
  * This padding makes the debug target component taller and also creates
  * spacing with the debug-target-title separator.
  * See Bug 1641920 for improving the CSS of the DebugTargetInfo component.
  */
 .debug-target-info .debug-target-url-readonly {
   padding: 8px;
 }
 
-.debug-target-info .reload-button {
+.debug-target-info .navigation-button {
   padding: 8px;
   grid-column-gap: 0;
   border: 0;
   background: var(--theme-toolbarbutton-background);
   --border-inline-end-width: 0;
 }
 
-.debug-target-info .reload-button:hover {
+.debug-target-info .navigation-button:hover {
   background-color: var(--toolbarbutton-hover-background);
 }
 
-.debug-target-info .reload-button:focus {
+.debug-target-info .navigation-button:focus {
   box-shadow: 0 0 0 1px #0a84ff inset, 0 0 0 1px #0a84ff, 0 0 0 4px rgba(10, 132, 255, 0.3)
 }
 
-.debug-target-info .reload-button:active {
+.debug-target-info .navigation-button:active {
   background-color: var(--theme-toolbarbutton-active-background);
 }
 
-.debug-target-info .reload-button img {
+.debug-target-info .navigation-button img {
   fill: var(--theme-toolbarbutton-color);
 }
 
-.debug-target-info .reload-button:active img {
+.debug-target-info .navigation-button:active img {
   fill: var(--theme-toolbarbutton-active-color);
 }
 
 .debug-target-info .debug-target-url {
   display: flex;
   flex-grow: 1;
   padding-inline-end: var(--padding-inline-end-size);
   align-self: center;
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -283,16 +283,18 @@ const browsingContextTargetPrototype = {
       reconfigure: true,
       // Supports frame listing via `listFrames` request and `frameUpdate` events
       // as well as frame switching via `switchToFrame` request
       frames: true,
       // Supports the logInPage request.
       logInPage: true,
       // Supports watchpoints in the server for Fx71+
       watchpoints: true,
+      // Supports back and forward navigation
+      navigation: true,
     };
 
     this._workerTargetActorList = null;
     this._workerTargetActorPool = null;
     this._onWorkerTargetActorListChanged = this._onWorkerTargetActorListChanged.bind(
       this
     );
   },
@@ -1056,30 +1058,69 @@ const browsingContextTargetPrototype = {
    */
   focus() {
     if (this.window) {
       this.window.focus();
     }
     return {};
   },
 
+  // Added in Firefox 79
+  goForward() {
+    // Wait a tick so that the response packet can be dispatched before the
+    // subsequent navigation event packet.
+    Services.tm.dispatchToMainThread(
+      DevToolsUtils.makeInfallible(() => {
+        // This won't work while the browser is shutting down and we don't really
+        // care.
+        if (Services.startup.shuttingDown) {
+          return;
+        }
+
+        this.webNavigation.goForward();
+      }, "BrowsingContextTargetActor.prototype.goForward's delayed body")
+    );
+
+    return {};
+  },
+
+  // Added in Firefox 79
+  goBack() {
+    // Wait a tick so that the response packet can be dispatched before the
+    // subsequent navigation event packet.
+    Services.tm.dispatchToMainThread(
+      DevToolsUtils.makeInfallible(() => {
+        // This won't work while the browser is shutting down and we don't really
+        // care.
+        if (Services.startup.shuttingDown) {
+          return;
+        }
+
+        this.webNavigation.goBack();
+      }, "BrowsingContextTargetActor.prototype.goBack's delayed body")
+    );
+
+    return {};
+  },
+
   /**
    * Reload the page in this browsing context.
    */
   reload(request) {
     const force = request?.options?.force;
     // Wait a tick so that the response packet can be dispatched before the
     // subsequent navigation event packet.
     Services.tm.dispatchToMainThread(
       DevToolsUtils.makeInfallible(() => {
         // This won't work while the browser is shutting down and we don't really
         // care.
         if (Services.startup.shuttingDown) {
           return;
         }
+
         this.webNavigation.reload(
           force
             ? Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
             : Ci.nsIWebNavigation.LOAD_FLAGS_NONE
         );
       }, "BrowsingContextTargetActor.prototype.reload's delayed body")
     );
     return {};
--- a/devtools/shared/specs/targets/browsing-context.js
+++ b/devtools/shared/specs/targets/browsing-context.js
@@ -63,16 +63,24 @@ const browsingContextTargetSpecPrototype
     ensureCSSErrorReportingEnabled: {
       request: {},
       response: {},
     },
     focus: {
       request: {},
       response: {},
     },
+    goForward: {
+      request: {},
+      response: {},
+    },
+    goBack: {
+      request: {},
+      response: {},
+    },
     reload: {
       request: {
         options: Option(0, "browsingContextTarget.reload"),
       },
       response: {},
     },
     navigateTo: {
       request: {