Bug 1405235 - Part 1: Implement a mechanism to show USB devices. r=jdescottes
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Fri, 21 Sep 2018 11:24:18 +0000
changeset 437651 8ac3f47057c433f5f49ca028414a2d724dbe7c41
parent 437650 c130b91ccc54849b61a6c3ecb0176decb9dcec61
child 437652 b380a630683149aa315a476b4adb31a248b4e7ef
push id69741
push userdakatsuka@mozilla.com
push dateFri, 21 Sep 2018 11:24:53 +0000
treeherderautoland@b380a6306831 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1405235
milestone64.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 1405235 - Part 1: Implement a mechanism to show USB devices. r=jdescottes Depends on D5386 Differential Revision: https://phabricator.services.mozilla.com/D5647
devtools/client/aboutdebugging-new/aboutdebugging.js
devtools/client/aboutdebugging-new/src/actions/runtimes.js
devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
devtools/client/aboutdebugging-new/src/constants.js
devtools/client/aboutdebugging-new/src/modules/moz.build
devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
devtools/client/aboutdebugging-new/test/browser/browser.ini
devtools/client/aboutdebugging-new/test/browser/debug-target-pane_collapsibilities_head.js
--- a/devtools/client/aboutdebugging-new/aboutdebugging.js
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.js
@@ -21,45 +21,55 @@ const { configureStore } = require("./sr
 const {
   setDebugTargetCollapsibilities,
 } = require("./src/modules/debug-target-collapsibilities");
 const {
   addNetworkLocationsObserver,
   getNetworkLocations,
   removeNetworkLocationsObserver,
 } = require("./src/modules/network-locations");
+const {
+  addUSBRuntimesObserver,
+  disableUSBRuntimes,
+  enableUSBRuntimes,
+  getUSBRuntimes,
+  removeUSBRuntimesObserver,
+} = require("./src/modules/usb-runtimes");
 
 const App = createFactory(require("./src/components/App"));
 
 const { PAGES, RUNTIMES } = require("./src/constants");
 
 const AboutDebugging = {
   async init() {
     if (!Services.prefs.getBoolPref("devtools.enabled", true)) {
       // If DevTools are disabled, navigate to about:devtools.
       window.location = "about:devtools?reason=AboutDebugging";
       return;
     }
 
     this.onNetworkLocationsUpdated = this.onNetworkLocationsUpdated.bind(this);
+    this.onUSBRuntimesUpdated = this.onUSBRuntimesUpdated.bind(this);
 
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
     const messageContexts = await this.createMessageContexts();
 
     render(
       Provider({ store: this.store }, App({ messageContexts })),
       this.mount
     );
 
     this.actions.selectPage(PAGES.THIS_FIREFOX, RUNTIMES.THIS_FIREFOX);
     this.actions.updateNetworkLocations(getNetworkLocations());
 
     addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
+    addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
+    await enableUSBRuntimes();
   },
 
   async createMessageContexts() {
     // XXX Until the strings for the updated about:debugging stabilize, we
     // locate them outside the regular directory for locale resources so that
     // they don't get picked up by localization tools.
     if (!L10nRegistry.sources.has("aboutdebugging")) {
       const temporarySource = new FileSource(
@@ -81,27 +91,33 @@ const AboutDebugging = {
 
     return contexts;
   },
 
   onNetworkLocationsUpdated() {
     this.actions.updateNetworkLocations(getNetworkLocations());
   },
 
+  onUSBRuntimesUpdated() {
+    this.actions.updateUSBRuntimes(getUSBRuntimes());
+  },
+
   async destroy() {
     const state = this.store.getState();
 
     L10nRegistry.removeSource("aboutdebugging");
 
     const currentRuntimeId = state.runtimes.selectedRuntimeId;
     if (currentRuntimeId) {
       await this.actions.unwatchRuntime(currentRuntimeId);
     }
 
     removeNetworkLocationsObserver(this.onNetworkLocationsUpdated);
+    removeUSBRuntimesObserver(this.onUSBRuntimesUpdated);
+    disableUSBRuntimes();
     setDebugTargetCollapsibilities(state.ui.debugTargetCollapsibilities);
     unmountComponentAtNode(this.mount);
   },
 
   get mount() {
     return document.getElementById("mount");
   },
 };
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -19,16 +19,17 @@ const {
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_FAILURE,
   DISCONNECT_RUNTIME_START,
   DISCONNECT_RUNTIME_SUCCESS,
   RUNTIMES,
   UNWATCH_RUNTIME_FAILURE,
   UNWATCH_RUNTIME_START,
   UNWATCH_RUNTIME_SUCCESS,
+  USB_RUNTIMES_UPDATED,
   WATCH_RUNTIME_FAILURE,
   WATCH_RUNTIME_START,
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
 async function createLocalClient() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
@@ -136,14 +137,19 @@ function unwatchRuntime(id) {
 
       dispatch({ type: UNWATCH_RUNTIME_SUCCESS });
     } catch (e) {
       dispatch({ type: UNWATCH_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
+function updateUSBRuntimes(runtimes) {
+  return { type: USB_RUNTIMES_UPDATED, runtimes };
+}
+
 module.exports = {
   connectRuntime,
   disconnectRuntime,
   unwatchRuntime,
+  updateUSBRuntimes,
   watchRuntime,
 };
--- a/devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
+++ b/devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
@@ -25,50 +25,53 @@ class Sidebar extends PureComponent {
       dispatch: PropTypes.func.isRequired,
       runtimes: PropTypes.array.isRequired,
       selectedPage: PropTypes.string.isRequired,
     };
   }
 
   renderDevices() {
     const { dispatch, runtimes, selectedPage } = this.props;
-    if (!runtimes.networkRuntimes.length) {
+    if (!runtimes.networkRuntimes.length && !runtimes.usbRuntimes.length) {
       return Localized(
         {
           id: "about-debugging-sidebar-no-devices"
         }, dom.span(
           {
             className: "sidebar__devices__no-devices-message"
           },
           "No devices discovered"
         )
       );
     }
 
-    return runtimes.networkRuntimes.map(runtime => {
-      const pageId = "networklocation-" + runtime.id;
-      const runtimeHasClient = !!runtime.client;
+    return [
+      ...runtimes.networkRuntimes.map(runtime => {
+        const pageId = "networklocation-" + runtime.id;
+        const runtimeHasClient = !!runtime.client;
 
-      const connectComponent = DeviceSidebarItemAction({
-        connected: runtimeHasClient,
-        dispatch,
-        runtimeId: runtime.id,
-      });
+        const connectComponent = DeviceSidebarItemAction({
+          connected: runtimeHasClient,
+          dispatch,
+          runtimeId: runtime.id,
+        });
 
-      return SidebarItem({
-        connectComponent,
-        id: pageId,
-        dispatch,
-        icon: GLOBE_ICON,
-        isSelected: selectedPage === pageId,
-        name: runtime.id,
-        runtimeId: runtime.id,
-        selectable: runtimeHasClient,
-      });
-    });
+        return SidebarItem({
+          connectComponent,
+          id: pageId,
+          dispatch,
+          icon: GLOBE_ICON,
+          isSelected: selectedPage === pageId,
+          name: runtime.id,
+          runtimeId: runtime.id,
+          selectable: runtimeHasClient,
+        });
+      }),
+      ...runtimes.usbRuntimes.map(runtime => dom.li({}, runtime.name)),
+    ];
   }
 
   render() {
     const { dispatch, selectedPage } = this.props;
 
     return dom.aside(
       {
         className: "sidebar",
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -21,16 +21,17 @@ const actionTypes = {
   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",
   UNWATCH_RUNTIME_FAILURE: "UNWATCH_RUNTIME_FAILURE",
   UNWATCH_RUNTIME_START: "UNWATCH_RUNTIME_START",
   UNWATCH_RUNTIME_SUCCESS: "UNWATCH_RUNTIME_SUCCESS",
+  USB_RUNTIMES_UPDATED: "USB_RUNTIMES_UPDATED",
   WATCH_RUNTIME_FAILURE: "WATCH_RUNTIME_FAILURE",
   WATCH_RUNTIME_START: "WATCH_RUNTIME_START",
   WATCH_RUNTIME_SUCCESS: "WATCH_RUNTIME_SUCCESS",
 };
 
 const DEBUG_TARGETS = {
   EXTENSION: "EXTENSION",
   TAB: "TAB",
--- a/devtools/client/aboutdebugging-new/src/modules/moz.build
+++ b/devtools/client/aboutdebugging-new/src/modules/moz.build
@@ -2,9 +2,10 @@
 # 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(
     'debug-target-collapsibilities.js',
     'extensions-helper.js',
     'network-locations.js',
     'runtimes-state-helper.js',
+    'usb-runtimes.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
@@ -0,0 +1,44 @@
+/* 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 { check } = require("devtools/shared/adb/adb-running-checker");
+const { ADBScanner } = require("devtools/shared/adb/adb-scanner");
+const { GetAvailableAddons } = require("devtools/client/webide/modules/addons");
+
+/**
+ * This module provides a collection of helper methods to detect USB runtimes whom Firefox
+ * is running on.
+ */
+function addUSBRuntimesObserver(listener) {
+  ADBScanner.on("runtime-list-updated", listener);
+}
+exports.addUSBRuntimesObserver = addUSBRuntimesObserver;
+
+function disableUSBRuntimes() {
+  ADBScanner.disable();
+}
+exports.disableUSBRuntimes = disableUSBRuntimes;
+
+async function enableUSBRuntimes() {
+  const { adb } = GetAvailableAddons();
+  if (adb.status === "uninstalled" || !(await check())) {
+    console.error("ADB extension is not installed");
+    return;
+  }
+
+  ADBScanner.enable();
+}
+exports.enableUSBRuntimes = enableUSBRuntimes;
+
+function getUSBRuntimes() {
+  return ADBScanner.listRuntimes();
+}
+exports.getUSBRuntimes = getUSBRuntimes;
+
+function removeUSBRuntimesObserver(listener) {
+  ADBScanner.off("runtime-list-updated", listener);
+}
+exports.removeUSBRuntimesObserver = removeUSBRuntimesObserver;
--- a/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
@@ -5,16 +5,17 @@
 "use strict";
 
 const {
   CONNECT_RUNTIME_SUCCESS,
   DISCONNECT_RUNTIME_SUCCESS,
   NETWORK_LOCATIONS_UPDATED,
   RUNTIMES,
   UNWATCH_RUNTIME_SUCCESS,
+  USB_RUNTIMES_UPDATED,
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
 const {
   findRuntimeById,
 } = require("devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper");
 
 // Map between known runtime types and nodes in the runtimes state.
@@ -25,17 +26,18 @@ const TYPE_TO_RUNTIMES_KEY = {
 
 function RuntimesState(networkRuntimes = []) {
   return {
     networkRuntimes,
     selectedRuntimeId: null,
     thisFirefoxRuntimes: [{
       id: RUNTIMES.THIS_FIREFOX,
       type: RUNTIMES.THIS_FIREFOX,
-    }]
+    }],
+    usbRuntimes: [],
   };
 }
 
 function runtimesReducer(state = RuntimesState(), action) {
   switch (action.type) {
     case CONNECT_RUNTIME_SUCCESS: {
       const { id, client } = action.runtime;
 
@@ -82,16 +84,21 @@ function runtimesReducer(state = Runtime
       });
       return Object.assign({}, state, { networkRuntimes });
     }
 
     case UNWATCH_RUNTIME_SUCCESS: {
       return Object.assign({}, state, { selectedRuntimeId: null });
     }
 
+    case USB_RUNTIMES_UPDATED: {
+      const { runtimes } = action;
+      return Object.assign({}, state, { usbRuntimes: runtimes });
+    }
+
     case WATCH_RUNTIME_SUCCESS: {
       const { id } = action.runtime;
       return Object.assign({}, state, { selectedRuntimeId: id });
     }
 
     default:
       return state;
   }
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -1,13 +1,17 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   debug-target-pane_collapsibilities_head.js
   head.js
+  ../../../webide/test/addons/adb-extension-linux.xpi
+  ../../../webide/test/addons/adb-extension-linux64.xpi
+  ../../../webide/test/addons/adb-extension-mac64.xpi
+  ../../../webide/test/addons/adb-extension-win32.xpi
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js]
 [browser_aboutdebugging_debug-target-pane_collapsibilities_preference.js]
 [browser_aboutdebugging_thisfirefox.js]
 [browser_aboutdebugging_connect_networklocations.js]
--- a/devtools/client/aboutdebugging-new/test/browser/debug-target-pane_collapsibilities_head.js
+++ b/devtools/client/aboutdebugging-new/test/browser/debug-target-pane_collapsibilities_head.js
@@ -41,27 +41,27 @@ function getDebugTargetPane(title, docum
     return titleEl.closest(".js-debug-target-pane");
   }
 
   throw new Error(`DebugTargetPane for [${ title }] was not found`);
 }
 
 // eslint-disable-next-line no-unused-vars
 function prepareCollapsibilitiesTest() {
-  registerCleanupFunction(() => {
-    for (const { pref } of TARGET_PANES) {
-      Services.prefs.clearUserPref(pref);
-    }
-  });
-
   // Make all collapsibilities to be expanded.
   for (const { pref } of TARGET_PANES) {
     Services.prefs.setBoolPref(pref, false);
   }
 }
 
 // eslint-disable-next-line no-unused-vars
 async function toggleCollapsibility(debugTargetPane) {
   debugTargetPane.querySelector(".js-debug-target-pane-title").click();
   // Wait for animation of collapse/expand.
   const animations = debugTargetPane.ownerDocument.getAnimations();
   await Promise.all(animations.map(animation => animation.finished));
 }
+
+registerCleanupFunction(() => {
+  for (const { pref } of TARGET_PANES) {
+    Services.prefs.clearUserPref(pref);
+  }
+});