зеркало из https://github.com/mozilla/gecko-dev.git
Bug 733573 - Expose a client TCP socket API to web applications [r=honzab,fabrice]
This commit is contained in:
Родитель
dc03379403
Коммит
ea8f619eac
|
@ -396,6 +396,9 @@ pref("dom.mozSettings.enabled", true);
|
||||||
pref("device.camera.enabled", true);
|
pref("device.camera.enabled", true);
|
||||||
pref("media.realtime_decoder.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
|
// "Preview" landing of bug 710563, which is bogged down in analysis
|
||||||
// of talos regression. This is a needed change for higher-framerate
|
// of talos regression. This is a needed change for higher-framerate
|
||||||
// CSS animations, and incidentally works around an apparent bug in
|
// CSS animations, and incidentally works around an apparent bug in
|
||||||
|
|
|
@ -484,6 +484,9 @@
|
||||||
@BINPATH@/components/ActivityRequestHandler.js
|
@BINPATH@/components/ActivityRequestHandler.js
|
||||||
@BINPATH@/components/ActivityWrapper.js
|
@BINPATH@/components/ActivityWrapper.js
|
||||||
|
|
||||||
|
@BINPATH@/components/TCPSocket.js
|
||||||
|
@BINPATH@/components/TCPSocket.manifest
|
||||||
|
|
||||||
@BINPATH@/components/AppProtocolHandler.js
|
@BINPATH@/components/AppProtocolHandler.js
|
||||||
@BINPATH@/components/AppProtocolHandler.manifest
|
@BINPATH@/components/AppProtocolHandler.manifest
|
||||||
|
|
||||||
|
|
|
@ -484,6 +484,9 @@
|
||||||
@BINPATH@/components/ContactManager.manifest
|
@BINPATH@/components/ContactManager.manifest
|
||||||
@BINPATH@/components/AlarmsManager.js
|
@BINPATH@/components/AlarmsManager.js
|
||||||
@BINPATH@/components/AlarmsManager.manifest
|
@BINPATH@/components/AlarmsManager.manifest
|
||||||
|
@BINPATH@/components/TCPSocket.js
|
||||||
|
@BINPATH@/components/TCPSocket.manifest
|
||||||
|
|
||||||
#ifdef ENABLE_MARIONETTE
|
#ifdef ENABLE_MARIONETTE
|
||||||
@BINPATH@/chrome/marionette@JAREXT@
|
@BINPATH@/chrome/marionette@JAREXT@
|
||||||
@BINPATH@/chrome/marionette.manifest
|
@BINPATH@/chrome/marionette.manifest
|
||||||
|
|
|
@ -19,6 +19,7 @@ XPIDLSRCS = \
|
||||||
nsIDOMMobileConnection.idl \
|
nsIDOMMobileConnection.idl \
|
||||||
nsIMobileConnectionProvider.idl \
|
nsIMobileConnectionProvider.idl \
|
||||||
nsIDOMUSSDReceivedEvent.idl \
|
nsIDOMUSSDReceivedEvent.idl \
|
||||||
|
nsIDOMTCPSocket.idl \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,11 @@ LIBRARY_NAME = dom_network_s
|
||||||
LIBXUL_LIBRARY = 1
|
LIBXUL_LIBRARY = 1
|
||||||
FORCE_STATIC_LIB = 1
|
FORCE_STATIC_LIB = 1
|
||||||
|
|
||||||
|
EXTRA_COMPONENTS = \
|
||||||
|
TCPSocket.js \
|
||||||
|
TCPSocket.manifest \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
include $(topsrcdir)/dom/dom-config.mk
|
include $(topsrcdir)/dom/dom-config.mk
|
||||||
|
|
||||||
EXPORTS_NAMESPACES = mozilla/dom/network
|
EXPORTS_NAMESPACES = mozilla/dom/network
|
||||||
|
|
|
@ -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]);
|
|
@ -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
|
|
@ -16,6 +16,13 @@ DIRS = \
|
||||||
|
|
||||||
MOCHITEST_FILES = \
|
MOCHITEST_FILES = \
|
||||||
test_network_basics.html \
|
test_network_basics.html \
|
||||||
|
test_tcpsocket_default_permissions.html \
|
||||||
|
test_tcpsocket_enabled_no_perm.html \
|
||||||
|
test_tcpsocket_enabled_with_perm.html \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
MODULE = test_dom_socket
|
||||||
|
|
||||||
|
XPCSHELL_TESTS = unit
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
[DEFAULT]
|
||||||
|
head =
|
||||||
|
tail =
|
||||||
|
|
||||||
|
[test_tcpsocket.js]
|
|
@ -533,7 +533,8 @@ var interfaceNamesInGlobalScope =
|
||||||
"CameraCapabilities",
|
"CameraCapabilities",
|
||||||
"CameraManager",
|
"CameraManager",
|
||||||
"CSSSupportsRule",
|
"CSSSupportsRule",
|
||||||
"MozMobileCellInfo"
|
"MozMobileCellInfo",
|
||||||
|
"TCPSocket"
|
||||||
]
|
]
|
||||||
|
|
||||||
for (var i in Components.interfaces) {
|
for (var i in Components.interfaces) {
|
||||||
|
|
|
@ -352,6 +352,9 @@
|
||||||
@BINPATH@/components/AppsService.js
|
@BINPATH@/components/AppsService.js
|
||||||
@BINPATH@/components/AppsService.manifest
|
@BINPATH@/components/AppsService.manifest
|
||||||
|
|
||||||
|
@BINPATH@/components/TCPSocket.js
|
||||||
|
@BINPATH@/components/TCPSocket.manifest
|
||||||
|
|
||||||
; Modules
|
; Modules
|
||||||
@BINPATH@/modules/*
|
@BINPATH@/modules/*
|
||||||
|
|
||||||
|
|
|
@ -438,6 +438,9 @@
|
||||||
@BINPATH@/components/TelemetryPing.js
|
@BINPATH@/components/TelemetryPing.js
|
||||||
@BINPATH@/components/TelemetryPing.manifest
|
@BINPATH@/components/TelemetryPing.manifest
|
||||||
|
|
||||||
|
@BINPATH@/components/TCPSocket.js
|
||||||
|
@BINPATH@/components/TCPSocket.manifest
|
||||||
|
|
||||||
; Modules
|
; Modules
|
||||||
@BINPATH@/modules/*
|
@BINPATH@/modules/*
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[include:dom/plugins/test/unit/xpcshell.ini]
|
[include:dom/plugins/test/unit/xpcshell.ini]
|
||||||
[include:dom/sms/tests/xpcshell.ini]
|
[include:dom/sms/tests/xpcshell.ini]
|
||||||
[include:dom/mms/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/src/json/test/unit/xpcshell.ini]
|
||||||
[include:dom/system/gonk/tests/xpcshell.ini]
|
[include:dom/system/gonk/tests/xpcshell.ini]
|
||||||
[include:dom/tests/unit/xpcshell.ini]
|
[include:dom/tests/unit/xpcshell.ini]
|
||||||
|
|
Загрузка…
Ссылка в новой задаче