Bug 1509121 - Display error screen if about:devtools-toolbox fails to start. r=jdescottes,daisuke
authorBelén Albeza <balbeza@mozilla.com>
Thu, 14 Feb 2019 08:13:16 +0000
changeset 459009 194d4681c00cde28b3a3213dcbcda5fa17159c68
parent 459008 01bc5a0dc801885894b7ce17e0bdbaf73b87c020
child 459010 3e5099a32d142d2bf42935b3ed1325f3a073c542
push id35554
push userrgurzau@mozilla.com
push dateThu, 14 Feb 2019 17:00:27 +0000
treeherdermozilla-central@db6bcdbe4040 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes, daisuke
bugs1509121
milestone67.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 1509121 - Display error screen if about:devtools-toolbox fails to start. r=jdescottes,daisuke Differential Revision: https://phabricator.services.mozilla.com/D18842
devtools/client/framework/components/DebugTargetErrorPage.css
devtools/client/framework/components/DebugTargetErrorPage.js
devtools/client/framework/components/moz.build
devtools/client/framework/target-from-url.js
devtools/client/framework/toolbox-init.js
devtools/client/framework/toolbox.xul
devtools/client/locales/en-US/toolbox.properties
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/components/DebugTargetErrorPage.css
@@ -0,0 +1,21 @@
+/* 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/. */
+
+.error-page {
+  --base-unit: 4px; /* from photon */
+
+  padding: calc(var(--base-unit) * 4);
+  font-size: 15px; /* from photon */
+  min-height: 100vh;
+}
+
+.error-page__title {
+  margin: 0;
+  font-size: 36px; /* from photon */
+  font-weight: 200; /* from photon */
+}
+
+.error-page__details {
+  font-family: monospace;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/components/DebugTargetErrorPage.js
@@ -0,0 +1,50 @@
+/* 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 { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+
+/**
+ * This component is displayed when the about:devtools-toolbox fails to load
+ * properly due to wrong parameters or debug targets that don't exist.
+ */
+class DebugTargetErrorPage extends PureComponent {
+  static get propTypes() {
+    return {
+      errorMessage: PropTypes.string.isRequired,
+      L10N: PropTypes.object.isRequired,
+    };
+  }
+
+  render() {
+    const { errorMessage, L10N } = this.props;
+
+    return dom.article(
+      {
+        className: "error-page",
+      },
+      dom.h1(
+        {
+          className: "error-page__title",
+        },
+        L10N.getStr("toolbox.debugTargetErrorPage.title"),
+      ),
+      dom.p(
+        {},
+        L10N.getStr("toolbox.debugTargetErrorPage.description"),
+      ),
+      dom.output(
+        {
+          className: "error-page__details",
+        },
+        errorMessage,
+      ),
+    );
+  }
+}
+
+module.exports = DebugTargetErrorPage;
--- a/devtools/client/framework/components/moz.build
+++ b/devtools/client/framework/components/moz.build
@@ -1,15 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 
 DevToolsModules(
+  'DebugTargetErrorPage.css',
+  'DebugTargetErrorPage.js',
   'DebugTargetInfo.js',
   'MeatballMenu.js',
   'ToolboxController.js',
   'ToolboxTab.js',
   'ToolboxTabs.js',
   'ToolboxToolbar.js',
 )
--- a/devtools/client/framework/target-from-url.js
+++ b/devtools/client/framework/target-from-url.js
@@ -132,17 +132,21 @@ exports.targetFromURL = async function t
  * @return a promise that resolves a DebuggerClient object
  */
 async function clientFromURL(url) {
   const params = url.searchParams;
 
   // If a remote id was provided we should already have a connected client available.
   const remoteId = params.get("remoteId");
   if (remoteId) {
-    return remoteClientManager.getClientByRemoteId(remoteId);
+    const client = remoteClientManager.getClientByRemoteId(remoteId);
+    if (!client) {
+      throw new Error(`Could not find client with remote id: ${remoteId}`);
+    }
+    return client;
   }
 
   const host = params.get("host");
   const port = params.get("port");
   const webSocket = !!params.get("ws");
 
   let transport;
   if (port) {
--- a/devtools/client/framework/toolbox-init.js
+++ b/devtools/client/framework/toolbox-init.js
@@ -2,53 +2,88 @@
  * 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/. */
 
 /* eslint-env browser */
 /* global XPCNativeWrapper */
 
 "use strict";
 
+const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+
 // URL constructor doesn't support about: scheme
 const href = window.location.href.replace("about:", "http://");
 const url = new window.URL(href);
 
-// Only use this method to attach the toolbox if some query parameters are given
-if (url.search.length > 1) {
-  const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+// `host` is the frame element loading the toolbox.
+let host = window.windowUtils.containerElement;
+
+// If there's no containerElement (which happens when loading about:devtools-toolbox as
+// a top level document), use the current window.
+if (!host) {
+  host = {
+    contentWindow: window,
+    contentDocument: document,
+    // toolbox-host-manager.js wants to set attributes on the frame that contains it,
+    // but that is fine to skip and doesn't make sense when using the current window.
+    setAttribute() {},
+    ownerDocument: document,
+    // toolbox-host-manager.js wants to listen for unload events from outside the frame,
+    // but this is fine to skip since the toolbox code listens inside the frame as well,
+    // and there is no outer document in this case.
+    addEventListener() {},
+  };
+}
+
+const onLoad = new Promise(r => {
+  host.contentWindow.addEventListener("DOMContentLoaded", r, { once: true });
+});
+
+async function showErrorPage(doc, errorMessage) {
+  const win = doc.defaultView;
+  const { BrowserLoader } =
+    ChromeUtils.import("resource://devtools/client/shared/browser-loader.js");
+  const browserRequire = BrowserLoader({
+    window: win,
+    useOnlyShared: true,
+  }).require;
+
+  const React = browserRequire("devtools/client/shared/vendor/react");
+  const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
+  const DebugTargetErrorPage = React.createFactory(
+    require("devtools/client/framework/components/DebugTargetErrorPage"));
+  const { LocalizationHelper } = browserRequire("devtools/shared/l10n");
+  const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
+
+  // mount the React component into our XUL container once the DOM is ready
+  await onLoad;
+  const mountEl = doc.querySelector("#toolbox-error-mount");
+  const element = DebugTargetErrorPage({
+    errorMessage,
+    L10N,
+  });
+  ReactDOM.render(element, mountEl);
+
+  // make sure we unmount the component when the page is destroyed
+  win.addEventListener("unload", () => {
+    ReactDOM.unmountComponentAtNode(mountEl);
+  }, { once: true });
+}
+
+async function initToolbox(url, host) {
   const { gDevTools } = require("devtools/client/framework/devtools");
   const { targetFromURL } = require("devtools/client/framework/target-from-url");
   const { Toolbox } = require("devtools/client/framework/toolbox");
   const { DebuggerServer } = require("devtools/server/main");
   const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 
-  // `host` is the frame element loading the toolbox.
-  let host = window.windowUtils.containerElement;
-
-  // If there's no containerElement (which happens when loading about:devtools-toolbox as
-  // a top level document), use the current window.
-  if (!host) {
-    host = {
-      contentWindow: window,
-      contentDocument: document,
-      // toolbox-host-manager.js wants to set attributes on the frame that contains it,
-      // but that is fine to skip and doesn't make sense when using the current window.
-      setAttribute() {},
-      ownerDocument: document,
-      // toolbox-host-manager.js wants to listen for unload events from outside the frame,
-      // but this is fine to skip since the toolbox code listens inside the frame as well,
-      // and there is no outer document in this case.
-      addEventListener() {},
-    };
-  }
-
   // Specify the default tool to open
   const tool = url.searchParams.get("tool");
 
-  (async function() {
+  try {
     let target;
     if (url.searchParams.has("target")) {
       // Attach toolbox to a given browser iframe (<xul:browser> or <html:iframe
       // mozbrowser>) whose reference is set on the host iframe.
 
       // `iframe` is the targeted document to debug
       let iframe = host.wrappedJSObject ? host.wrappedJSObject.target
                                         : host.target;
@@ -71,12 +106,21 @@ if (url.search.length > 1) {
       await client.connect();
       // Creates a target for a given browser iframe.
       target = await client.mainRoot.getTab({ tab });
     } else {
       target = await targetFromURL(url);
     }
     const options = { customIframe: host };
     await gDevTools.showToolbox(target, tool, Toolbox.HostType.PAGE, options);
-  })().catch(error => {
+  } catch (error) {
+    // When an error occurs, show error page with message.
     console.error("Exception while loading the toolbox", error);
-  });
+    showErrorPage(host.contentDocument, `${error}`);
+  }
 }
+
+// Only use this method to attach the toolbox if some query parameters are given
+if (url.search.length > 1) {
+  initToolbox(url, host);
+}
+// TODO: handle no params in about:devtool-toolbox
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1526996
--- a/devtools/client/framework/toolbox.xul
+++ b/devtools/client/framework/toolbox.xul
@@ -1,15 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/toolbox.css" type="text/css"?>
 <?xml-stylesheet href="resource://devtools/client/shared/components/NotificationBox.css" type="text/css"?>
+<?xml-stylesheet href="resource://devtools/client/framework/components/DebugTargetErrorPage.css" type="text/css"?>
 
 <!DOCTYPE window [
 <!ENTITY % toolboxDTD SYSTEM "chrome://devtools/locale/toolbox.dtd" >
 %toolboxDTD;
 <!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
 %globalKeysDTD;
 ]>
 
@@ -22,16 +23,17 @@
           src="chrome://global/content/viewSourceUtils.js"/>
 
   <script type="application/javascript"
           src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript"
           src="chrome://devtools/content/framework/toolbox-init.js"/>
 
   <vbox id="toolbox-container" flex="1">
+    <div xmlns="http://www.w3.org/1999/xhtml" id="toolbox-error-mount"/>
     <div xmlns="http://www.w3.org/1999/xhtml" id="toolbox-notificationbox"/>
     <div xmlns="http://www.w3.org/1999/xhtml" id="toolbox-toolbar-mount"
          role="toolbar" />
     <vbox flex="1" class="theme-body">
       <!-- Set large flex to allow the toolbox-panel-webconsole to have a
            height set to a small value without flexing to fill up extra
            space. There must be a flex on both to ensure that the console
            panel itself is sized properly -->
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -239,8 +239,18 @@ toolbox.debugTargetInfo.connection.netwo
 # LOCALIZATION NOTE (browserToolbox.statusMessage): This is the label
 # shown next to status details when the Browser Toolbox fails to connect or
 # appears to be taking a while to do so.
 browserToolbox.statusMessage=Browser Toolbox connection status:
 
 # LOCALIZATION NOTE (toolbox.replay.jumpMessage): This is the label
 # shown in the web replay timeline marker
 toolbox.replay.jumpMessage=Jump to message %1$S
+
+# LOCALIZATION NOTE (toolbox.debugTargetErrorPage.title): This is the title
+# for the Error view shown by the toolbox when a connection to a debug target
+# could not be made
+toolbox.debugTargetErrorPage.title = Error
+
+# LOCALIZATION NOTE (toolbox.debugTargetErrorPage.description): This is the
+# text that appears in the Error view and explains to the user that an error
+# has happened while trying to connect to a debug target
+toolbox.debugTargetErrorPage.description = Cannot connect to the debug target. See error details below: