tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RTCPeerConnection-iceGatheringState.html (12697B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCPeerConnection.prototype.iceGatheringState</title>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="RTCPeerConnection-helper.js"></script>
      8 <script>
      9  'use strict';
     10 
     11  // Test is based on the following editor draft:
     12  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     13 
     14  // The following helper functions are called from RTCPeerConnection-helper.js:
     15  // exchangeAnswer
     16  // exchangeIceCandidates
     17  // generateAudioReceiveOnlyOffer
     18 
     19  /*
     20    4.3.2.  Interface Definition
     21      interface RTCPeerConnection : EventTarget {
     22        ...
     23        readonly  attribute RTCIceGatheringState   iceGatheringState;
     24                  attribute EventHandler           onicegatheringstatechange;
     25      };
     26 
     27    4.4.2.  RTCIceGatheringState Enum
     28      enum RTCIceGatheringState {
     29        "new",
     30        "gathering",
     31        "complete"
     32      };
     33 
     34    5.6.  RTCIceTransport Interface
     35      interface RTCIceTransport {
     36        readonly attribute RTCIceGathererState  gatheringState;
     37        ...
     38      };
     39 
     40      enum RTCIceGathererState {
     41        "new",
     42        "gathering",
     43        "complete"
     44      };
     45   */
     46 
     47  /*
     48    4.4.2.  RTCIceGatheringState Enum
     49      new
     50        Any of the RTCIceTransport s are in the new gathering state and
     51        none of the transports are in the gathering state, or there are
     52        no transports.
     53   */
     54  test(t => {
     55    const pc = new RTCPeerConnection();
     56    t.add_cleanup(() => pc.close());
     57 
     58    assert_equals(pc.iceGatheringState, 'new');
     59  }, 'Initial iceGatheringState should be new');
     60 
     61  async_test(t => {
     62    const pc = new RTCPeerConnection();
     63 
     64    t.add_cleanup(() => pc.close());
     65 
     66    let reachedGathering = false;
     67    const onIceGatheringStateChange = t.step_func(() => {
     68      const { iceGatheringState } = pc;
     69 
     70      if(iceGatheringState === 'gathering') {
     71        reachedGathering = true;
     72      } else if(iceGatheringState === 'complete') {
     73        assert_true(reachedGathering, 'iceGatheringState should reach gathering before complete');
     74        t.done();
     75      }
     76    });
     77 
     78    assert_equals(pc.onicegatheringstatechange, null,
     79      'Expect connection to have icegatheringstatechange event');
     80    assert_equals(pc.iceGatheringState, 'new');
     81 
     82    pc.addEventListener('icegatheringstatechange', onIceGatheringStateChange);
     83 
     84    generateAudioReceiveOnlyOffer(pc)
     85    .then(offer => pc.setLocalDescription(offer))
     86    .then(err => t.step_func(err =>
     87      assert_unreached(`Unhandled rejection ${err.name}: ${err.message}`)));
     88  }, 'iceGatheringState should eventually become complete after setLocalDescription');
     89 
     90  promise_test(async t => {
     91    const pc1 = new RTCPeerConnection();
     92    t.add_cleanup(() => pc1.close());
     93    const pc2 = new RTCPeerConnection();
     94    t.add_cleanup(() => pc2.close());
     95    pc1.addTransceiver('audio', { direction: 'recvonly' });
     96    await initialOfferAnswerWithIceGatheringStateTransitions(
     97        pc1, pc2);
     98 
     99    expectNoMoreGatheringStateChanges(t, pc1);
    100    expectNoMoreGatheringStateChanges(t, pc2);
    101 
    102    await pc1.setLocalDescription(await pc1.createOffer());
    103    await pc2.setLocalDescription(await pc2.createOffer());
    104 
    105    await new Promise(r => t.step_timeout(r, 500));
    106  }, 'setLocalDescription(reoffer) with no new transports should not cause iceGatheringState to change');
    107 
    108  promise_test(async t => {
    109    const pc1 = new RTCPeerConnection();
    110    t.add_cleanup(() => pc1.close());
    111 
    112    expectNoMoreGatheringStateChanges(t, pc1);
    113 
    114    await pc1.setLocalDescription(await pc1.createOffer());
    115 
    116    await new Promise(r => t.step_timeout(r, 500));
    117  }, 'setLocalDescription() with no transports should not cause iceGatheringState to change');
    118 
    119  promise_test(async t => {
    120    const pc1 = new RTCPeerConnection();
    121    t.add_cleanup(() => pc1.close());
    122    pc1.addTransceiver('audio', { direction: 'recvonly' });
    123    await pc1.setLocalDescription();
    124    await iceGatheringStateTransitions(pc1, 'gathering', 'complete');
    125    assert_true(pc1.localDescription.sdp.includes('a=end-of-candidates'));
    126  }, 'local description should have a=end-of-candidates when gathering completes');
    127 
    128  promise_test(async t => {
    129    const pc1 = new RTCPeerConnection();
    130    t.add_cleanup(() => pc1.close());
    131    const transceiver = pc1.addTransceiver('audio', { direction: 'recvonly' });
    132    await pc1.setLocalDescription();
    133    const iceTransport = transceiver.sender.transport.iceTransport;
    134 
    135    // This test code assumes that https://github.com/w3c/webrtc-pc/pull/2894
    136    // will be merged. The spec will say to dispatch two tasks; one that fires
    137    // the empty candidate, and another that fires
    138    // RTCIceTransport.gatheringstatechange, then
    139    // RTCPeerConnection.icegatheringstatechange, then the global null
    140    // candidate.
    141    while (true) {
    142      const {candidate} = await new Promise(r => pc1.onicecandidate = r);
    143      assert_not_equals(candidate, null, 'Global null candidate event should not fire yet');
    144      if (candidate.candidate == '') {
    145        break;
    146      }
    147    }
    148    assert_equals(iceTransport.gatheringState, 'gathering');
    149    assert_equals(pc1.iceGatheringState, 'gathering');
    150 
    151    // Now, we test the stuff that happens in the second queued task.
    152    const events = [];
    153    await new Promise(r => {
    154      iceTransport.ongatheringstatechange = () => {
    155        assert_equals(iceTransport.gatheringState, 'complete');
    156        assert_equals(pc1.iceGatheringState, 'complete');
    157        events.push('gatheringstatechange');
    158      };
    159      pc1.onicegatheringstatechange = () => {
    160        assert_equals(iceTransport.gatheringState, 'complete');
    161        assert_equals(pc1.iceGatheringState, 'complete');
    162        events.push('icegatheringstatechange');
    163      }
    164      pc1.onicecandidate = e => {
    165        assert_equals(e.candidate, null);
    166        assert_equals(iceTransport.gatheringState, 'complete');
    167        assert_equals(pc1.iceGatheringState, 'complete');
    168        events.push('icecandidate');
    169        r();
    170      };
    171    });
    172 
    173    assert_array_equals(events, [
    174      'gatheringstatechange',
    175      'icegatheringstatechange',
    176      'icecandidate'
    177    ], 'events must be fired on the same task in this order');
    178 }, 'gathering state and candidate callbacks should fire in the correct order');
    179 
    180  promise_test(async t => {
    181    const pc1 = new RTCPeerConnection();
    182    t.add_cleanup(() => pc1.close());
    183    const pc2 = new RTCPeerConnection();
    184    t.add_cleanup(() => pc2.close());
    185    pc1.addTransceiver('audio', { direction: 'recvonly' });
    186    await initialOfferAnswerWithIceGatheringStateTransitions(
    187        pc1, pc2);
    188    await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true}));
    189    await iceGatheringStateTransitions(pc1, 'gathering', 'complete');
    190  }, 'setLocalDescription(reoffer) with a restarted transport should cause iceGatheringState to go to "gathering" and then "complete"');
    191 
    192  promise_test(async t => {
    193    const pc1 = new RTCPeerConnection();
    194    t.add_cleanup(() => pc1.close());
    195    const pc2 = new RTCPeerConnection();
    196    t.add_cleanup(() => pc2.close());
    197    pc1.addTransceiver('audio', { direction: 'recvonly' });
    198    pc1.addTransceiver('video', { direction: 'recvonly' });
    199    exchangeIceCandidates(pc1, pc2);
    200    await pc1.setLocalDescription();
    201    const firstGather = Promise.all([
    202      iceGatheringStateTransitions(pc1, 'gathering', 'complete'),
    203      iceGatheringStateTransitions(pc2, 'gathering', 'complete')]);
    204    const mungedOffer = {type: 'offer', sdp: pc1.localDescription.sdp.replace('BUNDLE', 'BUNGLE')};
    205    await pc2.setRemoteDescription(mungedOffer);
    206    await pc2.setLocalDescription();
    207    await pc1.setRemoteDescription(pc2.localDescription);
    208    // Let gathering finish, so we don't have two generations gathering at once
    209    // This can cause errors depending on timing.
    210    await firstGather;
    211    await pc1.setLocalDescription(await pc1.createOffer({iceRestart: true}));
    212    // We only do this so we don't get errors in addCandidate. We don't want
    213    // to wait for it, because we might miss the gathering transitions.
    214    pc2.setRemoteDescription(pc1.localDescription);
    215    await iceGatheringStateTransitions(pc1, 'gathering', 'complete');
    216  }, 'setLocalDescription(reoffer) with two restarted transports should cause iceGatheringState to go to "gathering" and then "complete"');
    217 
    218  promise_test(async t => {
    219    const pc1 = new RTCPeerConnection();
    220    t.add_cleanup(() => pc1.close());
    221    const pc2 = new RTCPeerConnection();
    222    t.add_cleanup(() => pc2.close());
    223    expectNoMoreGatheringStateChanges(t, pc2);
    224    pc1.addTransceiver('audio', { direction: 'recvonly' });
    225    const offer = await pc1.createOffer();
    226    await pc2.setRemoteDescription(offer);
    227    await pc2.setRemoteDescription(offer);
    228    await pc2.setRemoteDescription({type: 'rollback'});
    229    await pc2.setRemoteDescription(offer);
    230  }, 'sRD does not cause ICE gathering state changes');
    231 
    232  promise_test(async t => {
    233    const pc1 = new RTCPeerConnection();
    234    t.add_cleanup(() => pc1.close());
    235    const pc2 = new RTCPeerConnection();
    236    t.add_cleanup(() => pc2.close());
    237    pc1.addTransceiver('audio', { direction: 'recvonly' });
    238    await initialOfferAnswerWithIceGatheringStateTransitions(
    239        pc1, pc2);
    240 
    241    const pc1waiter = iceGatheringStateTransitions(pc1, 'new');
    242    const pc2waiter = iceGatheringStateTransitions(pc2, 'new');
    243    pc1.getTransceivers()[0].stop();
    244    await pc1.setLocalDescription(await pc1.createOffer());
    245    await pc2.setRemoteDescription(pc1.localDescription);
    246    await pc2.setLocalDescription(await pc2.createAnswer());
    247    assert_equals(pc2.getTransceivers().length, 0,
    248                 'PC2 transceivers should be invisible after negotiation');
    249    assert_equals(pc2.iceGatheringState, 'new');
    250    await pc2waiter;
    251    await pc1.setRemoteDescription(pc2.localDescription);
    252    assert_equals(pc1.getTransceivers().length, 0,
    253                  'PC1 transceivers should be invisible after negotiation');
    254    assert_equals(pc1.iceGatheringState, 'new');
    255    await pc1waiter;
    256  }, 'renegotiation that closes all transports should result in ICE gathering state "new"');
    257 
    258  /*
    259    4.3.2.  RTCIceGatheringState Enum
    260      new
    261        Any of the RTCIceTransports are in the "new" gathering state and none
    262        of the transports are in the "gathering" state, or there are no
    263        transports.
    264 
    265      gathering
    266        Any of the RTCIceTransport s are in the gathering state.
    267 
    268      complete
    269        At least one RTCIceTransport exists, and all RTCIceTransports are
    270        in the completed gathering state.
    271 
    272    5.6.  RTCIceGathererState
    273      gathering
    274        The RTCIceTransport is in the process of gathering candidates.
    275 
    276      complete
    277        The RTCIceTransport has completed gathering and the end-of-candidates
    278        indication for this transport has been sent. It will not gather candidates
    279        again until an ICE restart causes it to restart.
    280   */
    281  promise_test(async t => {
    282    const pc1 = new RTCPeerConnection();
    283    t.add_cleanup(() => pc1.close());
    284    const pc2 = new RTCPeerConnection();
    285 
    286    t.add_cleanup(() => pc2.close());
    287 
    288    const onIceGatheringStateChange = t.step_func(() => {
    289      const { iceGatheringState } = pc2;
    290 
    291      if(iceGatheringState === 'gathering') {
    292        const iceTransport = pc2.sctp.transport.iceTransport;
    293 
    294        assert_equals(iceTransport.gatheringState, 'gathering',
    295          'Expect ICE transport to be in gathering gatheringState when iceGatheringState is gathering');
    296 
    297      } else if(iceGatheringState === 'complete') {
    298        const iceTransport = pc2.sctp.transport.iceTransport;
    299 
    300        assert_equals(iceTransport.gatheringState, 'complete',
    301          'Expect ICE transport to be in complete gatheringState when iceGatheringState is complete');
    302 
    303        t.done();
    304      }
    305    });
    306 
    307    pc1.createDataChannel('test');
    308 
    309    // Spec bug w3c/webrtc-pc#1382
    310    // Because sctp is only defined when answer is set, we listen
    311    // to pc2 so that we can be confident that sctp is defined
    312    // when icegatheringstatechange event is fired.
    313    pc2.addEventListener('icegatheringstatechange', onIceGatheringStateChange);
    314 
    315 
    316    exchangeIceCandidates(pc1, pc2);
    317 
    318    await pc1.setLocalDescription();
    319    assert_equals(pc1.sctp.transport.iceTransport.gatheringState, 'new');
    320    await pc2.setRemoteDescription(pc1.localDescription);
    321 
    322    await exchangeAnswer(pc1, pc2);
    323  }, 'connection with one data channel should eventually have connected connection state');
    324 
    325  /*
    326    TODO
    327    5.6.  RTCIceTransport Interface
    328      new
    329        The RTCIceTransport was just created, and has not started gathering
    330        candidates yet.
    331   */
    332 </script>