153 lines
6 KiB
JavaScript
153 lines
6 KiB
JavaScript
'use strict'
|
|
|
|
function peer(other, polite, fail = null) {
|
|
const send = (tgt, msg) => tgt.postMessage(JSON.parse(JSON.stringify(msg)),
|
|
"*");
|
|
if (!fail) fail = e => send(window.parent, {error: `${e.name}: ${e.message}`});
|
|
const pc = new RTCPeerConnection();
|
|
|
|
if (!window.assert_equals) {
|
|
window.assert_equals = (a, b, msg) => a === b ||
|
|
fail(new Error(`${msg} expected ${b} but got ${a}`));
|
|
}
|
|
|
|
const commands = {
|
|
async addTransceiver() {
|
|
const transceiver = pc.addTransceiver("video");
|
|
await new Promise(r => pc.addEventListener("negotiated", r, {once: true}));
|
|
if (!transceiver.currentDirection) {
|
|
// Might have just missed the negotiation train. Catch next one.
|
|
await new Promise(r => pc.addEventListener("negotiated", r, {once: true}));
|
|
}
|
|
assert_equals(transceiver.currentDirection, "sendonly", "have direction");
|
|
return pc.getTransceivers().length;
|
|
},
|
|
async simpleConnect() {
|
|
const p = commands.addTransceiver();
|
|
await new Promise(r => pc.oniceconnectionstatechange =
|
|
() => pc.iceConnectionState == "connected" && r());
|
|
return await p;
|
|
},
|
|
async getNumTransceivers() {
|
|
return pc.getTransceivers().length;
|
|
},
|
|
};
|
|
|
|
try {
|
|
pc.addEventListener("icecandidate", ({candidate}) => send(other,
|
|
{candidate}));
|
|
let makingOffer = false, ignoreIceCandidateFailures = false;
|
|
let srdAnswerPending = false;
|
|
pc.addEventListener("negotiationneeded", async () => {
|
|
try {
|
|
assert_equals(pc.signalingState, "stable", "negotiationneeded always fires in stable state");
|
|
assert_equals(makingOffer, false, "negotiationneeded not already in progress");
|
|
makingOffer = true;
|
|
await pc.setLocalDescription();
|
|
assert_equals(pc.signalingState, "have-local-offer", "negotiationneeded not racing with onmessage");
|
|
assert_equals(pc.localDescription.type, "offer", "negotiationneeded SLD worked");
|
|
send(other, {description: pc.localDescription});
|
|
} catch (e) {
|
|
fail(e);
|
|
} finally {
|
|
makingOffer = false;
|
|
}
|
|
});
|
|
window.onmessage = async ({data: {description, candidate, run}}) => {
|
|
try {
|
|
if (description) {
|
|
// If we have a setRemoteDescription() answer operation pending, then
|
|
// we will be "stable" by the time the next setRemoteDescription() is
|
|
// executed, so we count this being stable when deciding whether to
|
|
// ignore the offer.
|
|
let isStable =
|
|
pc.signalingState == "stable" ||
|
|
(pc.signalingState == "have-local-offer" && srdAnswerPending);
|
|
const ignoreOffer = description.type == "offer" && !polite &&
|
|
(makingOffer || !isStable);
|
|
if (ignoreOffer) {
|
|
ignoreIceCandidateFailures = true;
|
|
return;
|
|
}
|
|
if (description.type == "answer")
|
|
srdAnswerPending = true;
|
|
await pc.setRemoteDescription(description);
|
|
ignoreIceCandidateFailures = false;
|
|
srdAnswerPending = false;
|
|
if (description.type == "offer") {
|
|
assert_equals(pc.signalingState, "have-remote-offer", "Remote offer");
|
|
assert_equals(pc.remoteDescription.type, "offer", "SRD worked");
|
|
await pc.setLocalDescription();
|
|
assert_equals(pc.signalingState, "stable", "onmessage not racing with negotiationneeded");
|
|
assert_equals(pc.localDescription.type, "answer", "onmessage SLD worked");
|
|
send(other, {description: pc.localDescription});
|
|
} else {
|
|
assert_equals(pc.remoteDescription.type, "answer", "Answer was set");
|
|
assert_equals(pc.signalingState, "stable", "answered");
|
|
pc.dispatchEvent(new Event("negotiated"));
|
|
}
|
|
} else if (candidate) {
|
|
try {
|
|
await pc.addIceCandidate(candidate);
|
|
} catch (e) {
|
|
if (!ignoreIceCandidateFailures) throw e;
|
|
}
|
|
} else if (run) {
|
|
send(window.parent, {[run.id]: await commands[run.cmd]() || 0});
|
|
}
|
|
} catch (e) {
|
|
fail(e);
|
|
}
|
|
};
|
|
} catch (e) {
|
|
fail(e);
|
|
}
|
|
return pc;
|
|
}
|
|
|
|
async function setupPeerIframe(t, polite) {
|
|
const iframe = document.createElement("iframe");
|
|
t.add_cleanup(() => iframe.remove());
|
|
iframe.srcdoc =
|
|
`<html\><script\>(${peer.toString()})(window.parent, ${polite});</script\></html\>`;
|
|
document.documentElement.appendChild(iframe);
|
|
|
|
const failCatcher = t.step_func(({data}) =>
|
|
("error" in data) && assert_unreached(`Error in iframe: ${data.error}`));
|
|
window.addEventListener("message", failCatcher);
|
|
t.add_cleanup(() => window.removeEventListener("message", failCatcher));
|
|
await new Promise(r => iframe.onload = r);
|
|
return iframe;
|
|
}
|
|
|
|
function setupPeerTopLevel(t, other, polite) {
|
|
const pc = peer(other, polite, t.step_func(e => { throw e; }));
|
|
t.add_cleanup(() => { pc.close(); window.onmessage = null; });
|
|
}
|
|
|
|
let counter = 0;
|
|
async function run(target, cmd) {
|
|
const id = `result${counter++}`;
|
|
target.postMessage({run: {cmd, id}}, "*");
|
|
return new Promise(r => window.addEventListener("message",
|
|
function listen({data}) {
|
|
if (!(id in data)) return;
|
|
window.removeEventListener("message", listen);
|
|
r(data[id]);
|
|
}));
|
|
}
|
|
|
|
let iframe;
|
|
async function setupAB(t, politeA, politeB) {
|
|
iframe = await setupPeerIframe(t, politeB);
|
|
return setupPeerTopLevel(t, iframe.contentWindow, politeA);
|
|
}
|
|
const runA = cmd => run(window, cmd);
|
|
const runB = cmd => run(iframe.contentWindow, cmd);
|
|
const runBoth = (cmdA, cmdB = cmdA) => Promise.all([runA(cmdA), runB(cmdB)]);
|
|
|
|
async function promise_test_both_roles(f, name) {
|
|
promise_test(async t => f(t, await setupAB(t, true, false)), name);
|
|
promise_test(async t => f(t, await setupAB(t, false, true)),
|
|
`${name} with roles reversed`);
|
|
}
|