198 lines
5.7 KiB
JavaScript
198 lines
5.7 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
|
*/
|
|
|
|
"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(
|
|
"chrome://mochitests/content",
|
|
`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);
|
|
});
|