|  | <!doctype html> | 
|  | <meta charset=utf-8> | 
|  | <title>RTCPeerConnection.prototype.setRemoteDescription</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: | 
|  | // assert_session_desc_not_similar() | 
|  | // assert_session_desc_similar() | 
|  |  | 
|  | /* | 
|  | 4.3.2. Interface Definition | 
|  | [Constructor(optional RTCConfiguration configuration)] | 
|  | interface RTCPeerConnection : EventTarget { | 
|  | Promise<void> setRemoteDescription( | 
|  | RTCSessionDescriptionInit description); | 
|  |  | 
|  | readonly attribute RTCSessionDescription? remoteDescription; | 
|  | readonly attribute RTCSessionDescription? currentRemoteDescription; | 
|  | readonly attribute RTCSessionDescription? pendingRemoteDescription; | 
|  | ... | 
|  | }; | 
|  |  | 
|  | 4.6.2. RTCSessionDescription Class | 
|  | dictionary RTCSessionDescriptionInit { | 
|  | required RTCSdpType type; | 
|  | DOMString sdp = ""; | 
|  | }; | 
|  |  | 
|  | 4.6.1. RTCSdpType | 
|  | enum RTCSdpType { | 
|  | "offer", | 
|  | "pranswer", | 
|  | "answer", | 
|  | "rollback" | 
|  | }; | 
|  | */ | 
|  |  | 
|  | /* | 
|  | 4.6.1. enum RTCSdpType | 
|  | */ | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  |  | 
|  | // SDP is validated after WebIDL validation | 
|  | try { | 
|  | await pc.setRemoteDescription({ type: 'bogus', sdp: 'bogus' }); | 
|  | t.unreached_func("Should have rejected."); | 
|  | } catch (e) { | 
|  | assert_throws_js(TypeError, () => { throw e }); | 
|  | } | 
|  | }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError'); | 
|  |  | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  |  | 
|  | // SDP is validated after validating type | 
|  | try { | 
|  | await pc.setRemoteDescription({ type: 'answer', sdp: 'invalid' }); | 
|  | t.unreached_func("Should have rejected."); | 
|  | } catch (e) { | 
|  | assert_throws_dom('InvalidStateError', () => { throw e }); | 
|  | } | 
|  | }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError'); | 
|  |  | 
|  | /* Dedicated signalingstate events test. */ | 
|  |  | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | const pc2 = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  | t.add_cleanup(() => pc2.close()); | 
|  |  | 
|  | let eventCount = 0; | 
|  | const states = [ | 
|  | 'stable', 'have-local-offer', 'stable', 'have-remote-offer', | 
|  | ]; | 
|  | pc.onsignalingstatechange = t.step_func(() => | 
|  | assert_equals(pc.signalingState, states[++eventCount])); | 
|  |  | 
|  | const assert_state = state => { | 
|  | assert_equals(state, pc.signalingState); | 
|  | assert_equals(state, states[eventCount]); | 
|  | }; | 
|  |  | 
|  | const offer = await generateAudioReceiveOnlyOffer(pc); | 
|  | assert_state('stable'); | 
|  | await pc.setLocalDescription(offer); | 
|  | assert_state('have-local-offer'); | 
|  | await pc2.setRemoteDescription(offer); | 
|  | await exchangeAnswer(pc, pc2); | 
|  | assert_state('stable'); | 
|  | await exchangeOffer(pc2, pc); | 
|  | assert_state('have-remote-offer'); | 
|  | }, 'Negotiation should fire signalingsstate events'); | 
|  |  | 
|  | /* Operations after returning to stable state */ | 
|  |  | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | const pc2 = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  | t.add_cleanup(() => pc2.close()); | 
|  |  | 
|  | const offer1 = await generateAudioReceiveOnlyOffer(pc2); | 
|  | await pc2.setLocalDescription(offer1); | 
|  | await pc.setRemoteDescription(offer1); | 
|  | await exchangeAnswer(pc2, pc); | 
|  | const offer2 = await generateVideoReceiveOnlyOffer(pc2); | 
|  | await pc2.setLocalDescription(offer2); | 
|  | await pc.setRemoteDescription(offer2); | 
|  | assert_session_desc_not_similar(offer1, offer2); | 
|  | assert_session_desc_similar(pc.remoteDescription, offer2); | 
|  | assert_session_desc_similar(pc.currentRemoteDescription, offer1); | 
|  | assert_session_desc_similar(pc.pendingRemoteDescription, offer2); | 
|  | }, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed'); | 
|  |  | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | const pc2 = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  | t.add_cleanup(() => pc2.close()); | 
|  |  | 
|  | const offer = await generateAudioReceiveOnlyOffer(pc); | 
|  | await pc.setLocalDescription(offer); | 
|  | await pc2.setRemoteDescription(offer); | 
|  | const answer = await pc2.createAnswer(); | 
|  | await pc2.setLocalDescription(answer); | 
|  | await pc.setRemoteDescription(answer); | 
|  | await exchangeOffer(pc2, pc); | 
|  | assert_equals(pc.remoteDescription.sdp, pc.pendingRemoteDescription.sdp); | 
|  | assert_session_desc_similar(pc.remoteDescription, offer); | 
|  | assert_session_desc_similar(pc.currentRemoteDescription, answer); | 
|  | }, 'Switching role from offerer to answerer after going back to stable state should succeed'); | 
|  |  | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  | const offer = await pc.createOffer(); | 
|  | const p = Promise.race([ | 
|  | pc.setRemoteDescription(offer), | 
|  | new Promise(r => t.step_timeout(() => r("timeout"), 200)) | 
|  | ]); | 
|  | pc.close(); | 
|  | assert_equals(await p, "timeout"); | 
|  | assert_equals(pc.signalingState, "closed", "In closed state"); | 
|  | }, 'Closing on setRemoteDescription() neither resolves nor rejects'); | 
|  |  | 
|  | promise_test(async t => { | 
|  | const pc = new RTCPeerConnection(); | 
|  | t.add_cleanup(() => pc.close()); | 
|  | const offer = await pc.createOffer(); | 
|  | await pc.setLocalDescription(offer); | 
|  | const p = Promise.race([ | 
|  | pc.setRemoteDescription(offer), | 
|  | new Promise(r => t.step_timeout(() => r("timeout"), 200)) | 
|  | ]); | 
|  | pc.close(); | 
|  | assert_equals(await p, "timeout"); | 
|  | assert_equals(pc.signalingState, "closed", "In closed state"); | 
|  | }, 'Closing on rollback neither resolves nor rejects'); | 
|  |  | 
|  | </script> |