--- a/b2g/chrome/content/devtools/debugger.js
+++ b/b2g/chrome/content/devtools/debugger.js
@@ -178,16 +178,17 @@ let WiFiRemoteDebugger = {
RemoteDebugger.initServer();
try {
debug("Starting WiFi debugger");
this._listener = DebuggerServer.createListener();
this._listener.portOrPath = -1 /* any available port */;
this._listener.allowConnection = RemoteDebugger.prompt;
this._listener.discoverable = true;
+ this._listener.encryption = true;
this._listener.open();
let port = this._listener.port;
debug("Started WiFi debugger on " + port);
} catch (e) {
debug("Unable to start WiFi debugger server: " + e);
}
},
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -36,47 +36,51 @@ window.addEventListener("DOMContentLoade
}
if (port) {
document.getElementById("port").value = port;
}
let form = document.querySelector("#connection-form form");
form.addEventListener("submit", function() {
- window.submit();
+ window.submit().catch(e => {
+ Cu.reportError(e);
+ // Bug 921850: catch rare exception from DebuggerClient.socketConnect
+ showError("unexpected");
+ });
});
}, true);
/**
* Called when the "connect" button is clicked.
*/
-function submit() {
+let submit = Task.async(function*() {
// Show the "connecting" screen
document.body.classList.add("connecting");
// Save the host/port values
let host = document.getElementById("host").value;
Services.prefs.setCharPref("devtools.debugger.remote-host", host);
let port = document.getElementById("port").value;
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
// Initiate the connection
- let transport;
- try {
- transport = DebuggerClient.socketConnect(host, port);
- } catch(e) {
- // Bug 921850: catch rare exception from DebuggerClient.socketConnect
- showError("unexpected");
- return;
- }
+ let transport = yield DebuggerClient.socketConnect({ host, port });
gClient = new DebuggerClient(transport);
let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
gConnectionTimeout = setTimeout(handleConnectionTimeout, delay);
- gClient.connect(onConnectionReady);
+ let response = yield clientConnect();
+ yield onConnectionReady(...response);
+});
+
+function clientConnect() {
+ let deferred = promise.defer();
+ gClient.connect((...args) => deferred.resolve(args));
+ return deferred.promise;
}
/**
* Connection is ready. List actors and build buttons.
*/
let onConnectionReady = Task.async(function*(aType, aTraits) {
clearTimeout(gConnectionTimeout);
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -7,61 +7,62 @@ const { classes: Cc, interfaces: Ci, uti
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
let { DebuggerClient } =
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { ViewHelpers } =
Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
+let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
/**
* Shortcuts for accessing various debugger preferences.
*/
let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
chromeDebuggingHost: ["Char", "chrome-debugging-host"],
chromeDebuggingPort: ["Int", "chrome-debugging-port"]
});
let gToolbox, gClient;
-function connect() {
+let connect = Task.async(function*() {
window.removeEventListener("load", connect);
// Initiate the connection
- let transport = DebuggerClient.socketConnect(
- Prefs.chromeDebuggingHost,
- Prefs.chromeDebuggingPort
- );
+ let transport = yield DebuggerClient.socketConnect({
+ host: Prefs.chromeDebuggingHost,
+ port: Prefs.chromeDebuggingPort
+ });
gClient = new DebuggerClient(transport);
gClient.connect(() => {
let addonID = getParameterByName("addonID");
if (addonID) {
gClient.listAddons(({addons}) => {
let addonActor = addons.filter(addon => addon.id === addonID).pop();
openToolbox(addonActor);
});
} else {
gClient.listTabs(openToolbox);
}
});
-}
+});
// Certain options should be toggled since we can assume chrome debugging here
function setPrefDefaults() {
Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
Services.prefs.setBoolPref("devtools.profiler.ui.show-platform-data", true);
Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", false);
}
window.addEventListener("load", function() {
let cmdClose = document.getElementById("toolbox-cmd-close");
cmdClose.addEventListener("command", onCloseCommand);
setPrefDefaults();
- connect();
+ connect().catch(Cu.reportError);
});
function onCloseCommand(event) {
window.close();
}
function openToolbox(form) {
let options = {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -790,16 +790,18 @@ pref("devtools.dump.emit", false);
// Disable device discovery logging
pref("devtools.discovery.log", false);
// Disable scanning for DevTools devices via WiFi
pref("devtools.remote.wifi.scan", false);
// Hide UI options for controlling device visibility over WiFi
// N.B.: This does not set whether the device can be discovered via WiFi, only
// whether the UI control to make such a choice is shown to the user
pref("devtools.remote.wifi.visible", false);
+// Client must complete TLS handshake within this window (ms)
+pref("devtools.remote.tls-handshake-timeout", 10000);
// view source
pref("view_source.syntax_highlight", true);
pref("view_source.wrap_long_lines", false);
pref("view_source.editor.external", false);
pref("view_source.editor.path", "");
// allows to add further arguments to the editor; use the %LINE% placeholder
// for jumping to a specific line (e.g. "/line:%LINE%" or "--goto %LINE%")
--- a/toolkit/devtools/apps/tests/debugger-protocol-helper.js
+++ b/toolkit/devtools/apps/tests/debugger-protocol-helper.js
@@ -23,18 +23,22 @@ function connect(onDone) {
let settingsService = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
settingsService.createLock().set("devtools.debugger.remote-enabled", true, null);
// We can't use `set` callback as it is fired before shell.js code listening for this setting
// is actually called. Same thing applies to mozsettings-changed obs notification.
// So listen to a custom event until bug 942756 lands
let observer = {
observe: function (subject, topic, data) {
Services.obs.removeObserver(observer, "debugger-server-started");
- let transport = DebuggerClient.socketConnect("127.0.0.1", 6000);
- startClient(transport, onDone);
+ DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: 6000
+ }).then(transport => {
+ startClient(transport, onDone);
+ }, e => dump("Connection failed: " + e + "\n"));
}
};
Services.obs.addObserver(observer, "debugger-server-started", false);
} else {
// Initialize a loopback remote protocol connection
DebuggerServer.init();
// We need to register browser actors to have `listTabs` working
// and also have a root actor
--- a/toolkit/devtools/client/connection-manager.js
+++ b/toolkit/devtools/client/connection-manager.js
@@ -4,20 +4,23 @@
* 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, Cu} = require("chrome");
const {setTimeout, clearTimeout} = require('sdk/timers');
const EventEmitter = require("devtools/toolkit/event-emitter");
+const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+DevToolsUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
/**
* Connection Manager.
*
* To use this module:
* const {ConnectionManager} = require("devtools/client/connection-manager");
*
* # ConnectionManager
@@ -43,17 +46,18 @@ Cu.import("resource://gre/modules/devtoo
* . connect(transport) Connect via transport. Expect a "connecting" event.
* . disconnect() Disconnect if connected. Expect a "disconnecting" event
*
* Properties:
* . host IP address or hostname
* . port Port
* . logs Current logs. "newlog" event notifies new available logs
* . store Reference to a local data store (see below)
- * . keepConnecting Should the connection keep trying connecting
+ * . keepConnecting Should the connection keep trying to connect?
+ * . encryption Should the connection be encrypted?
* . status Connection status:
* Connection.Status.CONNECTED
* Connection.Status.DISCONNECTED
* Connection.Status.CONNECTING
* Connection.Status.DISCONNECTING
* Connection.Status.DESTROYED
*
* Events (as in event-emitter.js):
@@ -108,16 +112,17 @@ function Connection(host, port) {
this.uid = ++lastID;
this.host = host;
this.port = port;
this._setStatus(Connection.Status.DISCONNECTED);
this._onDisconnected = this._onDisconnected.bind(this);
this._onConnected = this._onConnected.bind(this);
this._onTimeout = this._onTimeout.bind(this);
this.keepConnecting = false;
+ this.encryption = false;
}
Connection.Status = {
CONNECTED: "connected",
DISCONNECTED: "disconnected",
CONNECTING: "connecting",
DISCONNECTING: "disconnecting",
DESTROYED: "destroyed",
@@ -217,40 +222,48 @@ Connection.prototype = {
this.keepConnecting = false;
if (this._client) {
this._client.close();
this._client = null;
}
this._setStatus(Connection.Status.DESTROYED);
},
- _clientConnect: function () {
- let transport;
+ _getTransport: Task.async(function*() {
if (this._customTransport) {
- transport = this._customTransport;
- } else {
- if (!this.host) {
- transport = DebuggerServer.connectPipe();
- } else {
- try {
- transport = DebuggerClient.socketConnect(this.host, this.port);
- } catch (e) {
- // In some cases, especially on Mac, the openOutputStream call in
- // DebuggerClient.socketConnect may throw NS_ERROR_NOT_INITIALIZED.
- // It occurs when we connect agressively to the simulator,
- // and keep trying to open a socket to the server being started in
- // the simulator.
- this._onDisconnected();
- return;
- }
+ return this._customTransport;
+ }
+ if (!this.host) {
+ return DebuggerServer.connectPipe();
+ }
+ let transport = yield DebuggerClient.socketConnect({
+ host: this.host,
+ port: this.port,
+ encryption: this.encryption
+ });
+ return transport;
+ }),
+
+ _clientConnect: function () {
+ this._getTransport().then(transport => {
+ if (!transport) {
+ return;
}
- }
- this._client = new DebuggerClient(transport);
- this._client.addOneTimeListener("closed", this._onDisconnected);
- this._client.connect(this._onConnected);
+ this._client = new DebuggerClient(transport);
+ this._client.addOneTimeListener("closed", this._onDisconnected);
+ this._client.connect(this._onConnected);
+ }, e => {
+ console.error(e);
+ // In some cases, especially on Mac, the openOutputStream call in
+ // DebuggerClient.socketConnect may throw NS_ERROR_NOT_INITIALIZED.
+ // It occurs when we connect agressively to the simulator,
+ // and keep trying to open a socket to the server being started in
+ // the simulator.
+ this._onDisconnected();
+ });
},
get status() {
return this._status
},
_setStatus: function(value) {
if (this._status && this._status == value)
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -367,19 +367,19 @@ DebuggerClient.Argument = function (aPos
DebuggerClient.Argument.prototype.getArgument = function (aParams) {
if (!(this.position in aParams)) {
throw new Error("Bad index into params: " + this.position);
}
return aParams[this.position];
};
// Expose this to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function(host, port) {
+DebuggerClient.socketConnect = function(options) {
// Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(host, port);
+ return DebuggerSocket.connect(options);
};
DebuggerClient.prototype = {
/**
* Connect to the server and start exchanging protocol messages.
*
* @param aOnConnected function
* If specified, will be called when the greeting packet is
--- a/toolkit/devtools/gcli/source/lib/gcli/connectors/rdp.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/connectors/rdp.js
@@ -14,16 +14,17 @@
* limitations under the License.
*/
'use strict';
var Cu = require('chrome').Cu;
var DebuggerClient = Cu.import('resource://gre/modules/devtools/dbg-client.jsm', {}).DebuggerClient;
+var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
var Promise = require('../util/promise').Promise;
var Connection = require('./connectors').Connection;
/**
* What port should we use by default?
*/
Object.defineProperty(exports, 'defaultPort', {
@@ -56,36 +57,40 @@ exports.items = [
*/
function RdpConnection(url) {
throw new Error('Use RdpConnection.create');
}
/**
* Asynchronous construction
*/
-RdpConnection.create = function(url) {
+RdpConnection.create = Task.async(function*(url) {
this.host = url;
this.port = undefined; // TODO: Split out the port number
this.requests = {};
this.nextRequestId = 0;
this._emit = this._emit.bind(this);
+ let transport = yield DebuggerClient.socketConnect({
+ host: this.host,
+ port: this.port
+ });
+
return new Promise(function(resolve, reject) {
- this.transport = DebuggerClient.socketConnect(this.host, this.port);
- this.client = new DebuggerClient(this.transport);
+ this.client = new DebuggerClient(transport);
this.client.connect(function() {
this.client.listTabs(function(response) {
this.actor = response.gcliActor;
resolve();
}.bind(this));
}.bind(this));
}.bind(this));
-};
+});
RdpConnection.prototype = Object.create(Connection.prototype);
RdpConnection.prototype.call = function(command, data) {
return new Promise(function(resolve, reject) {
var request = { to: this.actor, type: command, data: data };
this.client.request(request, function(response) {
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/security/cert.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+let { Ci, Cc } = require("chrome");
+let promise = require("promise");
+let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+DevToolsUtils.defineLazyGetter(this, "localCertService", () => {
+ return Cc["@mozilla.org/security/local-cert-service;1"]
+ .getService(Ci.nsILocalCertService);
+});
+
+const localCertName = "devtools";
+
+exports.local = {
+
+ /**
+ * Get or create a new self-signed X.509 cert to represent this device for
+ * DevTools purposes over a secure transport, like TLS.
+ *
+ * The cert is stored permanently in the profile's key store after first use,
+ * and is valid for 1 year. If an expired or otherwise invalid cert is found,
+ * it is removed and a new one is made.
+ *
+ * @return promise
+ */
+ getOrCreate() {
+ let deferred = promise.defer();
+ localCertService.getOrCreateCert(localCertName, {
+ handleCert: function(cert, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(cert);
+ }
+ });
+ return deferred.promise;
+ },
+
+ /**
+ * Remove the DevTools self-signed X.509 cert for this device.
+ *
+ * @return promise
+ */
+ remove() {
+ let deferred = promise.defer();
+ localCertService.removeCert(localCertName, {
+ handleCert: function(rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve();
+ }
+ });
+ return deferred.promise;
+ }
+
+};
--- a/toolkit/devtools/security/moz.build
+++ b/toolkit/devtools/security/moz.build
@@ -16,10 +16,11 @@ UNIFIED_SOURCES += [
'LocalCertService.cpp',
]
FAIL_ON_WARNINGS = True
FINAL_LIBRARY = 'xul'
EXTRA_JS_MODULES.devtools.security += [
+ 'cert.js',
'socket.js',
]
--- a/toolkit/devtools/security/socket.js
+++ b/toolkit/devtools/security/socket.js
@@ -2,76 +2,181 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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";
let { Ci, Cc, CC, Cr } = require("chrome");
+
+// Ensure PSM is initialized to support TLS sockets
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
let Services = require("Services");
+let promise = require("promise");
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
-let { dumpn } = DevToolsUtils;
+let { dumpn, dumpv } = DevToolsUtils;
loader.lazyRequireGetter(this, "DebuggerTransport",
"devtools/toolkit/transport/transport", true);
loader.lazyRequireGetter(this, "DebuggerServer",
"devtools/server/main", true);
loader.lazyRequireGetter(this, "discovery",
"devtools/toolkit/discovery/discovery");
-
-DevToolsUtils.defineLazyGetter(this, "ServerSocket", () => {
- return CC("@mozilla.org/network/server-socket;1",
- "nsIServerSocket",
- "initSpecialConnection");
-});
-
-DevToolsUtils.defineLazyGetter(this, "UnixDomainServerSocket", () => {
- return CC("@mozilla.org/network/server-socket;1",
- "nsIServerSocket",
- "initWithFilename");
-});
+loader.lazyRequireGetter(this, "cert",
+ "devtools/toolkit/security/cert");
+loader.lazyRequireGetter(this, "setTimeout", "Timer", true);
+loader.lazyRequireGetter(this, "clearTimeout", "Timer", true);
DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
});
DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
return Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService);
});
+DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
+ return Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+});
+
+DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
+ return Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+});
+
+DevToolsUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
+let DebuggerSocket = {};
+
/**
- * Connects to a debugger server socket and returns a DebuggerTransport.
+ * Connects to a debugger server socket.
*
* @param host string
* The host name or IP address of the debugger server.
* @param port number
* The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @return promise
+ * Resolved to a DebuggerTransport instance.
*/
-function socketConnect(host, port) {
- let s = socketTransportService.createTransport(null, 0, host, port, null);
+DebuggerSocket.connect = Task.async(function*({ host, port, encryption }) {
+ {
+ // _attemptConnect only opens the streams. Any failures at that stage
+ // aborts the connection process immedidately.
+ let { s, input, output } = _attemptConnect({ host, port, encryption });
+
+ // Check if the input stream is alive. If encryption is enabled, we need to
+ // watch out for cert errors by testing the input stream.
+ let { alive, certError } = yield _isInputAlive(input);
+ dumpv("Server cert accepted? " + !certError);
+ if (alive) {
+ return new DebuggerTransport(input, output);
+ }
+
+ // If the server cert failed validation, store a temporary override and make
+ // a second attempt.
+ if (encryption && certError) {
+ _storeCertOverride(s, host, port);
+ } else {
+ throw new Error("Connection failed");
+ }
+ }
+
+ {
+ let { input, output } = _attemptConnect({ host, port, encryption });
+ let { alive, certError } = yield _isInputAlive(input);
+ dumpv("Server cert accepted? " + !certError);
+ if (alive) {
+ return new DebuggerTransport(input, output);
+ }
+ throw new Error("Connection failed even after cert override");
+ }
+});
+
+/**
+ * Try to connect to a remote server socket.
+ * If successsful, the socket transport and its opened streams are returned.
+ */
+function _attemptConnect({ host, port, encryption }) {
+ let s;
+ if (encryption) {
+ s = socketTransportService.createTransport(["ssl"], 1, host, port, null);
+ } else {
+ s = socketTransportService.createTransport(null, 0, host, port, null);
+ }
// By default the CONNECT socket timeout is very long, 65535 seconds,
// so that if we race to be in CONNECT state while the server socket is still
// initializing, the connection is stuck in connecting state for 18.20 hours!
s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
// openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
// where the nsISocketTransport gets shutdown in between its instantiation and
// the call to this method.
- let transport;
+ let input;
+ let output;
try {
- transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
- s.openOutputStream(0, 0, 0));
+ input = s.openInputStream(0, 0, 0);
+ output = s.openOutputStream(0, 0, 0);
} catch(e) {
DevToolsUtils.reportException("socketConnect", e);
throw e;
}
- return transport;
+
+ return { s, input, output };
+}
+
+/**
+ * Check if the input stream is alive. For an encrypted connection, it may not
+ * be if the client refuses the server's cert. A cert error is expected on
+ * first connection to a new host because the cert is self-signed.
+ */
+function _isInputAlive(input) {
+ let deferred = promise.defer();
+ input.asyncWait({
+ onInputStreamReady(stream) {
+ try {
+ stream.available();
+ deferred.resolve({ alive: true });
+ } catch (e) {
+ try {
+ // getErrorClass may throw if you pass a non-NSS error
+ let errorClass = nssErrorsService.getErrorClass(e.result);
+ if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
+ deferred.resolve({ certError: true });
+ } else {
+ deferred.reject(e);
+ }
+ } catch (nssErr) {
+ deferred.reject(e);
+ }
+ }
+ }
+ }, 0, 0, Services.tm.currentThread);
+ return deferred.promise;
+}
+
+/**
+ * To allow the connection to proceed with self-signed cert, we store a cert
+ * override. This implies that we take on the burden of authentication for
+ * these connections.
+ */
+function _storeCertOverride(s, host, port) {
+ let cert = s.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+ let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(host, port, cert, overrideBits,
+ true /* temporary */);
}
/**
* Creates a new socket listener for remote connections to the DebuggerServer.
* This helps contain and organize the parts of the server that may differ or
* are particular to one given listener mechanism vs. another.
*/
function SocketListener() {}
@@ -129,16 +234,21 @@ SocketListener.prototype = {
/**
* Controls whether this listener is announced via the service discovery
* mechanism.
*/
discoverable: false,
/**
+ * Controls whether this listener's transport uses encryption.
+ */
+ encryption: false,
+
+ /**
* Validate that all options have been set to a supported configuration.
*/
_validateOptions: function() {
if (this.portOrPath === null) {
throw new Error("Must set a port / path to listen on.");
}
if (this.discoverable && !Number(this.portOrPath)) {
throw new Error("Discovery only supported for TCP sockets.");
@@ -153,41 +263,63 @@ SocketListener.prototype = {
DebuggerServer._addListener(this);
let flags = Ci.nsIServerSocket.KeepWhenOffline;
// A preference setting can force binding on the loopback interface.
if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
flags |= Ci.nsIServerSocket.LoopbackOnly;
}
- try {
+ let self = this;
+ return Task.spawn(function*() {
let backlog = 4;
- let port = Number(this.portOrPath);
+ let port = Number(self.portOrPath);
+ self._socket = self._createSocketInstance();
if (port) {
- this._socket = new ServerSocket(port, flags, backlog);
+ self._socket.initSpecialConnection(port, flags, backlog);
} else {
- let file = nsFile(this.portOrPath);
- if (file.exists())
+ let file = nsFile(self.portOrPath);
+ if (file.exists()) {
file.remove(false);
- this._socket = new UnixDomainServerSocket(file, parseInt("666", 8),
- backlog);
+ }
+ self._socket.initWithFilename(file, parseInt("666", 8), backlog);
}
- this._socket.asyncListen(this);
- } catch (e) {
+ yield self._setAdditionalSocketOptions();
+ self._socket.asyncListen(self);
+ dumpn("Socket listening on port: " + self.port || self.portOrPath);
+ }).then(() => {
+ if (this.discoverable && this.port) {
+ discovery.addService("devtools", { port: this.port });
+ }
+ }, e => {
dumpn("Could not start debugging listener on '" + this.portOrPath +
"': " + e);
this.close();
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- }
+ });
+ },
- if (this.discoverable && this.port) {
- discovery.addService("devtools", { port: this.port });
+ _createSocketInstance: function() {
+ if (this.encryption) {
+ return Cc["@mozilla.org/network/tls-server-socket;1"]
+ .createInstance(Ci.nsITLSServerSocket);
}
+ return Cc["@mozilla.org/network/server-socket;1"]
+ .createInstance(Ci.nsIServerSocket);
},
+ _setAdditionalSocketOptions: Task.async(function*() {
+ if (this.encryption) {
+ this._socket.serverCert = yield cert.local.getOrCreate();
+ this._socket.setSessionCache(false);
+ this._socket.setSessionTickets(false);
+ let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
+ this._socket.setRequestClientCertificate(requestCert);
+ }
+ }),
+
/**
* Closes the SocketListener. Notifies the server to remove the listener from
* the set of active SocketListeners.
*/
close: function() {
if (this.discoverable && this.port) {
discovery.removeService("devtools");
}
@@ -208,16 +340,19 @@ SocketListener.prototype = {
}
return this._socket.port;
},
// nsIServerSocketListener implementation
onSocketAccepted:
DevToolsUtils.makeInfallible(function(socket, socketTransport) {
+ if (this.encryption) {
+ new SecurityObserver(socketTransport);
+ }
if (Services.prefs.getBoolPref("devtools.debugger.prompt-connection") &&
!this.allowConnection()) {
return;
}
dumpn("New debugging connection on " +
socketTransport.host + ":" + socketTransport.port);
let input = socketTransport.openInputStream(0, 0, 0);
@@ -227,18 +362,59 @@ SocketListener.prototype = {
}, "SocketListener.onSocketAccepted"),
onStopListening: function(socket, status) {
dumpn("onStopListening, status: " + status);
}
};
-// TODO: These high-level entry points will branch based on TLS vs. bare TCP as
-// part of bug 1059001.
-exports.DebuggerSocket = {
- createListener() {
- return new SocketListener();
+// Client must complete TLS handshake within this window (ms)
+loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
+ return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
+});
+
+function SecurityObserver(socketTransport) {
+ this.socketTransport = socketTransport;
+ let connectionInfo = socketTransport.securityInfo
+ .QueryInterface(Ci.nsITLSServerConnectionInfo);
+ connectionInfo.setSecurityObserver(this);
+ this._handshakeTimeout = setTimeout(this._onHandshakeTimeout.bind(this),
+ HANDSHAKE_TIMEOUT);
+}
+
+SecurityObserver.prototype = {
+
+ _onHandshakeTimeout() {
+ dumpv("Client failed to complete handshake");
+ this.socketTransport.close(Cr.NS_ERROR_NET_TIMEOUT);
},
- connect(host, port) {
- return socketConnect(host, port);
+
+ // nsITLSServerSecurityObserver implementation
+ onHandshakeDone(socket, clientStatus) {
+ clearTimeout(this._handshakeTimeout);
+ dumpv("TLS version: " + clientStatus.tlsVersionUsed.toString(16));
+ dumpv("TLS cipher: " + clientStatus.cipherName);
+ dumpv("TLS key length: " + clientStatus.keyLength);
+ dumpv("TLS MAC length: " + clientStatus.macLength);
+ /*
+ * TODO: These rules should be really be set on the TLS socket directly, but
+ * this would need more platform work to expose it via XPCOM.
+ *
+ * Server *will* send hello packet when any rules below are not met, but the
+ * socket then closes after that.
+ *
+ * Enforcing cipher suites here would be a bad idea, as we want TLS
+ * cipher negotiation to work correctly. The server already allows only
+ * Gecko's normal set of cipher suites.
+ */
+ if (clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2) {
+ this.socketTransport.close(Cr.NS_ERROR_CONNECTION_REFUSED);
+ }
}
+
};
+
+DebuggerSocket.createListener = function() {
+ return new SocketListener();
+};
+
+exports.DebuggerSocket = DebuggerSocket;
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/security/tests/unit/head_dbg.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+const CC = Components.Constructor;
+
+const { devtools } =
+ Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const { Promise: promise } =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+
+const Services = devtools.require("Services");
+const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
+
+// We do not want to log packets by default, because in some tests,
+// we can be sending large amounts of data. The test harness has
+// trouble dealing with logging all the data, and we end up with
+// intermittent time outs (e.g. bug 775924).
+// Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+// Fast timeout for TLS tests
+Services.prefs.setIntPref("devtools.remote.tls-handshake-timeout", 1000);
+
+function tryImport(url) {
+ try {
+ Cu.import(url);
+ } catch (e) {
+ dump("Error importing " + url + "\n");
+ dump(DevToolsUtils.safeErrorString(e) + "\n");
+ throw e;
+ }
+}
+
+tryImport("resource://gre/modules/devtools/dbg-server.jsm");
+tryImport("resource://gre/modules/devtools/dbg-client.jsm");
+
+// Convert an nsIScriptError 'aFlags' value into an appropriate string.
+function scriptErrorFlagsToKind(aFlags) {
+ var kind;
+ if (aFlags & Ci.nsIScriptError.warningFlag)
+ kind = "warning";
+ if (aFlags & Ci.nsIScriptError.exceptionFlag)
+ kind = "exception";
+ else
+ kind = "error";
+
+ if (aFlags & Ci.nsIScriptError.strictFlag)
+ kind = "strict " + kind;
+
+ return kind;
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+let errorCount = 0;
+let listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
+ DebuggerServer.xpcInspector.exitNestedEventLoop();
+ }
+
+ // Throw in most cases, but ignore the "strict" messages
+ if (!(aMessage.flags & Ci.nsIScriptError.strictFlag)) {
+ do_throw("head_dbg.js got console message: " + string + "\n");
+ }
+ }
+};
+
+let consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
+/**
+ * Initialize the testing debugger server.
+ */
+function initTestDebuggerServer() {
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init();
+}
--- a/toolkit/devtools/security/tests/unit/test_cert.js
+++ b/toolkit/devtools/security/tests/unit/test_cert.js
@@ -1,17 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
-const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
-
-const { Promise: promise } =
- Cu.import("resource://gre/modules/Promise.jsm", {});
const certService = Cc["@mozilla.org/security/local-cert-service;1"]
.getService(Ci.nsILocalCertService);
const gNickname = "devtools";
function run_test() {
// Need profile dir to store the key / cert
do_get_profile();
@@ -44,17 +40,17 @@ function removeCert(nickname) {
}
deferred.resolve();
}
});
return deferred.promise;
}
add_task(function*() {
- // No master password, so prompt required here
+ // No master password, so no prompt required here
ok(!certService.loginPromptRequired);
let certA = yield getOrCreateCert(gNickname);
equal(certA.nickname, gNickname);
// Getting again should give the same cert
let certB = yield getOrCreateCert(gNickname);
equal(certB.nickname, gNickname);
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/security/tests/unit/test_encryption.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ // Need profile dir to store the key / cert
+ do_get_profile();
+ // Ensure PSM is initialized
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+ run_next_test();
+}
+
+function connectClient(client) {
+ let deferred = promise.defer();
+ client.connect(() => {
+ client.listTabs(deferred.resolve);
+ });
+ return deferred.promise;
+}
+
+add_task(function*() {
+ initTestDebuggerServer();
+});
+
+// Client w/ encryption connects successfully to server w/ encryption
+add_task(function*() {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1 /* any available port */;
+ listener.allowConnection = () => true;
+ listener.encryption = true;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true
+ });
+ ok(transport, "Client transport created");
+
+ let client = new DebuggerClient(transport);
+ yield connectClient(client);
+
+ // Send a message the server will echo back
+ let message = "secrets";
+ let reply = yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ equal(reply.message, message, "Encrypted echo matches");
+
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+});
+
+// Client w/o encryption fails to connect to server w/ encryption
+add_task(function*() {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1 /* any available port */;
+ listener.allowConnection = () => true;
+ listener.encryption = true;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port
+ // encryption: false is the default
+ });
+ } catch(e) {
+ ok(true, "Client failed to connect as expected");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+add_task(function*() {
+ DebuggerServer.destroy();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/security/tests/unit/testactors.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ActorPool, appendExtraActors, createExtraActors } =
+ require("devtools/server/actors/common");
+const { RootActor } = require("devtools/server/actors/root");
+const { ThreadActor } = require("devtools/server/actors/script");
+const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
+
+var gTestGlobals = [];
+DebuggerServer.addTestGlobal = function(aGlobal) {
+ gTestGlobals.push(aGlobal);
+};
+
+// A mock tab list, for use by tests. This simply presents each global in
+// gTestGlobals as a tab, and the list is fixed: it never calls its
+// onListChanged handler.
+//
+// As implemented now, we consult gTestGlobals when we're constructed, not
+// when we're iterated over, so tests have to add their globals before the
+// root actor is created.
+function TestTabList(aConnection) {
+ this.conn = aConnection;
+
+ // An array of actors for each global added with
+ // DebuggerServer.addTestGlobal.
+ this._tabActors = [];
+
+ // A pool mapping those actors' names to the actors.
+ this._tabActorPool = new ActorPool(aConnection);
+
+ for (let global of gTestGlobals) {
+ let actor = new TestTabActor(aConnection, global);
+ actor.selected = false;
+ this._tabActors.push(actor);
+ this._tabActorPool.addActor(actor);
+ }
+ if (this._tabActors.length > 0) {
+ this._tabActors[0].selected = true;
+ }
+
+ aConnection.addActorPool(this._tabActorPool);
+}
+
+TestTabList.prototype = {
+ constructor: TestTabList,
+ getList: function () {
+ return promise.resolve([tabActor for (tabActor of this._tabActors)]);
+ }
+};
+
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ tabList: new TestTabList(aConnection),
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ return root;
+}
+
+function TestTabActor(aConnection, aGlobal) {
+ this.conn = aConnection;
+ this._global = aGlobal;
+ this._threadActor = new ThreadActor(this, this._global);
+ this.conn.addActor(this._threadActor);
+ this._attached = false;
+ this._extraActors = {};
+}
+
+TestTabActor.prototype = {
+ constructor: TestTabActor,
+ actorPrefix: "TestTabActor",
+
+ get window() {
+ return { wrappedJSObject: this._global };
+ },
+
+ get url() {
+ return this._global.__name;
+ },
+
+ form: function() {
+ let response = { actor: this.actorID, title: this._global.__name };
+
+ // Walk over tab actors added by extensions and add them to a new ActorPool.
+ let actorPool = new ActorPool(this.conn);
+ this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+ if (!actorPool.isEmpty()) {
+ this._tabActorPool = actorPool;
+ this.conn.addActorPool(this._tabActorPool);
+ }
+
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onAttach: function(aRequest) {
+ this._attached = true;
+
+ let response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onDetach: function(aRequest) {
+ if (!this._attached) {
+ return { "error":"wrongState" };
+ }
+ return { type: "detached" };
+ },
+
+ /* Support for DebuggerServer.addTabActor. */
+ _createExtraActors: createExtraActors,
+ _appendExtraActors: appendExtraActors
+};
+
+TestTabActor.prototype.requestTypes = {
+ "attach": TestTabActor.prototype.onAttach,
+ "detach": TestTabActor.prototype.onDetach
+};
+
+exports.register = function(handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function(handle) {
+ handle.setRootActor(null);
+};
--- a/toolkit/devtools/security/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/security/tests/unit/xpcshell.ini
@@ -1,6 +1,10 @@
[DEFAULT]
-head =
+head = head_dbg.js
tail =
skip-if = toolkit == 'android'
+support-files=
+ testactors.js
+
[test_cert.js]
+[test_encryption.js]
--- a/toolkit/devtools/transport/tests/unit/head_dbg.js
+++ b/toolkit/devtools/transport/tests/unit/head_dbg.js
@@ -7,16 +7,17 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const CC = Components.Constructor;
const { devtools } =
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { Promise: promise } =
Cu.import("resource://gre/modules/Promise.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const Services = devtools.require("Services");
const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
// We do not want to log packets by default, because in some tests,
// we can be sending large amounts of data. The test harness has
// trouble dealing with logging all the data, and we end up with
// intermittent time outs (e.g. bug 775924).
@@ -253,30 +254,30 @@ function writeTestTempFile(aFileName, aC
} while (aContent.length > 0);
} finally {
stream.close();
}
}
/*** Transport Factories ***/
-function socket_transport() {
+let socket_transport = Task.async(function*() {
if (!DebuggerServer.listeningSockets) {
let listener = DebuggerServer.createListener();
listener.portOrPath = -1 /* any available port */;
listener.allowConnection = () => true;
- listener.open();
+ yield listener.open();
}
let port = DebuggerServer._listeners[0].port;
do_print("Debugger server port is " + port);
- return DebuggerClient.socketConnect("127.0.0.1", port);
-}
+ return DebuggerClient.socketConnect({ host: "127.0.0.1", port });
+});
function local_transport() {
- return DebuggerServer.connectPipe();
+ return promise.resolve(DebuggerServer.connectPipe());
}
/*** Sample Data ***/
let gReallyLong;
function really_long() {
if (gReallyLong) {
return gReallyLong;
--- a/toolkit/devtools/transport/tests/unit/test_bulk_error.js
+++ b/toolkit/devtools/transport/tests/unit/test_bulk_error.js
@@ -46,33 +46,33 @@ TestBulkActor.prototype.requestTypes = {
};
function add_test_bulk_actor() {
DebuggerServer.addGlobalActor(TestBulkActor);
}
/*** Tests ***/
-function test_string_error(transportFactory, onReady) {
+let test_string_error = Task.async(function*(transportFactory, onReady) {
let deferred = promise.defer();
- let transport = transportFactory();
+ let transport = yield transportFactory();
let client = new DebuggerClient(transport);
client.connect((app, traits) => {
do_check_eq(traits.bulk, true);
client.listTabs(response => {
deferred.resolve(onReady(client, response).then(() => {
client.close();
transport.close();
}));
});
});
return deferred.promise;
-}
+});
/*** Reply Types ***/
function json_reply(client, response) {
let reallyLong = really_long();
let request = client.startBulkRequest({
actor: response.testBulk,
--- a/toolkit/devtools/transport/tests/unit/test_client_server_bulk.js
+++ b/toolkit/devtools/transport/tests/unit/test_client_server_bulk.js
@@ -129,26 +129,26 @@ let replyHandlers = {
});
return replyDeferred.promise;
}
};
/*** Tests ***/
-function test_bulk_request_cs(transportFactory, actorType, replyType) {
+let test_bulk_request_cs = Task.async(function*(transportFactory, actorType, replyType) {
// Ensure test files are not present from a failed run
cleanup_files();
writeTestTempFile("bulk-input", really_long());
let clientDeferred = promise.defer();
let serverDeferred = promise.defer();
let bulkCopyDeferred = promise.defer();
- let transport = transportFactory();
+ let transport = yield transportFactory();
let client = new DebuggerClient(transport);
client.connect((app, traits) => {
do_check_eq(traits.bulk, true);
client.listTabs(clientDeferred.resolve);
});
clientDeferred.promise.then(response => {
@@ -181,27 +181,27 @@ function test_bulk_request_cs(transportF
}
});
return promise.all([
clientDeferred.promise,
bulkCopyDeferred.promise,
serverDeferred.promise
]);
-}
+});
-function test_json_request_cs(transportFactory, actorType, replyType) {
+let test_json_request_cs = Task.async(function*(transportFactory, actorType, replyType) {
// Ensure test files are not present from a failed run
cleanup_files();
writeTestTempFile("bulk-input", really_long());
let clientDeferred = promise.defer();
let serverDeferred = promise.defer();
- let transport = transportFactory();
+ let transport = yield transportFactory();
let client = new DebuggerClient(transport);
client.connect((app, traits) => {
do_check_eq(traits.bulk, true);
client.listTabs(clientDeferred.resolve);
});
clientDeferred.promise.then(response => {
@@ -222,17 +222,17 @@ function test_json_request_cs(transportF
serverDeferred.resolve();
}
});
return promise.all([
clientDeferred.promise,
serverDeferred.promise
]);
-}
+});
/*** Test Utils ***/
function verify_files() {
let reallyLong = really_long();
let inputFile = getTestTempFile("bulk-input");
let outputFile = getTestTempFile("bulk-output");
--- a/toolkit/devtools/transport/tests/unit/test_dbgsocket.js
+++ b/toolkit/devtools/transport/tests/unit/test_dbgsocket.js
@@ -7,24 +7,24 @@ Cu.import("resource://gre/modules/devtoo
let gPort;
let gExtraListener;
function run_test()
{
do_print("Starting test at " + new Date().toTimeString());
initTestDebuggerServer();
- add_test(test_socket_conn);
- add_test(test_socket_shutdown);
+ add_task(test_socket_conn);
+ add_task(test_socket_shutdown);
add_test(test_pipe_conn);
run_next_test();
}
-function test_socket_conn()
+function* test_socket_conn()
{
do_check_eq(DebuggerServer.listeningSockets, 0);
let listener = DebuggerServer.createListener();
do_check_true(listener);
listener.portOrPath = -1 /* any available port */;
listener.allowConnection = () => true;
listener.open();
do_check_eq(DebuggerServer.listeningSockets, 1);
@@ -34,69 +34,69 @@ function test_socket_conn()
gExtraListener = DebuggerServer.createListener();
gExtraListener.portOrPath = -1;
gExtraListener.allowConnection = () => true;
gExtraListener.open();
do_check_eq(DebuggerServer.listeningSockets, 2);
do_print("Starting long and unicode tests at " + new Date().toTimeString());
let unicodeString = "(╯°□°)╯︵ ┻━┻";
- let transport = DebuggerClient.socketConnect("127.0.0.1", gPort);
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort
+ });
+ let closedDeferred = promise.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
// transport buffers and when transporting unicode...
transport.send({to: "root",
type: "echo",
reallylong: really_long(),
unicode: unicodeString});
do_check_eq(aPacket.from, "root");
},
onClosed: function(aStatus) {
- run_next_test();
+ closedDeferred.resolve();
},
};
transport.ready();
+ return closedDeferred.promise;
}
function test_socket_shutdown()
{
do_check_eq(DebuggerServer.listeningSockets, 2);
gExtraListener.close();
do_check_eq(DebuggerServer.listeningSockets, 1);
do_check_true(DebuggerServer.closeAllListeners());
do_check_eq(DebuggerServer.listeningSockets, 0);
// Make sure closing the listener twice does nothing.
do_check_false(DebuggerServer.closeAllListeners());
do_check_eq(DebuggerServer.listeningSockets, 0);
do_print("Connecting to a server socket at " + new Date().toTimeString());
- let transport = DebuggerClient.socketConnect("127.0.0.1", gPort);
- transport.hooks = {
- onPacket: function(aPacket) {
- // Shouldn't reach this, should never connect.
- do_check_true(false);
- },
+ try {
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort
+ });
+ } catch(e if e.result == Cr.NS_ERROR_CONNECTION_REFUSED ||
+ e.result == Cr.NS_ERROR_NET_TIMEOUT) {
+ // Expected failure
+ do_check_true(true);
+ return;
+ }
- onClosed: function(aStatus) {
- do_print("test_socket_shutdown onClosed called at " + new Date().toTimeString());
- // The connection should be refused here, but on slow or overloaded
- // machines it may just time out.
- let expected = [ Cr.NS_ERROR_CONNECTION_REFUSED, Cr.NS_ERROR_NET_TIMEOUT ];
- do_check_neq(expected.indexOf(aStatus), -1);
- run_next_test();
- }
- };
-
- do_print("Initializing input stream at " + new Date().toTimeString());
- transport.ready();
+ // Shouldn't reach this, should never connect.
+ do_check_true(false);
}
function test_pipe_conn()
{
let transport = DebuggerServer.connectPipe();
transport.hooks = {
onPacket: function(aPacket) {
do_check_eq(aPacket.from, "root");
--- a/toolkit/devtools/transport/tests/unit/test_dbgsocket_connection_drop.js
+++ b/toolkit/devtools/transport/tests/unit/test_dbgsocket_connection_drop.js
@@ -12,20 +12,20 @@ Cu.import("resource://gre/modules/devtoo
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
const { RawPacket } = devtools.require("devtools/toolkit/transport/packets");
function run_test() {
do_print("Starting test at " + new Date().toTimeString());
initTestDebuggerServer();
- add_test(test_socket_conn_drops_after_invalid_header);
- add_test(test_socket_conn_drops_after_invalid_header_2);
- add_test(test_socket_conn_drops_after_too_large_length);
- add_test(test_socket_conn_drops_after_too_long_header);
+ add_task(test_socket_conn_drops_after_invalid_header);
+ add_task(test_socket_conn_drops_after_invalid_header_2);
+ add_task(test_socket_conn_drops_after_too_large_length);
+ add_task(test_socket_conn_drops_after_too_long_header);
run_next_test();
}
function test_socket_conn_drops_after_invalid_header() {
return test_helper('fluff30:27:{"to":"root","type":"echo"}');
}
function test_socket_conn_drops_after_invalid_header_2() {
@@ -41,33 +41,38 @@ function test_socket_conn_drops_after_to
// The packet header is currently limited to no more than 200 bytes
let rawPacket = '4305724038957487634549823475894325';
for (let i = 0; i < 8; i++) {
rawPacket += rawPacket;
}
return test_helper(rawPacket + ':');
}
-function test_helper(payload) {
+let test_helper = Task.async(function*(payload) {
let listener = DebuggerServer.createListener();
listener.portOrPath = -1;
listener.allowConnection = () => true;
listener.open();
- let transport = DebuggerClient.socketConnect("127.0.0.1", listener.port);
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port
+ });
+ let closedDeferred = promise.defer();
transport.hooks = {
onPacket: function(aPacket) {
this.onPacket = function(aPacket) {
do_throw(new Error("This connection should be dropped."));
transport.close();
};
// Inject the payload directly into the stream.
transport._outgoing.push(new RawPacket(transport, payload));
transport._flushOutgoing();
},
onClosed: function(aStatus) {
do_check_true(true);
- run_next_test();
+ closedDeferred.resolve();
},
};
transport.ready();
-}
+ return closedDeferred.promise;
+});
--- a/toolkit/devtools/transport/tests/unit/test_no_bulk.js
+++ b/toolkit/devtools/transport/tests/unit/test_no_bulk.js
@@ -21,28 +21,28 @@ function run_test() {
DebuggerServer.destroy();
});
run_next_test();
}
/*** Tests ***/
-function test_bulk_send_error(transportFactory) {
+let test_bulk_send_error = Task.async(function*(transportFactory) {
let deferred = promise.defer();
- let transport = transportFactory();
+ let transport = yield transportFactory();
let client = new DebuggerClient(transport);
client.connect((app, traits) => {
do_check_false(traits.bulk);
try {
client.startBulkRequest();
do_throw(new Error("Can't use bulk since server doesn't support it"));
} catch(e) {
do_check_true(true);
}
deferred.resolve();
});
return deferred.promise;
-}
+});
--- a/toolkit/devtools/transport/tests/unit/test_queue.js
+++ b/toolkit/devtools/transport/tests/unit/test_queue.js
@@ -20,28 +20,28 @@ function run_test() {
DebuggerServer.destroy();
});
run_next_test();
}
/*** Tests ***/
-function test_transport(transportFactory) {
+let test_transport = Task.async(function*(transportFactory) {
let clientDeferred = promise.defer();
let serverDeferred = promise.defer();
// Ensure test files are not present from a failed run
cleanup_files();
let reallyLong = really_long();
writeTestTempFile("bulk-input", reallyLong);
do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
- let transport = transportFactory();
+ let transport = yield transportFactory();
// Sending from client to server
function write_data({copyFrom}) {
NetUtil.asyncFetch(getTestTempFile("bulk-input"), function(input, status) {
copyFrom(input).then(() => {
input.close();
});
});
@@ -128,17 +128,17 @@ function test_transport(transportFactory
onClosed: function() {
do_throw("Transport closed before we expected");
}
};
transport.ready();
return promise.all([clientDeferred.promise, serverDeferred.promise]);
-}
+});
/*** Test Utils ***/
function verify() {
let reallyLong = really_long();
let inputFile = getTestTempFile("bulk-input");
let outputFile = getTestTempFile("bulk-output");
--- a/toolkit/devtools/transport/tests/unit/test_transport_bulk.js
+++ b/toolkit/devtools/transport/tests/unit/test_transport_bulk.js
@@ -18,30 +18,30 @@ function run_test() {
run_next_test();
}
/*** Tests ***/
/**
* This tests a one-way bulk transfer at the transport layer.
*/
-function test_bulk_transfer_transport(transportFactory) {
+let test_bulk_transfer_transport = Task.async(function*(transportFactory) {
do_print("Starting bulk transfer test at " + new Date().toTimeString());
let clientDeferred = promise.defer();
let serverDeferred = promise.defer();
// Ensure test files are not present from a failed run
cleanup_files();
let reallyLong = really_long();
writeTestTempFile("bulk-input", reallyLong);
do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
- let transport = transportFactory();
+ let transport = yield transportFactory();
// Sending from client to server
function write_data({copyFrom}) {
NetUtil.asyncFetch(getTestTempFile("bulk-input"), function(input, status) {
copyFrom(input).then(() => {
input.close();
});
});
@@ -99,17 +99,17 @@ function test_bulk_transfer_transport(tr
onClosed: function() {
do_throw("Transport closed before we expected");
}
};
transport.ready();
return promise.all([clientDeferred.promise, serverDeferred.promise]);
-}
+});
/*** Test Utils ***/
function verify() {
let reallyLong = really_long();
let inputFile = getTestTempFile("bulk-input");
let outputFile = getTestTempFile("bulk-output");