https://github.com/web-platform-tests/wpt
Raw File
Tip revision: 85f064aac43a66ea301553d6873a499aab3cf340 authored by Marcos Cáceres on 21 March 2018, 02:45:39 UTC
Edge doesn't do object spread yet
Tip revision: 85f064a
RTCPeerConnection-onnegotiationneeded.html
<!doctype html>
<meta charset="utf-8">
<title>Test RTCPeerConnection.prototype.onnegotiationneeded</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="RTCPeerConnection-helper.js"></script>
<script>
  'use strict';

  // Test is based on the following editor draft:
  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html

  // The following helper functions are called from RTCPeerConnection-helper.js:
  //   generateAnswer
  //   test_never_resolve

  // Listen to the negotiationneeded event on a peer connection
  // Returns a promise that resolves when the first event is fired.
  // The resolve result is a dictionary with event and nextPromise,
  // which resolves when the next negotiationneeded event is fired.
  // This allow us to promisify the event listening and assert whether
  // an event is fired or not by testing whether a promise is resolved.
  function awaitNegotiation(pc) {
    if(pc.onnegotiationneeded) {
      throw new Error('connection is already attached with onnegotiationneeded event handler');
    }

    function waitNextNegotiation() {
      return new Promise(resolve => {
        pc.onnegotiationneeded = event => {
          const nextPromise = waitNextNegotiation();
          resolve({ nextPromise, event });
        }
      });
    }

    return waitNextNegotiation();
  }

  // Return a promise that rejects if the first promise is resolved before second promise.
  // Also rejects when either promise rejects.
  function assert_first_promise_fulfill_after_second(promise1, promise2, message) {
    if(!message) {
      message = 'first promise is resolved before second promise';
    }

    return new Promise((resolve, reject) => {
      let secondResolved = false;

      promise1.then(() => {
        if(secondResolved) {
          resolve();
        } else {
          assert_unreached(message);
        }
      })
      .catch(reject);

      promise2.then(() => {
        secondResolved = true;
      }, reject);
    });
  }

  /*
    4.7.3.  Updating the Negotiation-Needed flag

      To update the negotiation-needed flag
      5.  Set connection's [[needNegotiation]] slot to true.
      6.  Queue a task that runs the following steps:
          3.  Fire a simple event named negotiationneeded at connection.

      To check if negotiation is needed
      2.  If connection has created any RTCDataChannels, and no m= section has
          been negotiated yet for data, return "true".

    6.1.  RTCPeerConnection Interface Extensions

      createDataChannel
        14. If channel was the first RTCDataChannel created on connection,
            update the negotiation-needed flag for connection.
   */
  promise_test(t => {
    const pc = new RTCPeerConnection();
    const negotiated = awaitNegotiation(pc);

    pc.createDataChannel('test');
    return negotiated;
  }, 'Creating first data channel should fire negotiationneeded event');

  test_never_resolve(t => {
    const pc = new RTCPeerConnection();
    const negotiated = awaitNegotiation(pc);

    pc.createDataChannel('foo');
    return negotiated
      .then(({nextPromise}) => {
      pc.createDataChannel('bar');
      return nextPromise;
    });
  }, 'calling createDataChannel twice should fire negotiationneeded event once');

  /*
    4.7.3.  Updating the Negotiation-Needed flag
      To check if negotiation is needed
      3.  For each transceiver t in connection's set of transceivers, perform
          the following checks:
          1.  If t isn't stopped and isn't yet associated with an m= section
              according to [JSEP] (section 3.4.1.), return "true".

    5.1.  RTCPeerConnection Interface Extensions
      addTransceiver
        9.  Update the negotiation-needed flag for connection.
   */
  promise_test(t => {
    const pc = new RTCPeerConnection();
    const negotiated = awaitNegotiation(pc);

    pc.addTransceiver('audio');
    return negotiated;
  }, 'addTransceiver() should fire negotiationneeded event');

  /*
    4.7.3.  Updating the Negotiation-Needed flag
      To update the negotiation-needed flag
      4.  If connection's [[needNegotiation]] slot is already true, abort these steps.
   */
  test_never_resolve(t => {
    const pc = new RTCPeerConnection();
    const negotiated = awaitNegotiation(pc);

    pc.addTransceiver('audio');
    return negotiated
    .then(({nextPromise}) => {
      pc.addTransceiver('video');
      return nextPromise;
    });
  }, 'Calling addTransceiver() twice should fire negotiationneeded event once');

  /*
    4.7.3.  Updating the Negotiation-Needed flag
      To update the negotiation-needed flag
      4.  If connection's [[needNegotiation]] slot is already true, abort these steps.
   */
  test_never_resolve(t => {
    const pc = new RTCPeerConnection();
    const negotiated = awaitNegotiation(pc);

    pc.createDataChannel('test');
    return negotiated
    .then(({nextPromise}) => {
      pc.addTransceiver('video');
      return nextPromise;
    });
  }, 'Calling both addTransceiver() and createDataChannel() should fire negotiationneeded event once');

  /*
    4.7.3.  Updating the Negotiation-Needed flag
      To update the negotiation-needed flag
      2.  If connection's signaling state is not "stable", abort these steps.
   */
  test_never_resolve(t => {
    const pc = new RTCPeerConnection();
    const negotiated = awaitNegotiation(pc);

    return pc.createOffer({ offerToReceiveAudio: true })
    .then(offer => pc.setLocalDescription(offer))
    .then(() => negotiated)
    .then(({nextPromise}) => {
      assert_equals(pc.signalingState, 'have-local-offer');
      pc.createDataChannel('test');
      return nextPromise;
    });
  }, 'negotiationneeded event should not fire if signaling state is not stable');

  /*
    4.4.1.6.  Set the RTCSessionSessionDescription
      2.2.10. If connection's signaling state is now stable, update the negotiation-needed
              flag. If connection's [[NegotiationNeeded]] slot was true both before and after
              this update, queue a task that runs the following steps:
        2.  If connection's [[NegotiationNeeded]] slot is false, abort these steps.
        3.  Fire a simple event named negotiationneeded at connection.
   */
  promise_test(t => {
    const pc = new RTCPeerConnection();

    return assert_first_promise_fulfill_after_second(
      awaitNegotiation(pc),
      pc.createOffer({ offerToReceiveAudio: true })
      .then(offer =>
        pc.setLocalDescription(offer)
        .then(() => {
          pc.createDataChannel('test');
          return generateAnswer(offer);
        }))
      .then(answer => pc.setRemoteDescription(answer)),
      'Expect negotiationneeded promise to resolve after pc has set remote answer and go back to stable state');
  }, 'negotiationneeded event should fire only after signaling state go back to stable');

  /*
    TODO
    4.7.3.  Updating the Negotiation-Needed flag

      To update the negotiation-needed flag
      3.  If the result of checking if negotiation is needed is "false",
          clear the negotiation-needed flag by setting connection's
          [[needNegotiation]] slot to false, and abort these steps.
      6.  Queue a task that runs the following steps:
          2.  If connection's [[needNegotiation]] slot is false, abort these steps.

      To check if negotiation is needed
      3.  For each transceiver t in connection's set of transceivers, perform
          the following checks:
          2.  If t isn't stopped and is associated with an m= section according
              to [JSEP] (section 3.4.1.), then perform the following checks:
              1.  If t's direction is "sendrecv" or "sendonly", and the
                  associated m= section in connection's currentLocalDescription
                  doesn't contain an "a=msid" line, return "true".
              2.  If connection's currentLocalDescription if of type "offer",
                  and the direction of the associated m= section in neither the
                  offer nor answer matches t's direction, return "true".
              3.  If connection's currentLocalDescription if of type "answer",
                  and the direction of the associated m= section in the answer
                  does not match t's direction intersected with the offered
                  direction (as described in [JSEP] (section 5.3.1.)),
                  return "true".
          3.  If t is stopped and is associated with an m= section according
              to [JSEP] (section 3.4.1.), but the associated m= section is
              not yet rejected in connection's currentLocalDescription or
              currentRemoteDescription , return "true".
      4.  If all the preceding checks were performed and "true" was not returned,
          nothing remains to be negotiated; return "false".

    4.3.1.  RTCPeerConnection Operation

      When the RTCPeerConnection() constructor is invoked
        7.  Let connection have a [[needNegotiation]] internal slot, initialized to false.

    5.1.  RTCPeerConnection Interface Extensions

      addTrack
        10. Update the negotiation-needed flag for connection.

      removeTrack
        12. Update the negotiation-needed flag for connection.

    5.4.  RTCRtpTransceiver Interface

      setDirection
        7.  Update the negotiation-needed flag for connection.

      stop
        11. Update the negotiation-needed flag for connection.

    Untestable
    4.7.3.  Updating the Negotiation-Needed flag
      1.  If connection's [[isClosed]] slot is true, abort these steps.
      6.  Queue a task that runs the following steps:
          1.  If connection's [[isClosed]] slot is true, abort these steps.
   */

</script>
back to top