Bug 1471795 - Part 10: Implement extension list. r?jdescottes, r?ladybenko draft
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 19 Jul 2018 17:17:52 +0900
changeset 820259 2848a3b8240f3573c15c4244a4c3c610dd01e752
parent 820258 7d7a5c8aca027013c2cc42b030dcb80e28156f3e
child 820260 6f2906372b33ac92277c219b143bb434dad1747a
push id116772
push userbmo:dakatsuka@mozilla.com
push dateThu, 19 Jul 2018 09:51:56 +0000
reviewersjdescottes, ladybenko
bugs1471795
milestone63.0a1
Bug 1471795 - Part 10: Implement extension list. r?jdescottes, r?ladybenko MozReview-Commit-ID: IDQhQgtBUMA
devtools/client/aboutdebugging-new/aboutdebugging.css
devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionItem.css
devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionItem.js
devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
devtools/client/aboutdebugging-new/src/runtimes/runtime.js
devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -5,14 +5,15 @@
 @import "chrome://global/skin/in-content/common.css";
 @import "resource://devtools/client/themes/variables.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/RuntimesPane.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/ExtensionItem.css";
 @import "resource://devtools/client/aboutdebugging-new/src/components/runtime/ThisFirefoxItem.css";
 
 #mount {
   height: 100%;
   width: 100%;
 }
--- a/devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
+++ b/devtools/client/aboutdebugging-new/src/components/DebugTargetsPane.js
@@ -6,16 +6,17 @@
 
 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 Runtime = require("../runtimes/runtime");
 
 const DebugTargetList = createFactory(require("./debugtarget/DebugTargetList"));
+const ExtensionItem = createFactory(require("./debugtarget/ExtensionItem"));
 const RuntimeInfo = createFactory(require("./RuntimeInfo"));
 const TabItem = createFactory(require("./debugtarget/TabItem"));
 
 class DebugTargetsPane extends PureComponent {
   static get propTypes() {
     return {
       runtime: PropTypes.instanceOf(Runtime).isRequired,
     };
@@ -23,17 +24,19 @@ class DebugTargetsPane extends PureCompo
 
   constructor(props) {
     super(props);
 
     this.onTabsUpdated = this.onTabsUpdated.bind(this);
 
     this.state = {
       info: {},
+      installedExtensions: [],
       tabs: [],
+      temporaryExtensions: [],
     };
     this.update(props.runtime);
   }
 
   componentDidUpdate(prevProps) {
     if (prevProps.runtime !== this.props.runtime) {
       prevProps.runtime.removeTabsUpdateListener(this.onTabsUpdated);
       this.update(this.props.runtime);
@@ -42,20 +45,28 @@ class DebugTargetsPane extends PureCompo
 
   componentWillUnmount() {
     this.props.runtime.removeTabsUpdateListener(this.onTabsUpdated);
   }
 
   update(runtime) {
     this.updateRuntimeInfo(runtime);
     this.updateTabs(runtime);
+    this.updateExtensions(runtime);
 
     runtime.addTabsUpdateListener(this.onTabsUpdated);
   }
 
+  async updateExtensions(runtime) {
+    const extensions = (await runtime.getExtensions()).filter(t => t.debuggable);
+    const installedExtensions = extensions.filter(t => !t.temporarilyInstalled);
+    const temporaryExtensions = extensions.filter(t => t.temporarilyInstalled);
+    this.setState({ installedExtensions, temporaryExtensions });
+  }
+
   async updateRuntimeInfo(runtime) {
     const info = await runtime.getRuntimeInfo(runtime);
     this.setState({ info });
   }
 
   async updateTabs(runtime) {
     // Filter out closed tabs (represented as `null`).
     const tabs = (await runtime.getTabs()).filter(t => !!t);
@@ -65,25 +76,39 @@ class DebugTargetsPane extends PureCompo
   onTabsUpdated() {
     this.updateTabs(this.props.runtime);
   }
 
   render() {
     const { runtime } = this.props;
     const {
       info,
+      installedExtensions,
       tabs,
+      temporaryExtensions,
     } = this.state;
 
     return dom.div(
       {
         className: "debug-targets-pane",
       },
       RuntimeInfo({ info }),
       DebugTargetList({
+        className: "debug-target-list--temporary-extensions",
+        debugTargetItemComponent: ExtensionItem,
+        debugTargets: temporaryExtensions,
+        title: "Temporary Extensions",
+      }),
+      DebugTargetList({
+        className: "debug-target-list--installed-extensions",
+        debugTargetItemComponent: ExtensionItem,
+        debugTargets: installedExtensions,
+        title: "Extensions",
+      }),
+      DebugTargetList({
         className: "debug-target-list--tabs",
         debugTargetItemComponent: TabItem,
         debugTargets: tabs,
         runtime,
         title: "Tabs",
       })
     );
   }
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionItem.css
@@ -0,0 +1,19 @@
+/* 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/. */
+
+.debug-target-item__info__detail__extension {
+  display: grid;
+  grid-template-columns: 100px 1fr;
+  margin-block-start: 4px;
+}
+
+.debug-target-item__info__detail__extension__content {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.debug-target-item__info__detail__extension__content__manifest {
+  margin-inline-start: 1ch;
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionItem.js
@@ -0,0 +1,133 @@
+/* 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 dom = require("devtools/client/shared/vendor/react-dom-factories");
+
+const DebugTargetItem = require("./DebugTargetItem");
+
+class ExtensionItem extends DebugTargetItem {
+  getIcon() {
+    const { debugTarget } = this.props;
+    return debugTarget.iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
+  }
+
+  getName() {
+    const { debugTarget } = this.props;
+    return debugTarget.name;
+  }
+
+  parseFileUri(url) {
+    // 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(url)) {
+      return windowsRegex.exec(url)[1];
+    }
+
+    return url.slice("file://".length);
+  }
+
+  renderActionComponents() {
+    return null;
+  }
+
+  renderDetailComponents() {
+    return dom.div(
+      {
+        className: "debug-target-item__info__detail__extension",
+      },
+      this.renderFilePath(),
+      this.renderExtensionId(),
+      this.renderInternalId()
+    );
+  }
+
+  renderFilePath() {
+    const { debugTarget } = this.props;
+    // Only show file system paths, and only for temporarily installed add-ons.
+    if (!debugTarget.temporarilyInstalled ||
+        !debugTarget.url ||
+        !debugTarget.url.startsWith("file://")) {
+      return null;
+    }
+
+    const path = this.parseFileUri(debugTarget.url);
+
+    return [
+      dom.dt(
+        {
+          className: "debug-target-item__info__detail__extension__label",
+        },
+        "Location"
+      ),
+      dom.dd(
+        {
+          className: "debug-target-item__info__detail__extension__content",
+          title: path,
+        },
+        path,
+      ),
+    ];
+  }
+
+  renderExtensionId() {
+    const { debugTarget } = this.props;
+
+    return [
+      dom.dt(
+        {
+          className: "debug-target-item__info__detail__extension__label",
+        },
+        "Extension ID",
+      ),
+      dom.dd(
+        {
+          className: "debug-target-item__info__detail__extension__content",
+          title: debugTarget.id,
+        },
+        debugTarget.id
+      ),
+    ];
+  }
+
+  renderInternalId() {
+    const { debugTarget } = this.props;
+
+    if (!debugTarget.manifestURL) {
+      return [];
+    }
+
+    // Strip off the protocol and rest, leaving us with just the UUID.
+    const uuid = /moz-extension:\/\/([^/]*)/.exec(debugTarget.manifestURL)[1];
+    return [
+      dom.dt(
+        {
+          className: "debug-target-item__info__detail__extension__label",
+        },
+        "Internal UUID",
+      ),
+      dom.dd(
+        {
+          className: "debug-target-item__info__detail__extension__content",
+          title: uuid,
+        },
+        uuid,
+        dom.a(
+          {
+            className: "debug-target-item__info__detail__extension__content__manifest",
+            href: debugTarget.manifestURL,
+            target: "_blank",
+          },
+          "Manifest URL",
+        ),
+      ),
+    ];
+  }
+}
+
+module.exports = ExtensionItem;
--- a/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/debugtarget/moz.build
@@ -2,10 +2,12 @@
 # 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(
     'DebugTargetItem.css',
     'DebugTargetItem.js',
     'DebugTargetList.css',
     'DebugTargetList.js',
+    'ExtensionItem.css',
+    'ExtensionItem.js',
     'TabItem.js'
 )
--- a/devtools/client/aboutdebugging-new/src/runtimes/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/runtimes/runtime.js
@@ -21,16 +21,25 @@ class Runtime {
    * Connect to this runtime.
    * Subclass should override this method.
    */
   async connect() {
     throw new Error("Subclass of Runtime should override connect()");
   }
 
   /**
+   * Return extensions on this runtime.
+   * Subclass should override this method.
+   * @return {Array}
+   */
+  async getExtensions() {
+    throw new Error("Subclass of Runtime should override getExtensions()");
+  }
+
+  /**
    * Return this runtime's information.
    * Subclass should override this method.
    * @return {Object}
    *         {
    *           icon: URL of the icon which represents this runtime
    *                 e.g. chrome://branding/content/icon64.png
    *           name: Name of this runtime
    *                 e.g. Firefox
--- a/devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
+++ b/devtools/client/aboutdebugging-new/src/runtimes/this-firefox.js
@@ -23,16 +23,21 @@ class ThisFirefox extends Runtime {
     // Setup a server if we don't have one already running
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     const transport = DebuggerServer.connectPipe();
     this.client = new DebuggerClient(transport);
     return this.client.connect();
   }
 
+  async getExtensions() {
+    const { addons } = await this.client.listAddons();
+    return addons;
+  }
+
   async getRuntimeInfo() {
     return {
       icon: "chrome://branding/content/icon64.png",
       name: Services.appinfo.name,
       version: Services.appinfo.version,
     };
   }