Bug 1477598 - Part 2: Implement UI for extensions. r=jdescottes
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Fri, 10 Aug 2018 16:04:47 +0900
changeset 431171 72e1e5b24aaa8de32c254d19b68b70412414819d
parent 431170 a6ccfdee5d73e4acb85fdbfee7d78007ef659b82
child 431172 7e20f49730d2f249fa382479ec2a58f33bf91373
push id106369
push userdakatsuka@mozilla.com
push dateMon, 13 Aug 2018 04:26:08 +0000
treeherdermozilla-inbound@f4bbda249bee [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1477598
milestone63.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 1477598 - Part 2: Implement UI for extensions. r=jdescottes Differential Revision: https://phabricator.services.mozilla.com/D3084
devtools/client/aboutdebugging-new/aboutdebugging.css
devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
devtools/client/aboutdebugging-new/src/components/RuntimePage.js
devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css
devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.js
devtools/client/aboutdebugging-new/src/components/debugtarget/TabDetail.js
devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
devtools/client/aboutdebugging-new/src/components/moz.build
devtools/client/aboutdebugging-new/src/constants.js
devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -1,29 +1,36 @@
 /* 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/. */
 
 @import "chrome://global/skin/in-content/common.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/ConnectPage.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetItem.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetList.css";
-@import "resource://devtools/client/aboutdebugging-new/src/components/ConnectPage.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/Sidebar.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/SidebarItem.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/TabDetail.css";
 
 :root {
   /* Import css variables from common.css */
   --text-color: var(--in-content-page-color);
 }
 
 html, body {
   margin: 0;
   padding: 0;
   color: var(--text-color);
 }
 
+dd {
+  margin: 0;
+  padding: 0;
+}
+
 ul {
   list-style: none;
   margin: 0;
   padding: 0;
 }
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
+++ b/devtools/client/aboutdebugging-new/src/components/DebugTargetItem.js
@@ -1,65 +1,76 @@
 /* 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 { 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 ExtensionDetail = createFactory(require("./debugtarget/ExtensionDetail"));
+const TabDetail = createFactory(require("./debugtarget/TabDetail"));
+
 const Actions = require("../actions/index");
+const { DEBUG_TARGETS } = require("../constants");
 
 /**
  * This component displays debug target.
  */
 class DebugTargetItem extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
-      icon: PropTypes.string.isRequired,
-      id: PropTypes.string.isRequired,
-      name: PropTypes.string.isRequired,
-      type: PropTypes.string.isRequired,
-      url: PropTypes.string.isRequired,
+      target: PropTypes.object.isRequired,
     };
   }
 
   inspect() {
-    const { dispatch, type, id } = this.props;
-    dispatch(Actions.inspectDebugTarget(type, id));
+    const { dispatch, target } = this.props;
+    dispatch(Actions.inspectDebugTarget(target.type, target.id));
+  }
+
+  renderDetail() {
+    const { target } = this.props;
+
+    switch (target.type) {
+      case DEBUG_TARGETS.EXTENSION:
+        return ExtensionDetail({ target });
+      case DEBUG_TARGETS.TAB:
+        return TabDetail({ target });
+
+      default:
+        return null;
+    }
   }
 
   renderIcon() {
     return dom.img({
       className: "debug-target-item__icon",
-      src: this.props.icon,
+      src: this.props.target.icon,
     });
   }
 
   renderInfo() {
+    const { target } = this.props;
+
     return dom.div(
       {
         className: "debug-target-item__info",
       },
       dom.div(
         {
           className: "debug-target-item__info__name ellipsis-text",
-          title: this.props.name,
+          title: target.name,
         },
-        this.props.name
+        target.name
       ),
-      dom.div(
-        {
-          className: "debug-target-item__info__url ellipsis-text",
-        },
-        this.props.url
-      ),
+      this.renderDetail(),
     );
   }
 
   renderInspectButton() {
     return dom.button(
       {
         onClick: e => this.inspect(),
         className: "debug-target-item__inspect-button",
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
+++ b/devtools/client/aboutdebugging-new/src/components/DebugTargetList.js
@@ -12,34 +12,25 @@ const DebugTargetItem = createFactory(re
 
 /**
  * This component displays list of debug target.
  */
 class DebugTargetList extends PureComponent {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
-      targets: PropTypes.arrayOf(PropTypes.Object).isRequired,
+      targets: PropTypes.arrayOf(PropTypes.object).isRequired,
     };
   }
 
   render() {
     const { dispatch, targets } = this.props;
 
     return dom.ul(
       {
         className: "debug-target-list",
       },
-      targets.map(target =>
-        DebugTargetItem({
-          dispatch,
-          icon: target.icon,
-          id: target.id,
-          name: target.name,
-          type: target.type,
-          url: target.url,
-        })
-      ),
+      targets.map(target => DebugTargetItem({ dispatch, target })),
     );
   }
 }
 
 module.exports = DebugTargetList;
--- a/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
+++ b/devtools/client/aboutdebugging-new/src/components/RuntimePage.js
@@ -33,28 +33,29 @@ class RuntimePage extends PureComponent 
       },
       RuntimeInfo({
         icon: "chrome://branding/content/icon64.png",
         name: Services.appinfo.name,
         version: Services.appinfo.version,
       }),
       DebugTargetPane({
         dispatch,
+        name: "Temporary Extensions",
+        targets: temporaryExtensions,
+      }),
+      DebugTargetPane({
+        dispatch,
+        name: "Extensions",
+        targets: installedExtensions,
+      }),
+      DebugTargetPane({
+        dispatch,
         name: "Tabs",
         targets: tabs
       }),
-      // Temporary implementation
-      dom.ul(
-        {},
-        temporaryExtensions.map(e => dom.li({}, e.name))
-      ),
-      dom.ul(
-        {},
-        installedExtensions.map(e => dom.li({}, e.name))
-      ),
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
     installedExtensions: state.runtime.installedExtensions,
     tabs: state.runtime.tabs,
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css
@@ -0,0 +1,25 @@
+/* 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/. */
+
+/*
+ * The current layout of extension detail is
+ *
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  | (120px)        | (auto)             |
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  +----------------+--------------------+
+ *  | detail name dt | detail value dd    |
+ *  +----------------+--------------------+
+ */
+.extension-detail {
+  display: grid;
+  grid-template-columns: 120px auto;
+  margin-block-start: 4px;
+}
+
+.extension-detail__manifest {
+  margin-inline-start: 1ch;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.js
@@ -0,0 +1,68 @@
+/* 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 displays detail information for extension.
+ */
+class ExtensionDetail extends PureComponent {
+  static get propTypes() {
+    return {
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  renderField(name, value, title) {
+    return [
+      dom.dt({}, name),
+      dom.dd(
+        {
+          className: "ellipsis-text",
+          title: title || value,
+        },
+        value,
+      ),
+    ];
+  }
+
+  renderUUID() {
+    const { target } = this.props;
+    const { manifestURL, uuid } = target;
+
+    const value = [
+      uuid,
+      dom.a(
+        {
+          className: "extension-detail__manifest",
+          href: manifestURL,
+          target: "_blank",
+        },
+        "Manifest URL",
+      )
+    ];
+
+    return this.renderField("Internal UUID", value, uuid);
+  }
+
+  render() {
+    const { target } = this.props;
+    const { id, location, uuid } = target;
+
+    return dom.dl(
+      {
+        className: "extension-detail",
+      },
+      location ? this.renderField("Location", location) : null,
+      this.renderField("Extension ID", id),
+      uuid ? this.renderUUID() : null,
+    );
+  }
+}
+
+module.exports = ExtensionDetail;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/TabDetail.js
@@ -0,0 +1,26 @@
+/* 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 displays detail information for tab.
+ */
+class TabDetail extends PureComponent {
+  static get propTypes() {
+    return {
+      target: PropTypes.object.isRequired,
+    };
+  }
+
+  render() {
+    return dom.div({ className: "ellipsis-text" }, this.props.target.url);
+  }
+}
+
+module.exports = TabDetail;
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
@@ -0,0 +1,9 @@
+# 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(
+    'ExtensionDetail.css',
+    'ExtensionDetail.js',
+    'TabDetail.js',
+)
--- a/devtools/client/aboutdebugging-new/src/components/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/moz.build
@@ -1,12 +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 += [
+    'debugtarget',
+]
+
 DevToolsModules(
     'App.css',
     'App.js',
     'ConnectPage.css',
     'ConnectPage.js',
     'DebugTargetItem.css',
     'DebugTargetItem.js',
     'DebugTargetList.css',
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -16,16 +16,17 @@ const actionTypes = {
   REQUEST_EXTENSIONS_SUCCESS: "REQUEST_EXTENSIONS_SUCCESS",
   REQUEST_TABS_FAILURE: "REQUEST_TABS_FAILURE",
   REQUEST_TABS_START: "REQUEST_TABS_START",
   REQUEST_TABS_SUCCESS: "REQUEST_TABS_SUCCESS",
   PAGE_SELECTED: "PAGE_SELECTED",
 };
 
 const DEBUG_TARGETS = {
+  EXTENSION: "EXTENSION",
   TAB: "TAB",
 };
 
 const PAGES = {
   THIS_FIREFOX: "this-firefox",
   CONNECT: "connect",
 };
 
--- a/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtime-state.js
@@ -27,28 +27,62 @@ function runtimeReducer(state = RuntimeS
       const { client } = action;
       return Object.assign({}, state, { client });
     }
     case DISCONNECT_RUNTIME_SUCCESS: {
       return RuntimeState();
     }
     case REQUEST_EXTENSIONS_SUCCESS: {
       const { installedExtensions, temporaryExtensions } = action;
-      return Object.assign({}, state, { installedExtensions, temporaryExtensions });
+      return Object.assign({}, state, {
+        installedExtensions: toExtensionComponentData(installedExtensions),
+        temporaryExtensions: toExtensionComponentData(temporaryExtensions),
+      });
     }
     case REQUEST_TABS_SUCCESS: {
       const { tabs } = action;
       return Object.assign({}, state, { tabs: toTabComponentData(tabs) });
     }
 
     default:
       return state;
   }
 }
 
+function getExtensionFilePath(extension) {
+  // Only show file system paths, and only for temporarily installed add-ons.
+  if (!extension.temporarilyInstalled ||
+      !extension.url ||
+      !extension.url.startsWith("file://")) {
+    return null;
+  }
+
+  // Strip a leading slash from Windows drive letter URIs.
+  // file:///home/foo ~> /home/foo
+  // file:///C:/foo ~> C:/foo
+  const windowsRegex = /^file:\/\/\/([a-zA-Z]:\/.*)/;
+
+  if (windowsRegex.test(extension.url)) {
+    return windowsRegex.exec(extension.url)[1];
+  }
+
+  return extension.url.slice("file://".length);
+}
+
+function toExtensionComponentData(extensions) {
+  return extensions.map(extension => {
+    const type = DEBUG_TARGETS.EXTENSION;
+    const { iconURL, id, manifestURL, name } = extension;
+    const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+    const location = getExtensionFilePath(extension);
+    const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
+    return { type, id, icon, location, manifestURL, name, uuid };
+  });
+}
+
 function toTabComponentData(tabs) {
   return tabs.map(tab => {
     const type = DEBUG_TARGETS.TAB;
     const id = tab.outerWindowID;
     const icon = tab.favicon
       ? `data:image/png;base64,${ btoa(String.fromCharCode.apply(String, tab.favicon)) }`
       : "chrome://devtools/skin/images/globe.svg";
     const name = tab.title || tab.url;