dom/media/tests/mochitest/test_peerConnection_close.html
author Jan-Ivar Bruaroey <jib@mozilla.com>
Tue, 29 Mar 2016 16:27:03 -0400
changeset 345689 1c3e41c0903525bdf6c82cf817cbea62632aa049
parent 297521 42657128cf5602b4844b5913722b94ab4e236588
child 376381 0e015f933aaaa5daa8f21da70d24325476e0ecdb
permissions -rw-r--r--
Bug 1259728 - minimal fix for reentrancy in pc.close(). MozReview-Commit-ID: Dji4d2bYTcj

<!DOCTYPE HTML>
<html>
<head>
  <script type="application/javascript" src="pc.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript">
  createHTML({
    bug: "991877",
    title: "Basic RTCPeerConnection.close() tests"
  });

  runNetworkTest(function () {
    var pc = new RTCPeerConnection();
    var exception = null;
    var eTimeout = null;

    // everything should be in initial state
    is(pc.signalingState, "stable", "Initial signalingState is 'stable'");
    is(pc.iceConnectionState, "new", "Initial iceConnectionState is 'new'");
    is(pc.iceGatheringState, "new", "Initial iceGatheringState is 'new'");

    var finish;
    var finished = new Promise(resolve => finish = resolve);

    pc.onsignalingstatechange = function(e) {
      clearTimeout(eTimeout);
      is(pc.signalingState, "closed", "signalingState is 'closed'");
      is(pc.iceConnectionState, "closed", "iceConnectionState is 'closed'");

      // test that pc is really closed (and doesn't crash, bug 1259728)
      try {
        pc.getLocalStreams();
      } catch (e) {
        exception = e;
      }
      is(exception && exception.name, "InvalidStateError",
         "pc.getLocalStreams should throw when closed");
      exception = null;

      try {
        pc.close();
      } catch (e) {
        exception = e;
      }
      is(exception, null, "A second close() should not raise an exception");
      is(pc.signalingState, "closed", "Final signalingState stays at 'closed'");
      is(pc.iceConnectionState, "closed", "Final iceConnectionState stays at 'closed'");

      // Due to a limitation in our WebIDL compiler that prevents overloads with
      // both Promise and non-Promise return types, legacy APIs with callbacks
      // are unable to continue to throw exceptions. Luckily the spec uses
      // exceptions solely for "programming errors" so this should not hinder
      // working code from working, which is the point of the legacy API. All
      // new code should use the promise API.
      //
      // The legacy methods that no longer throw on programming errors like
      // "invalid-on-close" are:
      // - createOffer
      // - createAnswer
      // - setLocalDescription
      // - setRemoteDescription
      // - addIceCandidate
      // - getStats
      //
      // These legacy methods fire the error callback instead. This is not
      // entirely to spec but is better than ignoring programming errors.

      var offer = new RTCSessionDescription({ sdp: "sdp", type: "offer" });
      var answer = new RTCSessionDescription({ sdp: "sdp", type: "answer" });
      var candidate = new RTCIceCandidate({ candidate: "dummy",
                                               sdpMid: "test",
                                               sdpMLineIndex: 3 });

      var doesFail = (p, msg) => p.then(generateErrorCallback(),
                                        r => is(r.name, "InvalidStateError", msg));

      doesFail(pc.createOffer(), "createOffer fails on close")
      .then(() => doesFail(pc.createAnswer(), "createAnswer fails on close"))
      .then(() => doesFail(pc.setLocalDescription(offer),
                           "setLocalDescription fails on close"))
      .then(() => doesFail(pc.setRemoteDescription(answer),
                           "setRemoteDescription fails on close"))
      .then(() => doesFail(pc.addIceCandidate(candidate),
                           "addIceCandidate fails on close"))
      .then(() => doesFail(new Promise((y, n) => pc.createOffer(y, n)),
                           "Legacy createOffer fails on close"))
      .then(() => doesFail(new Promise((y, n) => pc.createAnswer(y, n)),
                           "Legacy createAnswer fails on close"))
      .then(() => doesFail(new Promise((y, n) => pc.setLocalDescription(offer, y, n)),
                           "Legacy setLocalDescription fails on close"))
      .then(() => doesFail(new Promise((y, n) => pc.setRemoteDescription(answer, y, n)),
                           "Legacy setRemoteDescription fails on close"))
      .then(() => doesFail(new Promise((y, n) => pc.addIceCandidate(candidate, y, n)),
                           "Legacy addIceCandidate fails on close"))
      .catch(reason => ok(false, "unexpected failure: " + reason))
      .then(finish);

      // Other methods are unaffected.

      SimpleTest.doesThrow(function() {
        pc.updateIce("Invalid RTC Configuration")},
        "updateIce() on closed PC raised expected exception");

      SimpleTest.doesThrow(function() {
        pc.addStream("Invalid Media Stream")},
        "addStream() on closed PC raised expected exception");

      SimpleTest.doesThrow(function() {
        pc.removeStream("Invalid Media Stream")},
        "removeStream() on closed PC raised expected exception");

      SimpleTest.doesThrow(function() {
        pc.createDataChannel({})},
        "createDataChannel() on closed PC raised expected exception");

      SimpleTest.doesThrow(function() {
        pc.setIdentityProvider("Invalid Provider")},
        "setIdentityProvider() on closed PC raised expected exception");
    };

    // This prevents a mochitest timeout in case the event does not fire
    eTimeout = setTimeout(function() {
      ok(false, "Failed to receive expected onsignalingstatechange event in 60s");
      finish();
    }, 60000);

    try {
      pc.close();
    } catch (e) {
      exception = e;
    }
    is(exception, null, "closing the connection raises no exception");
    is(pc.signalingState, "closed", "Final signalingState is 'closed'");
    is(pc.iceConnectionState, "closed", "Final iceConnectionState is 'closed'");

    finished.then(networkTestFinished);
  });
</script>
</pre>
</body>
</html>