Bug 733573 - Expose a client TCP socket API to web applications [r=honzab,fabrice]
authorDonovan Preston <dpreston@mozilla.com>
Tue, 21 Aug 2012 09:46:27 -0700
changeset 102988 3764a9891e74
parent 102987 ec5685278f82
child 102989 ea6a5e9b25db
push id23319
push useremorley@mozilla.com
push date2012-08-22 09:28 +0000
Treeherderresults
reviewershonzab, fabrice
bugs733573
milestone17.0a1
Bug 733573 - Expose a client TCP socket API to web applications [r=honzab,fabrice]
b2g/app/b2g.js
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
dom/network/interfaces/Makefile.in
dom/network/interfaces/nsIDOMTCPSocket.idl
dom/network/src/Makefile.in
dom/network/src/TCPSocket.js
dom/network/src/TCPSocket.manifest
dom/network/tests/Makefile.in
dom/network/tests/test_tcpsocket_default_permissions.html
dom/network/tests/test_tcpsocket_enabled_no_perm.html
dom/network/tests/test_tcpsocket_enabled_with_perm.html
dom/network/tests/unit/test_tcpsocket.js
dom/network/tests/unit/xpcshell.ini
dom/tests/mochitest/general/test_interfaces.html
mobile/android/installer/package-manifest.in
mobile/xul/installer/package-manifest.in
testing/xpcshell/xpcshell.ini
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -391,16 +391,19 @@ pref("dom.mozAlarms.enabled", true);
 
 // WebSettings
 pref("dom.mozSettings.enabled", true);
 
 // controls if we want camera support
 pref("device.camera.enabled", true);
 pref("media.realtime_decoder.enabled", true);
 
+// TCPSocket
+pref("dom.mozTCPSocket.enabled", true);
+
 // "Preview" landing of bug 710563, which is bogged down in analysis
 // of talos regression.  This is a needed change for higher-framerate
 // CSS animations, and incidentally works around an apparent bug in
 // our handling of requestAnimationFrame() listeners, which are
 // supposed to enable this REPEATING_PRECISE_CAN_SKIP behavior.  The
 // secondary bug isn't really worth investigating since it's obseleted
 // by bug 710563.
 pref("layout.frame_rate.precise", true);
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -479,16 +479,19 @@
 @BINPATH@/components/SystemMessageManager.manifest
 
 @BINPATH@/components/Activities.manifest
 @BINPATH@/components/ActivityOptions.js
 @BINPATH@/components/ActivityProxy.js
 @BINPATH@/components/ActivityRequestHandler.js
 @BINPATH@/components/ActivityWrapper.js
 
+@BINPATH@/components/TCPSocket.js
+@BINPATH@/components/TCPSocket.manifest
+
 @BINPATH@/components/AppProtocolHandler.js
 @BINPATH@/components/AppProtocolHandler.manifest
 
 ; Modules
 @BINPATH@/modules/*
 
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -479,16 +479,19 @@
 @BINPATH@/components/nsDOMIdentity.js
 @BINPATH@/components/nsIDService.js
 @BINPATH@/components/Identity.manifest
 
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/AlarmsManager.js
 @BINPATH@/components/AlarmsManager.manifest
+@BINPATH@/components/TCPSocket.js
+@BINPATH@/components/TCPSocket.manifest
+
 #ifdef ENABLE_MARIONETTE
 @BINPATH@/chrome/marionette@JAREXT@
 @BINPATH@/chrome/marionette.manifest
 @BINPATH@/components/MarionetteComponents.manifest
 @BINPATH@/components/marionettecomponent.js
 #endif
 
 ; Modules
--- a/dom/network/interfaces/Makefile.in
+++ b/dom/network/interfaces/Makefile.in
@@ -14,11 +14,12 @@ XPIDL_MODULE = dom_network
 include $(topsrcdir)/dom/dom-config.mk
 
 XPIDLSRCS = \
   nsIDOMNavigatorNetwork.idl \
   nsIDOMConnection.idl \
   nsIDOMMobileConnection.idl \
   nsIMobileConnectionProvider.idl \
   nsIDOMUSSDReceivedEvent.idl \
+  nsIDOMTCPSocket.idl \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -0,0 +1,219 @@
+/* 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/. */
+
+/**
+ * MozTCPSocket exposes a TCP client socket (no server sockets yet)
+ * to highly privileged apps. It provides a buffered, non-blocking
+ * interface for sending. For receiving, it uses an asynchronous,
+ * event handler based interface.
+ */
+
+#include "domstubs.idl"
+#include "nsIDOMEvent.idl"
+
+// Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
+// nsITCPSocket should be an nsIEventTarget but js objects
+// cannot be an nsIEventTarget yet
+// #include "nsIEventTarget.idl"
+
+// Bug 723206 - Constructors implemented in JS from IDL should be
+//              allowed to have arguments
+//
+//  Once bug 723206 will be fixed, this method could be replaced by
+//  arguments when instantiating a TCPSocket object. For example it will
+//  be possible to do (similarly to the WebSocket API):
+//    var s = new MozTCPSocket(host, port); 
+
+[scriptable, uuid(b82e17da-6476-11e1-8813-57a2ffe9e42c)]
+interface nsIDOMTCPSocket : nsISupports
+{
+  /**
+   * Create and return a socket object which will attempt to connect to
+   * the given host and port.
+   *
+   * @param host The hostname of the server to connect to.
+   * @param port The port to connect to.
+   * @param options An object specifying one or more parameters which
+   *                determine the details of the socket.
+   *
+   *        useSSL: true to create an SSL socket. Defaults to false.
+   *
+   *        binaryType: "arraybuffer" to use UInt8 array
+   *          instances in the ondata callback and as the argument
+   *          to send. Defaults to "string", to use JavaScript strings.
+   *
+   * @return The new TCPSocket instance.
+   */
+  nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
+
+  /**
+   * The host of this socket object.
+   */
+  readonly attribute DOMString host;
+
+  /**
+   * The port of this socket object.
+   */
+  readonly attribute unsigned short port;
+
+  /**
+   * True if this socket object is an SSL socket.
+   */
+  readonly attribute boolean ssl;
+
+  /**
+   * The number of bytes which have previously been buffered by calls to
+   * send on this socket.
+   */
+  readonly attribute unsigned long bufferedAmount;
+
+  /**
+   * Pause reading incoming data and invocations of the ondata handler until
+   * resume is called.
+   */
+  void suspend();
+
+  /**
+   * Resume reading incoming data and invoking ondata as usual.
+   */
+  void resume();
+
+  /**
+   * Close the socket.
+   */
+  void close();
+
+  /**
+   * Write data to the socket.
+   *
+   * @param data The data to write to the socket. If
+   *             binaryType: "arraybuffer" was passed in the options
+   *             object, then this object should be an Uint8Array instance.
+   *             If binaryType: "string" was passed, or if no binaryType
+   *             option was specified, then this object should be an
+   *             ordinary JavaScript string.
+   *
+   * @return Send returns true or false as a hint to the caller that
+   *         they may either continue sending more data immediately, or
+   *         may want to wait until the other side has read some of the
+   *         data which has already been written to the socket before
+   *         buffering more. If send returns true, then less than 64k
+   *         has been buffered and it's safe to immediately write more.
+   *         If send returns false, then more than 64k has been buffered,
+   *         and the caller may wish to wait until the ondrain event
+   *         handler has been called before buffering more data by more
+   *         calls to send.
+   */
+  boolean send(in jsval data);
+
+  /**
+   * The readyState attribute indicates which state the socket is currently
+   * in. The state will be either CONNECTING, OPEN, CLOSING, or CLOSED.
+   */
+  readonly attribute DOMString readyState;
+  readonly attribute DOMString CONNECTING;
+  readonly attribute DOMString OPEN;
+  readonly attribute DOMString CLOSING;
+  readonly attribute DOMString CLOSED;
+
+  /**
+   * The binaryType attribute indicates which mode this socket uses for
+   * sending and receiving data. If the binaryType: "arraybuffer" option
+   * was passed to the open method that created this socket, binaryType
+   * will be "arraybuffer". Otherwise, it will be "string".
+   */
+  readonly attribute DOMString binaryType;
+
+  /**
+   * The onopen event handler is called when the connection to the server
+   * has been established. If the connection is refused, onerror will be
+   * called, instead.
+   */
+  attribute jsval onopen;
+
+  /**
+   * After send has buffered more than 64k of data, it returns false to
+   * indicate that the client should pause before sending more data, to
+   * avoid accumulating large buffers. This is only advisory, and the client
+   * is free to ignore it and buffer as much data as desired, but if reducing
+   * the size of buffers is important (especially for a streaming application)
+   * ondrain will be called once the previously-buffered data has been written
+   * to the network, at which point the client can resume calling send again.
+   */
+  attribute jsval ondrain;
+
+  /**
+   * The ondata handler will be called repeatedly and asynchronously after
+   * onopen has been called, every time some data was available from the server
+   * and was read. If binaryType: "arraybuffer" was passed to open, the data
+   * attribute of the event object will be an Uint8Array. If not, it will be a
+   * normal JavaScript string.
+   *
+   * At any time, the client may choose to pause reading and receiving ondata
+   * callbacks, by calling the socket's suspend() method. Further invocations
+   * of ondata will be paused until resume() is called.
+   */
+  attribute jsval ondata;
+
+  /**
+   * The onerror handler will be called when there is an error. The data
+   * attribute of the event passed to the onerror handler will have a
+   * description of the kind of error.
+   *
+   * If onerror is called before onopen, the error was connection refused,
+   * and onclose will not be called. If onerror is called after onopen,
+   * the connection was lost, and onclose will be called after onerror.
+   */
+  attribute jsval onerror;
+
+  /**
+   * The onclose handler is called once the underlying network socket
+   * has been closed, either by the server, or by the client calling
+   * close.
+   *
+   * If onerror was not called before onclose, then either side cleanly
+   * closed the connection.
+   */
+  attribute jsval onclose;
+};
+
+/**
+ * nsITCPSocketEvent is the event object which is passed as the
+ * first argument to all the event handler callbacks. It contains
+ * the socket that was associated with the event, the type of event,
+ * and the data associated with the event (if any).
+ */
+
+[scriptable, uuid(0f2abcca-b483-4539-a3e8-345707f75c44)]
+interface nsITCPSocketEvent : nsISupports {
+  /**
+   * The socket object which produced this event.
+   */
+  readonly attribute nsIDOMTCPSocket socket;
+
+  /**
+   * The type of this event. One of:
+   *
+   * onopen
+   * onerror
+   * ondata
+   * ondrain
+   * onclose
+   */
+  readonly attribute DOMString type;
+
+  /**
+   * The data related to this event, if any. In the ondata callback,
+   * data will be the bytes read from the network; if the binaryType
+   * of the socket was "arraybuffer", this value will be of type Uint8Array;
+   * otherwise, it will be a normal JavaScript string.
+   *
+   * In the onerror callback, data will be a string with a description
+   * of the error.
+   *
+   * In the other callbacks, data will be an empty string.
+   */
+  readonly attribute jsval data;
+};
+
--- a/dom/network/src/Makefile.in
+++ b/dom/network/src/Makefile.in
@@ -8,16 +8,21 @@ srcdir           = @srcdir@
 VPATH            = $(srcdir)
 
 include $(DEPTH)/config/autoconf.mk
 
 LIBRARY_NAME     = dom_network_s
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
+EXTRA_COMPONENTS = \
+	TCPSocket.js \
+	TCPSocket.manifest \
+	$(NULL)
+
 include $(topsrcdir)/dom/dom-config.mk
 
 EXPORTS_NAMESPACES = mozilla/dom/network
 
 EXPORTS_mozilla/dom/network = \
   Utils.h \
   Types.h \
   Constants.h \
new file mode 100644
--- /dev/null
+++ b/dom/network/src/TCPSocket.js
@@ -0,0 +1,556 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+const CC = Components.Constructor;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const InputStreamPump = CC(
+        "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"),
+      AsyncStreamCopier = CC(
+        "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"),
+      ScriptableInputStream = CC(
+        "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
+      BinaryInputStream = CC(
+        "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"),
+      StringInputStream = CC(
+        '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'),
+      MultiplexInputStream = CC(
+        '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
+
+const kCONNECTING = 'connecting';
+const kOPEN = 'open';
+const kCLOSING = 'closing';
+const kCLOSED = 'closed';
+
+const BUFFER_SIZE = 65536;
+
+/*
+ * Debug logging function
+ */
+
+let debug = true;
+function LOG(msg) {
+  if (debug)
+    dump("TCPSocket: " + msg + "\n");
+}
+
+/*
+ * nsITCPSocketEvent object
+ */
+
+function TCPSocketEvent(type, sock, data) {
+  this._type = type;
+  this._socket = sock;
+  this._data = data;
+}
+
+TCPSocketEvent.prototype = {
+  __exposedProps__: {
+    type: 'r',
+    socket: 'r',
+    data: 'r'
+  },
+  get type() {
+    return this._type;
+  },
+  get socket() {
+    return this._socket;
+  },
+  get data() {
+    return this._data;
+  }
+}
+
+/*
+ * nsIDOMTCPSocket object
+ */
+
+function TCPSocket() {
+  this._readyState = kCLOSED;
+
+  this._onopen = null;
+  this._ondrain = null;
+  this._ondata = null;
+  this._onerror = null;
+  this._onclose = null;
+
+  this._binaryType = "string";
+
+  this._host = "";
+  this._port = 0;
+  this._ssl = false;
+}
+
+TCPSocket.prototype = {
+  __exposedProps__: {
+    open: 'r',
+    host: 'r',
+    port: 'r',
+    ssl: 'r',
+    bufferedAmount: 'r',
+    suspend: 'r',
+    resume: 'r',
+    close: 'r',
+    send: 'r',
+    readyState: 'r',
+    CONNECTING: 'r',
+    OPEN: 'r',
+    CLOSING: 'r',
+    CLOSED: 'r',
+    binaryType: 'r',
+    onopen: 'rw',
+    ondrain: 'rw',
+    ondata: 'rw',
+    onerror: 'rw',
+    onclose: 'rw'
+  },
+  // Constants
+  CONNECTING: kCONNECTING,
+  OPEN: kOPEN,
+  CLOSING: kCLOSING,
+  CLOSED: kCLOSED,
+
+  // The binary type, "string" or "arraybuffer"
+  _binaryType: null,
+
+  // Internal
+  _hasPrivileges: null,
+
+  // Raw socket streams
+  _transport: null,
+  _socketInputStream: null,
+  _socketOutputStream: null,
+
+  // Input stream machinery
+  _inputStreamPump: null,
+  _inputStreamScriptable: null,
+  _inputStreamBinary: null,
+
+  // Output stream machinery
+  _multiplexStream: null,
+  _multiplexStreamCopier: null,
+
+  _asyncCopierActive: false,
+  _waitingForDrain: false,
+  _suspendCount: 0,
+
+  // Public accessors.
+  get readyState() {
+    return this._readyState;
+  },
+  get binaryType() {
+    return this._binaryType;
+  },
+  get host() {
+    return this._host;
+  },
+  get port() {
+    return this._port;
+  },
+  get ssl() {
+    return this._ssl;
+  },
+  get bufferedAmount() {
+    return this._multiplexStream.available();
+  },
+  get onopen() {
+    return this._onopen;
+  },
+  set onopen(f) {
+    this._onopen = f;
+  },
+  get ondrain() {
+    return this._ondrain;
+  },
+  set ondrain(f) {
+    this._ondrain = f;
+  },
+  get ondata() {
+    return this._ondata;
+  },
+  set ondata(f) {
+    this._ondata = f;
+  },
+  get onerror() {
+    return this._onerror;
+  },
+  set onerror(f) {
+    this._onerror = f;
+  },
+  get onclose() {
+    return this._onclose;
+  },
+  set onclose(f) {
+    this._onclose = f;
+  },
+
+  // Helper methods.
+  _createTransport: function ts_createTransport(host, port, sslMode) {
+    let options, optlen;
+    if (sslMode) {
+      options = [sslMode];
+      optlen = 1;
+    } else {
+      options = null;
+      optlen = 0;
+    }
+    return Cc["@mozilla.org/network/socket-transport-service;1"]
+             .getService(Ci.nsISocketTransportService)
+             .createTransport(options, optlen, host, port, null);
+  },
+
+  _ensureCopying: function ts_ensureCopying() {
+    let self = this;
+    if (this._asyncCopierActive) {
+      return;
+    }
+    this._asyncCopierActive = true;
+    this._multiplexStreamCopier.asyncCopy({
+      onStartRequest: function ts_output_onStartRequest() {
+      },
+      onStopRequest: function ts_output_onStopRequest(request, context, status) {
+        self._asyncCopierActive = false;
+        self._multiplexStream.removeStream(0);
+
+        if (status) {
+          this._readyState = kCLOSED;
+          let err = new Error("Connection closed while writing: " + status);
+          err.status = status;
+          this.callListener("onerror", err);
+          this.callListener("onclose");
+          return;
+        }
+
+        if (self._multiplexStream.count) {
+          self._ensureCopying();
+        } else {
+          if (self._waitingForDrain) {
+            self._waitingForDrain = false;
+            self.callListener("ondrain");
+          }
+          if (self._readyState === kCLOSING) {
+            self._socketOutputStream.close();
+            self._readyState = kCLOSED;
+            self.callListener("onclose");
+          }
+        }
+      }
+    }, null);
+  },
+
+  callListener: function ts_callListener(type, data) {
+    if (!this[type])
+      return;
+
+    this[type].call(null, new TCPSocketEvent(type, this, data || ""));
+  },
+
+  init: function ts_init(aWindow) {
+    if (!Services.prefs.getBoolPref("dom.mozTCPSocket.enabled"))
+      return null;
+
+    let principal = aWindow.document.nodePrincipal;
+    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                   .getService(Ci.nsIScriptSecurityManager);
+
+    let perm = principal == secMan.getSystemPrincipal()
+                 ? Ci.nsIPermissionManager.ALLOW_ACTION
+                 : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket");
+
+    this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
+
+    let util = aWindow.QueryInterface(
+      Ci.nsIInterfaceRequestor
+    ).getInterface(Ci.nsIDOMWindowUtils);
+
+    this.innerWindowID = util.currentInnerWindowID;
+    LOG("window init: " + this.innerWindowID);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "inner-window-destroyed") {
+      let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+      if (wId == this.innerWindowID) {
+        LOG("inner-window-destroyed: " + this.innerWindowID);
+
+        // This window is now dead, so we want to clear the callbacks
+        // so that we don't get a "can't access dead object" when the
+        // underlying stream goes to tell us that we are closed
+        this.onopen = null;
+        this.ondrain = null;
+        this.ondata = null;
+        this.onerror = null;
+        this.onclose = null;
+
+        // Clean up our socket
+        this.close();
+      }
+    }
+  },
+
+  // nsIDOMTCPSocket
+  open: function ts_open(host, port, options) {
+    // in the testing case, init won't be called and
+    // hasPrivileges will be null. We want to proceed to test.
+    if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
+      throw new Error("TCPSocket does not have permission in this context.\n");
+    }
+    let that = new TCPSocket();
+
+    that.innerWindowID = this.innerWindowID;
+
+    LOG("window init: " + that.innerWindowID);
+    Services.obs.addObserver(that, "inner-window-destroyed", true);
+
+    LOG("startup called\n");
+    LOG("Host info: " + host + ":" + port + "\n");
+
+    that._readyState = kCONNECTING;
+    that._host = host;
+    that._port = port;
+    if (options !== undefined) {
+      if (options.useSSL) {
+          that._ssl = 'ssl';
+      } else {
+          that._ssl = false;
+      }
+      that._binaryType = options.binaryType || that._binaryType;
+    }
+
+    LOG("SSL: " + that.ssl + "\n");
+
+    let transport = that._transport = this._createTransport(host, port, that._ssl);
+    transport.setEventSink(that, Services.tm.currentThread);
+    transport.securityCallbacks = new SecurityCallbacks(that);
+
+    that._socketInputStream = transport.openInputStream(0, 0, 0);
+    that._socketOutputStream = transport.openOutputStream(
+      Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
+
+    // If the other side is not listening, we will
+    // get an onInputStreamReady callback where available
+    // raises to indicate the connection was refused.
+    that._socketInputStream.asyncWait(
+      that, that._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
+
+    if (that._binaryType === "arraybuffer") {
+      that._inputStreamBinary = new BinaryInputStream(that._socketInputStream);
+    } else {
+      that._inputStreamScriptable = new ScriptableInputStream(that._socketInputStream);
+    }
+
+    that._multiplexStream = new MultiplexInputStream();
+
+    that._multiplexStreamCopier = new AsyncStreamCopier(
+      that._multiplexStream,
+      that._socketOutputStream,
+      // (nsSocketTransport uses gSocketTransportService)
+      Cc["@mozilla.org/network/socket-transport-service;1"]
+        .getService(Ci.nsIEventTarget),
+      /* source buffered */ true, /* sink buffered */ false,
+      BUFFER_SIZE, /* close source*/ false, /* close sink */ false);
+
+    return that;
+  },
+
+  close: function ts_close() {
+    if (this._readyState === kCLOSED || this._readyState === kCLOSING)
+      return;
+
+    LOG("close called\n");
+    this._readyState = kCLOSING;
+
+    if (!this._multiplexStream.count) {
+      this._socketOutputStream.close();
+    }
+    this._socketInputStream.close();
+  },
+
+  send: function ts_send(data) {
+    if (this._readyState !== kOPEN) {
+      throw new Error("Socket not open.");
+    }
+
+    let new_stream = new StringInputStream();
+    if (this._binaryType === "arraybuffer") {
+      // It would be really nice if there were an interface
+      // that took an ArrayBuffer like StringInputStream takes
+      // a string. There is one, but only in C++ and not exposed
+      // to js as far as I can tell
+      var dataLen = data.length;
+      var offset = 0;
+      var result = "";
+      while (dataLen) {
+        var fragmentLen = dataLen;
+        if (fragmentLen > 32768)
+          fragmentLen = 32768;
+        dataLen -= fragmentLen;
+
+        var fragment = data.subarray(offset, offset + fragmentLen);
+        offset += fragmentLen;
+        result += String.fromCharCode.apply(null, fragment);
+      }
+      data = result;
+    }
+    var newBufferedAmount = this.bufferedAmount + data.length;
+    new_stream.setData(data, data.length);
+    this._multiplexStream.appendStream(new_stream);
+
+    if (newBufferedAmount >= BUFFER_SIZE) {
+      // If we buffered more than some arbitrary amount of data,
+      // (65535 right now) we should tell the caller so they can
+      // wait until ondrain is called if they so desire. Once all the
+      //buffered data has been written to the socket, ondrain is
+      // called.
+      this._waitingForDrain = true;
+    }
+
+    this._ensureCopying();
+    return newBufferedAmount < BUFFER_SIZE;
+  },
+
+  suspend: function ts_suspend() {
+    if (this._inputStreamPump) {
+      this._inputStreamPump.suspend();
+    } else {
+      ++this._suspendCount;
+    }
+  },
+
+  resume: function ts_resume() {
+    if (this._inputStreamPump) {
+      this._inputStreamPump.resume();
+    } else {
+      --this._suspendCount;
+    }
+  },
+
+  // nsITransportEventSink (Triggered by transport.setEventSink)
+  onTransportStatus: function ts_onTransportStatus(
+    transport, status, progress, max) {
+
+    if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+      this._readyState = kOPEN;
+      this.callListener("onopen");
+
+      this._inputStreamPump = new InputStreamPump(
+        this._socketInputStream, -1, -1, 0, 0, false
+      );
+
+      while (this._suspendCount--) {
+        this._inputStreamPump.suspend();
+      }
+
+      this._inputStreamPump.asyncRead(this, null);
+    }
+  },
+
+  // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait)
+  // Only used for detecting connection refused
+  onInputStreamReady: function ts_onInputStreamReady(input) {
+    try {
+      input.available();
+    } catch (e) {
+      this.callListener("onerror", new Error("Connection refused"));
+    }
+  },
+
+  // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
+  onStartRequest: function ts_onStartRequest(request, context) {
+  },
+
+  // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
+  onStopRequest: function ts_onStopRequest(request, context, status) {
+    let buffered_output = this._multiplexStream.count !== 0;
+
+    this._inputStreamPump = null;
+
+    if (buffered_output && !status) {
+      // If we have some buffered output still, and status is not an
+      // error, the other side has done a half-close, but we don't 
+      // want to be in the close state until we are done sending
+      // everything that was buffered. We also don't want to call onclose
+      // yet.
+      return;
+    }
+
+    this._readyState = kCLOSED;
+
+    if (status) {
+      let err = new Error("Connection closed: " + status);
+      err.status = status;
+      this.callListener("onerror", err);
+    }
+
+    this.callListener("onclose");
+  },
+
+  // nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
+  onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
+    if (this._binaryType === "arraybuffer") {
+      let ua = new Uint8Array(count);
+      ua.set(this._inputStreamBinary.readByteArray(count));
+      this.callListener("ondata", ua);
+    } else {
+      this.callListener("ondata", this._inputStreamScriptable.read(count));
+    }
+  },
+
+  classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
+
+  classInfo: XPCOMUtils.generateCI({
+    classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
+    contractID: "@mozilla.org/tcp-socket;1",
+    classDescription: "Client TCP Socket",
+    interfaces: [
+      Ci.nsIDOMTCPSocket,
+      Ci.nsIDOMGlobalPropertyInitializer,
+      Ci.nsIObserver,
+      Ci.nsISupportsWeakReference
+    ],
+    flags: Ci.nsIClassInfo.DOM_OBJECT,
+  }),
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIDOMTCPSocket,
+    Ci.nsIDOMGlobalPropertyInitializer,
+    Ci.nsIObserver,
+    Ci.nsISupportsWeakReference
+  ])
+}
+
+
+function SecurityCallbacks(socket) {
+  this._socket = socket;
+}
+
+SecurityCallbacks.prototype = {
+  notifyCertProblem: function sc_notifyCertProblem(socketInfo, status,
+                                                   targetSite) {
+    this._socket.callListener("onerror", status);
+    this._socket.close();
+    return true;
+  },
+
+  getInterface: function sc_getInterface(iid) {
+    return this.QueryInterface(iid);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIBadCertListener2,
+    Ci.nsIInterfaceRequestor,
+    Ci.nsISupports
+  ])
+};
+
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);
new file mode 100644
--- /dev/null
+++ b/dom/network/src/TCPSocket.manifest
@@ -0,0 +1,4 @@
+# TCPSocket.js
+component {cda91b22-6472-11e1-aa11-834fec09cd0a} TCPSocket.js
+contract @mozilla.org/tcp-socket;1 {cda91b22-6472-11e1-aa11-834fec09cd0a}
+category JavaScript-navigator-property mozTCPSocket @mozilla.org/tcp-socket;1
--- a/dom/network/tests/Makefile.in
+++ b/dom/network/tests/Makefile.in
@@ -11,11 +11,18 @@ relativesrcdir   = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 DIRS = \
   $(NULL)
 
 MOCHITEST_FILES = \
   test_network_basics.html \
+  test_tcpsocket_default_permissions.html \
+  test_tcpsocket_enabled_no_perm.html \
+  test_tcpsocket_enabled_with_perm.html \
   $(NULL)
 
+MODULE = test_dom_socket
+
+XPCSHELL_TESTS = unit
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_default_permissions.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test to ensure TCPSocket permission is disabled by default</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test to ensure TCPSocket permission is disabled by default **/
+
+try {
+  navigator.mozTCPSocket;
+  throw new Error("Error: navigator.mozTCPSocket should not exist by default");
+} catch (e) {
+  ok(true, "navigator.mozTCPSocket should not exist by default");  
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_enabled_no_perm.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test to ensure TCPSocket permission enabled and no tcp-socket perm does not allow open</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test to ensure TCPSocket permission being turned on enables 
+  navigator.mozTCPSocket, but mozTCPSocket.open does not work
+  in content.
+**/
+SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", true);
+
+ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
+
+try {
+  navigator.mozTCPSocket.open('localhost', 80);
+  throw new Error("Error: navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
+} catch (e) {
+  ok(true, "navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
+}
+
+SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test to ensure TCPSocket permission enabled and open works with tcp-socket perm</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test to ensure TCPSocket permission being turned on enables 
+  navigator.mozTCPSocket, and mozTCPSocket.open works when
+  the tcp-socket permission has been granted.
+**/
+SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", true);
+SpecialPowers.addPermission("tcp-socket", true, document);
+
+ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
+
+ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
+
+SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/unit/test_tcpsocket.js
@@ -0,0 +1,479 @@
+/**
+ * Test TCPSocket.js by creating an XPCOM-style server socket, then sending
+ * data in both directions and making sure each side receives their data
+ * correctly and with the proper events.
+ *
+ * This test is derived from netwerk/test/unit/test_socks.js, except we don't
+ * involve a subprocess.
+ *
+ * Future work:
+ * - SSL.  see https://bugzilla.mozilla.org/show_bug.cgi?id=466524
+ *             https://bugzilla.mozilla.org/show_bug.cgi?id=662180
+ *   Alternatively, mochitests could be used.
+ * - Testing overflow logic.
+ *
+ **/
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+/**
+ *
+ * Constants
+ *
+ */
+
+// Some binary data to send.
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
+      TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY),
+      HELLO_WORLD = "hlo wrld. ",
+      BIG_ARRAY = new Array(65539),
+      BIG_ARRAY_2 = new Array(65539);
+
+for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
+  BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
+  BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
+}
+
+const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY),
+      BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_2);
+      
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+                        "nsIServerSocket",
+                        "init"),
+      InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
+                           "nsIInputStreamPump",
+                           "init"),
+      BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream"),
+      BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+                              "nsIBinaryOutputStream",
+                              "setOutputStream"),
+      TCPSocket = new (CC("@mozilla.org/tcp-socket;1",
+                     "nsIDOMTCPSocket"))();
+
+/**
+ *
+ * Helper functions
+ *
+ */
+
+/**
+ * Spin up a listening socket and associate at most one live, accepted socket
+ * with ourselves.
+ */
+function TestServer() {
+  this.listener = ServerSocket(-1, true, -1);
+  do_print('server: listening on', this.listener.port);
+  this.listener.asyncListen(this);
+
+  this.binaryInput = null;
+  this.input = null;
+  this.binaryOutput = null;
+  this.output = null;
+
+  this.onaccept = null;
+  this.ondata = null;
+  this.onclose = null;
+}
+
+TestServer.prototype = {
+  onSocketAccepted: function(socket, trans) {
+    if (this.input)
+      do_throw("More than one live connection!?");
+
+    do_print('server: got client connection');
+    this.input = trans.openInputStream(0, 0, 0);
+    this.binaryInput = new BinaryInputStream(this.input);
+    this.output = trans.openOutputStream(0, 0, 0);
+    this.binaryOutput = new BinaryOutputStream(this.output);
+
+    new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null);
+
+    if (this.onaccept)
+      this.onaccept();
+    else
+      do_throw("Received unexpected connection!");
+  },
+
+  onStopListening: function(socket) {
+  },
+
+  onDataAvailable: function(request, context, inputStream, offset, count) {
+    var readData = this.binaryInput.readByteArray(count);
+    if (this.ondata) {
+      try {
+        this.ondata(readData);
+      } catch(ex) {
+        // re-throw if this is from do_throw
+        if (ex === Cr.NS_ERROR_ABORT)
+          throw ex;
+        // log if there was a test problem
+        do_print('Caught exception: ' + ex + '\n' + ex.stack);
+        do_throw('test is broken; bad ondata handler; see above');
+      }
+    } else {
+      do_throw('Received ' + count + ' bytes of unexpected data!');
+    }
+  },
+
+  onStartRequest: function(request, context) {
+  },
+
+  onStopRequest: function(request, context, status) {
+    if (this.onclose)
+      this.onclose();
+    else
+      do_throw("Received unexpected close!");
+  },
+
+  close: function() {
+    this.binaryInput.close();
+    this.binaryOutput.close();
+  },
+
+  /**
+   * Forget about the socket we knew about before.
+   */
+  reset: function() {
+    this.binaryInput = null;
+    this.input = null;
+    this.binaryOutput = null;
+    this.output = null;
+  },
+};
+
+function makeSuccessCase(name) {
+  return function() {
+    do_print('got expected: ' + name);
+    run_next_test();
+  };
+}
+
+function makeJointSuccess(names) {
+  let funcs = {}, successCount = 0;
+  names.forEach(function(name) {
+    funcs[name] = function() {
+      do_print('got expected: ' + name);
+      if (++successCount === names.length)
+        run_next_test();
+    };
+  });
+  return funcs;
+}
+
+function makeFailureCase(name) {
+  return function() {
+    let argstr;
+    if (arguments.length) {
+      argstr = '(args: ' +
+        Array.map(arguments, function(x) { return x + ""; }).join(" ") + ')';
+    }
+    else {
+      argstr = '(no arguments)';
+    }
+    do_throw('got unexpected: ' + name + ' ' + argstr);
+  };
+}
+
+function makeExpectData(name, expectedData, fromEvent, callback) {
+  let dataBuffer = fromEvent ? null : [], done = false;
+  return function(receivedData) {
+    if (fromEvent) {
+      receivedData = receivedData.data;
+      if (dataBuffer) {
+        let newBuffer = new Uint8Array(dataBuffer.length + receivedData.length);
+        newBuffer.set(dataBuffer, 0);
+        newBuffer.set(receivedData, dataBuffer.length);
+        dataBuffer = newBuffer;
+      }
+      else {
+        dataBuffer = receivedData;
+      }
+    }
+    else {
+      dataBuffer = dataBuffer.concat(receivedData);
+    }
+    do_print(name + ' received ' + receivedData.length + ' bytes');
+
+    if (done)
+      do_throw(name + ' Received data event when already done!');
+
+    if (dataBuffer.length >= expectedData.length) {
+      // check the bytes are equivalent
+      for (let i = 0; i < expectedData.length; i++) {
+        if (dataBuffer[i] !== expectedData[i]) {
+          do_throw(name + ' Received mismatched character at position ' + i);
+        }
+      }
+      if (dataBuffer.length > expectedData.length)
+        do_throw(name + ' Received ' + dataBuffer.length + ' bytes but only expected ' +
+                 expectedData.length + ' bytes.');
+
+      done = true;
+      if (callback) {
+        callback();
+      } else {
+        run_next_test();
+      }
+    }
+  };
+}
+
+var server = null, sock = null, failure_drain = null;
+
+/**
+ *
+ * Test functions
+ *
+ */
+
+/**
+ * Connect the socket to the server. This test is added as the first
+ * test, and is also added after every test which results in the socket
+ * being closed.
+ */
+
+function connectSock() {
+  server.reset();
+  var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']);
+
+  sock = TCPSocket.open(
+    '127.0.0.1', server.listener.port,
+    { binaryType: 'arraybuffer' });
+
+  sock.onopen = yayFuncs.clientopen;
+  sock.ondrain = null;
+  sock.ondata = makeFailureCase('data');
+  sock.onerror = makeFailureCase('error');
+  sock.onclose = makeFailureCase('close');
+
+  server.onaccept = yayFuncs.serveropen;
+  server.ondata = makeFailureCase('serverdata');
+  server.onclose = makeFailureCase('serverclose');
+}
+
+/**
+ * Test that sending a small amount of data works, and that buffering
+ * does not take place for this small amount of data.
+ */
+
+function sendData() {
+  server.ondata = makeExpectData('serverdata', DATA_ARRAY);
+  if (!sock.send(TYPED_DATA_ARRAY)) {
+    do_throw("send should not have buffered such a small amount of data");
+  }
+}
+
+/**
+ * Test that sending a large amount of data works, that buffering
+ * takes place (send returns true), and that ondrain is called once
+ * the data has been sent.
+ */
+
+function sendBig() {
+  var yays = makeJointSuccess(['serverdata', 'clientdrain']),
+      amount = 0;
+      
+  server.ondata = function (data) {
+    amount += data.length;
+    if (amount === BIG_TYPED_ARRAY.length) {
+      yays.serverdata();      
+    }
+  };
+  sock.ondrain = function(evt) {
+    if (sock.bufferedAmount) {
+      do_throw("sock.bufferedAmount was > 0 in ondrain");
+    }
+    yays.clientdrain(evt);
+  }
+  if (sock.send(BIG_TYPED_ARRAY)) {
+    do_throw("expected sock.send to return false on large buffer send");
+  }
+}
+
+/**
+ * Test that data sent from the server correctly fires the ondata
+ * callback on the client side.
+ */
+
+function receiveData() {
+  server.ondata = makeFailureCase('serverdata');
+  sock.ondata = makeExpectData('data', DATA_ARRAY, true);
+
+  server.binaryOutput.writeByteArray(DATA_ARRAY, DATA_ARRAY.length);
+}
+
+/**
+ * Test that when the server closes the connection, the onclose callback
+ * is fired on the client side.
+ */
+
+function serverCloses() {
+  // we don't really care about the server's close event, but we do want to
+  // make sure it happened for sequencing purposes.
+  var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
+  sock.ondata = makeFailureCase('data');
+  sock.onclose = yayFuncs.clientclose;
+  server.onclose = yayFuncs.serverclose;
+
+  server.close();
+}
+
+/**
+ * Test that when the client closes the connection, the onclose callback
+ * is fired on the server side.
+ */
+
+function clientCloses() {
+  // we want to make sure the server heard the close and also that the client's
+  // onclose event fired for consistency.
+  var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
+  server.onclose = yayFuncs.serverclose;
+  sock.onclose = yayFuncs.clientclose;
+
+  sock.close();
+}
+
+/**
+ * Send a large amount of data and immediately call close
+ */
+
+function bufferedClose() {
+  var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
+  server.ondata = makeExpectData(
+    "ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
+  server.onclose = yays.serverclose;
+  sock.onclose = yays.clientclose;
+  sock.send(BIG_TYPED_ARRAY);
+  sock.close();
+}
+
+/**
+ * Connect to a port we know is not listening so an error is assured,
+ * and make sure that onerror and onclose are fired on the client side.
+ */
+ 
+function badConnect() {
+  // There's probably nothing listening on tcp port 2.
+  sock = TCPSocket.open('127.0.0.1', 2);
+
+  sock.onopen = makeFailureCase('open');
+  sock.ondata = makeFailureCase('data');
+  sock.onclose = makeFailureCase('close');
+
+  sock.onerror = makeSuccessCase('error');
+}
+
+/**
+ * Test that calling send with enough data to buffer causes ondrain to
+ * be invoked once the data has been sent, and then test that calling send
+ * and buffering again causes ondrain to be fired again.
+ */
+
+function drainTwice() {
+  let yays = makeJointSuccess(
+    ['ondrain', 'ondrain2',
+    'ondata', 'ondata2',
+    'serverclose', 'clientclose']);
+
+  function serverSideCallback() {
+    yays.ondata();
+    server.ondata = makeExpectData(
+      "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
+
+    sock.ondrain = yays.ondrain2;
+
+    if (sock.send(BIG_TYPED_ARRAY_2)) {
+      do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
+    }
+
+    sock.close();
+  }
+
+  server.onclose = yays.serverclose;
+  server.ondata = makeExpectData(
+    "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
+
+  sock.onclose = yays.clientclose;
+  sock.ondrain = yays.ondrain;
+
+  if (sock.send(BIG_TYPED_ARRAY)) {
+    throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
+  }
+}
+
+function cleanup() {
+  do_print("Cleaning up");
+  sock.close();
+  run_next_test();
+}
+
+/**
+ * Test that calling send with enough data to buffer twice in a row without
+ * waiting for ondrain still results in ondrain being invoked at least once.
+ */
+
+function bufferTwice() {
+  let yays = makeJointSuccess(
+    ['ondata', 'ondrain', 'serverclose', 'clientclose']);
+
+  let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2));
+  server.ondata = makeExpectData(
+    "ondata", double_array, false, yays.ondata);
+
+  server.onclose = yays.serverclose;
+  sock.onclose = yays.clientclose;
+
+  sock.ondrain = function () {
+    sock.close();
+    yays.ondrain();
+  }
+
+  if (sock.send(BIG_TYPED_ARRAY)) {
+    throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
+  }
+  if (sock.send(BIG_TYPED_ARRAY_2)) {
+    throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
+  }
+}
+
+// - connect, data and events work both ways
+add_test(connectSock);
+add_test(sendData);
+add_test(sendBig);
+add_test(receiveData);
+// - server closes on us
+add_test(serverCloses);
+
+// - connect, we close on the server
+add_test(connectSock);
+add_test(clientCloses);
+
+// - connect, buffer, close
+add_test(connectSock);
+add_test(bufferedClose);
+
+// - get an error on an attempt to connect to a non-listening port
+add_test(badConnect);
+
+// send a buffer, get a drain, send a buffer, get a drain
+add_test(connectSock);
+add_test(drainTwice);
+
+// send a buffer, get a drain, send a buffer, get a drain
+add_test(connectSock);
+add_test(bufferTwice);
+
+// clean up
+add_test(cleanup);
+
+function run_test() {
+  server = new TestServer();
+
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_tcpsocket.js]
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -528,17 +528,18 @@ var interfaceNamesInGlobalScope =
     "ContactField",
     "SVGFitToViewBox",
     "SVGAElement",
     "NavigatorCamera",
     "CameraControl",
     "CameraCapabilities",
     "CameraManager",
     "CSSSupportsRule",
-    "MozMobileCellInfo"
+    "MozMobileCellInfo",
+    "TCPSocket"
   ]
 
 for (var i in Components.interfaces) {
   var s = i.toString();
   var name = null;
   if (s.indexOf("nsIDOM") == 0) {
     name = s.substring("nsIDOM".length);
   } else if (s.indexOf("nsI") == 0) {
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -347,16 +347,19 @@
 #endif
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
 
+@BINPATH@/components/TCPSocket.js
+@BINPATH@/components/TCPSocket.manifest
+
 ; Modules
 @BINPATH@/modules/*
 
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
 @BINPATH@/components/nsUrlClassifierLib.js
--- a/mobile/xul/installer/package-manifest.in
+++ b/mobile/xul/installer/package-manifest.in
@@ -433,16 +433,19 @@
 @BINPATH@/components/SyncComponents.manifest
 @BINPATH@/components/Weave.js
 @BINPATH@/components/WeaveCrypto.manifest
 @BINPATH@/components/WeaveCrypto.js
 #endif
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 
+@BINPATH@/components/TCPSocket.js
+@BINPATH@/components/TCPSocket.manifest
+
 ; Modules
 @BINPATH@/modules/*
 
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
 @BINPATH@/components/nsUrlClassifierLib.js
--- a/testing/xpcshell/xpcshell.ini
+++ b/testing/xpcshell/xpcshell.ini
@@ -7,16 +7,17 @@
 [include:netwerk/cookie/test/unit/xpcshell.ini]
 [include:modules/libjar/zipwriter/test/unit/xpcshell.ini]
 [include:uriloader/exthandler/tests/unit/xpcshell.ini]
 [include:parser/xml/test/unit/xpcshell.ini]
 [include:image/test/unit/xpcshell.ini]
 [include:dom/plugins/test/unit/xpcshell.ini]
 [include:dom/sms/tests/xpcshell.ini]
 [include:dom/mms/tests/xpcshell.ini]
+[include:dom/network/tests/unit/xpcshell.ini]
 [include:dom/src/json/test/unit/xpcshell.ini]
 [include:dom/system/gonk/tests/xpcshell.ini]
 [include:dom/tests/unit/xpcshell.ini]
 [include:dom/indexedDB/test/unit/xpcshell.ini]
 [include:content/xtf/test/unit/xpcshell.ini]
 [include:docshell/test/unit/xpcshell.ini]
 [include:docshell/test/unit_ipc/xpcshell.ini]
 [include:embedding/tests/unit/xpcshell.ini]