tor-browser

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

RTCPeerConnection-onnegotiationneeded.html (24004B)


      1 <!doctype html>
      2 <meta charset="utf-8">
      3 <title>Test RTCPeerConnection.prototype.onnegotiationneeded</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="RTCPeerConnection-helper.js"></script>
      7 <script>
      8  'use strict';
      9 
     10  // Test is based on the following editor draft:
     11  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     12 
     13  // The following helper functions are called from RTCPeerConnection-helper.js:
     14  //   generateOffer
     15  //   generateAnswer
     16  //   generateAudioReceiveOnlyOffer
     17  //   test_never_resolve
     18 
     19  // Listen to the negotiationneeded event on a peer connection
     20  // Returns a promise that resolves when the first event is fired.
     21  // The resolve result is a dictionary with event and nextPromise,
     22  // which resolves when the next negotiationneeded event is fired.
     23  // This allow us to promisify the event listening and assert whether
     24  // an event is fired or not by testing whether a promise is resolved.
     25  function awaitNegotiation(pc) {
     26    if(pc.onnegotiationneeded) {
     27      throw new Error('connection is already attached with onnegotiationneeded event handler');
     28    }
     29 
     30    function waitNextNegotiation() {
     31      return new Promise(resolve => {
     32        pc.onnegotiationneeded = event => {
     33          const nextPromise = waitNextNegotiation();
     34          resolve({ nextPromise, event });
     35        }
     36      });
     37    }
     38 
     39    return waitNextNegotiation();
     40  }
     41 
     42  // Return a promise that rejects if the first promise is resolved before second promise.
     43  // Also rejects when either promise rejects.
     44  function assert_first_promise_fulfill_after_second(promise1, promise2, message) {
     45    if(!message) {
     46      message = 'first promise is resolved before second promise';
     47    }
     48 
     49    return new Promise((resolve, reject) => {
     50      let secondResolved = false;
     51 
     52      promise1.then(() => {
     53        if(secondResolved) {
     54          resolve();
     55        } else {
     56          assert_unreached(message);
     57        }
     58      })
     59      .catch(reject);
     60 
     61      promise2.then(() => {
     62        secondResolved = true;
     63      }, reject);
     64    });
     65  }
     66 
     67  /*
     68    4.7.3.  Updating the Negotiation-Needed flag
     69 
     70      To update the negotiation-needed flag
     71      5.  Set connection's [[needNegotiation]] slot to true.
     72      6.  Queue a task that runs the following steps:
     73          3.  Fire a simple event named negotiationneeded at connection.
     74 
     75      To check if negotiation is needed
     76      2.  If connection has created any RTCDataChannels, and no m= section has
     77          been negotiated yet for data, return "true".
     78 
     79    6.1.  RTCPeerConnection Interface Extensions
     80 
     81      createDataChannel
     82        14. If channel was the first RTCDataChannel created on connection,
     83            update the negotiation-needed flag for connection.
     84   */
     85  promise_test(t => {
     86    const pc = new RTCPeerConnection();
     87    t.add_cleanup(() => pc.close());
     88    const negotiated = awaitNegotiation(pc);
     89 
     90    pc.createDataChannel('test');
     91    return negotiated;
     92  }, 'Creating first data channel should fire negotiationneeded event');
     93 
     94  test_never_resolve(t => {
     95    const pc = new RTCPeerConnection();
     96    const negotiated = awaitNegotiation(pc);
     97 
     98    pc.createDataChannel('foo');
     99    return negotiated
    100      .then(({nextPromise}) => {
    101      pc.createDataChannel('bar');
    102      return nextPromise;
    103    });
    104  }, 'calling createDataChannel twice should fire negotiationneeded event once');
    105 
    106  /*
    107    4.7.3.  Updating the Negotiation-Needed flag
    108      To check if negotiation is needed
    109      3.  For each transceiver t in connection's set of transceivers, perform
    110          the following checks:
    111          1.  If t isn't stopped and isn't yet associated with an m= section
    112              according to [JSEP] (section 3.4.1.), return "true".
    113 
    114    5.1.  RTCPeerConnection Interface Extensions
    115      addTransceiver
    116        9.  Update the negotiation-needed flag for connection.
    117   */
    118  promise_test(t => {
    119    const pc = new RTCPeerConnection();
    120    t.add_cleanup(() => pc.close());
    121    const negotiated = awaitNegotiation(pc);
    122 
    123    pc.addTransceiver('audio');
    124    return negotiated;
    125  }, 'addTransceiver() should fire negotiationneeded event');
    126 
    127  /*
    128    4.7.3.  Updating the Negotiation-Needed flag
    129      To update the negotiation-needed flag
    130      4.  If connection's [[needNegotiation]] slot is already true, abort these steps.
    131   */
    132  test_never_resolve(t => {
    133    const pc = new RTCPeerConnection();
    134    const negotiated = awaitNegotiation(pc);
    135 
    136    pc.addTransceiver('audio');
    137    return negotiated
    138    .then(({nextPromise}) => {
    139      pc.addTransceiver('video');
    140      return nextPromise;
    141    });
    142  }, 'Calling addTransceiver() twice should fire negotiationneeded event once');
    143 
    144  /*
    145    4.7.3.  Updating the Negotiation-Needed flag
    146      To update the negotiation-needed flag
    147      4.  If connection's [[needNegotiation]] slot is already true, abort these steps.
    148   */
    149  test_never_resolve(t => {
    150    const pc = new RTCPeerConnection();
    151    const negotiated = awaitNegotiation(pc);
    152 
    153    pc.createDataChannel('test');
    154    return negotiated
    155    .then(({nextPromise}) => {
    156      pc.addTransceiver('video');
    157      return nextPromise;
    158    });
    159  }, 'Calling both addTransceiver() and createDataChannel() should fire negotiationneeded event once');
    160 
    161  /*
    162    4.7.3.  Updating the Negotiation-Needed flag
    163      To update the negotiation-needed flag
    164      2.  If connection's signaling state is not "stable", abort these steps.
    165   */
    166  test_never_resolve(t => {
    167    const pc = new RTCPeerConnection();
    168    let negotiated;
    169 
    170    return generateAudioReceiveOnlyOffer(pc)
    171    .then(offer => {
    172      pc.setLocalDescription(offer);
    173      negotiated = awaitNegotiation(pc);
    174    })
    175    .then(() => negotiated)
    176    .then(({nextPromise}) => {
    177      assert_equals(pc.signalingState, 'have-local-offer');
    178      pc.createDataChannel('test');
    179      return nextPromise;
    180    });
    181  }, 'negotiationneeded event should not fire if signaling state is not stable');
    182 
    183  /*
    184    4.4.1.6.  Set the RTCSessionSessionDescription
    185      2.2.10. If connection's signaling state is now stable, update the negotiation-needed
    186              flag. If connection's [[NegotiationNeeded]] slot was true both before and after
    187              this update, queue a task that runs the following steps:
    188        2.  If connection's [[NegotiationNeeded]] slot is false, abort these steps.
    189        3.  Fire a simple event named negotiationneeded at connection.
    190   */
    191  promise_test(async t => {
    192    const pc = new RTCPeerConnection();
    193 
    194    t.add_cleanup(() => pc.close());
    195 
    196    pc.addTransceiver('audio');
    197    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    198 
    199    const offer = await pc.createOffer();
    200    await pc.setLocalDescription(offer);
    201    let fired = false;
    202    pc.onnegotiationneeded = e => fired = true;
    203    pc.createDataChannel('test');
    204    await pc.setRemoteDescription(await generateAnswer(offer));
    205    await undefined;
    206    assert_false(fired, "negotiationneeded should not fire until the next iteration of the event loop after SRD success");
    207    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    208  }, 'negotiationneeded event should fire only after signaling state goes back to stable after setRemoteDescription');
    209 
    210  promise_test(async t => {
    211    const pc = new RTCPeerConnection();
    212    t.add_cleanup(() => pc.close());
    213 
    214    pc.addTransceiver('audio');
    215    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    216 
    217    let fired = false;
    218    pc.onnegotiationneeded = e => fired = true;
    219    await pc.setRemoteDescription(await generateOffer());
    220    pc.createDataChannel('test');
    221    await pc.setLocalDescription(await pc.createAnswer());
    222    await undefined;
    223    assert_false(fired, "negotiationneeded should not fire until the next iteration of the event loop after SLD success");
    224 
    225    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    226  }, 'negotiationneeded event should fire only after signaling state goes back to stable after setLocalDescription');
    227 
    228  promise_test(async t => {
    229    const pc = new RTCPeerConnection();
    230 
    231    t.add_cleanup(() => pc.close());
    232 
    233    pc.addTransceiver('audio');
    234    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    235 
    236    const offer = await pc.createOffer();
    237    await pc.setLocalDescription(offer);
    238    let fired = false;
    239    pc.onnegotiationneeded = e => fired = true;
    240    pc.createDataChannel('test');
    241    const p = pc.setRemoteDescription(await generateAnswer(offer));
    242    await new Promise(resolve => pc.onsignalingstatechange = resolve);
    243    assert_false(fired, "negotiationneeded should not fire before signalingstatechange fires");
    244    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    245    await p;
    246  }, 'negotiationneeded event should fire only after signalingstatechange event fires from setRemoteDescription');
    247 
    248  promise_test(async t => {
    249    const pc = new RTCPeerConnection();
    250    t.add_cleanup(() => pc.close());
    251 
    252    pc.addTransceiver('audio');
    253    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    254 
    255    let fired = false;
    256    pc.onnegotiationneeded = e => fired = true;
    257    await pc.setRemoteDescription(await generateOffer());
    258    pc.createDataChannel('test');
    259 
    260    const p = pc.setLocalDescription(await pc.createAnswer());
    261    await new Promise(resolve => pc.onsignalingstatechange = resolve);
    262    assert_false(fired, "negotiationneeded should not fire until the next iteration of the event loop after returning to stable");
    263    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    264    await p;
    265  }, 'negotiationneeded event should fire only after signalingstatechange event fires from setLocalDescription');
    266 
    267  /*
    268    5.1.  RTCPeerConnection Interface Extensions
    269 
    270      addTrack
    271        10. Update the negotiation-needed flag for connection.
    272  */
    273  promise_test(async t => {
    274    const pc = new RTCPeerConnection();
    275    t.add_cleanup(() => pc.close());
    276 
    277    const stream = await getNoiseStream({ audio: true });
    278    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    279    const [track] = stream.getTracks();
    280    pc.addTrack(track, stream);
    281 
    282    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    283  }, 'addTrack should cause negotiationneeded to fire');
    284 
    285  /*
    286    5.1.  RTCPeerConnection Interface Extensions
    287 
    288      removeTrack
    289        12. Update the negotiation-needed flag for connection.
    290  */
    291  promise_test(async t => {
    292    const pc = new RTCPeerConnection();
    293    t.add_cleanup(() => pc.close());
    294 
    295    const stream = await getNoiseStream({ audio: true });
    296    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    297    const [track] = stream.getTracks();
    298    const sender = pc.addTrack(track, stream);
    299 
    300    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    301    pc.onnegotiationneeded = t.step_func(() => {
    302      assert_unreached('onnegotiationneeded misfired');
    303    });
    304 
    305    const offer = await pc.createOffer();
    306    await pc.setLocalDescription(offer);
    307 
    308    const answer = await generateAnswer(offer);
    309    await pc.setRemoteDescription(answer);
    310 
    311    pc.removeTrack(sender);
    312    await new Promise(resolve => pc.onnegotiationneeded = resolve)
    313  }, 'removeTrack should cause negotiationneeded to fire on the caller');
    314 
    315  /*
    316    5.1.  RTCPeerConnection Interface Extensions
    317 
    318      removeTrack
    319        12. Update the negotiation-needed flag for connection.
    320  */
    321  promise_test(async t => {
    322    const caller = new RTCPeerConnection();
    323    t.add_cleanup(() => caller.close());
    324    caller.addTransceiver('audio', {direction:'recvonly'});
    325    const offer = await caller.createOffer();
    326 
    327    const callee = new RTCPeerConnection();
    328    t.add_cleanup(() => callee.close());
    329 
    330    const stream = await getNoiseStream({ audio: true });
    331    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    332    const [track] = stream.getTracks();
    333    const sender = callee.addTrack(track, stream);
    334 
    335    await new Promise(resolve => callee.onnegotiationneeded = resolve);
    336    callee.onnegotiationneeded = t.step_func(() => {
    337      assert_unreached('onnegotiationneeded misfired');
    338    });
    339 
    340    await callee.setRemoteDescription(offer);
    341    const answer = await callee.createAnswer();
    342    callee.setLocalDescription(answer);
    343 
    344    callee.removeTrack(sender);
    345    await new Promise(resolve => callee.onnegotiationneeded = resolve)
    346  }, 'removeTrack should cause negotiationneeded to fire on the callee');
    347 
    348  /*
    349    5.4.  RTCRtpTransceiver Interface
    350 
    351      setDirection
    352        7.  Update the negotiation-needed flag for connection.
    353  */
    354  promise_test(async t => {
    355    const pc = new RTCPeerConnection();
    356    t.add_cleanup(() => pc.close());
    357 
    358    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    359    const offer = await pc.createOffer();
    360    await pc.setLocalDescription(offer);
    361    const answer = await generateAnswer(offer);
    362    await pc.setRemoteDescription(answer);
    363    transceiver.direction = 'recvonly';
    364    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    365  }, 'Updating the direction of the transceiver should cause negotiationneeded to fire');
    366 
    367  /*
    368    5.2.  RTCRtpSender Interface
    369 
    370      setStreams
    371        7.  Update the negotiation-needed flag for connection.
    372  */
    373  promise_test(async t => {
    374    const pc = new RTCPeerConnection();
    375    t.add_cleanup(() => pc.close());
    376 
    377    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    378    const offer = await pc.createOffer();
    379    await pc.setLocalDescription(offer);
    380    const answer = await generateAnswer(offer);
    381    await pc.setRemoteDescription(answer);
    382 
    383    const stream = new MediaStream();
    384    transceiver.sender.setStreams(stream);
    385    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    386  }, 'Calling setStreams should cause negotiationneeded to fire');
    387 
    388  promise_test(async t => {
    389    const pc = new RTCPeerConnection();
    390    t.add_cleanup(() => pc.close());
    391 
    392    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    393    const stream = new MediaStream();
    394    transceiver.sender.setStreams(stream);
    395    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    396 
    397    const offer = await pc.createOffer();
    398    await pc.setLocalDescription(offer);
    399    const answer = await generateAnswer(offer);
    400    await pc.setRemoteDescription(answer);
    401 
    402    const stream2 = new MediaStream();
    403    transceiver.sender.setStreams(stream2);
    404    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    405  }, 'Calling setStreams with a different stream as before should cause negotiationneeded to fire');
    406 
    407  promise_test(async t => {
    408    const pc = new RTCPeerConnection();
    409    t.add_cleanup(() => pc.close());
    410 
    411    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    412    const stream = new MediaStream();
    413    transceiver.sender.setStreams(stream);
    414    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    415 
    416    const offer = await pc.createOffer();
    417    await pc.setLocalDescription(offer);
    418    const answer = await generateAnswer(offer);
    419    await pc.setRemoteDescription(answer);
    420 
    421    const stream2 = new MediaStream();
    422    transceiver.sender.setStreams(stream, stream2);
    423    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    424  }, 'Calling setStreams with an additional stream should cause negotiationneeded to fire');
    425 
    426  promise_test(async t => {
    427    const pc = new RTCPeerConnection();
    428    t.add_cleanup(() => pc.close());
    429 
    430    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    431    const stream1 = new MediaStream();
    432    const stream2 = new MediaStream();
    433    transceiver.sender.setStreams(stream1, stream2);
    434    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    435 
    436    const offer = await pc.createOffer();
    437    await pc.setLocalDescription(offer);
    438    const answer = await generateAnswer(offer);
    439    await pc.setRemoteDescription(answer);
    440 
    441    transceiver.sender.setStreams(stream2);
    442    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    443  }, 'Calling setStreams with a stream removed should cause negotiationneeded to fire');
    444 
    445  promise_test(async t => {
    446    const pc = new RTCPeerConnection();
    447    t.add_cleanup(() => pc.close());
    448 
    449    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    450    const stream1 = new MediaStream();
    451    const stream2 = new MediaStream();
    452    transceiver.sender.setStreams(stream1, stream2);
    453    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    454 
    455    const offer = await pc.createOffer();
    456    await pc.setLocalDescription(offer);
    457    const answer = await generateAnswer(offer);
    458    await pc.setRemoteDescription(answer);
    459 
    460    transceiver.sender.setStreams();
    461    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    462  }, 'Calling setStreams with all streams removed should cause negotiationneeded to fire');
    463 
    464  promise_test(async t => {
    465    const pc = new RTCPeerConnection();
    466    t.add_cleanup(() => pc.close());
    467 
    468    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    469    const stream = new MediaStream();
    470    transceiver.sender.setStreams(stream);
    471    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    472 
    473    const offer = await pc.createOffer();
    474    await pc.setLocalDescription(offer);
    475    const answer = await generateAnswer(offer);
    476    await pc.setRemoteDescription(answer);
    477 
    478    transceiver.sender.setStreams(stream);
    479    const event = await Promise.race([
    480      new Promise(r => pc.onnegotiationneeded = r),
    481      new Promise(r => t.step_timeout(r, 10))
    482    ]);
    483    assert_equals(event, undefined, "No negotiationneeded event");
    484  }, 'Calling setStreams with the same stream as before should not cause negotiationneeded to fire');
    485 
    486  promise_test(async t => {
    487    const pc = new RTCPeerConnection();
    488    t.add_cleanup(() => pc.close());
    489 
    490    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    491    const stream = new MediaStream();
    492    transceiver.sender.setStreams(stream);
    493    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    494 
    495    const offer = await pc.createOffer();
    496    await pc.setLocalDescription(offer);
    497    const answer = await generateAnswer(offer);
    498    await pc.setRemoteDescription(answer);
    499 
    500    transceiver.sender.setStreams(stream, stream);
    501    const event = await Promise.race([
    502      new Promise(r => pc.onnegotiationneeded = r),
    503      new Promise(r => t.step_timeout(r, 10))
    504    ]);
    505    assert_equals(event, undefined, "No negotiationneeded event");
    506  }, 'Calling setStreams with duplicates of the same stream as before should not cause negotiationneeded to fire');
    507 
    508  promise_test(async t => {
    509    const pc = new RTCPeerConnection();
    510    t.add_cleanup(() => pc.close());
    511 
    512    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    513    const stream1 = new MediaStream();
    514    const stream2 = new MediaStream();
    515    transceiver.sender.setStreams(stream1, stream2);
    516    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    517 
    518    const offer = await pc.createOffer();
    519    await pc.setLocalDescription(offer);
    520    const answer = await generateAnswer(offer);
    521    await pc.setRemoteDescription(answer);
    522 
    523    transceiver.sender.setStreams(stream2, stream1);
    524    const event = await Promise.race([
    525      new Promise(r => pc.onnegotiationneeded = r),
    526      new Promise(r => t.step_timeout(r, 10))
    527    ]);
    528    assert_equals(event, undefined, "No negotiationneeded event");
    529  }, 'Calling setStreams with the same streams as before in a different order should not cause negotiationneeded to fire');
    530 
    531  promise_test(async t => {
    532    const pc = new RTCPeerConnection();
    533    t.add_cleanup(() => pc.close());
    534 
    535    const transceiver = pc.addTransceiver('audio', {direction:'sendrecv'});
    536    const stream1 = new MediaStream();
    537    const stream2 = new MediaStream();
    538    transceiver.sender.setStreams(stream1, stream2);
    539    await new Promise(resolve => pc.onnegotiationneeded = resolve);
    540 
    541    const offer = await pc.createOffer();
    542    await pc.setLocalDescription(offer);
    543    const answer = await generateAnswer(offer);
    544    await pc.setRemoteDescription(answer);
    545 
    546    transceiver.sender.setStreams(stream1, stream2, stream1);
    547    const event = await Promise.race([
    548      new Promise(r => pc.onnegotiationneeded = r),
    549      new Promise(r => t.step_timeout(r, 10))
    550    ]);
    551    assert_equals(event, undefined, "No negotiationneeded event");
    552  }, 'Calling setStreams with duplicates of the same streams as before should not cause negotiationneeded to fire');
    553 
    554  promise_test(async t => {
    555    const pc1 = new RTCPeerConnection();
    556    t.add_cleanup(() => pc1.close());
    557    const pc2 = new RTCPeerConnection();
    558    t.add_cleanup(() => pc2.close());
    559 
    560    let negotiationCount = 0;
    561    pc1.onnegotiationneeded = async () => {
    562      negotiationCount++;
    563      await pc1.setLocalDescription(await pc1.createOffer());
    564      await pc2.setRemoteDescription(pc1.localDescription);
    565      await pc2.setLocalDescription(await pc2.createAnswer());
    566      await pc1.setRemoteDescription(pc2.localDescription);
    567    }
    568 
    569    pc1.addTransceiver("video");
    570    await new Promise(r => pc1.onsignalingstatechange = () => pc1.signalingState == "stable" && r());
    571    pc1.addTransceiver("audio");
    572    await new Promise(r => pc1.onsignalingstatechange = () => pc1.signalingState == "stable" && r());
    573    assert_equals(negotiationCount, 2);
    574  }, 'Adding two transceivers, one at a time, results in the expected number of negotiationneeded events');
    575 
    576  /*
    577    TODO
    578    4.7.3.  Updating the Negotiation-Needed flag
    579 
    580      To update the negotiation-needed flag
    581      3.  If the result of checking if negotiation is needed is "false",
    582          clear the negotiation-needed flag by setting connection's
    583          [[needNegotiation]] slot to false, and abort these steps.
    584      6.  Queue a task that runs the following steps:
    585          2.  If connection's [[needNegotiation]] slot is false, abort these steps.
    586 
    587      To check if negotiation is needed
    588      3.  For each transceiver t in connection's set of transceivers, perform
    589          the following checks:
    590          2.  If t isn't stopped and is associated with an m= section according
    591              to [JSEP] (section 3.4.1.), then perform the following checks:
    592              1.  If t's direction is "sendrecv" or "sendonly", and the
    593                  associated m= section in connection's currentLocalDescription
    594                  doesn't contain an "a=msid" line, return "true".
    595              2.  If connection's currentLocalDescription if of type "offer",
    596                  and the direction of the associated m= section in neither the
    597                  offer nor answer matches t's direction, return "true".
    598              3.  If connection's currentLocalDescription if of type "answer",
    599                  and the direction of the associated m= section in the answer
    600                  does not match t's direction intersected with the offered
    601                  direction (as described in [JSEP] (section 5.3.1.)),
    602                  return "true".
    603          3.  If t is stopped and is associated with an m= section according
    604              to [JSEP] (section 3.4.1.), but the associated m= section is
    605              not yet rejected in connection's currentLocalDescription or
    606              currentRemoteDescription , return "true".
    607      4.  If all the preceding checks were performed and "true" was not returned,
    608          nothing remains to be negotiated; return "false".
    609 
    610    4.3.1.  RTCPeerConnection Operation
    611 
    612      When the RTCPeerConnection() constructor is invoked
    613        7.  Let connection have a [[needNegotiation]] internal slot, initialized to false.
    614 
    615    5.4.  RTCRtpTransceiver Interface
    616 
    617      stop
    618        11. Update the negotiation-needed flag for connection.
    619 
    620    Untestable
    621    4.7.3.  Updating the Negotiation-Needed flag
    622      1.  If connection's [[isClosed]] slot is true, abort these steps.
    623      6.  Queue a task that runs the following steps:
    624          1.  If connection's [[isClosed]] slot is true, abort these steps.
    625   */
    626 
    627 </script>