Bug 1487078 - Reuse addon debugging helpers from aboutdebugging in new ui;r=daisuke
authorJulian Descottes <jdescottes@mozilla.com>
Fri, 31 Aug 2018 06:52:16 +0000
changeset 491946 bc09f31ad41fb40241111822c17e0ec0fbd00f28
parent 491945 e67f28fac303774e424dee53737744f3fb2bf77e
child 491947 c42178e6c20793d921d770010bb2f1bdd6b2f2c6
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaisuke
bugs1487078
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 1487078 - Reuse addon debugging helpers from aboutdebugging in new ui;r=daisuke Differential Revision: https://phabricator.services.mozilla.com/D4548
devtools/client/aboutdebugging-new/src/actions/runtime.js
devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js
devtools/client/aboutdebugging/components/addons/Controls.js
devtools/client/aboutdebugging/components/addons/Target.js
devtools/client/aboutdebugging/modules/addon.js
--- a/devtools/client/aboutdebugging-new/src/actions/runtime.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtime.js
@@ -1,23 +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 { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
-const { BrowserToolboxProcess } =
-  require("resource://devtools/client/framework/ToolboxProcess.jsm");
-const { Cc, Ci } = require("chrome");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const { DebuggerServer } = require("devtools/server/main");
 const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
 
 const {
+  debugLocalAddon,
+  openTemporaryExtension,
+  uninstallAddon,
+} = require("devtools/client/aboutdebugging/modules/addon");
+
+const {
   CONNECT_RUNTIME_FAILURE,
   CONNECT_RUNTIME_START,
   CONNECT_RUNTIME_SUCCESS,
   DEBUG_TARGETS,
   DISCONNECT_RUNTIME_FAILURE,
   DISCONNECT_RUNTIME_START,
   DISCONNECT_RUNTIME_SUCCESS,
   REQUEST_EXTENSIONS_FAILURE,
@@ -26,18 +29,16 @@ const {
   REQUEST_TABS_FAILURE,
   REQUEST_TABS_START,
   REQUEST_TABS_SUCCESS,
   REQUEST_WORKERS_FAILURE,
   REQUEST_WORKERS_START,
   REQUEST_WORKERS_SUCCESS,
 } = require("../constants");
 
-let browserToolboxProcess = null;
-
 function connectRuntime() {
   return async (dispatch, getState) => {
     dispatch({ type: CONNECT_RUNTIME_START });
 
     DebuggerServer.init();
     DebuggerServer.registerAllActors();
     const client = new DebuggerClient(DebuggerServer.connectPipe());
 
@@ -75,27 +76,17 @@ function inspectDebugTarget(type, id) {
   return async (_, getState) => {
     switch (type) {
       case DEBUG_TARGETS.TAB: {
         // Open tab debugger in new window.
         window.open(`about:devtools-toolbox?type=tab&id=${ id }`);
         break;
       }
       case DEBUG_TARGETS.EXTENSION: {
-        // Close current debugging toolbox and open a new one.
-        if (browserToolboxProcess) {
-          browserToolboxProcess.close();
-        }
-
-        browserToolboxProcess = BrowserToolboxProcess.init({
-          addonID: id,
-          onClose: () => {
-            browserToolboxProcess = null;
-          }
-        });
+        debugLocalAddon(id);
         break;
       }
       case DEBUG_TARGETS.WORKER: {
         // Open worker toolbox in new window.
         gDevToolsBrowser.openWorkerToolbox(getState().runtime.client, id);
         break;
       }
 
@@ -103,40 +94,25 @@ function inspectDebugTarget(type, id) {
         console.error("Failed to inspect the debug target of " +
                       `type: ${ type } id: ${ id }`);
       }
     }
   };
 }
 
 function installTemporaryExtension() {
-  const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-  fp.init(window, "Select Manifest File or Package (.xpi)", Ci.nsIFilePicker.modeOpen);
-  fp.open(async res => {
-    if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
-      return;
-    }
-
-    let file = fp.file;
-
-    // AddonManager.installTemporaryAddon accepts either
-    // addon directory or final xpi file.
-    if (!file.isDirectory() &&
-        !file.leafName.endsWith(".xpi") && !file.leafName.endsWith(".zip")) {
-      file = file.parent;
-    }
-
+  return async (dispatch, getState) => {
+    const message = "Select Manifest File or Package (.xpi)";
+    const file = await openTemporaryExtension(window, message);
     try {
       await AddonManager.installTemporaryAddon(file);
     } catch (e) {
       console.error(e);
     }
-  });
-
-  return () => {};
+  };
 }
 
 function pushServiceWorker(actor) {
   return async (_, getState) => {
     const client = getState().runtime.client;
 
     try {
       await client.request({ to: actor, type: "push" });
@@ -156,21 +132,17 @@ function reloadTemporaryExtension(actor)
       console.error(e);
     }
   };
 }
 
 function removeTemporaryExtension(id) {
   return async () => {
     try {
-      const addon = await AddonManager.getAddonByID(id);
-
-      if (addon) {
-        await addon.uninstall();
-      }
+      await uninstallAddon(id);
     } catch (e) {
       console.error(e);
     }
   };
 }
 
 function requestTabs() {
   return async (dispatch, getState) => {
--- a/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js
+++ b/devtools/client/aboutdebugging-new/src/middleware/extension-component-data.js
@@ -4,16 +4,21 @@
 
 "use strict";
 
 const {
   DEBUG_TARGETS,
   REQUEST_EXTENSIONS_SUCCESS,
 } = require("../constants");
 
+const {
+  getExtensionUuid,
+  parseFileUri
+} = require("devtools/client/aboutdebugging/modules/addon");
+
 /**
  * This middleware converts extensions object that get from DebuggerClient.listAddons()
  * to data which is used in DebugTargetItem.
  */
 const extensionComponentDataMiddleware = store => next => action => {
   switch (action.type) {
     case REQUEST_EXTENSIONS_SUCCESS: {
       action.installedExtensions = toComponentData(action.installedExtensions);
@@ -28,35 +33,26 @@ const extensionComponentDataMiddleware =
 function getFilePath(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);
+  return parseFileUri(extension.url);
 }
 
 function toComponentData(extensions) {
   return extensions.map(extension => {
     const type = DEBUG_TARGETS.EXTENSION;
     const { actor, iconURL, id, manifestURL, name } = extension;
     const icon = iconURL || "chrome://mozapps/skin/extensions/extensionGeneric.svg";
     const location = getFilePath(extension);
-    const uuid = manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
+    const uuid = getExtensionUuid(extension);
     return {
       name,
       icon,
       id,
       type,
       details: {
         actor,
         location,
--- a/devtools/client/aboutdebugging/components/addons/Controls.js
+++ b/devtools/client/aboutdebugging/components/addons/Controls.js
@@ -5,20 +5,20 @@
 /* eslint-env browser */
 /* globals AddonManager */
 
 "use strict";
 
 loader.lazyImporter(this, "AddonManager",
   "resource://gre/modules/AddonManager.jsm");
 
-const { Cc, Ci } = require("chrome");
 const { createFactory, Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { openTemporaryExtension } = require("devtools/client/aboutdebugging/modules/addon");
 const Services = require("Services");
 const AddonsInstallError = createFactory(require("./InstallError"));
 
 const Strings = Services.strings.createBundle(
   "chrome://devtools/locale/aboutdebugging.properties");
 
 const MORE_INFO_URL = "https://developer.mozilla.org/docs/Tools" +
                       "/about:debugging#Enabling_add-on_debugging";
@@ -44,36 +44,20 @@ class AddonsControls extends Component {
   }
 
   onEnableAddonDebuggingChange(event) {
     const enabled = event.target.checked;
     Services.prefs.setBoolPref("devtools.chrome.enabled", enabled);
     Services.prefs.setBoolPref("devtools.debugger.remote-enabled", enabled);
   }
 
-  loadAddonFromFile() {
-    this.setState({ installError: null });
-    const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-    fp.init(window,
-      Strings.GetStringFromName("selectAddonFromFile2"),
-      Ci.nsIFilePicker.modeOpen);
-    fp.open(res => {
-      if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
-        return;
-      }
-      let file = fp.file;
-      // AddonManager.installTemporaryAddon accepts either
-      // addon directory or final xpi file.
-      if (!file.isDirectory() &&
-          !file.leafName.endsWith(".xpi") && !file.leafName.endsWith(".zip")) {
-        file = file.parent;
-      }
-
-      this.installAddon(file);
-    });
+  async loadAddonFromFile() {
+    const message = Strings.GetStringFromName("selectAddonFromFile2");
+    const file = await openTemporaryExtension(window, message);
+    this.installAddon(file);
   }
 
   retryInstall() {
     this.setState({ installError: null });
     this.installAddon(this.state.lastInstallErrorFile);
   }
 
   installAddon(file) {
--- a/devtools/client/aboutdebugging/components/addons/Target.js
+++ b/devtools/client/aboutdebugging/components/addons/Target.js
@@ -7,16 +7,17 @@
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const {
   debugLocalAddon,
   debugRemoteAddon,
+  getExtensionUuid,
   isLegacyTemporaryExtension,
   isTemporaryID,
   parseFileUri,
   uninstallAddon
 } = require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
@@ -60,21 +61,21 @@ function addonIDforTarget(target) {
         { title: target.addonID },
         target.addonID
       )
     ),
   ];
 }
 
 function internalIDForTarget(target) {
-  if (!target.manifestURL) {
+  const uuid = getExtensionUuid(target);
+  if (!uuid) {
     return [];
   }
-  // Strip off the protocol and rest, leaving us with just the UUID.
-  const uuid = /moz-extension:\/\/([^/]*)/.exec(target.manifestURL)[1];
+
   return [
     dom.dt(
       { className: "addon-target-info-label" },
       Strings.GetStringFromName("internalUUID"),
     ),
     dom.dd(
       { className: "addon-target-info-content internal-uuid" },
       dom.span(
--- a/devtools/client/aboutdebugging/modules/addon.js
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -1,14 +1,15 @@
 /* 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 { Cc, Ci } = require("chrome");
 loader.lazyImporter(this, "BrowserToolboxProcess",
   "resource://devtools/client/framework/ToolboxProcess.jsm");
 loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 loader.lazyImporter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
 
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
@@ -67,16 +68,20 @@ exports.debugRemoteAddon = async functio
 
   const hostType = Toolbox.HostType.WINDOW;
   remoteAddonToolbox = await gDevTools.showToolbox(target, null, hostType);
   remoteAddonToolbox.once("destroy", () => {
     remoteAddonToolbox = null;
   });
 };
 
+/**
+ * Uninstall the addon with the provided id.
+ * Resolves when the addon shutdown has completed.
+ */
 exports.uninstallAddon = async function(addonID) {
   const addon = await AddonManager.getAddonByID(addonID);
   return addon && addon.uninstall();
 };
 
 exports.isTemporaryID = function(addonID) {
   return AddonManagerPrivate.isTemporaryInstallID(addonID);
 };
@@ -99,8 +104,52 @@ exports.parseFileUri = function(url) {
   // 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);
 };
+
+exports.getExtensionUuid = function(extension) {
+  const { manifestURL } = extension;
+  // Strip off the protocol and rest, leaving us with just the UUID.
+  return manifestURL ? /moz-extension:\/\/([^/]*)/.exec(manifestURL)[1] : null;
+};
+
+/**
+ * Open a file picker to allow the user to locate a temporary extension. A temporary
+ * extension can either be:
+ * - a folder
+ * - a .xpi file
+ * - a .zip file
+ *
+ * @param  {Window} win
+ *         The window object where the filepicker should be opened.
+ *         Note: We cannot use the global window object here because it is undefined if
+ *         this module is loaded from a file outside of devtools/client/aboutdebugging/.
+ *         See browser-loader.js `uri.startsWith(baseURI)` for more details.
+ * @param  {String} message
+ *         The help message that should be displayed to the user in the filepicker.
+ * @return {Promise} returns a promise that resolves a File object corresponding to the
+ *         file selected by the user.
+ */
+exports.openTemporaryExtension = function(win, message) {
+  return new Promise(resolve => {
+    const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+    fp.init(win, message, Ci.nsIFilePicker.modeOpen);
+    fp.open(res => {
+      if (res == Ci.nsIFilePicker.returnCancel || !fp.file) {
+        return;
+      }
+      let file = fp.file;
+      // AddonManager.installTemporaryAddon accepts either
+      // addon directory or final xpi file.
+      if (!file.isDirectory() &&
+          !file.leafName.endsWith(".xpi") && !file.leafName.endsWith(".zip")) {
+        file = file.parent;
+      }
+
+      resolve(file);
+    });
+  });
+};