summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/RTCPeerConnection-perfect-negotiation-helper.js
blob: ed647bbe78fed07eb31fb89274edee8be96ae8e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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`);
}