diff options
Diffstat (limited to 'testing/web-platform/tests/webrtc/RTCPeerConnection-perfect-negotiation-helper.js')
-rw-r--r-- | testing/web-platform/tests/webrtc/RTCPeerConnection-perfect-negotiation-helper.js | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc/RTCPeerConnection-perfect-negotiation-helper.js b/testing/web-platform/tests/webrtc/RTCPeerConnection-perfect-negotiation-helper.js new file mode 100644 index 0000000000..ed647bbe78 --- /dev/null +++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-perfect-negotiation-helper.js @@ -0,0 +1,153 @@ +'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`); +} |