diff options
Diffstat (limited to 'dom/network/tests')
-rw-r--r-- | dom/network/tests/chrome.ini | 11 | ||||
-rw-r--r-- | dom/network/tests/file_postMessage_opener.html | 11 | ||||
-rw-r--r-- | dom/network/tests/file_udpsocket_iframe.html | 22 | ||||
-rw-r--r-- | dom/network/tests/mochitest.ini | 9 | ||||
-rw-r--r-- | dom/network/tests/tcpsocket_test.sys.mjs | 12 | ||||
-rw-r--r-- | dom/network/tests/test_network_basics.html | 38 | ||||
-rw-r--r-- | dom/network/tests/test_network_basics_worker.html | 35 | ||||
-rw-r--r-- | dom/network/tests/test_tcpsocket_client_and_server_basics.html | 46 | ||||
-rw-r--r-- | dom/network/tests/test_tcpsocket_client_and_server_basics.js | 617 | ||||
-rw-r--r-- | dom/network/tests/test_tcpsocket_jsm.html | 30 | ||||
-rw-r--r-- | dom/network/tests/test_tcpsocket_legacy.html | 57 | ||||
-rw-r--r-- | dom/network/tests/test_tcpsocket_not_exposed_to_content.html | 25 | ||||
-rw-r--r-- | dom/network/tests/test_udpsocket.html | 403 | ||||
-rw-r--r-- | dom/network/tests/worker_network_basics.js | 28 |
14 files changed, 1344 insertions, 0 deletions
diff --git a/dom/network/tests/chrome.ini b/dom/network/tests/chrome.ini new file mode 100644 index 0000000000..a0b6c2ae3f --- /dev/null +++ b/dom/network/tests/chrome.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + tcpsocket_test.sys.mjs + test_tcpsocket_client_and_server_basics.js + file_postMessage_opener.html + file_udpsocket_iframe.html + +[test_tcpsocket_jsm.html] +[test_tcpsocket_client_and_server_basics.html] +[test_tcpsocket_legacy.html] +[test_udpsocket.html] diff --git a/dom/network/tests/file_postMessage_opener.html b/dom/network/tests/file_postMessage_opener.html new file mode 100644 index 0000000000..87359e25c2 --- /dev/null +++ b/dom/network/tests/file_postMessage_opener.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <script> + opener.postMessage("hello", "*"); + </script> +</head> +<body> +</body> +</html> diff --git a/dom/network/tests/file_udpsocket_iframe.html b/dom/network/tests/file_udpsocket_iframe.html new file mode 100644 index 0000000000..d48e03ee2b --- /dev/null +++ b/dom/network/tests/file_udpsocket_iframe.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test UDPSocket BFCache</title> +</head> +<body> +<script type="application/javascript"> +'use strict'; +window.addEventListener('load', function() { + let remotePort = parseInt(window.location.search.substring(1), 10); + let socket = new UDPSocket(); + socket.addEventListener('message', function () { + socket.send('fail', '127.0.0.1', remotePort); + }); + + socket.opened.then(function() { + socket.send('ready', '127.0.0.1', remotePort); + }); +}, {once: true}); +</script> +</body> +</html> diff --git a/dom/network/tests/mochitest.ini b/dom/network/tests/mochitest.ini new file mode 100644 index 0000000000..3fdb2ebd0a --- /dev/null +++ b/dom/network/tests/mochitest.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + worker_network_basics.js + +[test_network_basics.html] +skip-if = toolkit == 'android' +[test_network_basics_worker.html] +skip-if = toolkit == 'android' +[test_tcpsocket_not_exposed_to_content.html] diff --git a/dom/network/tests/tcpsocket_test.sys.mjs b/dom/network/tests/tcpsocket_test.sys.mjs new file mode 100644 index 0000000000..7a51aced41 --- /dev/null +++ b/dom/network/tests/tcpsocket_test.sys.mjs @@ -0,0 +1,12 @@ +export var createSocket = function (host, port, options) { + return new TCPSocket(host, port, options); +}; + +export var createServer = function (port, options, backlog) { + return new TCPServerSocket(port, options, backlog); +}; + +// See test_tcpsocket_client_and_server_basics.html's version for rationale. +export var socketCompartmentInstanceOfArrayBuffer = function (obj) { + return obj instanceof ArrayBuffer; +}; diff --git a/dom/network/tests/test_network_basics.html b/dom/network/tests/test_network_basics.html new file mode 100644 index 0000000000..e657af916d --- /dev/null +++ b/dom/network/tests/test_network_basics.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Network API</title> + <script 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 for Network Information API **/ +function test() { + ok('connection' in navigator, "navigator.connection should exist"); + + ok(navigator.connection, "navigator.connection returns an object"); + + ok(navigator.connection instanceof EventTarget, + "navigator.connection is a EventTarget object"); + + ok('type' in navigator.connection, + "type should be a Connection attribute"); + is(navigator.connection.type, "none", + "By default connection.type equals to none"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({'set': [["dom.netinfo.enabled", true]]}, test); + +</script> +</pre> +</body> +</html> diff --git a/dom/network/tests/test_network_basics_worker.html b/dom/network/tests/test_network_basics_worker.html new file mode 100644 index 0000000000..5763ee361b --- /dev/null +++ b/dom/network/tests/test_network_basics_worker.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Network in workers API</title> + <script 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 for Network Information API **/ +function test() { + let w = new Worker('worker_network_basics.js'); + w.onmessage = function(e) { + if (e.data.type == 'status') { + ok(e.data.status, e.data.msg); + } else if (e.data.type == 'finish') { + SimpleTest.finish(); + } else { + ok(false, "Unknown message type"); + } + } +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({'set': [["dom.netinfo.enabled", true]]}, test); + +</script> +</pre> +</body> +</html> diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.html b/dom/network/tests/test_tcpsocket_client_and_server_basics.html new file mode 100644 index 0000000000..d7e40a4328 --- /dev/null +++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.html @@ -0,0 +1,46 @@ +<!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 src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + function createServer(port, options, backlog) { + return new TCPServerSocket(port, options, backlog); + } + + function createSocket(host, port, options) { + return new TCPSocket(host, port, options); + } + + // In the JSM case, ArrayBuffers will be created in the compartment of the + // JSM with different globals than the + // test_tcpsocket_client_and_server_basics.js test logic sees, so we (and + // tcpsocket_test.sys.mjs) need to do something. To avoid complexity relating + // to wrappers and the varying nuances of the module scope and global scope + // in JSM's (they differ on B2G), we hardcode ArrayBuffer rather than taking + // a string that we look up, etc. + function socketCompartmentInstanceOfArrayBuffer(obj) { + return obj instanceof ArrayBuffer; + } + </script> + <script type="application/javascript" 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> diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.js b/dom/network/tests/test_tcpsocket_client_and_server_basics.js new file mode 100644 index 0000000000..ad653838be --- /dev/null +++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js @@ -0,0 +1,617 @@ +"use strict"; + +// These are defined in test_tcpsocket_client_and_server_basics.html +/* global createServer, createSocket, socketCompartmentInstanceOfArrayBuffer */ + +// Bug 788960 and later bug 1329245 have taught us that attempting to connect to +// a port that is not listening or is no longer listening fails to consistently +// result in the error (or any) event we expect on Darwin/OSX/"OS X". +const isOSX = Services.appinfo.OS === "Darwin"; +const testConnectingToNonListeningPort = !isOSX; + +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's constructor is called or the onconnect method is being + * invoked. + */ +function listenForEventsOnSocket(socket, socketType) { + let wantDataLength = null; + let wantDataAndClose = false; + 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 = function (event) { + if (!wantDataAndClose) { + handleGenericEvent(event); + } else if (pendingResolve) { + dump("(" + socketType + " event: close)\n"); + pendingResolve(receivedData); + pendingResolve = null; + wantDataAndClose = false; + } + }; + socket.ondata = function (event) { + dump( + "(" + + socketType + + " event: " + + event.type + + " length: " + + event.data.byteLength + + ")\n" + ); + ok( + socketCompartmentInstanceOfArrayBuffer(event.data), + "payload is ArrayBuffer" + ); + 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() { + 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(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; + }); + }, + waitForAnyDataAndClose() { + if (pendingResolve) { + throw new Error("only one wait allowed at a time."); + } + + return new Promise(function (resolve, reject) { + pendingResolve = resolve; + // we may receive no data before getting close, in which case we want to + // return an empty array + receivedData = new Uint8Array(); + wantDataAndClose = true; + }); + }, + }; +} + +/** + * 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 (event) { + // 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: event.socket, + queue: listenForEventsOnSocket(event.socket, "server"), + }); + }; + }); +} + +function defer() { + var deferred = {}; + deferred.promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; +} + +async function test_basics() { + // 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; + + // - Start up a listening socket. + let listeningServer = createServer( + serverPort, + { binaryType: "arraybuffer" }, + SERVER_BACKLOG + ); + + let connectedPromise = waitForConnection(listeningServer); + + // -- Open a connection to the server + let clientSocket = createSocket("127.0.0.1", serverPort, { + binaryType: "arraybuffer", + }); + let clientQueue = listenForEventsOnSocket(clientSocket, "client"); + + // (the client connects) + is((await clientQueue.waitForEvent()).type, "open", "got open event"); + is(clientSocket.readyState, "open", "client readyState is open"); + + // (the server connected) + let { socket: serverSocket, queue: serverQueue } = await 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 = await 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 = await 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 = await 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 = await 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; + } + // This can be anything from 1 to 65536. The idea is spliting and sending + // bigUint8Array in two chunks should trigger ondrain the same as sending + // bigUint8Array in one chunk. + let lengthOfChunk1 = 65536; + is( + clientSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1), + true, + "Client sending chunk1 should not result in the buffer being full." + ); + // Do this twice so we have confidence that the 'drain' event machinery + // doesn't break after the first use. The first time we send bigUint8Array in + // two chunks, the second time we send bigUint8Array in one chunk. + for (let iSend = 0; iSend < 2; iSend++) { + // - Send "big" data from the client to the server + let offset = iSend == 0 ? lengthOfChunk1 : 0; + is( + clientSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length), + false, + "Client sending more than 64k should result in the buffer being full." + ); + is( + (await clientQueue.waitForEvent()).type, + "drain", + "The drain event should fire after a large send that indicated full." + ); + + serverReceived = await serverQueue.waitForDataWithAtLeastLength( + bigUint8Array.length + ); + assertUint8ArraysEqual( + serverReceived, + bigUint8Array, + "server received/client sent" + ); + + if (iSend == 0) { + is( + serverSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1), + true, + "Server sending chunk1 should not result in the buffer being full." + ); + } + // - Send "big" data from the server to the client + is( + serverSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length), + false, + "Server sending more than 64k should result in the buffer being full." + ); + is( + (await serverQueue.waitForEvent()).type, + "drain", + "The drain event should fire after a large send that indicated full." + ); + + clientReceived = await 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( + (await 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( + (await 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 = createSocket("127.0.0.1", serverPort, { + binaryType: "arraybuffer", + }); + clientQueue = listenForEventsOnSocket(clientSocket, "client"); + is((await clientQueue.waitForEvent()).type, "open", "got open event"); + + let connectedResult = await 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( + (await 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( + (await 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 = createSocket("127.0.0.1", serverPort, { + binaryType: "arraybuffer", + }); + clientQueue = listenForEventsOnSocket(clientSocket, "client"); + is((await clientQueue.waitForEvent()).type, "open", "got open event"); + + connectedResult = await 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( + (await clientQueue.waitForEvent()).type, + "drain", + "The drain event should fire after a large send that returned true." + ); + // Then we'll get a close + is( + (await clientQueue.waitForEvent()).type, + "close", + "The close event should fire after the drain event." + ); + + // The server will get its data + serverReceived = await serverQueue.waitForDataWithAtLeastLength( + bigUint8Array.length + ); + assertUint8ArraysEqual( + serverReceived, + bigUint8Array, + "server received/client sent" + ); + // And a close. + is( + (await serverQueue.waitForEvent()).type, + "close", + "The drain event should fire after a large send that returned true." + ); + + // -- Re-establish connection + connectedPromise = waitForConnection(listeningServer); + clientSocket = createSocket("127.0.0.1", serverPort, { + binaryType: "string", + }); + clientQueue = listenForEventsOnSocket(clientSocket, "client"); + is((await clientQueue.waitForEvent()).type, "open", "got open event"); + + connectedResult = await connectedPromise; + // destructuring assignment is not yet ES6 compliant, must manually unpack + serverSocket = connectedResult.socket; + serverQueue = connectedResult.queue; + + // -- Attempt to send non-string data. + // Restore the original behavior by replacing toString with + // Object.prototype.toString. (bug 1121938) + bigUint8Array.toString = Object.prototype.toString; + is( + clientSocket.send(bigUint8Array), + true, + "Client sending a large non-string should only send a small string." + ); + clientSocket.close(); + // The server will get its data + serverReceived = await serverQueue.waitForDataWithAtLeastLength( + bigUint8Array.toString().length + ); + // Then we'll get a close + is( + (await clientQueue.waitForEvent()).type, + "close", + "The close event should fire after the drain event." + ); + + // -- Re-establish connection (Test for Close Immediately) + connectedPromise = waitForConnection(listeningServer); + clientSocket = createSocket("127.0.0.1", serverPort, { + binaryType: "arraybuffer", + }); + clientQueue = listenForEventsOnSocket(clientSocket, "client"); + is((await clientQueue.waitForEvent()).type, "open", "got open event"); + + connectedResult = await connectedPromise; + // destructuring assignment is not yet ES6 compliant, must manually unpack + serverSocket = connectedResult.socket; + serverQueue = connectedResult.queue; + + // -- Attempt to send two non-string data. + is( + clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), + false, + "Server sending more than 64k should result in the buffer being full." + ); + is( + clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), + false, + "Server sending more than 64k should result in the buffer being full." + ); + clientSocket.closeImmediately(); + + serverReceived = await serverQueue.waitForAnyDataAndClose(); + + is( + serverReceived.length < 2 * bigUint8Array.length, + true, + "Received array length less than sent array length" + ); + + // -- 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(); + + // (We don't run this check on OS X where it's flakey; see definition up top.) + if (testConnectingToNonListeningPort) { + // - try and connect, get an error + clientSocket = createSocket("127.0.0.1", serverPort, { + binaryType: "arraybuffer", + }); + clientQueue = listenForEventsOnSocket(clientSocket, "client"); + is((await 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); + +/** + * Test that TCPSocket works with ipv6 address. + */ +add_task(async function test_ipv6() { + const { HttpServer } = ChromeUtils.import( + "resource://testing-common/httpd.js" + ); + let deferred = defer(); + let httpServer = new HttpServer(); + httpServer.start_ipv6(-1); + + let clientSocket = new TCPSocket("::1", httpServer.identity.primaryPort); + clientSocket.onopen = () => { + ok(true, "Connect to ipv6 address succeeded"); + deferred.resolve(); + }; + clientSocket.onerror = () => { + ok(false, "Connect to ipv6 address failed"); + deferred.reject(); + }; + await deferred.promise; + await httpServer.stop(); +}); diff --git a/dom/network/tests/test_tcpsocket_jsm.html b/dom/network/tests/test_tcpsocket_jsm.html new file mode 100644 index 0000000000..026d71ed14 --- /dev/null +++ b/dom/network/tests/test_tcpsocket_jsm.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<!-- +--> +<head> + <meta charset="utf-8"> + <title>Test for 1207090</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + // These are used in the loaded script file. + // eslint-disable-next-line no-unused-vars + let { + createSocket, + createServer, + socketCompartmentInstanceOfArrayBuffer + } = ChromeUtils.importESModule("chrome://mochitests/content/chrome/dom/network/tests/tcpsocket_test.sys.mjs"); + </script> + <script type="application/javascript" src="test_tcpsocket_client_and_server_basics.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1207090">Mozilla Bug 1207090</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<div id="container"></div> +</body> +</html> diff --git a/dom/network/tests/test_tcpsocket_legacy.html b/dom/network/tests/test_tcpsocket_legacy.html new file mode 100644 index 0000000000..f5c76fb37f --- /dev/null +++ b/dom/network/tests/test_tcpsocket_legacy.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test of legacy navigator interface for opening TCPSocket/TCPServerSocket. +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 885982</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</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> +<script> + SimpleTest.waitForExplicitFinish(); + + function runTest() { + // 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. + var serverPort = 8085; + + var listeningServer = navigator.mozTCPSocket.listen(serverPort, + { binaryType: 'arraybuffer' }, + -1); + listeningServer.onconnect = function(ev) { + ok(true, "got server connect"); + listeningServer.close(); + listeningServer = null; + ev.socket.close() + } + + var clientSocket = navigator.mozTCPSocket.open('127.0.0.1', serverPort, + { binaryType: 'arraybuffer' }); + clientSocket.onopen = function() { ok(true, "got client open"); } + clientSocket.onclose = function() { + ok(true, "got client close"); + clientSocket.close(); + clientSocket = null; + setTimeout(function() { + // This just helps the test harness clean up quickly + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SimpleTest.finish(); + }, 0); + } + } + runTest(); // we used to invoke this as part of a moot pref-setting callback +</script> +</body> +</html> diff --git a/dom/network/tests/test_tcpsocket_not_exposed_to_content.html b/dom/network/tests/test_tcpsocket_not_exposed_to_content.html new file mode 100644 index 0000000000..4154eeca5c --- /dev/null +++ b/dom/network/tests/test_tcpsocket_not_exposed_to_content.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test to ensure TCPSocket permission enabled and no tcp-socket perm does not allow open</title> + <script 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"> +/** + * TCPSocket and its legacy mozTCPSocket variant should never be exposed to + * content. + */ + +is('TCPSocket' in this, false, "TCPSocket should not be accessible to content"); +is('TCPServerSocket' in this, false, "TCPServerSocket should not be accessible to content"); +is('mozTCPSocket' in navigator, false, "mozTCPSocket should not be accessible to content"); +</script> +</pre> +</body> +</html> diff --git a/dom/network/tests/test_udpsocket.html b/dom/network/tests/test_udpsocket.html new file mode 100644 index 0000000000..1ca42f2432 --- /dev/null +++ b/dom/network/tests/test_udpsocket.html @@ -0,0 +1,403 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test UDPSocket API</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<iframe id="iframe"></iframe> +<pre id="test"> +<script type="application/javascript"> +'use strict'; +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +const HELLO_WORLD = 'hlo wrld. '; +const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0]; +const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length); +const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER); +const BIG_ARRAY = new Array(4096); +const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length); +const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER); + +for (let i = 0; i < BIG_ARRAY.length; i++) { + BIG_ARRAY[i] = Math.floor(Math.random() * 256); +} + +TYPED_DATA_ARRAY.set(DATA_ARRAY); +BIG_TYPED_ARRAY.set(BIG_ARRAY); + +function is_same_buffer(recv_data, expect_data) { + let recv_dataview = new Uint8Array(recv_data); + let expected_dataview = new Uint8Array(expect_data); + + if (recv_dataview.length !== expected_dataview.length) { + return false; + } + + for (let i = 0; i < recv_dataview.length; i++) { + if (recv_dataview[i] != expected_dataview[i]) { + info('discover byte differenct at ' + i); + return false; + } + } + return true; +} + +function testOpen() { + info('test for creating an UDP Socket'); + let socket = new UDPSocket(); + is(socket.localPort, null, 'expect no local port before socket opened'); + is(socket.localAddress, null, 'expect no local address before socket opened'); + is(socket.remotePort, null, 'expected no default remote port'); + is(socket.remoteAddress, null, 'expected no default remote address'); + is(socket.readyState, 'opening', 'expected ready state = opening'); + is(socket.loopback, false, 'expected no loopback'); + is(socket.addressReuse, true, 'expect to reuse address'); + + return socket.opened.then(function() { + ok(true, 'expect openedPromise to be resolved after successful socket binding'); + ok(!(socket.localPort === 0), 'expect allocated a local port'); + is(socket.localAddress, '0.0.0.0', 'expect assigned to default address'); + is(socket.readyState, 'open', 'expected ready state = open'); + + return socket; + }); +} + +function testSendString(socket) { + info('test for sending string data'); + + socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort); + + return new Promise(function(resolve, reject) { + socket.addEventListener('message', function(msg) { + let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + is(recvData, HELLO_WORLD, 'expected same string data'); + resolve(socket); + }, {once: true}); + }); +} + +function testSendArrayBuffer(socket) { + info('test for sending ArrayBuffer'); + + socket.send(DATA_ARRAY_BUFFER, '127.0.0.1', socket.localPort); + + return new Promise(function(resolve, reject) { + socket.addEventListener('message', function(msg) { + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + ok(is_same_buffer(msg.data, DATA_ARRAY_BUFFER), 'expected same buffer data'); + resolve(socket); + }, {once: true}); + }); +} + +function testSendArrayBufferView(socket) { + info('test for sending ArrayBufferView'); + + socket.send(TYPED_DATA_ARRAY, '127.0.0.1', socket.localPort); + + return new Promise(function(resolve, reject) { + socket.addEventListener('message', function(msg) { + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + ok(is_same_buffer(msg.data, TYPED_DATA_ARRAY), 'expected same buffer data'); + resolve(socket); + }, {once: true}); + }); +} + +function testSendBlob(socket) { + info('test for sending Blob'); + + let blob = new Blob([HELLO_WORLD], {type : 'text/plain'}); + socket.send(blob, '127.0.0.1', socket.localPort); + + return new Promise(function(resolve, reject) { + socket.addEventListener('message', function(msg) { + let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + is(recvData, HELLO_WORLD, 'expected same string data'); + resolve(socket); + }, {once: true}); + }); +} + +function testSendBigArray(socket) { + info('test for sending Big ArrayBuffer'); + + socket.send(BIG_TYPED_ARRAY, '127.0.0.1', socket.localPort); + + return new Promise(function(resolve, reject) { + let byteReceived = 0; + socket.addEventListener('message', function recv_callback(msg) { + let byteBegin = byteReceived; + byteReceived += msg.data.byteLength; + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']'); + if (byteReceived >= BIG_TYPED_ARRAY.length) { + socket.removeEventListener('message', recv_callback); + resolve(socket); + } + }); + }); +} + +function testSendBigBlob(socket) { + info('test for sending Big Blob'); + + let blob = new Blob([BIG_TYPED_ARRAY]); + socket.send(blob, '127.0.0.1', socket.localPort); + + return new Promise(function(resolve, reject) { + let byteReceived = 0; + socket.addEventListener('message', function recv_callback(msg) { + let byteBegin = byteReceived; + byteReceived += msg.data.byteLength; + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']'); + if (byteReceived >= BIG_TYPED_ARRAY.length) { + socket.removeEventListener('message', recv_callback); + resolve(socket); + } + }); + }); +} + +function testUDPOptions(socket) { + info('test for UDP init options'); + + let remoteSocket = new UDPSocket({addressReuse: false, + loopback: true, + localAddress: '127.0.0.1', + remoteAddress: '127.0.0.1', + remotePort: socket.localPort}); + is(remoteSocket.localAddress, '127.0.0.1', 'expected local address'); + is(remoteSocket.remoteAddress, '127.0.0.1', 'expected remote address'); + is(remoteSocket.remotePort, socket.localPort, 'expected remote port'); + is(remoteSocket.addressReuse, false, 'expected address not reusable'); + is(remoteSocket.loopback, true, 'expected loopback mode is on'); + + return remoteSocket.opened.then(function() { + remoteSocket.send(HELLO_WORLD); + return new Promise(function(resolve, reject) { + socket.addEventListener('message', function(msg) { + let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); + is(msg.remotePort, remoteSocket.localPort, 'expected packet from ' + remoteSocket.localPort); + is(recvData, HELLO_WORLD, 'expected same string data'); + resolve(socket); + }, {once: true}); + }); + }); +} + +function testClose(socket) { + info('test for close'); + + socket.close(); + is(socket.readyState, 'closed', 'expect ready state to be "closed"'); + try { + socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort); + ok(false, 'unexpect to send successfully'); + } catch (e) { + ok(true, 'expected send fail after socket closed'); + } + + return socket.closed.then(function() { + ok(true, 'expected closedPromise is resolved after socket.close()'); + }); +} + +function testMulticast() { + info('test for multicast'); + + let socket = new UDPSocket({loopback: true}); + + const MCAST_ADDRESS = '224.0.0.255'; + socket.joinMulticastGroup(MCAST_ADDRESS); + + return socket.opened.then(function() { + socket.send(HELLO_WORLD, MCAST_ADDRESS, socket.localPort); + + return new Promise(function(resolve, reject) { + socket.addEventListener('message', function(msg) { + let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data)); + is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); + is(recvData, HELLO_WORLD, 'expected same string data'); + socket.leaveMulticastGroup(MCAST_ADDRESS); + resolve(); + }, {once: true}); + }); + }); +} + +function testInvalidUDPOptions() { + info('test for invalid UDPOptions'); + try { + new UDPSocket({localAddress: 'not-a-valid-address'}); + ok(false, 'should not create an UDPSocket with an invalid localAddress'); + } catch (e) { + is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localAddress is not a valid IPv4/6 address'); + } + + try { + new UDPSocket({localPort: 0}); + ok(false, 'should not create an UDPSocket with an invalid localPort'); + } catch (e) { + is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number'); + } + + try { + new UDPSocket({remotePort: 0}); + ok(false, 'should not create an UDPSocket with an invalid remotePort'); + } catch (e) { + is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number'); + } +} + +function testOpenFailed() { + info('test for falied on open'); + + //according to RFC5737, address block 192.0.2.0/24 should not be used in both local and public contexts + let socket = new UDPSocket({localAddress: '192.0.2.0'}); + + return socket.opened.then(function() { + ok(false, 'should not resolve openedPromise while fail to bind socket'); + socket.close(); + }).catch(function(reason) { + is(reason.name, 'NetworkError', 'expected openedPromise to be rejected while fail to bind socket'); + }); +} + +function testSendBeforeOpen() { + info('test for send before open'); + + let socket = new UDPSocket(); + + try { + socket.send(HELLO_WORLD, '127.0.0.1', 9); + ok(false, 'unexpect to send successfully'); + } catch (e) { + ok(true, 'expected send fail before openedPromise is resolved'); + } + + return socket.opened.then(function() { + socket.close(); + }); +} + +function testCloseBeforeOpened() { + info('test for close socket before opened'); + + let socket = new UDPSocket(); + socket.opened.then(function() { + ok(false, 'should not resolve openedPromise if it has already been closed'); + }).catch(function(reason) { + is(reason.name, 'AbortError', 'expected openedPromise to be rejected while socket is closed during opening'); + }); + + return socket.close().then(function() { + ok(true, 'expected closedPromise to be resolved'); + }).then(socket.opened); +} + +function testOpenWithoutClose() { + info('test for open without close'); + + let closed = []; + for (let i = 0; i < 50; i++) { + let socket = new UDPSocket(); + closed.push(socket.closed); + } + + SpecialPowers.gc(); + info('all unrefereced socket should be closed right after GC'); + + return Promise.all(closed); +} + +function awaitEvent(target, event) { + return new Promise(resolve => { + target.addEventListener(event, resolve, { once: true }); + }); +} + +async function testBFCache() { + info('test for bfcache behavior'); + + let socket = new UDPSocket(); + + await socket.opened; + + let win = window.open(`file_udpsocket_iframe.html?${socket.localPort}`); + + let msg = await awaitEvent(socket, "message"); + + win.location = "file_postMessage_opener.html"; + await awaitEvent(window, "message"); + + socket.send(HELLO_WORLD, '127.0.0.1', msg.remotePort); + + await new Promise(resolve => { + function recv_again_callback(message) { + socket.removeEventListener('message', recv_again_callback); + ok(false, 'should not receive packet after page unload'); + } + + socket.addEventListener('message', recv_again_callback); + + setTimeout(function() { + socket.removeEventListener('message', recv_again_callback); + socket.close(); + resolve(); + }, 5000); + }); + + win.close(); +} + +function runTest() { + testOpen() + .then(testSendString) + .then(testSendArrayBuffer) + .then(testSendArrayBufferView) + .then(testSendBlob) + .then(testSendBigArray) + .then(testSendBigBlob) + .then(testUDPOptions) + .then(testClose) + .then(testMulticast) + .then(testInvalidUDPOptions) + .then(testOpenFailed) + .then(testSendBeforeOpen) + .then(testCloseBeforeOpened) + .then(testOpenWithoutClose) + .then(testBFCache) + .then(function() { + info('test finished'); + SimpleTest.finish(); + }) + .catch(function(err) { + ok(false, 'test failed due to: ' + err); + SimpleTest.finish(); + }); +} + +window.addEventListener('load', function () { + SpecialPowers.pushPrefEnv({ + 'set': [ + ['dom.udpsocket.enabled', true], + ['browser.sessionhistory.max_total_viewers', 10] + ] + }, runTest); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/network/tests/worker_network_basics.js b/dom/network/tests/worker_network_basics.js new file mode 100644 index 0000000000..25e8c5dd8e --- /dev/null +++ b/dom/network/tests/worker_network_basics.js @@ -0,0 +1,28 @@ +function ok(a, msg) { + postMessage({ type: "status", status: !!a, msg }); +} + +function is(a, b, msg) { + ok(a === b, msg); +} + +function finish() { + postMessage({ type: "finish" }); +} + +ok("connection" in navigator, "navigator.connection should exist"); + +ok(navigator.connection, "navigator.connection returns an object"); + +ok( + navigator.connection instanceof EventTarget, + "navigator.connection is a EventTarget object" +); + +ok("type" in navigator.connection, "type should be a Connection attribute"); +is( + navigator.connection.type, + "none", + "By default connection.type equals to none" +); +finish(); |