Bug 1299503 - Support connecting to remote targets via about:devtools-toolbox query parameters. r=jryans
MozReview-Commit-ID: 7EFCxnKkO6r
--- 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 = !!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