summaryrefslogtreecommitdiffstats
path: root/dom/network/tests
diff options
context:
space:
mode:
Diffstat (limited to 'dom/network/tests')
-rw-r--r--dom/network/tests/chrome.ini11
-rw-r--r--dom/network/tests/file_postMessage_opener.html11
-rw-r--r--dom/network/tests/file_udpsocket_iframe.html22
-rw-r--r--dom/network/tests/mochitest.ini9
-rw-r--r--dom/network/tests/tcpsocket_test.sys.mjs12
-rw-r--r--dom/network/tests/test_network_basics.html38
-rw-r--r--dom/network/tests/test_network_basics_worker.html35
-rw-r--r--dom/network/tests/test_tcpsocket_client_and_server_basics.html46
-rw-r--r--dom/network/tests/test_tcpsocket_client_and_server_basics.js617
-rw-r--r--dom/network/tests/test_tcpsocket_jsm.html30
-rw-r--r--dom/network/tests/test_tcpsocket_legacy.html57
-rw-r--r--dom/network/tests/test_tcpsocket_not_exposed_to_content.html25
-rw-r--r--dom/network/tests/test_udpsocket.html403
-rw-r--r--dom/network/tests/worker_network_basics.js28
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();