Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
*/
"use strict";
const FIRST_PARTY_A = "http://example.com";
const FIRST_PARTY_B = "http://example.org";
const THIRD_PARTY = "http://example.net";
const WS_ENDPOINT_HOST = "mochi.test:8888";
function getWSTestUrlForHost(host) {
return (
getRootDirectory(gTestPath).replace(
`ws://${host}`
) + `file_ws_handshake_delay`
);
}
function connect(browsingContext, host, protocol) {
let url = getWSTestUrlForHost(host);
info("Creating websocket with endpoint " + url);
// Create websocket connection in third party iframe.
let createPromise = SpecialPowers.spawn(
browsingContext.children[0],
[url, protocol],
(url, protocol) => {
let ws = new content.WebSocket(url, [protocol]);
ws.addEventListener("error", () => {
ws._testError = true;
});
if (!content.ws) {
content.ws = {};
}
content.ws[protocol] = ws;
}
);
let openPromise = createPromise.then(() =>
SpecialPowers.spawn(
browsingContext.children[0],
[protocol],
async protocol => {
let ws = content.ws[protocol];
if (ws.readyState != 0) {
return !ws._testError;
}
// Still connecting.
let result = await Promise.race([
ContentTaskUtils.waitForEvent(ws, "open"),
ContentTaskUtils.waitForEvent(ws, "error"),
]);
return result.type != "error";
}
)
);
let result = { createPromise, openPromise };
return result;
}
// Open 3 websockets which target the same ip/port combination, but have
// different principals. We send a protocol identifier to the server to signal
// how long the request should be delayed.
//
// When partitioning is disabled A blocks B and B blocks C. The timeline will
// look like this:
// A________
// B____
// C_
//
// When partitioning is enabled, connection handshakes for A and B will run
// (semi-) parallel since they have different origin attributes. B and C share
// origin attributes and therefore still run serially.
// A________
// B____
// C_
//
// By observing the order of the handshakes we can ensure that the queue
// partitioning is working correctly.
async function runTest(partitioned) {
await SpecialPowers.pushPrefEnv({
set: [["privacy.partition.network_state", partitioned]],
});
let tabA = BrowserTestUtils.addTab(gBrowser, FIRST_PARTY_A);
await BrowserTestUtils.browserLoaded(tabA.linkedBrowser);
let tabB = BrowserTestUtils.addTab(gBrowser, FIRST_PARTY_B);
await BrowserTestUtils.browserLoaded(tabB.linkedBrowser);
for (let tab of [tabA, tabB]) {
await SpecialPowers.spawn(tab.linkedBrowser, [THIRD_PARTY], async src => {
let frame = content.document.createElement("iframe");
frame.src = src;
let loadPromise = ContentTaskUtils.waitForEvent(frame, "load");
content.document.body.appendChild(frame);
await loadPromise;
});
}
// First ensure that we can open websocket connections to the test endpoint.
let { openPromise, createPromise } = await connect(
tabA.linkedBrowser.browsingContext,
WS_ENDPOINT_HOST,
false
);
await createPromise;
let openPromiseResult = await openPromise;
ok(openPromiseResult, "Websocket endpoint accepts connections.");
let openedA;
let openedB;
let openedC;
let { createPromise: createPromiseA, openPromise: openPromiseA } = connect(
tabA.linkedBrowser.browsingContext,
WS_ENDPOINT_HOST,
"test-6"
);
openPromiseA = openPromiseA.then(opened => {
openedA = opened;
info("Completed WS connection A");
if (partitioned) {
ok(openedA, "Should have opened A");
ok(openedB, "Should have opened B");
} else {
ok(openedA, "Should have opened A");
ok(openedB == null, "B should be pending");
}
});
await createPromiseA;
// The frame of connection B is embedded in a different first party as A.
let { createPromise: createPromiseB, openPromise: openPromiseB } = connect(
tabB.linkedBrowser.browsingContext,
WS_ENDPOINT_HOST,
"test-3"
);
openPromiseB = openPromiseB.then(opened => {
openedB = opened;
info("Completed WS connection B");
if (partitioned) {
ok(openedA == null, "A should be pending");
ok(openedB, "Should have opened B");
ok(openedC == null, "C should be pending");
} else {
ok(openedA, "Should have opened A");
ok(openedB, "Should have opened B");
ok(openedC == null, "C should be pending");
}
});
await createPromiseB;
// The frame of connection C is embedded in the same first party as B.
let { createPromise: createPromiseC, openPromise: openPromiseC } = connect(
tabB.linkedBrowser.browsingContext,
WS_ENDPOINT_HOST,
"test-0"
);
openPromiseC = openPromiseC.then(opened => {
openedC = opened;
info("Completed WS connection C");
if (partitioned) {
ok(openedB, "Should have opened B");
ok(openedC, "Should have opened C");
} else {
ok(opened, "Should have opened B");
ok(opened, "Should have opened C");
}
});
await createPromiseC;
// Wait for all connections to complete before closing the tabs.
await Promise.all([openPromiseA, openPromiseB, openPromiseC]);
BrowserTestUtils.removeTab(tabA);
BrowserTestUtils.removeTab(tabB);
await SpecialPowers.popPrefEnv();
}
add_setup(async function () {
// This test relies on a WS connection timeout > 6 seconds.
await SpecialPowers.pushPrefEnv({
set: [["network.websocket.timeout.open", 20]],
});
});
add_task(async function test_non_partitioned() {
await runTest(false);
});
add_task(async function test_partitioned() {
await runTest(true);
});