Bug 1059001 - Part 3: Add encryption socket option. r=past
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 10 Dec 2014 20:55:52 -0600
changeset 219132 40e8d75da54f24fc2f48361bdb5760d570f4d993
parent 219131 20a3c5ad55c69e9c2f6de60066a3e3951ce15832
child 219133 5fc8a787af0fcd015c0e883232920bec5c7736c7
push id27956
push userkwierso@gmail.com
push dateFri, 12 Dec 2014 00:47:19 +0000
treeherdermozilla-central@32a2c5bd2f68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast
bugs1059001
milestone37.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 1059001 - Part 3: Add encryption socket option. r=past
b2g/chrome/content/devtools/debugger.js
browser/devtools/framework/connect/connect.js
browser/devtools/framework/toolbox-process-window.js
modules/libpref/init/all.js
toolkit/devtools/apps/tests/debugger-protocol-helper.js
toolkit/devtools/client/connection-manager.js
toolkit/devtools/client/dbg-client.jsm
toolkit/devtools/gcli/source/lib/gcli/connectors/rdp.js
toolkit/devtools/security/cert.js
toolkit/devtools/security/moz.build
toolkit/devtools/security/socket.js
toolkit/devtools/security/tests/unit/head_dbg.js
toolkit/devtools/security/tests/unit/test_cert.js
toolkit/devtools/security/tests/unit/test_encryption.js
toolkit/devtools/security/tests/unit/testactors.js
toolkit/devtools/security/tests/unit/xpcshell.ini
toolkit/devtools/transport/tests/unit/head_dbg.js
toolkit/devtools/transport/tests/unit/test_bulk_error.js
toolkit/devtools/transport/tests/unit/test_client_server_bulk.js
toolkit/devtools/transport/tests/unit/test_dbgsocket.js
toolkit/devtools/transport/tests/unit/test_dbgsocket_connection_drop.js
toolkit/devtools/transport/tests/unit/test_no_bulk.js
toolkit/devtools/transport/tests/unit/test_queue.js
toolkit/devtools/transport/tests/unit/test_transport_bulk.js
--- 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,51 +36,55 @@ 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");
 
   let host = document.getElementById("host").value;
   let port = document.getElementById("port").value;
 
   // Save the host/port values
   try {
     Services.prefs.setCharPref("devtools.debugger.remote-host", host);
     Services.prefs.setIntPref("devtools.debugger.remote-port", port);
   } catch(e) {
     // Fails in e10s mode, but not a critical feature.
   }
 
   // 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
@@ -794,16 +794,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,66 @@
+/* -*- 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", () => {
+  // Ensure PSM is initialized to support TLS sockets
+  Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+  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,212 @@
 /* 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 }) {
+  let attempt = yield _attemptTransport({ host, port, encryption });
+  if (attempt.transport) {
+    return attempt.transport; // Success
+  }
+
+  // If the server cert failed validation, store a temporary override and make
+  // a second attempt.
+  if (encryption && attempt.certError) {
+    _storeCertOverride(attempt.s, host, port);
+  } else {
+    throw new Error("Connection failed");
+  }
+
+  attempt = yield _attemptTransport({ host, port, encryption });
+  if (attempt.transport) {
+    return attempt.transport; // Success
+  }
+
+  throw new Error("Connection failed even after cert override");
+});
+
+/**
+ * Try to connect and create a DevTools transport.
+ *
+ * @return transport DebuggerTransport
+ *         A possible DevTools transport (if connection succeeded and streams
+ *         are actually alive and working)
+ * @return certError boolean
+ *         Flag noting if cert trouble caused the streams to fail
+ * @return s nsISocketTransport
+ *         Underlying socket transport, in case more details are needed.
+ */
+let _attemptTransport = 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);
+
+  let transport;
+  if (alive) {
+    transport = new DebuggerTransport(input, output);
+  } else {
+    // Something went wrong, close the streams.
+    input.close();
+    output.close();
+  }
+
+  return { transport, certError, s };
+});
+
+/**
+ * Try to connect to a remote server socket.
+ *
+ * If successsful, the socket transport and its opened streams are returned.
+ * Typically, this will only fail if the host / port is unreachable.  Other
+ * problems, such as security errors, will allow this stage to succeed, but then
+ * fail later when the streams are actually used.
+ * @return s nsISocketTransport
+ *         Underlying socket transport, in case more details are needed.
+ * @return input nsIAsyncInputStream
+ *         The socket's input stream.
+ * @return output nsIAsyncOutputStream
+ *         The socket's output stream.
+ */
+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);
+    DevToolsUtils.reportException("_attemptConnect", 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.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 +265,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,71 +294,103 @@ 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);
-      if (port) {
-        this._socket = new ServerSocket(port, flags, backlog);
+      self._socket = self._createSocketInstance();
+      if (self.isPortBased) {
+        let port = Number(self.portOrPath);
+        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: " + (self.port || self.portOrPath));
+    }).then(() => {
+      if (this.discoverable && this.port) {
+        discovery.addService("devtools", { port: this.port });
+      }
+    }).catch(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");
     }
     if (this._socket) {
       this._socket.close();
       this._socket = null;
     }
     DebuggerServer._removeListener(this);
   },
 
   /**
+   * Gets whether this listener uses a port number vs. a path.
+   */
+  get isPortBased() {
+    return !!Number(this.portOrPath);
+  },
+
+  /**
    * Gets the port that a TCP socket listener is listening on, or null if this
    * is not a TCP socket (so there is no port).
    */
   get port() {
-    if (!this._socket) {
+    if (!this.isPortBased || !this._socket) {
       return null;
     }
     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 +400,68 @@ 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.destroy(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.destroy(Cr.NS_ERROR_CONNECTION_REFUSED);
+    }
+  },
+
+  destroy(result) {
+    clearTimeout(this._handshakeTimeout);
+    let connectionInfo = this.socketTransport.securityInfo
+                         .QueryInterface(Ci.nsITLSServerConnectionInfo);
+    connectionInfo.setSecurityObserver(null);
+    this.socketTransport.close(result);
+    this.socketTransport = null;
   }
+
 };
+
+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,107 @@
+/* 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");
+const xpcInspector = devtools.require("xpcInspector");
+
+// 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 (xpcInspector.eventLoopNestLevel > 0) {
+      xpcInspector.exitNestedEventLoop();
+    }
+
+    // Print in most cases, but ignore the "strict" messages
+    if (!(aMessage.flags & Ci.nsIScriptError.strictFlag)) {
+      do_print("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,98 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test basic functionality of DevTools client and server TLS encryption mode
+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);
+  let onUnexpectedClose = () => {
+    do_throw("Closed unexpectedly");
+  };
+  client.addListener("closed", onUnexpectedClose);
+  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");
+
+  client.removeListener("closed", onUnexpectedClose);
+  transport.close();
+  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");
+    listener.close();
+    equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+    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,70 @@ 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()
+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) {
+    // The connection should be refused here, but on slow or overloaded
+    // machines it may just time out.
+    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");