Bug 1483497 - Allow to connect to network locations runtimes;r=daisuke,ladybenko
authorJulian Descottes <jdescottes@mozilla.com>
Thu, 06 Sep 2018 11:47:00 +0200
changeset 485005 0d62d337ff34f1ef8d21c85722d4272ac302ad2f
parent 485004 4b2013edb3c836aca2395e8d2e44e80cdbc66d86
child 485006 f90c1e080761485436620b02e3f8d99b2b20aebd
push id241
push userfmarier@mozilla.com
push dateMon, 24 Sep 2018 21:48:02 +0000
reviewersdaisuke, ladybenko
bugs1483497
milestone64.0a1
Bug 1483497 - Allow to connect to network locations runtimes;r=daisuke,ladybenko
devtools/client/aboutdebugging-new/src/actions/debug-targets.js
devtools/client/aboutdebugging-new/src/actions/runtimes.js
devtools/client/aboutdebugging-new/src/components/sidebar/DeviceSidebarItemAction.js
devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
devtools/client/aboutdebugging-new/src/constants.js
devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -39,17 +39,17 @@ function inspectDebugTarget(type, id) {
         break;
       }
       case DEBUG_TARGETS.EXTENSION: {
         debugLocalAddon(id);
         break;
       }
       case DEBUG_TARGETS.WORKER: {
         // Open worker toolbox in new window.
-        const client = getCurrentClient(getState());
+        const client = getCurrentClient(getState().runtimes);
         gDevToolsBrowser.openWorkerToolbox(client, id);
         break;
       }
 
       default: {
         console.error("Failed to inspect the debug target of " +
                       `type: ${ type } id: ${ id }`);
       }
@@ -66,29 +66,29 @@ function installTemporaryExtension() {
     } catch (e) {
       console.error(e);
     }
   };
 }
 
 function pushServiceWorker(actor) {
   return async (_, getState) => {
-    const client = getCurrentClient(getState());
+    const client = getCurrentClient(getState().runtimes);
 
     try {
       await client.request({ to: actor, type: "push" });
     } catch (e) {
       console.error(e);
     }
   };
 }
 
 function reloadTemporaryExtension(actor) {
   return async (_, getState) => {
-    const client = getCurrentClient(getState());
+    const client = getCurrentClient(getState().runtimes);
 
     try {
       await client.request({ to: actor, type: "reload" });
     } catch (e) {
       console.error(e);
     }
   };
 }
@@ -102,33 +102,33 @@ function removeTemporaryExtension(id) {
     }
   };
 }
 
 function requestTabs() {
   return async (dispatch, getState) => {
     dispatch({ type: REQUEST_TABS_START });
 
-    const client = getCurrentClient(getState());
+    const client = getCurrentClient(getState().runtimes);
 
     try {
       const { tabs } = await client.listTabs({ favicons: true });
 
       dispatch({ type: REQUEST_TABS_SUCCESS, tabs });
     } catch (e) {
       dispatch({ type: REQUEST_TABS_FAILURE, error: e.message });
     }
   };
 }
 
 function requestExtensions() {
   return async (dispatch, getState) => {
     dispatch({ type: REQUEST_EXTENSIONS_START });
 
-    const client = getCurrentClient(getState());
+    const client = getCurrentClient(getState().runtimes);
 
     try {
       const { addons } = await client.listAddons();
       const extensions = addons.filter(a => a.debuggable);
       const installedExtensions = extensions.filter(e => !e.temporarilyInstalled);
       const temporaryExtensions = extensions.filter(e => e.temporarilyInstalled);
 
       dispatch({
@@ -141,17 +141,17 @@ function requestExtensions() {
     }
   };
 }
 
 function requestWorkers() {
   return async (dispatch, getState) => {
     dispatch({ type: REQUEST_WORKERS_START });
 
-    const client = getCurrentClient(getState());
+    const client = getCurrentClient(getState().runtimes);
 
     try {
       const {
         other: otherWorkers,
         service: serviceWorkers,
         shared: sharedWorkers,
       } = await client.mainRoot.listAllWorkers();
 
@@ -164,17 +164,17 @@ function requestWorkers() {
     } catch (e) {
       dispatch({ type: REQUEST_WORKERS_FAILURE, error: e.message });
     }
   };
 }
 
 function startServiceWorker(actor) {
   return async (_, getState) => {
-    const client = getCurrentClient(getState());
+    const client = getCurrentClient(getState().runtimes);
 
     try {
       await client.request({ to: actor, type: "start" });
     } catch (e) {
       console.error(e);
     }
   };
 }
--- a/devtools/client/aboutdebugging-new/src/actions/runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/actions/runtimes.js
@@ -5,62 +5,145 @@
 "use strict";
 
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const { DebuggerServer } = require("devtools/server/main");
 
 const Actions = require("./index");
 
 const {
-  getCurrentClient
+  getCurrentClient,
+  findRuntimeById,
 } = require("devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper");
 
 const {
+  CONNECT_RUNTIME_FAILURE,
+  CONNECT_RUNTIME_START,
+  CONNECT_RUNTIME_SUCCESS,
+  DISCONNECT_RUNTIME_FAILURE,
+  DISCONNECT_RUNTIME_START,
+  DISCONNECT_RUNTIME_SUCCESS,
+  RUNTIMES,
   UNWATCH_RUNTIME_FAILURE,
   UNWATCH_RUNTIME_START,
   UNWATCH_RUNTIME_SUCCESS,
   WATCH_RUNTIME_FAILURE,
   WATCH_RUNTIME_START,
   WATCH_RUNTIME_SUCCESS,
 } = require("../constants");
 
+async function createLocalClient() {
+  DebuggerServer.init();
+  DebuggerServer.registerAllActors();
+  const client = new DebuggerClient(DebuggerServer.connectPipe());
+  await client.connect();
+  return client;
+}
+
+async function createNetworkClient(host, port) {
+  const transport = await DebuggerClient.socketConnect({ host, port });
+  const client = new DebuggerClient(transport);
+  await client.connect();
+  return client;
+}
+
+async function createClientForRuntime(id, type) {
+  if (type === RUNTIMES.THIS_FIREFOX) {
+    return createLocalClient();
+  } else if (type === RUNTIMES.NETWORK) {
+    const [host, port] = id.split(":");
+    return createNetworkClient(host, port);
+  }
+
+  return null;
+}
+
+function connectRuntime(id) {
+  return async (dispatch, getState) => {
+    dispatch({ type: CONNECT_RUNTIME_START });
+    try {
+      const runtime = findRuntimeById(id, getState().runtimes);
+      const client = await createClientForRuntime(id, runtime.type);
+
+      dispatch({
+        type: CONNECT_RUNTIME_SUCCESS,
+        runtime: {
+          id,
+          client,
+          type: runtime.type,
+        }
+      });
+    } catch (e) {
+      dispatch({ type: CONNECT_RUNTIME_FAILURE, error: e.message });
+    }
+  };
+}
+
+function disconnectRuntime(id) {
+  return async (dispatch, getState) => {
+    dispatch({ type: DISCONNECT_RUNTIME_START });
+    try {
+      const runtime = findRuntimeById(id, getState().runtimes);
+      const client = runtime.client;
+
+      await client.close();
+      DebuggerServer.destroy();
+
+      dispatch({
+        type: DISCONNECT_RUNTIME_SUCCESS,
+        runtime: {
+          id,
+          type: runtime.type,
+        }
+      });
+    } catch (e) {
+      dispatch({ type: DISCONNECT_RUNTIME_FAILURE, error: e.message });
+    }
+  };
+}
+
 function watchRuntime() {
   return async (dispatch, getState) => {
     dispatch({ type: WATCH_RUNTIME_START });
 
-    DebuggerServer.init();
-    DebuggerServer.registerAllActors();
-    const client = new DebuggerClient(DebuggerServer.connectPipe());
+    try {
+      // THIS_FIREFOX connects and disconnects on the fly when opening the page.
+      // XXX: watchRuntime is only called when opening THIS_FIREFOX page for now.
+      await dispatch(connectRuntime(RUNTIMES.THIS_FIREFOX));
 
-    try {
-      await client.connect();
+      const runtime = findRuntimeById(RUNTIMES.THIS_FIREFOX, getState().runtimes);
 
-      dispatch({ type: WATCH_RUNTIME_SUCCESS, client });
+      dispatch({ type: WATCH_RUNTIME_SUCCESS, client: runtime.client });
       dispatch(Actions.requestExtensions());
       dispatch(Actions.requestTabs());
       dispatch(Actions.requestWorkers());
     } catch (e) {
       dispatch({ type: WATCH_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 function unwatchRuntime() {
   return async (dispatch, getState) => {
-    const client = getCurrentClient(getState());
+    // XXX: unwatchRuntime is only called when opening THIS_FIREFOX page for now.
+    const runtime = findRuntimeById(RUNTIMES.THIS_FIREFOX, getState().runtimes);
+    const client = runtime.client;
 
     dispatch({ type: UNWATCH_RUNTIME_START, client });
 
     try {
-      await client.close();
-      DebuggerServer.destroy();
+      // THIS_FIREFOX connects and disconnects on the fly when opening the page.
+      // XXX: unwatchRuntime is only called when closing THIS_FIREFOX page for now.
+      await dispatch(disconnectRuntime(RUNTIMES.THIS_FIREFOX));
 
       dispatch({ type: UNWATCH_RUNTIME_SUCCESS });
     } catch (e) {
       dispatch({ type: UNWATCH_RUNTIME_FAILURE, error: e.message });
     }
   };
 }
 
 module.exports = {
-  watchRuntime,
+  connectRuntime,
+  disconnectRuntime,
   unwatchRuntime,
+  watchRuntime,
 };
--- a/devtools/client/aboutdebugging-new/src/components/sidebar/DeviceSidebarItemAction.js
+++ b/devtools/client/aboutdebugging-new/src/components/sidebar/DeviceSidebarItemAction.js
@@ -3,35 +3,42 @@
  * 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");
 
+const Actions = require("../../actions/index");
+
 /**
  * This component displays actions for sidebar items representing a device.
  */
 class DeviceSidebarItemAction extends PureComponent {
   static get propTypes() {
     return {
       connected: PropTypes.bool.isRequired,
+      dispatch: PropTypes.func.isRequired,
+      runtimeId: PropTypes.string.isRequired,
     };
   }
 
   render() {
     const { connected } = this.props;
     if (connected) {
-      return null;
+      return "Connected";
     }
 
     return dom.button(
       {
         className: "sidebar-item__connect-button",
-        onClick: () => {}
+        onClick: () => {
+          const { dispatch, runtimeId } = this.props;
+          dispatch(Actions.connectRuntime(runtimeId));
+        }
       },
       "Connect"
     );
   }
 }
 
 module.exports = DeviceSidebarItemAction;
--- a/devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
+++ b/devtools/client/aboutdebugging-new/src/components/sidebar/Sidebar.js
@@ -40,17 +40,19 @@ class Sidebar extends PureComponent {
           },
           "No devices discovered"
         )
       );
     }
 
     return runtimes.networkRuntimes.map(runtime => {
       const connectComponent = DeviceSidebarItemAction({
-        connected: false,
+        connected: !!runtime.client,
+        dispatch,
+        runtimeId: runtime.id,
       });
 
       return SidebarItem({
         connectComponent,
         id: "networklocation-" + runtime.id,
         dispatch,
         icon: GLOBE_ICON,
         isSelected: false,
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -1,16 +1,22 @@
 /* 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 actionTypes = {
+  CONNECT_RUNTIME_FAILURE: "CONNECT_RUNTIME_FAILURE",
+  CONNECT_RUNTIME_START: "CONNECT_RUNTIME_START",
+  CONNECT_RUNTIME_SUCCESS: "CONNECT_RUNTIME_SUCCESS",
   DEBUG_TARGET_COLLAPSIBILITY_UPDATED: "DEBUG_TARGET_COLLAPSIBILITY_UPDATED",
+  DISCONNECT_RUNTIME_FAILURE: "DISCONNECT_RUNTIME_FAILURE",
+  DISCONNECT_RUNTIME_START: "DISCONNECT_RUNTIME_START",
+  DISCONNECT_RUNTIME_SUCCESS: "DISCONNECT_RUNTIME_SUCCESS",
   NETWORK_LOCATIONS_UPDATED: "NETWORK_LOCATIONS_UPDATED",
   PAGE_SELECTED: "PAGE_SELECTED",
   REQUEST_EXTENSIONS_FAILURE: "REQUEST_EXTENSIONS_FAILURE",
   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",
--- a/devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/runtimes-state-helper.js
@@ -1,11 +1,20 @@
 /* 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";
 
-function getCurrentClient(state) {
-  const thisFirefoxRuntime = state.runtimes.thisFirefoxRuntimes[0];
+function getCurrentClient(runtimesState) {
+  const thisFirefoxRuntime = runtimesState.thisFirefoxRuntimes[0];
   return thisFirefoxRuntime.client;
 }
 exports.getCurrentClient = getCurrentClient;
+
+function findRuntimeById(id, runtimesState) {
+  const allRuntimes = [
+    ...runtimesState.networkRuntimes,
+    ...runtimesState.thisFirefoxRuntimes,
+  ];
+  return allRuntimes.find(r => r.id === id);
+}
+exports.findRuntimeById = findRuntimeById;
--- a/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
+++ b/devtools/client/aboutdebugging-new/src/reducers/runtimes-state.js
@@ -1,49 +1,77 @@
 /* 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 {
+  CONNECT_RUNTIME_SUCCESS,
+  DISCONNECT_RUNTIME_SUCCESS,
   NETWORK_LOCATIONS_UPDATED,
   RUNTIMES,
-  WATCH_RUNTIME_SUCCESS,
-  UNWATCH_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.
+const TYPE_TO_RUNTIMES_KEY = {
+  [RUNTIMES.THIS_FIREFOX]: "thisFirefoxRuntimes",
+  [RUNTIMES.NETWORK]: "networkRuntimes",
+};
+
 function RuntimesState(networkRuntimes = []) {
   return {
     networkRuntimes,
     thisFirefoxRuntimes: [{
       id: RUNTIMES.THIS_FIREFOX,
       type: RUNTIMES.THIS_FIREFOX,
     }]
   };
 }
 
 function runtimesReducer(state = RuntimesState(), action) {
   switch (action.type) {
-    case WATCH_RUNTIME_SUCCESS: {
-      const { client } = action;
-      const thisFirefoxRuntimes = [{
-        id: RUNTIMES.THIS_FIREFOX,
-        type: RUNTIMES.THIS_FIREFOX,
-        client,
-      }];
-      return Object.assign({}, state, { thisFirefoxRuntimes });
+    case CONNECT_RUNTIME_SUCCESS: {
+      const { id, client } = action.runtime;
+
+      // Find the array of runtimes that contains the updated runtime.
+      const runtime = findRuntimeById(id, state);
+      const key = TYPE_TO_RUNTIMES_KEY[runtime.type];
+      const runtimesToUpdate = state[key];
+
+      // Add the new client to the runtime.
+      const updatedRuntimes = runtimesToUpdate.map(r => {
+        if (r.id === id) {
+          return Object.assign({}, r, { client });
+        }
+        return r;
+      });
+      return Object.assign({}, state, { [key]: updatedRuntimes });
     }
 
-    case UNWATCH_RUNTIME_SUCCESS: {
-      const thisFirefoxRuntimes = [{
-        id: RUNTIMES.THIS_FIREFOX,
-        type: RUNTIMES.THIS_FIREFOX
-      }];
-      return Object.assign({}, state, { thisFirefoxRuntimes });
+    case DISCONNECT_RUNTIME_SUCCESS: {
+      const { id } = action.runtime;
+
+      // Find the array of runtimes that contains the updated runtime.
+      const runtime = findRuntimeById(id, state);
+      const key = TYPE_TO_RUNTIMES_KEY[runtime.type];
+      const runtimesToUpdate = state[key];
+
+      // Remove the client from the updated runtime.
+      const updatedRuntimes = runtimesToUpdate.map(r => {
+        if (r.id === id) {
+          return Object.assign({}, r, { client: null });
+        }
+        return r;
+      });
+      return Object.assign({}, state, { [key]: updatedRuntimes });
     }
 
     case NETWORK_LOCATIONS_UPDATED: {
       const { locations } = action;
       const networkRuntimes = locations.map(location => {
         return {
           id: location,
           type: RUNTIMES.NETWORK,