Bug 1087145 - Move mozTCPSocket/TCPSocket unit tests from xpcshell tests to mochitest-plain tests. r=jdm

Most of the TCPSocket and TCPServerSocket coverage was implemented exclusively
in Chrome-privileged xpcshell tests.  This failed to provide coverage for the
key use case of content-privileged code using TCPSocket.

This cleans up the test implementation and migrates them to mochitests.
Coverage is improved as evidenced by two tested TCPServerSocket issues that were
addressed in this patch:
- ArrayBuffers weren't being created in the content page's context, so
  exceptions would be thrown when accessed.
- 'drain' notifications were not being hooked up.

The following fix that lacks coverage that notices the fix was implemented:
- TCPServerSocket now properly propagates the appId for network usage tracking.
This commit is contained in:
Andrew Sutherland 2014-10-29 22:03:54 -04:00
Родитель a3f2bc8e6f
Коммит 9a4520d5ff
17 изменённых файлов: 536 добавлений и 743 удалений

Просмотреть файл

@ -51,7 +51,7 @@ Cu.skipCOWCallableChecks();
TCPServerSocket.prototype = {
__exposedProps__: {
port: 'r',
localPort: 'r',
onconnect: 'rw',
onerror: 'rw'
},
@ -148,7 +148,8 @@ TCPServerSocket.prototype = {
onSocketAccepted: function tss_onSocketAccepted(server, trans) {
// precondition: this._inChild == false
try {
let that = TCPSocketInternal.createAcceptedParent(trans, this._binaryType);
let that = TCPSocketInternal.createAcceptedParent(trans, this._binaryType,
this.useWin);
this._callListenerAcceptCommon(that);
}
catch(e) {

Просмотреть файл

@ -7,6 +7,8 @@
#include "TCPSocketParent.h"
#include "mozilla/unused.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/TabParent.h"
namespace mozilla {
namespace dom {
@ -58,7 +60,8 @@ TCPServerSocketParent::Init(PNeckoParent* neckoParent, const uint16_t& aLocalPor
return true;
}
rv = mIntermediary->Listen(this, aLocalPort, aBacklog, aBinaryType, getter_AddRefs(mServerSocket));
rv = mIntermediary->Listen(this, aLocalPort, aBacklog, aBinaryType, GetAppId(),
getter_AddRefs(mServerSocket));
if (NS_FAILED(rv) || !mServerSocket) {
FireInteralError(this, __LINE__);
return true;
@ -66,6 +69,19 @@ TCPServerSocketParent::Init(PNeckoParent* neckoParent, const uint16_t& aLocalPor
return true;
}
uint32_t
TCPServerSocketParent::GetAppId()
{
uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
const PContentParent *content = Manager()->Manager();
const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
if (browsers.Length() > 0) {
TabParent *tab = static_cast<TabParent*>(browsers[0]);
appId = tab->OwnAppId();
}
return appId;
};
NS_IMETHODIMP
TCPServerSocketParent::SendCallbackAccept(nsITCPSocketParent *socket)
{

Просмотреть файл

@ -31,6 +31,8 @@ public:
virtual bool RecvClose() MOZ_OVERRIDE;
virtual bool RecvRequestDelete() MOZ_OVERRIDE;
uint32_t GetAppId();
void AddIPDLReference();
void ReleaseIPDLReference();

Просмотреть файл

@ -77,7 +77,13 @@ TCPSocketEvent.prototype = {
__exposedProps__: {
type: 'r',
target: 'r',
data: 'r'
data: 'r',
// Promise::ResolveInternal tries to check if the thing being resolved is
// itself a promise through the presence of "then". Accordingly, we list
// it as an exposed property, although we return undefined for it.
// Bug 882123 covers making TCPSocket be a proper event target with proper
// events.
then: 'r'
},
get type() {
return this._type;
@ -87,6 +93,9 @@ TCPSocketEvent.prototype = {
},
get data() {
return this._data;
},
get then() {
return undefined;
}
}
@ -431,7 +440,7 @@ TCPSocket.prototype = {
}
},
createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) {
createAcceptedParent: function ts_createAcceptedParent(transport, binaryType, windowObject) {
let that = new TCPSocket();
that._transport = transport;
that._initStream(binaryType);
@ -444,6 +453,7 @@ TCPSocket.prototype = {
// Grab host/port from SocketTransport.
that._host = transport.host;
that._port = transport.port;
that.useWin = windowObject;
return that;
},
@ -458,6 +468,7 @@ TCPSocket.prototype = {
that._socketBridge = socketChild;
that._host = socketChild.host;
that._port = socketChild.port;
that.useWin = windowObject;
return that;
},
@ -637,8 +648,7 @@ TCPSocket.prototype = {
if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
throw new Error("TCPSocket does not have permission in this context.\n");
}
let that = new TCPServerSocket(this.useWin || this);
let that = new TCPServerSocket(this.useWin);
options = options || { binaryType : this.binaryType };
backlog = backlog || -1;

Просмотреть файл

@ -43,9 +43,7 @@ TCPSocketParentIntermediary.prototype = {
return null;
let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
if (socketInternal) {
socketInternal.setAppId(aAppId);
}
// Handle parent's request to update buffered amount.
socketInternal.setOnUpdateBufferedAmountHandler(
@ -56,7 +54,7 @@ TCPSocketParentIntermediary.prototype = {
return socket;
},
listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType) {
listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType, aAppId) {
let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
let serverSocket = baseSocket.listen(aLocalPort, { binaryType: aBinaryType }, aBacklog);
if (!serverSocket)
@ -68,6 +66,12 @@ TCPSocketParentIntermediary.prototype = {
var socketParent = Cc["@mozilla.org/tcp-socket-parent;1"]
.createInstance(Ci.nsITCPSocketParent);
var intermediary = new TCPSocketParentIntermediary();
let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
socketInternal.setAppId(aAppId);
socketInternal.setOnUpdateBufferedAmountHandler(
intermediary._onUpdateBufferedAmountHandler.bind(intermediary, socketParent));
// Handlers are set to the JS-implemented socket object on the parent side,
// so that the socket parent object can communicate data
// with the corresponding socket child object through IPC.

Просмотреть файл

@ -216,7 +216,7 @@ interface nsIDOMTCPSocket : nsISupports
* Needed to account for multiple possible types that can be provided to
* the socket callbacks as arguments.
*/
[scriptable, uuid(017f130f-2477-4215-8783-57eada957699)]
[scriptable, uuid(b1235064-9a08-4714-ad03-1212e4562803)]
interface nsITCPSocketInternal : nsISupports {
// Trigger the callback for |type| and provide a DOMError() object with the given data
void callListenerError(in DOMString type, in DOMString name);
@ -253,8 +253,11 @@ interface nsITCPSocketInternal : nsISupports {
// @param binaryType
// "arraybuffer" to use ArrayBuffer instances
// in the ondata callback and as the argument to send.
// @param window
// An object to create ArrayBuffer for this window. See Bug 831107.
nsIDOMTCPSocket createAcceptedParent(in nsISocketTransport transport,
in DOMString binaryType);
in DOMString binaryType,
in nsIDOMWindow window);
// Create a DOM socket on the child side
// This is called when the socket is accepted on the parent side.

Просмотреть файл

@ -76,7 +76,8 @@ interface nsITCPSocketIntermediary : nsISupports {
// Listen on a port
nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent,
in unsigned short port, in unsigned short backlog,
in DOMString binaryType);
in DOMString binaryType,
in unsigned long appId);
// Called when received a child request to send a string.
void onRecvSendString(in DOMString data, in uint32_t trackingNumber);

Просмотреть файл

@ -0,0 +1,83 @@
// Temporary implementation of add_task for mochitest-plain until bug 1078657 is
// implemented.
SimpleTest.waitForExplicitFinish();
(function(scope) {
var pendingTasks = [];
var pendingPromise = null;
// Strict spawn function that takes a known generatorFunc and assumes that
// every yielded value will be a Promise. If nesting is desired, then yield*
// should be used!
function spawn(generatorFunc) {
return new Promise(function(resolve, reject) {
try {
var iterator = generatorFunc();
}
catch (ex) {
ok(false, 'Problem invoking generator func: ' + ex + ': ' + ex.stack);
return;
}
var stepResolved = function(result) {
try {
var iterStep = iterator.next(result);
}
catch (ex) {
ok(false, 'Problem invoking iterator step: ' + ex + ': ' + ex.stack);
return;
}
if (iterStep.done) {
resolve(iterStep.value);
return;
}
if (!iterStep.value || !iterStep.value.then) {
ok(false, 'Iterator step returned non-Promise: ' + iterStep.value);
}
iterStep.value.then(stepResolved, generalErrback);
};
stepResolved();
});
}
function maybeSpawn(promiseOrGenerator) {
if (promiseOrGenerator.then) {
return promiseOrGenerator;
}
return spawn(promiseOrGenerator);
}
scope.add_task = function(thing) {
pendingTasks.push(thing);
};
function generalErrback(ex) {
ok(false,
'A rejection happened: ' +
(ex ? (ex + ': ' + ex.stack) : ''));
}
function runNextTask() {
if (pendingTasks.length) {
pendingPromise = maybeSpawn(pendingTasks.shift());
pendingPromise.then(runNextTask, generalErrback);
} else {
SimpleTest.finish();
}
}
// Trigger runNextTask after we think all JS files have been loaded.
// The primary goal is that we can call SimpleTest.finish() after all test
// code has been loaded and run. We gate this based on the document's
// readyState.
var running = false;
function maybeStartRunning() {
if (!running && document.readyState === 'complete') {
running = true;
document.removeEventListener('readystateChange', maybeStartRunning);
// Defer to a subsequent turn of the event loop to let micro-tasks and any
// other clever setTimeout(0) instances run first.
window.setTimeout(runNextTask, 0);
}
}
document.addEventListener('readystatechange', maybeStartRunning);
maybeStartRunning();
})(this);

Просмотреть файл

@ -1,9 +1,12 @@
[DEFAULT]
support-files =
add_task.js
file_udpsocket_iframe.html
test_tcpsocket_client_and_server_basics.js
[test_network_basics.html]
skip-if = toolkit == "gonk" || toolkit == 'android'
[test_tcpsocket_client_and_server_basics.html]
[test_tcpsocket_default_permissions.html]
skip-if = toolkit == "gonk"
[test_tcpsocket_enabled_no_perm.html]

Просмотреть файл

@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<!--
Core tests for TCPSocket and TCPServerSocket that replace their previous
separate xpcshell incarnations. This migration and cleanup occurred as part
of bug 1084245 in order to get coverage of the tests from content.
https://bugzilla.mozilla.org/show_bug.cgi?id=1084245
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1084245</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="add_task.js"></script>
<script type="application/javascript;version=1.7" src="test_tcpsocket_client_and_server_basics.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,363 @@
'use strict';
const SERVER_BACKLOG = -1;
const SOCKET_EVENTS = ['open', 'data', 'drain', 'error', 'close'];
function concatUint8Arrays(a, b) {
let newArr = new Uint8Array(a.length + b.length);
newArr.set(a, 0);
newArr.set(b, a.length);
return newArr;
}
function assertUint8ArraysEqual(a, b, comparingWhat) {
if (a.length !== b.length) {
ok(false, comparingWhat + ' arrays do not have the same length; ' +
a.length + ' versus ' + b.length);
return;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
ok(false, comparingWhat + ' arrays differ at index ' + i +
a[i] + ' versus ' + b[i]);
return;
}
}
ok(true, comparingWhat + ' arrays were equivalent.');
}
/**
* Helper method to add event listeners to a socket and provide two Promise-returning
* helpers (see below for docs on them). This *must* be called during the turn of
* the event loop where TCPSocket.open is called or the onconnect method is being
* invoked.
*/
function listenForEventsOnSocket(socket, socketType) {
let wantDataLength = null;
let pendingResolve = null;
let receivedEvents = [];
let receivedData = null;
let handleGenericEvent = function(event) {
dump('(' + socketType + ' event: ' + event.type + ')\n');
if (pendingResolve && wantDataLength === null) {
pendingResolve(event);
pendingResolve = null;
} else {
receivedEvents.push(event);
}
};
socket.onopen = handleGenericEvent;
socket.ondrain = handleGenericEvent;
socket.onerror = handleGenericEvent;
socket.onclose = handleGenericEvent;
socket.ondata = function(event) {
dump('(' + socketType + ' event: ' + event.type + ' length: ' +
event.data.byteLength + ')\n');
var arr = new Uint8Array(event.data);
if (receivedData === null) {
receivedData = arr;
} else {
receivedData = concatUint8Arrays(receivedData, arr);
}
if (wantDataLength !== null &&
receivedData.length >= wantDataLength) {
pendingResolve(receivedData);
pendingResolve = null;
receivedData = null;
wantDataLength = null;
}
};
return {
/**
* Return a Promise that will be resolved with the next (non-data) event
* received by the socket. If there are queued events, the Promise will
* be immediately resolved (but you won't see that until a future turn of
* the event loop).
*/
waitForEvent: function() {
if (pendingResolve) {
throw new Error('only one wait allowed at a time.');
}
if (receivedEvents.length) {
return Promise.resolve(receivedEvents.shift());
}
dump('(' + socketType + ' waiting for event)\n');
return new Promise(function(resolve, reject) {
pendingResolve = resolve;
});
},
/**
* Return a Promise that will be resolved with a Uint8Array of at least the
* given length. We buffer / accumulate received data until we have enough
* data. Data is buffered even before you call this method, so be sure to
* explicitly wait for any and all data sent by the other side.
*/
waitForDataWithAtLeastLength: function(length) {
if (pendingResolve) {
throw new Error('only one wait allowed at a time.');
}
if (receivedData && receivedData.length >= length) {
let promise = Promise.resolve(receivedData);
receivedData = null;
return promise;
}
dump('(' + socketType + ' waiting for ' + length + ' bytes)\n');
return new Promise(function(resolve, reject) {
pendingResolve = resolve;
wantDataLength = length;
});
}
};
}
/**
* Return a promise that is resolved when the server receives a connection. The
* promise is resolved with { socket, queue } where `queue` is the result of
* calling listenForEventsOnSocket(socket). This must be done because we need
* to add the event listener during the connection.
*/
function waitForConnection(listeningServer) {
return new Promise(function(resolve, reject) {
// Because of the event model of sockets, we can't use the
// listenForEventsOnSocket mechanism; we need to hook up listeners during
// the connect event.
listeningServer.onconnect = function(socket) {
// Clobber the listener to get upset if it receives any more connections
// after this.
listeningServer.onconnect = function() {
ok(false, 'Received a connection when not expecting one.');
};
ok(true, 'Listening server accepted socket');
resolve({
socket: socket,
queue: listenForEventsOnSocket(socket, 'server')
});
};
});
}
function defer() {
var deferred = {};
deferred.promise = new Promise(function(resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
function* test_basics() {
// Enable our use of TCPSocket
let prefDeferred = defer();
SpecialPowers.pushPrefEnv(
{ set: [ ['dom.mozTCPSocket.enabled', true] ] },
prefDeferred.resolve);
yield prefDeferred.promise;
let permDeferred = defer();
SpecialPowers.pushPermissions(
[ { type: 'tcp-socket', allow: true, context: document } ],
permDeferred.resolve);
yield permDeferred.promise;
// See bug 903830; in e10s mode we never get to find out the localPort if we
// let it pick a free port by choosing 0. This is the same port the xpcshell
// test was using.
let serverPort = 8085;
let TCPSocket = navigator.mozTCPSocket;
// - Start up a listening socket.
let listeningServer = TCPSocket.listen(serverPort,
{ binaryType: 'arraybuffer' },
SERVER_BACKLOG);
let connectedPromise = waitForConnection(listeningServer);
// -- Open a connection to the server
let clientSocket = TCPSocket.open('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
let clientQueue = listenForEventsOnSocket(clientSocket, 'client');
// (the client connects)
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
is(clientSocket.readyState, 'open', 'client readyState is open');
// (the server connected)
let { socket: serverSocket, queue: serverQueue } = yield connectedPromise;
is(serverSocket.readyState, 'open', 'server readyState is open');
// -- Simple send / receive
// - Send data from client to server
// (But not so much we cross the drain threshold.)
let smallUint8Array = new Uint8Array(256);
for (let i = 0; i < smallUint8Array.length; i++) {
smallUint8Array[i] = i;
}
is(clientSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length), true,
'Client sending less than 64k, buffer should not be full.');
let serverReceived = yield serverQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(serverReceived, smallUint8Array,
'Server received/client sent');
// - Send data from server to client
// (But not so much we cross the drain threshold.)
is(serverSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length), true,
'Server sending less than 64k, buffer should not be full.');
let clientReceived = yield clientQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(clientReceived, smallUint8Array,
'Client received/server sent');
// -- Perform sending multiple times with different buffer slices
// - Send data from client to server
// (But not so much we cross the drain threshold.)
is(clientSocket.send(smallUint8Array.buffer, 0, 7),
true, 'Client sending less than 64k, buffer should not be full.');
is(clientSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
true, 'Client sending less than 64k, buffer should not be full.');
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(serverReceived, smallUint8Array,
'Server received/client sent');
// - Send data from server to client
// (But not so much we cross the drain threshold.)
is(serverSocket.send(smallUint8Array.buffer, 0, 7),
true, 'Server sending less than 64k, buffer should not be full.');
is(serverSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
true, 'Server sending less than 64k, buffer should not be full.');
clientReceived = yield clientQueue.waitForDataWithAtLeastLength(256);
assertUint8ArraysEqual(clientReceived, smallUint8Array,
'Client received/server sent');
// -- Send "big" data in both directions
// (Enough to cross the buffering/drain threshold; 64KiB)
let bigUint8Array = new Uint8Array(65536 + 3);
for (let i = 0; i < bigUint8Array.length; i++) {
bigUint8Array[i] = i % 256;
}
// Do this twice so we have confidence that the 'drain' event machinery
// doesn't break after the first use.
for (let iSend = 0; iSend < 2; iSend++) {
// - Send "big" data from the client to the server
is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Client sending more than 64k should result in the buffer being full.');
is((yield clientQueue.waitForEvent()).type, 'drain',
'The drain event should fire after a large send that indicated full.');
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
bigUint8Array.length);
assertUint8ArraysEqual(serverReceived, bigUint8Array,
'server received/client sent');
// - Send "big" data from the server to the client
is(serverSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Server sending more than 64k should result in the buffer being full.');
is((yield serverQueue.waitForEvent()).type, 'drain',
'The drain event should fire after a large send that indicated full.');
clientReceived = yield clientQueue.waitForDataWithAtLeastLength(
bigUint8Array.length);
assertUint8ArraysEqual(clientReceived, bigUint8Array,
'client received/server sent');
}
// -- Server closes the connection
serverSocket.close();
is(serverSocket.readyState, 'closing',
'readyState should be closing immediately after calling close');
is((yield clientQueue.waitForEvent()).type, 'close',
'The client should get a close event when the server closes.');
is(clientSocket.readyState, 'closed',
'client readyState should be closed after close event');
is((yield serverQueue.waitForEvent()).type, 'close',
'The server should get a close event when it closes itself.');
is(serverSocket.readyState, 'closed',
'server readyState should be closed after close event');
// -- Re-establish connection
connectedPromise = waitForConnection(listeningServer);
clientSocket = TCPSocket.open('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
let connectedResult = yield connectedPromise;
// destructuring assignment is not yet ES6 compliant, must manually unpack
serverSocket = connectedResult.socket;
serverQueue = connectedResult.queue;
// -- Client closes the connection
clientSocket.close();
is(clientSocket.readyState, 'closing',
'client readyState should be losing immediately after calling close');
is((yield clientQueue.waitForEvent()).type, 'close',
'The client should get a close event when it closes itself.');
is(clientSocket.readyState, 'closed',
'client readyState should be closed after the close event is received');
is((yield serverQueue.waitForEvent()).type, 'close',
'The server should get a close event when the client closes.');
is(serverSocket.readyState, 'closed',
'server readyState should be closed after the close event is received');
// -- Re-establish connection
connectedPromise = waitForConnection(listeningServer);
clientSocket = TCPSocket.open('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
connectedResult = yield connectedPromise;
// destructuring assignment is not yet ES6 compliant, must manually unpack
serverSocket = connectedResult.socket;
serverQueue = connectedResult.queue;
// -- Call close after enqueueing a lot of data, make sure it goes through.
// We'll have the client send and close.
is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
'Client sending more than 64k should result in the buffer being full.');
clientSocket.close();
// The drain will still fire
is((yield clientQueue.waitForEvent()).type, 'drain',
'The drain event should fire after a large send that returned true.');
// Then we'll get a close
is((yield clientQueue.waitForEvent()).type, 'close',
'The close event should fire after the drain event.');
// The server will get its data
serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
bigUint8Array.length);
assertUint8ArraysEqual(serverReceived, bigUint8Array,
'server received/client sent');
// And a close.
is((yield serverQueue.waitForEvent()).type, 'close',
'The drain event should fire after a large send that returned true.');
// -- Close the listening server (and try to connect)
// We want to verify that the server actually closes / stops listening when
// we tell it to.
listeningServer.close();
// - try and connect, get an error
clientSocket = TCPSocket.open('127.0.0.1', serverPort,
{ binaryType: 'arraybuffer' });
clientQueue = listenForEventsOnSocket(clientSocket, 'client');
is((yield clientQueue.waitForEvent()).type, 'error', 'fail to connect');
is(clientSocket.readyState, 'closed',
'client readyState should be closed after the failure to connect');
}
add_task(test_basics);

Просмотреть файл

@ -1,152 +0,0 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const CC = Components.Constructor;
Cu.import("resource://gre/modules/Services.jsm");
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"))();
var server = null, sock = null;
/**
* 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 run_test() {
Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
do_test_pending();
server = new TestServer();
server.reset();
sock = TCPSocket.open(
'127.0.0.1', server.listener.port,
{ binaryType: 'arraybuffer' });
var encoder = new TextEncoder();
var ok = encoder.encode("OKBYE");
var expected = ['O', 'K', 'B', 'Y', 'E']
.map(function(c) { return c.charCodeAt(0); });
var seenData = [];
server.onaccept = function() {};
server.ondata = function(data) {
do_print(data + ":" + data.length);
seenData = seenData.concat(data);
if (seenData.length == expected.length) {
do_print(expected);
do_check_eq(seenData.length, expected.length);
for (var i = 0; i < seenData.length; i++) {
do_check_eq(seenData[i], expected[i]);
}
sock.close();
server.close();
do_test_finished();
}
};
server.onclose = function() {};
sock.onopen = function() {
sock.send(ok.buffer, 0, 2);
sock.send(ok.buffer, 2, 3);
};
}

Просмотреть файл

@ -1,311 +0,0 @@
/**
* 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
*
*/
// Test parameter.
const PORT = 8085;
const BACKLOG = -1;
// Some binary data to send.
const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length),
TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER),
HELLO_WORLD = "hlo wrld. ",
BIG_ARRAY = new Array(65539);
TYPED_DATA_ARRAY.set(DATA_ARRAY, 0);
for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
}
const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length);
const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER);
BIG_TYPED_ARRAY.set(BIG_ARRAY);
const TCPSocket = new (CC("@mozilla.org/tcp-socket;1",
"nsIDOMTCPSocket"))();
const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
/**
*
* Helper functions
*
*/
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.data + ""; }).join(" ") + ')';
}
else {
argstr = '(no arguments)';
}
do_throw('got unexpected: ' + name + ' ' + argstr);
};
}
function makeExpectData(name, expectedData, fromEvent, callback) {
let dataBuffer = fromEvent ? null : [], done = false;
let dataBufferView = null;
return function(receivedData) {
if (receivedData.data) {
receivedData = receivedData.data;
}
let recvLength = receivedData.byteLength !== undefined ?
receivedData.byteLength : receivedData.length;
if (fromEvent) {
if (dataBuffer) {
let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength);
let newBufferView = new Uint8Array(newBuffer);
newBufferView.set(dataBufferView, 0);
newBufferView.set(receivedData, dataBuffer.byteLength);
dataBuffer = newBuffer;
dataBufferView = newBufferView;
}
else {
dataBuffer = receivedData;
dataBufferView = new Uint8Array(dataBuffer);
}
}
else {
dataBuffer = dataBuffer.concat(receivedData);
}
do_print(name + ' received ' + recvLength + ' bytes');
if (done)
do_throw(name + ' Received data event when already done!');
let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer;
if (dataView.length >= expectedData.length) {
// check the bytes are equivalent
for (let i = 0; i < expectedData.length; i++) {
if (dataView[i] !== expectedData[i]) {
do_throw(name + ' Received mismatched character at position ' + i);
}
}
if (dataView.length > expectedData.length)
do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' +
expectedData.length + ' bytes.');
done = true;
if (callback) {
callback();
} else {
run_next_test();
}
}
};
}
var server = null, sock = null, connectedsock = null, failure_drain = null;
var count = 0;
/**
*
* 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() {
if (server) {
server.close();
}
var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']);
var options = { binaryType: 'arraybuffer' };
server = TCPSocket.listen(PORT, options, BACKLOG);
server.onconnect = function(socket) {
// Bug 937528 - Accepted client tcp socket (mozTcpSocket) has
// uninitialized host and port.
if (socket.host !== '127.0.0.1') {
do_throw('got unexpected: connected socket host should be 127.0.0.1 but not ' + socket.host);
} else {
do_print('Got expected connected socket host: ' + socket.host);
}
connectedsock = socket;
connectedsock.ondata = makeFailureCase('serverdata');
connectedsock.onerror = makeFailureCase('servererror');
connectedsock.onclose = makeFailureCase('serverclose');
yayFuncs.serveropen();
};
server.onerror = makeFailureCase('error');
sock = TCPSocket.open(
'127.0.0.1', PORT, options);
sock.onopen = yayFuncs.clientopen;
sock.ondrain = null;
sock.ondata = makeFailureCase('data');
sock.onerror = makeFailureCase('error');
sock.onclose = makeFailureCase('close');
}
/**
* Connect the socket to the server after the server was closed.
* This test is added after test to close the server was conducted.
*/
function openSockInClosingServer() {
var success = makeSuccessCase('clientnotopen');
var options = { binaryType: 'arraybuffer' };
sock = TCPSocket.open(
'127.0.0.1', PORT, options);
sock.onopen = makeFailureCase('open');
sock.onerror = success;
}
/**
* Test that sending a small amount of data works, and that buffering
* does not take place for this small amount of data.
*/
function sendDataToServer() {
connectedsock.ondata = makeExpectData('serverdata', DATA_ARRAY, true);
if (!sock.send(DATA_ARRAY_BUFFER)) {
do_throw("send should not have buffered such a small amount of data");
}
}
/**
* Test that data sent from the server correctly fires the ondata
* callback on the client side.
*/
function receiveDataFromServer() {
connectedsock.ondata = makeFailureCase('serverdata');
sock.ondata = makeExpectData('data', DATA_ARRAY, true);
connectedsock.send(DATA_ARRAY_BUFFER);
}
/**
* 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.
sock.ondata = makeFailureCase('data');
sock.onclose = makeFailureCase('close1');
connectedsock.onclose = makeFailureCase('close2');
server.close();
run_next_test();
}
/**
* Test that when the client closes the connection, the onclose callback
* is fired on the server side.
*/
function cleanup() {
do_print("Cleaning up");
sock.onclose = null;
connectedsock.onclose = null;
server.close();
sock.close();
if (count == 1){
if (!gInChild)
Services.prefs.clearUserPref('dom.mozTCPSocket.enabled');
}
count++;
run_next_test();
}
// - connect, data and events work both ways
add_test(connectSock);
add_test(sendDataToServer);
add_test(receiveDataFromServer);
// - server closes on us
add_test(serverCloses);
// - send and receive after closing server
add_test(sendDataToServer);
add_test(receiveDataFromServer);
// - check a connection refused from client to server after closing server
add_test(serverCloses);
add_test(openSockInClosingServer);
// - clean up
add_test(cleanup);
// - send and receive in reverse order for client and server
add_test(connectSock);
add_test(receiveDataFromServer);
add_test(sendDataToServer);
// - clean up
add_test(cleanup);
function run_test() {
if (!gInChild)
Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
run_next_test();
}

Просмотреть файл

@ -74,12 +74,6 @@ Cu.import("resource://gre/modules/Services.jsm");
*
*/
function get_platform() {
var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULRuntime);
return xulRuntime.OS;
}
function is_content() {
return this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
@ -290,184 +284,6 @@ function connectSock() {
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(DATA_ARRAY_BUFFER)) {
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_ARRAY_BUFFER)) {
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_ARRAY_BUFFER);
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');
let success = makeSuccessCase('error');
let gotError = false;
sock.onerror = function(event) {
do_check_eq(event.data.name, 'ConnectionRefusedError');
gotError = true;
};
sock.onclose = function() {
if (!gotError)
do_throw('got close without error!');
else
success();
};
}
/**
* 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']);
let ondrainCalled = false,
ondataCalled = false;
function maybeSendNextData() {
if (!ondrainCalled || !ondataCalled) {
// make sure server got data and client got ondrain.
return;
}
server.ondata = makeExpectData(
"ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
sock.ondrain = yays.ondrain2;
if (sock.send(BIG_ARRAY_BUFFER_2)) {
do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
}
sock.close();
}
function clientOndrain() {
yays.ondrain();
ondrainCalled = true;
maybeSendNextData();
}
function serverSideCallback() {
yays.ondata();
ondataCalled = true;
maybeSendNextData();
}
server.onclose = yays.serverclose;
server.ondata = makeExpectData(
"ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
sock.onclose = yays.clientclose;
sock.ondrain = clientOndrain;
if (sock.send(BIG_ARRAY_BUFFER)) {
throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
}
}
function cleanup() {
do_print("Cleaning up");
sock.close();
@ -476,35 +292,6 @@ function cleanup() {
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_ARRAY_BUFFER)) {
throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
}
if (sock.send(BIG_ARRAY_BUFFER_2)) {
throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
}
}
// Test child behavior when child thinks it's buffering but parent doesn't
// buffer.
// 1. set bufferedAmount of content socket to a value that will make next
@ -561,47 +348,18 @@ function childnotbuffered() {
});
};
// - 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);
if (get_platform() !== "Darwin") {
// This test intermittently fails way too often on OS X, for unknown reasons.
// Please, diagnose and fix it if you can.
// - 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);
if (is_content()) {
add_test(connectSock);
add_test(childnotbuffered);
add_test(connectSock);
add_test(childbuffered);
}
// clean up
add_test(cleanup);
} else {
do_check_true(true, 'non-content process wants to pretend to one test');
}
function run_test() {
if (!gInChild)

Просмотреть файл

@ -4,7 +4,3 @@ tail =
skip-if = toolkit == 'gonk'
[test_tcpsocket.js]
[test_multisend.js]
[test_tcpserversocket.js]
run-sequentially = Uses hardcoded port, bug 903830.
skip-if = os == 'mac' # bug 953208 - frequent timeouts on OSX

Просмотреть файл

@ -1,9 +0,0 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function run_test() {
Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
run_test_in_child("../unit/test_tcpserversocket.js", function() {
Services.prefs.clearUserPref('dom.mozTCPSocket.enabled');
do_test_finished();
});
}

Просмотреть файл

@ -4,5 +4,3 @@ tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_tcpsocket_ipc.js]
[test_tcpserversocket_ipc.js]
run-sequentially = Uses hardcoded port, bug 903830.