Merge pull request #3220 from w3c/plh/page-visibility/onvisibilitychange

Plh/page visibility/onvisibilitychange
diff --git a/user-timing/measure.html b/user-timing/measure.html
index 4175dbb..8f2a618 100644
--- a/user-timing/measure.html
+++ b/user-timing/measure.html
@@ -95,7 +95,7 @@
 
                 // create the test end mark using the test delay; this will allow for a significant difference between
                 // the mark values that should be represented in the duration of measures using these marks
-                setTimeout(measure_test_cb, measureTestDelay);
+                step_timeout(measure_test_cb, measureTestDelay);
             }
         }
 
diff --git a/user-timing/measure_navigation_timing.html b/user-timing/measure_navigation_timing.html
index 93b6dc2..fa472fd 100644
--- a/user-timing/measure_navigation_timing.html
+++ b/user-timing/measure_navigation_timing.html
@@ -92,7 +92,7 @@
 
                 // create the test end mark using the test delay; this will allow for a significant difference between
                 // the mark values that should be represented in the duration of measures using these marks
-                setTimeout(measure_test_cb, measureTestDelay);
+                step_timeout(measure_test_cb, measureTestDelay);
             }
         }
 
diff --git a/webrtc/RTCPeerConnection-addIceCandidate.html b/webrtc/RTCPeerConnection-addIceCandidate.html
index 1e27f27..e2fc6fd 100644
--- a/webrtc/RTCPeerConnection-addIceCandidate.html
+++ b/webrtc/RTCPeerConnection-addIceCandidate.html
@@ -169,27 +169,6 @@
   }, 'Add ICE candidate before setting remote description should reject with InvalidStateError');
 
   /*
-    4.3.1.2.  Enqueue an operation
-      7.1.  If connection's [[isClosed]] slot is true, abort these steps.
-
-     4.3.2.  addIceCandidate
-      4.  Return the result of enqueuing the following steps:
-        1.  If remoteDescription is null return a promise rejected with a
-            newly created InvalidStateError.
-   */
-  test_never_resolve(t => {
-    const pc = new RTCPeerConnection();
-
-    const promise = pc.addIceCandidate({
-      candidate: candidateStr1,
-      sdpMid, sdpMLineIndex, ufrag
-    });
-
-    pc.close();
-    return promise;
-  }, 'Add candidate when remote description is null should never resolve when pc is closed');
-
-  /*
     Success cases
    */
   promise_test(t => {
@@ -523,63 +502,6 @@
         })));
   }, 'Add candidate with sdpMid belonging to different ufrag should reject with OperationError');
 
-  /*
-    4.3.2.  addIceCandidate
-      3.  If both sdpMid and sdpMLineIndex are null, return a promise rejected
-          with a newly created TypeError.
-      4.  Return the result of enqueuing the following steps
-
-      (Rejects because step 3 comes first)
-
-    this test is being deferred until w3c/webrtc-pc#1345 is resolved
-
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-
-    return pc.setRemoteDescription(sessionDesc)
-    .then(() => {
-      const promise = pc.addIceCandidate({
-        candidate: candidateStr1,
-        sdpMid: null,
-        sdpMLineIndex: null
-      });
-
-      pc.close();
-      return promise_rejects(t, new TypeError(), promise);
-    });
-  }, 'Add candidate with both sdpMid and sdpMLineIndex null should still reject with TypeError after pc is closed');
-   */
-
-  /*
-    4.3.1.2.  Enqueue an operation
-      7.1.  If connection's [[isClosed]] slot is true, abort these steps.
-
-    4.3.2.  addIceCandidate
-      4.  Return the result of enqueuing the following steps
-   */
-  test_never_resolve(t => {
-    const pc = new RTCPeerConnection();
-
-    return pc.setRemoteDescription(sessionDesc)
-    .then(() => {
-      const promise = pc.addIceCandidate({
-        candidate: candidateStr1,
-        sdpMid, sdpMLineIndex, ufrag
-      });
-
-      pc.close();
-
-      // When pc is closed, the remote description is not modified
-      // even if succeed
-      t.step_timeout(t.step_func(() => {
-        assert_false(pc.remoteDescription.sdp.includes(candidateLine1),
-          'Candidate should not be added to SDP because pc is closed');
-      }), 80);
-
-      return promise;
-    });
-  }, 'Add valid candidate should never resolve when pc is closed');
-
   test_never_resolve(t => {
     const pc = new RTCPeerConnection();
 
diff --git a/webrtc/RTCPeerConnection-onnegotiationneeded.html b/webrtc/RTCPeerConnection-onnegotiationneeded.html
index fb9fcbf..fbf3d5a 100644
--- a/webrtc/RTCPeerConnection-onnegotiationneeded.html
+++ b/webrtc/RTCPeerConnection-onnegotiationneeded.html
@@ -120,20 +120,6 @@
     return awaitNegotiation(pc);
   }, 'task for negotiationneeded event should be enqueued for next tick');
 
-  /*
-    4.7.3.  Updating the Negotiation-Needed flag
-
-      To update the negotiation-needed flag
-      6.  Queue a task that runs the following steps:
-          1.  If connection's [[isClosed]] slot is true, abort these steps.
-   */
-  test_never_resolve(t => {
-    const pc = new RTCPeerConnection();
-    pc.createDataChannel('test');
-    pc.close();
-    return awaitNegotiation(pc);
-  }, 'negotiationneeded event should not fire if connection is closed');
-
   test_never_resolve(t => {
     const pc = new RTCPeerConnection();
     pc.createDataChannel('foo');
@@ -216,7 +202,6 @@
       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:
-        1.  If connection's [[IsClosed]] slot is true, abort these steps.
         2.  If connection's [[NegotiationNeeded]] slot is false, abort these steps.
         3.  Fire a simple event named negotiationneeded at connection.
    */
@@ -241,7 +226,6 @@
     4.7.3.  Updating the Negotiation-Needed flag
 
       To update the negotiation-needed flag
-      1.  If connection's [[isClosed]] slot is true, abort these steps.
       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.
@@ -291,6 +275,12 @@
 
       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>
diff --git a/webrtc/RTCPeerConnection-ontrack.html b/webrtc/RTCPeerConnection-ontrack.html
index 22e9d28..6610139 100644
--- a/webrtc/RTCPeerConnection-ontrack.html
+++ b/webrtc/RTCPeerConnection-ontrack.html
@@ -3,16 +3,103 @@
 <title>RTCPeerConnection.prototype.ontrack</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:
+  //   getTrackFromUserMedia
+
+  /*
+    4.3.1.6.  Set the RTCSessionSessionDescription
+      2.2.8.  If description is set as a remote description, then run the following
+              steps for each media description in description:
+        3.  Set transceiver's mid value to the mid of the corresponding media
+            description. If the media description has no MID, and transceiver's
+            mid is unset, generate a random value as described in [JSEP] (section 5.9.).
+        4.  If the direction of the media description is sendrecv or sendonly, and
+            transceiver.receiver.track has not yet been fired in a track event,
+            process the remote track for the media description, given transceiver.
+
+    5.1.1. Processing Remote MediaStreamTracks
+      To process the remote track for an incoming media description [JSEP]
+      (section 5.9.) given RTCRtpTransceiver transceiver, the user agent MUST
+      run the following steps:
+
+      1.  Let connection be the RTCPeerConnection object associated with transceiver.
+      2.  Let streams be a list of MediaStream objects that the media description
+          indicates the MediaStreamTrack belongs to.
+      3.  Add track to all MediaStream objects in streams.
+      4.  Queue a task to fire an event named track with transceiver, track, and
+          streams at the connection object.
+
+    5.7.  RTCTrackEvent
+      [Constructor(DOMString type, RTCTrackEventInit eventInitDict)]
+      interface RTCTrackEvent : Event {
+        readonly attribute RTCRtpReceiver           receiver;
+        readonly attribute MediaStreamTrack         track;
+        [SameObject]
+        readonly attribute FrozenArray<MediaStream> streams;
+        readonly attribute RTCRtpTransceiver        transceiver;
+      };
+
+    [mediacapture-main]
+    4.2.  MediaStream
+      interface MediaStream : EventTarget {
+        readonly attribute DOMString    id;
+        sequence<MediaStreamTrack> getTracks();
+        ...
+      };
+
+    [mediacapture-main]
+    4.3.  MediaStreamTrack
+      interface MediaStreamTrack : EventTarget {
+        readonly attribute DOMString             kind;
+        readonly attribute DOMString             id;
+        ...
+      };
+   */
+
+  function validateTrackEvent(trackEvent) {
+    const { receiver, track, streams, transceiver } = trackEvent;
+
+    assert_true(track instanceof MediaStreamTrack,
+      'Expect track to be instance of MediaStreamTrack');
+
+    assert_true(Array.isArray(streams),
+      'Expect streams to be an array');
+
+    for(const mediaStream of streams) {
+      assert_true(mediaStream instanceof MediaStream,
+        'Expect elements in streams to be instance of MediaStream');
+
+      assert_true(mediaStream.getTracks().includes(track),
+        'Expect each mediaStream to have track as one of their tracks');
+    }
+
+    assert_true(receiver instanceof RTCRtpReceiver,
+      'Expect trackEvent.receiver to be defined and is instance of RTCRtpReceiver');
+
+    assert_equals(receiver.track, track,
+      'Expect trackEvent.receiver.track to be the same as trackEvent.track');
+
+    assert_true(transceiver instanceof RTCRtpTransceiver,
+      'Expect trackEvent.transceiver to be defined and is instance of RTCRtpTransceiver');
+
+    assert_equals(transceiver.receiver, receiver,
+      'Expect trackEvent.transceiver.receiver to be the same as trackEvent.receiver');
+  }
+
   // tests that ontrack is called and parses the msid information from the SDP and creates
   // the streams with matching identifiers.
   async_test(t => {
     const pc = new RTCPeerConnection();
 
     // Fail the test if the ontrack event handler is not implemented
-    assert_own_property(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
+    assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
 
     const sdp = `v=0
 o=- 166855176514521964 2 IN IP4 127.0.0.1
@@ -34,16 +121,150 @@
 a=ssrc:1001 cname:some
 `;
 
-    pc.ontrack = t.step_func(event => {
-      assert_equals(event.streams.length, 1, 'the track belongs to one MediaStream');
-      assert_equals(event.streams[0].id, 'stream1', 'the stream name is parsed from the MSID line');
+    pc.ontrack = t.step_func(trackEvent => {
+      const { streams, track, transceiver } = trackEvent;
+
+      assert_equals(streams.length, 1,
+        'the track belongs to one MediaStream');
+
+      const [stream] = streams;
+      assert_equals(stream.id, 'stream1',
+        'Expect stream.id to be the same as specified in the a=msid line');
+
+      assert_equals(track.kind, 'audio',
+        'Expect track.kind to be audio');
+
+      validateTrackEvent(trackEvent);
+
+      assert_equals(transceiver.direction, 'recvonly',
+        'Expect transceiver.direction to be reverse of sendonly (recvonly)');
+
       t.done();
     });
 
-    pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp}))
+    pc.setRemoteDescription({ type: 'offer', sdp })
     .catch(t.step_func(err => {
       assert_unreached('Error ' + err.name + ': ' + err.message);
     }));
   }, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.');
 
+  async_test(t => {
+    const pc = new RTCPeerConnection();
+
+    assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
+
+    const sdp = `v=0
+o=- 166855176514521964 2 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=msid-semantic:WMS *
+m=audio 9 UDP/TLS/RTP/SAVPF 111
+c=IN IP4 0.0.0.0
+a=rtcp:9 IN IP4 0.0.0.0
+a=ice-ufrag:someufrag
+a=ice-pwd:somelongpwdwithenoughrandomness
+a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
+a=setup:actpass
+a=rtcp-mux
+a=mid:mid1
+a=recvonly
+a=rtpmap:111 opus/48000/2
+a=msid:stream1 track1
+a=ssrc:1001 cname:some
+`;
+
+    pc.ontrack = t.unreached_func('ontrack event should not fire for track with recvonly direction');
+
+    pc.setRemoteDescription({ type: 'offer', sdp })
+    .catch(t.step_func(err => {
+      assert_unreached('Error ' + err.name + ': ' + err.message);
+    }))
+    .then(t.step_func(() => {
+      t.step_timeout(t.step_func_done(), 100);
+    }));
+
+  }, 'setRemoteDescription() with m= line of recvonly direction should not trigger track event');
+
+  async_test(t => {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+
+    pc2.ontrack = t.step_func(trackEvent => {
+      const { track } = trackEvent;
+
+      assert_equals(track.kind, 'audio',
+        'Expect track.kind to be audio');
+
+      validateTrackEvent(trackEvent);
+
+      t.done();
+    });
+
+    return getTrackFromUserMedia('audio')
+    .then(([track, mediaStream]) => {
+      pc1.addTrack(track, mediaStream);
+
+      return pc1.createOffer()
+      .then(offer => pc2.setRemoteDescription(offer));
+    })
+    .catch(t.step_func(err => {
+      assert_unreached('Error ' + err.name + ': ' + err.message);
+    }));
+
+  }, 'addTrack() should cause remote connection to fire ontrack when setRemoteDescription()');
+
+  async_test(t => {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+
+    pc2.ontrack = t.step_func(trackEvent => {
+      const { track } = trackEvent;
+
+      assert_equals(track.kind, 'video',
+        'Expect track.kind to be video');
+
+      validateTrackEvent(trackEvent);
+
+      t.done();
+    });
+
+    pc1.addTransceiver('video');
+
+    return pc1.createOffer()
+    .then(offer => pc2.setRemoteDescription(offer))
+    .catch(t.step_func(err => {
+      assert_unreached('Error ' + err.name + ': ' + err.message);
+    }));
+
+  }, `addTransceiver('video') should cause remote connection to fire ontrack when setRemoteDescription()`);
+
+  async_test(t => {
+    const pc1 = new RTCPeerConnection();
+    const pc2 = new RTCPeerConnection();
+
+    pc2.ontrack = t.step_func(trackEvent => {
+      const { track } = trackEvent;
+
+      assert_equals(track.kind, 'video',
+        'Expect track.kind to be video');
+
+      validateTrackEvent(trackEvent);
+
+      t.done();
+    });
+
+    pc1.addTransceiver('audio', { direction: 'inactive' });
+    pc2.ontrack = t.unreached_func('ontrack event should not fire for track with inactive direction');
+
+    return pc1.createOffer()
+    .then(offer => pc2.setRemoteDescription(offer))
+    .catch(t.step_func(err => {
+      assert_unreached('Error ' + err.name + ': ' + err.message);
+    }))
+    .then(t.step_func(() => {
+      t.step_timeout(t.step_func_done(), 100);
+    }));
+
+  }, `addTransceiver() with inactive direction should not cause remote connection to fire ontrack when setRemoteDescription()`);
+
 </script>
diff --git a/webrtc/RTCPeerConnection-setLocalDescription.html b/webrtc/RTCPeerConnection-setLocalDescription.html
index d11936b..d201859 100644
--- a/webrtc/RTCPeerConnection-setLocalDescription.html
+++ b/webrtc/RTCPeerConnection-setLocalDescription.html
@@ -146,24 +146,8 @@
           }))));
   }, 'Creating and setting offer multiple times should succeed');
 
-  /*
-    4.3.1.6.  Set the RTCSessionSessionDescription
-      2.2.1.  If connection's [[IsClosed]] slot is true, then abort these steps.
-   */
-  test_never_resolve(t => {
-    const pc = new RTCPeerConnection();
-
-    return pc.createOffer()
-    .then(offer => {
-      const promise = pc.setLocalDescription(offer);
-      pc.close();
-      return promise;
-    });
-  }, 'setLocalDescription(offer) should never resolve if connection is closed in parallel')
-
   /* setLocalDescription(answer) */
 
-
   /*
     4.3.1.6.  Set the RTCSessionSessionDescription
       2.  If description is set as a local description, then run one of the following
diff --git a/webrtc/RTCPeerConnection-setRemoteDescription.html b/webrtc/RTCPeerConnection-setRemoteDescription.html
index 50a719d..a913d2c 100644
--- a/webrtc/RTCPeerConnection-setRemoteDescription.html
+++ b/webrtc/RTCPeerConnection-setRemoteDescription.html
@@ -91,21 +91,6 @@
 
   /*
     4.3.1.6.  Set the RTCSessionSessionDescription
-      2.2.1.  If connection's [[IsClosed]] slot is true, then abort these steps.
-   */
-  test_never_resolve(t => {
-    const pc = new RTCPeerConnection();
-
-    return generateOffer()
-    .then(offer => {
-      const promise = pc.setRemoteDescription(offer);
-      pc.close();
-      return promise;
-    });
-  }, 'setRemoteDescription(offer) should never resolve if connection is closed in parallel')
-
-  /*
-    4.3.1.6.  Set the RTCSessionSessionDescription
       2.1.4.  If the content of description is not valid SDP syntax, then reject p with
               an RTCError (with errorDetail set to "sdp-syntax-error" and the
               sdpLineNumber attribute set to the line number in the SDP where the syntax
diff --git a/webrtc/RTCRtpSender-replaceTrack.html b/webrtc/RTCRtpSender-replaceTrack.html
index 0ec3a81..587ca57 100644
--- a/webrtc/RTCRtpSender-replaceTrack.html
+++ b/webrtc/RTCRtpSender-replaceTrack.html
@@ -220,29 +220,6 @@
   }, 'Calling replaceTrack on sender with similar track and and set to session description should resolve with sender.track set to new track');
 
   /*
-    5.2.  replaceTrack
-      Not 8.  If transceiver is not yet associated with a media description
-              [JSEP] (section 3.4.1.), then set sender's track attribute to
-              withTrack, and return a promise resolved with undefined.
-      10. Run the following steps in parallel:
-        3.  Queue a task that runs the following steps:
-          1.  If connection's [[isClosed]] slot is true, abort these steps.
-   */
-  test_never_resolve(t => {
-    const pc = new RTCPeerConnection();
-    const track = generateMediaStreamTrack('audio');
-    const { transceiver: { sender } } = pc.addTransceiver('audio');
-
-    return pc.createOffer()
-    .then(offer => pc.setLocalDescription(offer))
-    .then(() => {
-      const promise = sender.replaceTrack(track);
-      pc.close();
-      return promise;
-    });
-  }, 'replaceTrack should never resolve if connection is closed in parallel');
-
-  /*
     TODO
       5.2.  replaceTrack
         To avoid track identifiers changing on the remote receiving end when
@@ -266,5 +243,7 @@
                 negotiating. Otherwise, have the sender switch seamlessly to
                 transmitting withTrack instead of the sender's existing track,
                 without negotiating.
+            3.  Queue a task that runs the following steps:
+              1.  If connection's [[isClosed]] slot is true, abort these steps.
    */
 </script>
diff --git a/webrtc/coverage/set-session-description.txt b/webrtc/coverage/set-session-description.txt
index 1071778..0f7d65b 100644
--- a/webrtc/coverage/set-session-description.txt
+++ b/webrtc/coverage/set-session-description.txt
@@ -14,8 +14,7 @@
     1.  If the process to apply description fails for any reason, then user agent
         MUST queue a task that runs the following steps:
 
-      [RTCPeerConnection-setLocalDescription]
-      [RTCPeerConnection-setRemoteDescription]
+      [Untestable]
       1.  If connection's [[IsClosed]] slot is true, then abort these steps.
 
       [Untestable]
@@ -47,8 +46,7 @@
     2.  If description is applied successfully, the user agent MUST queue a task
         that runs the following steps:
 
-      [RTCPeerConnection-setLocalDescription]
-      [RTCPeerConnection-setRemoteDescription]
+      [Untestable]
       1.  If connection's [[isClosed]] slot is true, then abort these steps.
 
       [RTCPeerConnection-setLocalDescription]
@@ -158,7 +156,7 @@
       2.  Set transceiver's mid value to the mid of the corresponding media
           description.
 
-    [TODO]
+    [RTCPeerConnection-ontrack]
     8.  If description is set as a remote description, then run the following steps
         for each media description in description:
 
@@ -166,26 +164,26 @@
       1.  As described by [JSEP] (section 5.9.), attempt to find an existing
           RTCRtpTransceiver object, transceiver, to represent the media description.
 
-      [TODO]
+      [RTCPeerConnection-ontrack]
       2.  If no suitable transceiver is found (transceiver is unset), run the following
           steps:
 
-        [TODO]
+        [RTCPeerConnection-ontrack]
         1.  Create an RTCRtpSender, sender, from the media description.
 
-        [TODO]
+        [RTCPeerConnection-ontrack]
         2.  Create an RTCRtpReceiver, receiver, from the media description.
 
-        [TODO]
+        [RTCPeerConnection-ontrack]
         3.  Create an RTCRtpTransceiver with sender, receiver and direction, and let
             transceiver be the result.
 
-      [TODO]
+      [RTCPeerConnection-ontrack]
       3.  Set transceiver's mid value to the mid of the corresponding media description.
           If the media description has no MID, and transceiver's mid is unset, generate
           a random value as described in [JSEP] (section 5.9.).
 
-      [TODO]
+      [RTCPeerConnection-ontrack]
       4.  If the direction of the media description is sendrecv or sendonly, and
           transceiver.receiver.track has not yet been fired in a track event, process
           the remote track for the media description, given transceiver.
@@ -218,7 +216,7 @@
             flag. If connection's [[NegotiationNeeded]] slot was true both before and after
             this update, queue a task that runs the following steps:
 
-      [RTCPeerConnection-onnegotiationneeded]
+      [Untestable]
       1.  If connection's [[IsClosed]] slot is true, abort these steps.
 
       [RTCPeerConnection-onnegotiationneeded]
@@ -236,7 +234,7 @@
 
 Coverage Report
 
-  Tested        28
-  Not Tested    22
+  Tested        35
+  Not Tested    15
   Untestable     8
   Total         58