| '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`); |
| } |