| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame] | 1 | <!doctype html> | 
|  | 2 | <meta charset=utf-8> | 
|  | 3 | <title>RTCPeerConnection.prototype.createOffer</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/20170515/webrtc.html | 
|  | 12 |  | 
| Rick Waldron | 1c5c05d | 2017-06-07 17:54:42 | [diff] [blame] | 13 | // The following helper functions are called from RTCPeerConnection-helper.js: | 
|  | 14 | // generateOffer() | 
|  | 15 | // generateAnswer() | 
|  | 16 | // countAudioLine() | 
|  | 17 | // countVideoLine() | 
| Rick Waldron | 1c5c05d | 2017-06-07 17:54:42 | [diff] [blame] | 18 | // assert_session_desc_equals() | 
|  | 19 |  | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame] | 20 | /* | 
|  | 21 | * 4.3.2. createOffer() | 
|  | 22 | */ | 
|  | 23 |  | 
|  | 24 | /* | 
|  | 25 | * Final steps to create an offer | 
|  | 26 | * 4. Let offer be a newly created RTCSessionDescriptionInit dictionary | 
|  | 27 | * with its type member initialized to the string "offer" and its sdp member | 
|  | 28 | * initialized to sdpString. | 
|  | 29 | */ | 
|  | 30 | promise_test(t => { | 
|  | 31 | const pc = new RTCPeerConnection() | 
|  | 32 |  | 
|  | 33 | return pc.createOffer() | 
|  | 34 | .then(offer => { | 
|  | 35 | assert_equals(typeof offer, 'object', | 
|  | 36 | 'Expect offer to be plain object dictionary RTCSessionDescriptionInit'); | 
|  | 37 |  | 
|  | 38 | assert_false(offer instanceof RTCSessionDescription, | 
|  | 39 | 'Expect offer to not be instance of RTCSessionDescription') | 
|  | 40 | }); | 
|  | 41 | }, 'createOffer() with no argument from newly created RTCPeerConnection should succeed'); | 
|  | 42 |  | 
|  | 43 | promise_test(t => { | 
|  | 44 | const pc = new RTCPeerConnection(); | 
| Philipp Hancke | eb37a99 | 2018-05-30 15:55:02 | [diff] [blame^] | 45 |  | 
|  | 46 | const states = []; | 
|  | 47 | pc.addEventListener('signalingstatechange', () => states.push(pc.signalingState)); | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame] | 48 |  | 
|  | 49 | return pc.createOffer({ offerToReceiveAudio: true }) | 
|  | 50 | .then(offer => | 
|  | 51 | pc.setLocalDescription(offer) | 
| Nils Ohlmeier | 6cb9707 | 2017-08-31 00:38:29 | [diff] [blame] | 52 | .then(() => { | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame] | 53 | assert_equals(pc.signalingState, 'have-local-offer'); | 
|  | 54 | assert_session_desc_equals(pc.localDescription, offer); | 
|  | 55 | assert_session_desc_equals(pc.pendingLocalDescription, offer); | 
|  | 56 | assert_equals(pc.currentLocalDescription, null); | 
| Philipp Hancke | eb37a99 | 2018-05-30 15:55:02 | [diff] [blame^] | 57 |  | 
|  | 58 | assert_array_equals(states, ['have-local-offer']); | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame] | 59 | })); | 
|  | 60 | }, 'createOffer() and then setLocalDescription() should succeed'); | 
|  | 61 |  | 
|  | 62 | promise_test(t => { | 
|  | 63 | const pc = new RTCPeerConnection(); | 
|  | 64 | pc.close(); | 
|  | 65 |  | 
|  | 66 | return promise_rejects(t, 'InvalidStateError', | 
|  | 67 | pc.createOffer()); | 
|  | 68 | }, 'createOffer() after connection is closed should reject with InvalidStateError'); | 
|  | 69 |  | 
| Soares Chen | fda8782 | 2017-06-05 14:00:30 | [diff] [blame] | 70 | /* | 
|  | 71 | * Final steps to create an offer | 
|  | 72 | * 2. If connection was modified in such a way that additional inspection of the | 
|  | 73 | * system state is necessary, then in parallel begin the steps to create an | 
|  | 74 | * offer again, given p, and abort these steps. | 
|  | 75 | * | 
|  | 76 | * This test might hit step 2 of final steps to create an offer. But the media stream | 
|  | 77 | * is likely added already by the time steps to create an offer is executed, because | 
|  | 78 | * that is enqueued as an operation. | 
|  | 79 | * Either way it verifies that the media stream is included in the offer even though | 
|  | 80 | * the stream is added after synchronous call to createOffer. | 
|  | 81 | */ | 
|  | 82 | promise_test(t => { | 
|  | 83 | const pc = new RTCPeerConnection(); | 
|  | 84 | const promise = pc.createOffer(); | 
|  | 85 |  | 
|  | 86 | pc.addTransceiver('audio'); | 
|  | 87 | return promise.then(offer => { | 
|  | 88 | assert_equals(countAudioLine(offer.sdp), 1, | 
|  | 89 | 'Expect m=audio line to be found in offer SDP') | 
|  | 90 | }); | 
|  | 91 | }, 'When media stream is added when createOffer() is running in parallel, the result offer should contain the new media stream'); | 
|  | 92 |  | 
|  | 93 | /* | 
|  | 94 | * TODO | 
|  | 95 | * 4.3.2 createOffer | 
|  | 96 | * 3. If connection is configured with an identity provider, and an identity | 
|  | 97 | * assertion has not yet been generated using said identity provider, then | 
|  | 98 | * begin the identity assertion request process if it has not already begun. | 
|  | 99 | * Steps to create an offer | 
|  | 100 | * 1. If the need for an identity assertion was identified when createOffer was | 
|  | 101 | * invoked, wait for the identity assertion request process to complete. | 
|  | 102 | * | 
|  | 103 | * Non-Testable | 
|  | 104 | * 4.3.2 createOffer | 
|  | 105 | * Steps to create an offer | 
|  | 106 | * 4. Inspect the system state to determine the currently available resources as | 
|  | 107 | * necessary for generating the offer, as described in [JSEP] (section 4.1.6.). | 
|  | 108 | * 5. If this inspection failed for any reason, reject p with a newly created | 
|  | 109 | * OperationError and abort these steps. | 
|  | 110 | */ | 
|  | 111 |  | 
|  | 112 | /* | 
|  | 113 | * 4.3.3.2 Configuration data extensions | 
|  | 114 | * partial dictionary RTCOfferOptions | 
|  | 115 | */ | 
|  | 116 |  | 
|  | 117 | /* | 
|  | 118 | * offerToReceiveAudio of type boolean | 
|  | 119 | * When this is given a non-false value, no outgoing track of type | 
|  | 120 | * "audio" is attached to the PeerConnection, and the existing | 
|  | 121 | * localDescription (if any) doesn't contain any sendrecv or recv | 
|  | 122 | * audio media sections, createOffer() will behave as if | 
|  | 123 | * addTransceiver("audio") had been called once prior to the createOffer() call. | 
|  | 124 | */ | 
|  | 125 | promise_test(t => { | 
|  | 126 | const pc = new RTCPeerConnection(); | 
|  | 127 |  | 
|  | 128 | return pc.createOffer({ offerToReceiveAudio: true }) | 
|  | 129 | .then(offer1 => { | 
|  | 130 | assert_equals(countAudioLine(offer1.sdp), 1, | 
|  | 131 | 'Expect created offer to have audio line'); | 
|  | 132 |  | 
|  | 133 | // The first createOffer implicitly calls addTransceiver('audio'), | 
|  | 134 | // so all following offers will also have audio media section | 
|  | 135 | // in their SDP. | 
|  | 136 | return pc.createOffer({ offerToReceiveAudio: false }) | 
|  | 137 | .then(offer2 => { | 
|  | 138 | assert_equals(countAudioLine(offer2.sdp), 1, | 
|  | 139 | 'Expect audio line to remain in created offer'); | 
|  | 140 | }) | 
|  | 141 | }); | 
|  | 142 | }, 'createOffer() with offerToReceiveAudio should add audio line to all subsequent created offers'); | 
|  | 143 |  | 
|  | 144 | /* | 
|  | 145 | * offerToReceiveVideo of type boolean | 
|  | 146 | * When this is given a non-false value, and no outgoing track | 
|  | 147 | * of type "video" is attached to the PeerConnection, and the | 
|  | 148 | * existing localDescription (if any) doesn't contain any sendecv | 
|  | 149 | * or recv video media sections, createOffer() will behave as if | 
|  | 150 | * addTransceiver("video") had been called prior to the createOffer() call. | 
|  | 151 | */ | 
|  | 152 | promise_test(t => { | 
|  | 153 | const pc = new RTCPeerConnection(); | 
|  | 154 |  | 
|  | 155 | return pc.createOffer({ offerToReceiveVideo: true }) | 
|  | 156 | .then(offer1 => { | 
|  | 157 | assert_equals(countVideoLine(offer1.sdp), 1, | 
|  | 158 | 'Expect created offer to have video line'); | 
|  | 159 |  | 
|  | 160 | return pc.createOffer({ offerToReceiveVideo: false }) | 
|  | 161 | .then(offer2 => { | 
|  | 162 | assert_equals(countVideoLine(offer2.sdp), 1, | 
|  | 163 | 'Expect video line to remain in created offer'); | 
|  | 164 | }) | 
|  | 165 | }); | 
|  | 166 | }, 'createOffer() with offerToReceiveVideo should add video line to all subsequent created offers'); | 
|  | 167 |  | 
|  | 168 | promise_test(t => { | 
|  | 169 | const pc = new RTCPeerConnection(); | 
|  | 170 |  | 
|  | 171 | return pc.createOffer({ | 
|  | 172 | offerToReceiveAudio: true, | 
|  | 173 | offerToReceiveVideo: false | 
|  | 174 | }).then(offer1 => { | 
|  | 175 | assert_equals(countAudioLine(offer1.sdp), 1, | 
|  | 176 | 'Expect audio line to be found in created offer'); | 
|  | 177 |  | 
|  | 178 | assert_equals(countVideoLine(offer1.sdp), 0, | 
|  | 179 | 'Expect video line to not found in create offer'); | 
|  | 180 |  | 
|  | 181 | return pc.createOffer({ | 
|  | 182 | offerToReceiveAudio: false, | 
|  | 183 | offerToReceiveVideo: true | 
|  | 184 | }).then(offer2 => { | 
|  | 185 | assert_equals(countAudioLine(offer2.sdp), 1, | 
|  | 186 | 'Expect audio line to remain in created offer'); | 
|  | 187 |  | 
|  | 188 | assert_equals(countVideoLine(offer2.sdp), 1, | 
|  | 189 | 'Expect video line to be found in create offer'); | 
|  | 190 | }) | 
|  | 191 | }); | 
|  | 192 | }, 'createOffer() with offerToReceiveAudio:true then offerToReceiveVideo:true should have result offer with both audio and video line'); | 
|  | 193 |  | 
|  | 194 | </script> |