1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
<!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>
|