Skip to content

Instantly share code, notes, and snippets.

@fzzzy
Created August 14, 2012 00:09
Show Gist options
  • Select an option

  • Save fzzzy/3344980 to your computer and use it in GitHub Desktop.

Select an option

Save fzzzy/3344980 to your computer and use it in GitHub Desktop.

Revisions

  1. Donovan Preston created this gist Aug 14, 2012.
    1,436 changes: 1,436 additions & 0 deletions wip-0.13.1.patch
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1436 @@
    diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js
    --- a/b2g/app/b2g.js
    +++ b/b2g/app/b2g.js
    @@ -387,16 +387,19 @@

    // 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);
    diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in
    --- 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
    diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
    --- a/browser/installer/package-manifest.in
    +++ b/browser/installer/package-manifest.in
    @@ -478,16 +478,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
    diff --git a/dom/network/interfaces/Makefile.in b/dom/network/interfaces/Makefile.in
    --- a/dom/network/interfaces/Makefile.in
    +++ b/dom/network/interfaces/Makefile.in
    @@ -14,11 +14,12 @@
    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
    diff --git a/dom/network/interfaces/nsIDOMTCPSocket.idl b/dom/network/interfaces/nsIDOMTCPSocket.idl
    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;
    +};
    +
    diff --git a/dom/network/src/Makefile.in b/dom/network/src/Makefile.in
    --- a/dom/network/src/Makefile.in
    +++ b/dom/network/src/Makefile.in
    @@ -8,16 +8,21 @@
    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 \
    diff --git a/dom/network/src/TCPSocket.js b/dom/network/src/TCPSocket.js
    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]);
    diff --git a/dom/network/src/TCPSocket.manifest b/dom/network/src/TCPSocket.manifest
    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
    diff --git a/dom/network/tests/Makefile.in b/dom/network/tests/Makefile.in
    --- a/dom/network/tests/Makefile.in
    +++ b/dom/network/tests/Makefile.in
    @@ -13,9 +13,13 @@

    DIRS = \
    $(NULL)

    MOCHITEST_FILES = \
    test_network_basics.html \
    $(NULL)

    +MODULE = test_dom_socket
    +
    +XPCSHELL_TESTS = unit
    +
    include $(topsrcdir)/config/rules.mk
    diff --git a/dom/network/tests/unit/test_tcpsocket.js b/dom/network/tests/unit/test_tcpsocket.js
    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(524288),
    + BIG_ARRAY_2 = new Array(524288);
    +
    +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();
    +}
    diff --git a/dom/network/tests/unit/xpcshell.ini b/dom/network/tests/unit/xpcshell.ini
    new file mode 100644
    --- /dev/null
    +++ b/dom/network/tests/unit/xpcshell.ini
    @@ -0,0 +1,5 @@
    +[DEFAULT]
    +head =
    +tail =
    +
    +[test_tcpsocket.js]
    diff --git a/testing/xpcshell/xpcshell.ini b/testing/xpcshell/xpcshell.ini
    --- 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]