Bug 1488500 - Show install error message for temporary addons;r=daisuke,ladybenko
authorJulian Descottes <jdescottes@mozilla.com>
Fri, 07 Dec 2018 15:56:30 +0000
changeset 449702 11412ab473bc033509ceef42326eecaec4ec18c1
parent 449701 c85e2fe3af3bf7617cf737f7dad350b46c436b06
child 449703 8b2231ca82cdc24af258d660f7219558bcbe7221
push id74420
push userjdescottes@mozilla.com
push dateMon, 10 Dec 2018 10:06:03 +0000
treeherderautoland@8b2231ca82cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaisuke, ladybenko
bugs1488500
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 1488500 - Show install error message for temporary addons;r=daisuke,ladybenko Differential Revision: https://phabricator.services.mozilla.com/D12571
devtools/client/aboutdebugging-new/aboutdebugging.css
devtools/client/aboutdebugging-new/src/actions/debug-targets.js
devtools/client/aboutdebugging-new/src/base.css
devtools/client/aboutdebugging-new/src/components/RuntimePage.js
devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionInstaller.js
devtools/client/aboutdebugging-new/src/components/moz.build
devtools/client/aboutdebugging-new/src/components/shared/ErrorMessage.css
devtools/client/aboutdebugging-new/src/components/shared/ErrorMessage.js
devtools/client/aboutdebugging-new/src/components/shared/moz.build
devtools/client/aboutdebugging-new/src/constants.js
devtools/client/aboutdebugging-new/src/reducers/ui-state.js
devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -17,12 +17,13 @@
 @import "resource://devtools/client/aboutdebugging-new/src/components/connect/ConnectSteps.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/connect/NetworkLocationsForm.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/connect/NetworkLocationsList.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/shared/ErrorMessage.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/SidebarFixedItem.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/SidebarItem.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/sidebar/SidebarRuntimeItem.css";
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -29,16 +29,19 @@ const {
   REQUEST_EXTENSIONS_START,
   REQUEST_EXTENSIONS_SUCCESS,
   REQUEST_TABS_FAILURE,
   REQUEST_TABS_START,
   REQUEST_TABS_SUCCESS,
   REQUEST_WORKERS_FAILURE,
   REQUEST_WORKERS_START,
   REQUEST_WORKERS_SUCCESS,
+  TEMPORARY_EXTENSION_INSTALL_FAILURE,
+  TEMPORARY_EXTENSION_INSTALL_START,
+  TEMPORARY_EXTENSION_INSTALL_SUCCESS,
   RUNTIMES,
 } = require("../constants");
 
 function inspectDebugTarget(type, id) {
   return async (_, getState) => {
     const runtime = getCurrentRuntime(getState().runtimes);
     const { runtimeDetails, type: runtimeType } = runtime;
 
@@ -78,21 +81,23 @@ function inspectDebugTarget(type, id) {
       }
     }
   };
 }
 
 function installTemporaryExtension() {
   const message = l10n.getString("about-debugging-tmp-extension-install-message");
   return async (dispatch, getState) => {
+    dispatch({ type: TEMPORARY_EXTENSION_INSTALL_START });
     const file = await openTemporaryExtension(window, message);
     try {
       await AddonManager.installTemporaryAddon(file);
+      dispatch({ type: TEMPORARY_EXTENSION_INSTALL_SUCCESS });
     } catch (e) {
-      console.error(e);
+      dispatch({ type: TEMPORARY_EXTENSION_INSTALL_FAILURE, error: e });
     }
   };
 }
 
 function pushServiceWorker(actor) {
   return async (_, getState) => {
     const clientWrapper = getCurrentClient(getState().runtimes);
 
--- a/devtools/client/aboutdebugging-new/src/base.css
+++ b/devtools/client/aboutdebugging-new/src/base.css
@@ -39,16 +39,18 @@
   --base-distance: 4px;
 
   /* Global styles */
   --base-font-style: message-box;
   --base-font-size: 15px; /* root font of 11px * 1.36em = 15px */
   --base-line-height: 1.8;
   --micro-font-size: 11px;
 
+  --monospace-font-family: monospace;
+
   /*
   * Variables particular to about:debugging
   */
   --alt-heading-icon-size: calc(var(--base-distance) * 6);
   --alt-heading-icon-gap: var(--base-distance);
   --main-heading-icon-size: calc(var(--base-distance) * 16);
   --main-heading-icon-gap: calc(var(--base-distance) * 3);
   --main-subheading-icon-size: calc(var(--base-distance) * 5);
@@ -100,16 +102,21 @@ a:active {
 
 /* text that needs to be cut with … */
 .ellipsis-text {
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
 
+/* Technical text that should use a monospace font, such as code, error messages. */
+.technical-text {
+  font-family: var(--monospace-font-family);
+}
+
 /*
 * Typography
 */
 
 /* Main style for heading (i.e. h1) */
 /* +--------+-------------+
 *  | [Icon] | Lorem ipsum |
 *  +--------+-------------+
@@ -183,17 +190,16 @@ a:active {
 }
 
 .alt-subheading__icon {
   width: 100%;
   fill: currentColor;
   -moz-context-properties: fill;
 }
 
-
 /*
 * Layout elements
 */
 
 /* for horizontal rules / separators */
 .separator {
   border-style: solid none none none;
   border-color: var(--border-color);
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -42,16 +42,17 @@ class RuntimePage extends PureComponent 
       installedExtensions: PropTypes.arrayOf(PropTypes.object).isRequired,
       otherWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
       runtimeId: PropTypes.string.isRequired,
       runtimeInfo: PropTypes.object,
       serviceWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
       sharedWorkers: PropTypes.arrayOf(PropTypes.object).isRequired,
       tabs: PropTypes.arrayOf(PropTypes.object).isRequired,
       temporaryExtensions: PropTypes.arrayOf(PropTypes.object).isRequired,
+      temporaryInstallError: PropTypes.string,
     };
   }
 
   // TODO: avoid the use of this method
   // https://bugzilla.mozilla.org/show_bug.cgi?id=1508688
   componentWillMount() {
     const { dispatch, runtimeId } = this.props;
     dispatch(Actions.selectPage(PAGE_TYPES.RUNTIME, runtimeId));
@@ -99,16 +100,17 @@ class RuntimePage extends PureComponent 
       installedExtensions,
       otherWorkers,
       runtimeId,
       runtimeInfo,
       serviceWorkers,
       sharedWorkers,
       tabs,
       temporaryExtensions,
+      temporaryInstallError,
     } = this.props;
 
     if (!runtimeInfo) {
       // runtimeInfo can be null when the selectPage action navigates from a runtime A
       // to a runtime B (between unwatchRuntime and watchRuntime).
       return null;
     }
 
@@ -119,18 +121,20 @@ class RuntimePage extends PureComponent 
       {
         className: "page js-runtime-page",
       },
       RuntimeInfo(runtimeInfo),
       shallShowPromptSetting
         ? this.renderConnectionPromptSetting()
         : null,
       isSupportedDebugTargetPane(runtimeInfo.type, DEBUG_TARGET_PANE.TEMPORARY_EXTENSION)
-        ? TemporaryExtensionInstaller({ dispatch })
-        : null,
+        ? TemporaryExtensionInstaller({
+            dispatch,
+            temporaryInstallError,
+        }) : null,
       this.renderDebugTargetPane("Temporary Extensions",
                                  temporaryExtensions,
                                  TemporaryExtensionAction,
                                  ExtensionDetail,
                                  DEBUG_TARGET_PANE.TEMPORARY_EXTENSION,
                                  "about-debugging-runtime-temporary-extensions"),
       this.renderDebugTargetPane("Extensions",
                                  installedExtensions,
@@ -172,12 +176,13 @@ const mapStateToProps = state => {
     collapsibilities: state.ui.debugTargetCollapsibilities,
     installedExtensions: state.debugTargets.installedExtensions,
     otherWorkers: state.debugTargets.otherWorkers,
     runtimeInfo: getCurrentRuntimeInfo(state.runtimes),
     serviceWorkers: state.debugTargets.serviceWorkers,
     sharedWorkers: state.debugTargets.sharedWorkers,
     tabs: state.debugTargets.tabs,
     temporaryExtensions: state.debugTargets.temporaryExtensions,
+    temporaryInstallError: state.ui.temporaryInstallError,
   };
 };
 
 module.exports = connect(mapStateToProps)(RuntimePage);
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionInstaller.js
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/TemporaryExtensionInstaller.js
@@ -6,41 +6,59 @@
 
 const { createFactory, 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");
 
 const FluentReact = require("devtools/client/shared/vendor/fluent-react");
 const Localized = createFactory(FluentReact.Localized);
 
+const ErrorMessage = createFactory(require("../shared/ErrorMessage"));
+
 const Actions = require("../../actions/index");
 
 /**
  * This component provides an installer for temporary extension.
  */
 class TemporaryExtensionInstaller extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
+      temporaryInstallError: PropTypes.string,
     };
   }
 
   install() {
     this.props.dispatch(Actions.installTemporaryExtension());
   }
 
   render() {
-    return Localized(
-      {
-        id: "about-debugging-tmp-extension-install-button",
-      },
-      dom.button(
+    const { temporaryInstallError } = this.props;
+    return dom.div(
+      {},
+      Localized(
         {
-          className: "default-button js-temporary-extension-install-button",
-          onClick: e => this.install(),
+          id: "about-debugging-tmp-extension-install-button",
         },
-        "Load Temporary Add-on…"
-      )
+        dom.button(
+          {
+            className: "default-button js-temporary-extension-install-button",
+            onClick: e => this.install(),
+          },
+          "Load Temporary Add-on…"
+        )
+      ),
+      temporaryInstallError ? ErrorMessage(
+        {
+          errorDescriptionKey: "about-debugging-tmp-extension-install-error",
+        },
+        dom.div(
+          {
+            className: "technical-text",
+          },
+          temporaryInstallError
+        )
+      ) : null
     );
   }
 }
 
 module.exports = TemporaryExtensionInstaller;
--- a/devtools/client/aboutdebugging-new/src/components/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/moz.build
@@ -1,15 +1,16 @@
 # 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/.
 
 DIRS += [
     'connect',
     'debugtarget',
+    'shared',
     'sidebar',
 ]
 
 DevToolsModules(
     'App.css',
     'App.js',
     'ConnectionPromptSetting.js',
     'RuntimeInfo.js',
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/shared/ErrorMessage.css
@@ -0,0 +1,30 @@
+/* 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-message {
+  /* Temporary color chosen to match error background used in about:addons
+   * Pending UX in Bug 1509091 */
+  background-color: #FFE8E9;
+
+  /* Temporary color chosen to match chrome://mozapps/skin/extensions/alerticon-error.svg
+   * Pending UX in Bug 1509091 */
+  color: #E62117;
+
+  margin: calc(var(--base-distance) * 2) 0;
+  padding: var(--base-distance) calc(var(--base-distance) * 3);
+}
+
+/*
+ * Layout of the error message header
+ *
+ *  +--------+----------------+
+ *  | Icon   | Header message |
+ *  +--------+----------------+
+ */
+.error-message__header {
+  align-items: center;
+  display: grid;
+  grid-template-columns: calc(var(--base-distance) * 6) 1fr;
+  font-weight: bold;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/shared/ErrorMessage.js
@@ -0,0 +1,56 @@
+/* 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 { createFactory, 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");
+
+const FluentReact = require("devtools/client/shared/vendor/fluent-react");
+const Localized = createFactory(FluentReact.Localized);
+
+/**
+ * This component is designed to display an error message. It is composed of a header
+ * displaying an error icon followed by a provided localized error description.
+ * Children passed to this component will be displayed beflow the message header.
+ */
+class ErrorMessage extends PureComponent {
+  static get propTypes() {
+    return {
+      children: PropTypes.node.isRequired,
+      // Should match a valid localized string key.
+      errorDescriptionKey: PropTypes.string.isRequired,
+    };
+  }
+
+  render() {
+    return dom.div(
+      {
+        className: "error-message",
+      },
+      dom.div(
+        {
+          className: "error-message__header",
+        },
+        dom.img(
+          {
+            // Temporary image chosen to match error container in about:addons.
+            // Pending UX in Bug 1509091
+            src: "chrome://mozapps/skin/extensions/alerticon-error.svg",
+          }
+        ),
+        Localized(
+          {
+            id: this.props.errorDescriptionKey,
+          },
+          dom.span({}, this.props.errorDescriptionKey)
+        )
+      ),
+      this.props.children
+    );
+  }
+}
+
+module.exports = ErrorMessage;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/shared/moz.build
@@ -0,0 +1,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/.
+
+DevToolsModules(
+    'ErrorMessage.css',
+    'ErrorMessage.js',
+)
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -25,16 +25,19 @@ const actionTypes = {
   REQUEST_EXTENSIONS_START: "REQUEST_EXTENSIONS_START",
   REQUEST_EXTENSIONS_SUCCESS: "REQUEST_EXTENSIONS_SUCCESS",
   REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE",
   REQUEST_TABS_START: "REQUEST_TABS_START",
   REQUEST_TABS_SUCCESS: "REQUEST_TABS_SUCCESS",
   REQUEST_WORKERS_FAILURE: "REQUEST_WORKERS_FAILURE",
   REQUEST_WORKERS_START: "REQUEST_WORKERS_START",
   REQUEST_WORKERS_SUCCESS: "REQUEST_WORKERS_SUCCESS",
+  TEMPORARY_EXTENSION_INSTALL_FAILURE: "TEMPORARY_EXTENSION_INSTALL_FAILURE",
+  TEMPORARY_EXTENSION_INSTALL_START: "TEMPORARY_EXTENSION_INSTALL_START",
+  TEMPORARY_EXTENSION_INSTALL_SUCCESS: "TEMPORARY_EXTENSION_INSTALL_SUCCESS",
   UNWATCH_RUNTIME_FAILURE: "UNWATCH_RUNTIME_FAILURE",
   UNWATCH_RUNTIME_START: "UNWATCH_RUNTIME_START",
   UNWATCH_RUNTIME_SUCCESS: "UNWATCH_RUNTIME_SUCCESS",
   UPDATE_CONNECTION_PROMPT_SETTING_FAILURE: "UPDATE_CONNECTION_PROMPT_SETTING_FAILURE",
   UPDATE_CONNECTION_PROMPT_SETTING_START: "UPDATE_CONNECTION_PROMPT_SETTING_START",
   UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS: "UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS",
   USB_RUNTIMES_SCAN_START: "USB_RUNTIMES_SCAN_START",
   USB_RUNTIMES_SCAN_SUCCESS: "USB_RUNTIMES_SCAN_SUCCESS",
--- a/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/ui-state.js
@@ -4,31 +4,34 @@
 
 "use strict";
 
 const {
   ADB_ADDON_STATUS_UPDATED,
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
   NETWORK_LOCATIONS_UPDATED,
   PAGE_SELECTED,
+  TEMPORARY_EXTENSION_INSTALL_FAILURE,
+  TEMPORARY_EXTENSION_INSTALL_SUCCESS,
   USB_RUNTIMES_SCAN_START,
   USB_RUNTIMES_SCAN_SUCCESS,
 } = require("../constants");
 
 function UiState(locations = [], debugTargetCollapsibilities = {},
                  networkEnabled = false, wifiEnabled = false, showSystemAddons = false) {
   return {
     adbAddonStatus: null,
     debugTargetCollapsibilities,
     isScanningUsb: false,
     networkEnabled,
     networkLocations: locations,
     selectedPage: null,
     selectedRuntime: null,
     showSystemAddons,
+    temporaryInstallError: null,
     wifiEnabled,
   };
 }
 
 function uiReducer(state = UiState(), action) {
   switch (action.type) {
     case ADB_ADDON_STATUS_UPDATED: {
       const { adbAddonStatus } = action;
@@ -56,16 +59,25 @@ function uiReducer(state = UiState(), ac
     case USB_RUNTIMES_SCAN_START: {
       return Object.assign({}, state, { isScanningUsb: true });
     }
 
     case USB_RUNTIMES_SCAN_SUCCESS: {
       return Object.assign({}, state, { isScanningUsb: false });
     }
 
+    case TEMPORARY_EXTENSION_INSTALL_SUCCESS: {
+      return Object.assign({}, state, { temporaryInstallError: null });
+    }
+
+    case TEMPORARY_EXTENSION_INSTALL_FAILURE: {
+      const { error } = action;
+      return Object.assign({}, state, { temporaryInstallError: error.message });
+    }
+
     default:
       return state;
   }
 }
 
 module.exports = {
   UiState,
   uiReducer,
--- a/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
+++ b/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
@@ -114,16 +114,19 @@ about-debugging-debug-target-list-empty 
 # button will open a DevTools toolbox that will allow inspecting the target.
 # A target can be an addon, a tab, a worker...
 about-debugging-debug-target-inspect-button = Inspect
 
 # Text of a button displayed in the "This Firefox" page, in the Temporary Extension
 # section. Clicking on the button will open a file picker to load a temporary extension
 about-debugging-tmp-extension-install-button = Load Temporary Add-on…
 
+# Text displayed when trying to install a temporary extension in the "This Firefox" page.
+about-debugging-tmp-extension-install-error = There was an error during the temporary add-on installation.
+
 # Text of a button displayed for a temporary extension loaded in the "This Firefox" page.
 # Clicking on the button will reload the extension.
 about-debugging-tmp-extension-reload-button = Reload
 
 # Text of a button displayed for a temporary extension loaded in the "This Firefox" page.
 # Clicking on the button will uninstall the extension and remove it from the page.
 about-debugging-tmp-extension-remove-button = Remove