Bug 1299503 - Support connecting to remote targets via about:devtools-toolbox query parameters. r=jryans draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 31 Aug 2016 07:42:23 -0700
changeset 420139 9c4c96d6770ef17d597b8fffc95e020943209281
parent 419914 7c576fe3279d87543f0a03b844eba7bc215e17f1
child 420140 5b5d5aa8d4e0622d4ea5b62bc86312d1697a4ac1
push id31106
push userbmo:poirot.alex@gmail.com
push dateMon, 03 Oct 2016 14:02:35 +0000
reviewersjryans
bugs1299503
milestone52.0a1
Bug 1299503 - Support connecting to remote targets via about:devtools-toolbox query parameters. r=jryans MozReview-Commit-ID: 7EFCxnKkO6r
devtools/client/framework/target-from-url.js
devtools/client/framework/test/browser_target_from_url.js
devtools/shared/security/socket.js
devtools/shared/transport/tests/unit/test_dbgsocket.js
--- a/devtools/client/framework/target-from-url.js
+++ b/devtools/client/framework/target-from-url.js
@@ -9,16 +9,23 @@ const { Cu, Ci } = require("chrome");
 const { TargetFactory } = require("devtools/client/framework/target");
 const { DebuggerServer } = require("devtools/server/main");
 const { DebuggerClient } = require("devtools/shared/client/main");
 const { Task } = require("devtools/shared/task");
 
 /**
  * Construct a Target for a given URL object having various query parameters:
  *
+ * host:
+ *    {String} The hostname or IP address to connect to.
+ * port:
+ *    {Number} The TCP port to connect to, to use with `host` argument.
+ * ws:
+ *    {Boolean} If true, connect via websocket instread of regular TCP connection.
+ *
  * type: tab, process
  *    {String} The type of target to connect to.  Currently tabs and processes are supported types.
  *
  * If type="tab":
  * id:
  *    {Number} the tab outerWindowID
  * chrome: Optional
  *    {Boolean} Force the creation of a chrome target. Gives more privileges to the tab
@@ -40,19 +47,17 @@ exports.targetFromURL = Task.async(funct
   if (!type) {
     throw new Error("targetFromURL, missing type parameter");
   }
   let id = params.get("id");
   // Allows to spawn a chrome enabled target for any context
   // (handy to debug chrome stuff in a child process)
   let chrome = params.has("chrome");
 
-  // Once about:debugging start supporting remote targets and use this helper,
-  // client will also be defined by url params.
-  let client = createClient();
+  let client = yield createClient(params);
 
   yield client.connect();
 
   let form, isTabActor;
   if (type === "tab") {
     // Fetch target for a remote tab
     id = parseInt(id);
     if (isNaN(id)) {
@@ -90,17 +95,26 @@ exports.targetFromURL = Task.async(funct
     }
   } else {
     throw new Error("targetFromURL, unsupported type='" + type + "' parameter");
   }
 
   return TargetFactory.forRemoteTab({ client, form, chrome, isTabActor });
 });
 
-function createClient() {
-  // Setup a server if we don't have one already running
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init();
-    DebuggerServer.addBrowserActors();
+function* createClient(params) {
+  let host = params.get("host");
+  let port = params.get("port");
+  let webSocket = Boolean(params.get("ws"));
+
+  let transport;
+  if (port) {
+    transport = yield DebuggerClient.socketConnect({ host, port, webSocket });
+  } else {
+    // Setup a server if we don't have one already running
+    if (!DebuggerServer.initialized) {
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors();
+    }
+    transport = DebuggerServer.connectPipe()
   }
-
-  return new DebuggerClient(DebuggerServer.connectPipe());
+  return new DebuggerClient(transport);
 }
--- a/devtools/client/framework/test/browser_target_from_url.js
+++ b/devtools/client/framework/test/browser_target_from_url.js
@@ -1,18 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8," +
   "<p>browser_target-from-url.js</p>";
 
+const { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { targetFromURL } = require("devtools/client/framework/target-from-url");
 
-function assertIsTabTarget(target, chrome = false) {
-  is(target.url, TEST_URI);
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
+
+SimpleTest.registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.debugger.remote-enabled");
+  Services.prefs.clearUserPref("devtools.debugger.prompt-connection");
+});
+
+function assertIsTabTarget(target, url, chrome = false) {
+  is(target.url, url);
   is(target.isLocalTab, false);
   is(target.chrome, chrome);
   is(target.isTabActor, true);
   is(target.isRemote, true);
 }
 
 add_task(function* () {
   let tab = yield addTab(TEST_URI);
@@ -25,34 +34,100 @@ add_task(function* () {
     ok(false, "Shouldn't pass");
   } catch (e) {
     is(e.message, "targetFromURL, unsupported type='x' parameter");
   }
 
   info("Test tab");
   let windowId = browser.outerWindowID;
   target = yield targetFromURL(new URL("http://foo?type=tab&id=" + windowId));
-  assertIsTabTarget(target);
+  assertIsTabTarget(target, TEST_URI);
 
   info("Test tab with chrome privileges");
   target = yield targetFromURL(new URL("http://foo?type=tab&id=" + windowId + "&chrome"));
-  assertIsTabTarget(target, true);
+  assertIsTabTarget(target, TEST_URI, true);
 
   info("Test invalid tab id");
   try {
     yield targetFromURL(new URL("http://foo?type=tab&id=10000"));
     ok(false, "Shouldn't pass");
   } catch (e) {
     is(e.message, "targetFromURL, tab with outerWindowID:'10000' doesn't exist");
   }
 
   info("Test parent process");
   target = yield targetFromURL(new URL("http://foo?type=process"));
   let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
-  is(target.url, topWindow.location.href);
-  is(target.isLocalTab, false);
-  is(target.chrome, true);
-  is(target.isTabActor, true);
-  is(target.isRemote, true);
+  assertIsTabTarget(target, topWindow.location.href, true);
+
+  yield testRemoteTCP();
+  yield testRemoteWebSocket();
+
+  gBrowser.removeCurrentTab();
+});
+
+function* setupDebuggerServer(websocket) {
+  info("Create a separate loader instance for the DebuggerServer.");
+  let loader = new DevToolsLoader();
+  let { DebuggerServer } = loader.require("devtools/server/main");
+
+  DebuggerServer.init();
+  DebuggerServer.addBrowserActors();
+  DebuggerServer.allowChromeProcess = true;
+
+  let listener = DebuggerServer.createListener();
+  ok(listener, "Socket listener created");
+  // Pass -1 to automatically choose an available port
+  listener.portOrPath = -1;
+  listener.webSocket = websocket;
+  yield listener.open();
+  is(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+  return { DebuggerServer, listener };
+}
+
+function teardownDebuggerServer({ DebuggerServer, listener }) {
+  info("Close the listener socket");
+  listener.close();
+  is(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+  info("Destroy the temporary debugger server");
+  DebuggerServer.destroy();
+}
+
+function* testRemoteTCP() {
+  info("Test remote process via TCP Connection");
+
+  let server = yield setupDebuggerServer(false);
+
+  let { port } = server.listener;
+  let target = yield targetFromURL(new URL("http://foo?type=process&host=127.0.0.1&port=" + port));
+  let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
+  assertIsTabTarget(target, topWindow.location.href, true);
+
+  let settings = target.client._transport.connectionSettings;
+  is(settings.host, "127.0.0.1");
+  is(settings.port, port);
+  is(settings.webSocket, false);
 
   yield target.client.close();
-  gBrowser.removeCurrentTab();
-});
+
+  teardownDebuggerServer(server);
+}
+
+function* testRemoteWebSocket() {
+  info("Test remote process via WebSocket Connection");
+
+  let server = yield setupDebuggerServer(true);
+
+  let { port } = server.listener;
+  let target = yield targetFromURL(new URL("http://foo?type=process&host=127.0.0.1&port=" + port + "&ws=true"));
+  let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
+  assertIsTabTarget(target, topWindow.location.href, true);
+
+  let settings = target.client._transport.connectionSettings;
+  is(settings.host, "127.0.0.1");
+  is(settings.port, port);
+  is(settings.webSocket, true);
+  yield target.client.close();
+
+  teardownDebuggerServer(server);
+}
--- a/devtools/shared/security/socket.js
+++ b/devtools/shared/security/socket.js
@@ -85,16 +85,17 @@ DebuggerSocket.connect = Task.async(func
   let transport = yield _getTransport(settings);
   yield authenticator.authenticate({
     host,
     port,
     encryption,
     cert,
     transport
   });
+  transport.connectionSettings = settings;
   return transport;
 });
 
 /**
  * Validate that the connection settings have been set to a supported configuration.
  */
 function _validateSettings(settings) {
   let { encryption, webSocket, authenticator } = settings;
--- a/devtools/shared/transport/tests/unit/test_dbgsocket.js
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket.js
@@ -40,16 +40,22 @@ function* test_socket_conn()
   do_check_eq(DebuggerServer.listeningSockets, 2);
 
   do_print("Starting long and unicode tests at " + new Date().toTimeString());
   let unicodeString = "(╯°□°)╯︵ ┻━┻";
   let transport = yield DebuggerClient.socketConnect({
     host: "127.0.0.1",
     port: gPort
   });
+
+  // Assert that connection settings are available on transport object
+  let settings = transport.connectionSettings;
+  do_check_eq(settings.host, "127.0.0.1");
+  do_check_eq(settings.port, gPort);
+
   let closedDeferred = defer();
   transport.hooks = {
     onPacket: function (aPacket) {
       this.onPacket = function (aPacket) {
         do_check_eq(aPacket.unicode, unicodeString);
         transport.close();
       };
       // Verify that things work correctly when bigger than the output