diff --git a/webrtc/RTCDataChannel-send.html b/webrtc/RTCDataChannel-send.html
index 7cacaa4d37bb76..cb8b4edc3fae15 100644
--- a/webrtc/RTCDataChannel-send.html
+++ b/webrtc/RTCDataChannel-send.html
@@ -14,7 +14,7 @@
// createDataChannelPair
// awaitMessage
// blobToArrayBuffer
- // assert_equals_array_buffer
+ // assert_equals_typed_array
/*
6.2. RTCDataChannel
@@ -140,7 +140,7 @@
assert_true(messageBuffer instanceof ArrayBuffer,
'Expect messageBuffer to be an ArrayBuffer');
- assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+ assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
});
}, 'Data channel should be able to send Uint8Array message and receive as ArrayBuffer');
@@ -162,7 +162,7 @@
assert_true(messageBuffer instanceof ArrayBuffer,
'Expect messageBuffer to be an ArrayBuffer');
- assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+ assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
});
}, 'Data channel should be able to send ArrayBuffer message and receive as ArrayBuffer');
@@ -183,7 +183,7 @@
assert_true(messageBuffer instanceof ArrayBuffer,
'Expect messageBuffer to be an ArrayBuffer');
- assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+ assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
});
}, 'Data channel should be able to send Blob message and receive as ArrayBuffer');
@@ -210,7 +210,7 @@
assert_true(messageBuffer instanceof ArrayBuffer,
'Expect messageBuffer to be an ArrayBuffer');
- assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+ assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
});
}, 'Data channel should be able to send ArrayBuffer message and receive as Blob');
@@ -240,7 +240,7 @@
assert_true(messageBuffer instanceof ArrayBuffer,
'Expect messageBuffer to be an ArrayBuffer');
- assert_equals_array_buffer(messageBuffer, helloBuffer.buffer);
+ assert_equals_typed_array(messageBuffer, helloBuffer.buffer);
});
}, 'Data channel binaryType should receive message as Blob by default');
@@ -253,9 +253,9 @@
receivedMessages.push(data);
if(receivedMessages.length === 3) {
- assert_equals_array_buffer(receivedMessages[0], helloBuffer.buffer);
+ assert_equals_typed_array(receivedMessages[0], helloBuffer.buffer);
assert_equals(receivedMessages[1], unicodeString);
- assert_equals_array_buffer(receivedMessages[2], helloBuffer.buffer);
+ assert_equals_typed_array(receivedMessages[2], helloBuffer.buffer);
t.done();
}
diff --git a/webrtc/RTCPeerConnection-helper.js b/webrtc/RTCPeerConnection-helper.js
index 9baf840df21321..a2a3e296462bfc 100644
--- a/webrtc/RTCPeerConnection-helper.js
+++ b/webrtc/RTCPeerConnection-helper.js
@@ -159,39 +159,95 @@ function test_never_resolve(testFunc, testName) {
}, testName);
}
+// Helper class to queue ICE candidates of a peer connection
+// Can be passed to `exchangeIceCandidates` which will automatically empty the queue.
+class IceCandidateQueue {
+ constructor(pc) {
+ this.pc = pc;
+ this._queue = [];
+ this._listener = (event) => this._handleIceCandidate(event);
+
+ // Queue ICE candidates
+ this.pc.addEventListener('icecandidate', this._listener);
+ }
+
+ // Unbind the event listener and return all queued candidates
+ disband() {
+ this.pc.removeEventListener('icecandidate', this._listener);
+ return this._queue;
+ }
+
+ _handleIceCandidate(event) {
+ this._queue.push(event.candidate);
+ }
+}
+
// Helper function to exchange ice candidates between
// two local peer connections
-function exchangeIceCandidates(pc1, pc2) {
- // private function
- function doExchange(localPc, remotePc) {
- localPc.addEventListener('icecandidate', event => {
- const { candidate } = event;
+function exchangeIceCandidates(pc1OrQueue, pc2OrQueue) {
+ const handleCandidate = (remotePc, candidate) => {
+ // candidate may be null to indicate end of candidate gathering.
+ // There is ongoing discussion on w3c/webrtc-pc#1213
+ // that there should be an empty candidate string event
+ // for end of candidate for each m= section.
+ if (candidate && remotePc.signalingState !== 'closed') {
+ remotePc.addIceCandidate(candidate);
+ }
+ };
+
+ const exchangeCandidates = (localPcOrQueue, remotePcOrQueue) => {
+ let localPc = localPcOrQueue;
+ let remotePc = remotePcOrQueue;
+ if (remotePcOrQueue instanceof IceCandidateQueue) {
+ remotePc = remotePcOrQueue.pc;
+ }
- // candidate may be null to indicate end of candidate gathering.
- // There is ongoing discussion on w3c/webrtc-pc#1213
- // that there should be an empty candidate string event
- // for end of candidate for each m= section.
- if(candidate && remotePc.signalingState !== 'closed') {
- remotePc.addIceCandidate(candidate);
+ // Queue? Disband it first
+ if (localPcOrQueue instanceof IceCandidateQueue) {
+ localPc = localPcOrQueue.pc;
+ for (const candidate of localPcOrQueue.disband()) {
+ handleCandidate(remotePc, candidate);
}
+ }
+
+ // Exchange further candidates
+ localPc.addEventListener('icecandidate', event => {
+ const { candidate } = event;
+ handleCandidate(remotePc, candidate);
});
- }
+ };
- doExchange(pc1, pc2);
- doExchange(pc2, pc1);
+ exchangeCandidates(pc1OrQueue, pc2OrQueue);
+ exchangeCandidates(pc2OrQueue, pc1OrQueue);
}
// Helper function for doing one round of offer/answer exchange
// betweeen two local peer connections
-function doSignalingHandshake(localPc, remotePc) {
+function doSignalingHandshake(localPc, remotePc, options={}) {
return localPc.createOffer()
- .then(offer => Promise.all([
- localPc.setLocalDescription(offer),
- remotePc.setRemoteDescription(offer)]))
+ .then(offer => {
+ // Modify offer if callback has been provided
+ if (options.modifyOffer) {
+ offer = options.modifyOffer(offer);
+ }
+
+ // Apply offer
+ return Promise.all([
+ localPc.setLocalDescription(offer),
+ remotePc.setRemoteDescription(offer)])
+ })
.then(() => remotePc.createAnswer())
- .then(answer => Promise.all([
- remotePc.setLocalDescription(answer),
- localPc.setRemoteDescription(answer)]))
+ .then(answer => {
+ // Modify answer if callback has been provided
+ if (options.modifyAnswer) {
+ answer = options.modifyAnswer(answer);
+ }
+
+ // Apply answer
+ return Promise.all([
+ remotePc.setLocalDescription(answer),
+ localPc.setRemoteDescription(answer)])
+ });
}
// Helper function to create a pair of connected data channel.
@@ -295,23 +351,25 @@ function blobToArrayBuffer(blob) {
});
}
-// Assert that two ArrayBuffer objects have the same byte values
-function assert_equals_array_buffer(buffer1, buffer2) {
- assert_true(buffer1 instanceof ArrayBuffer,
- 'Expect buffer to be instance of ArrayBuffer');
-
- assert_true(buffer2 instanceof ArrayBuffer,
- 'Expect buffer to be instance of ArrayBuffer');
+// Assert that two TypedArray or ArrayBuffer objects have the same byte values
+function assert_equals_typed_array(array1, array2) {
+ const [view1, view2] = [array1, array2].map((array) => {
+ if (array instanceof ArrayBuffer) {
+ return new DataView(array);
+ } else {
+ assert_true(array.buffer instanceof ArrayBuffer,
+ 'Expect buffer to be instance of ArrayBuffer');
+ return new DataView(array.buffer, array.byteOffset, array.byteLength);
+ }
+ });
- assert_equals(buffer1.byteLength, buffer2.byteLength,
- 'Expect both array buffers to be of the same byte length');
+ assert_equals(view1.byteLength, view2.byteLength,
+ 'Expect both arrays to be of the same byte length');
- const byteLength = buffer1.byteLength;
- const byteArray1 = new Uint8Array(buffer1);
- const byteArray2 = new Uint8Array(buffer2);
+ const byteLength = view1.byteLength;
- for(let i=0; i {
+ resolve = resolve_;
+ reject = reject_;
+ if (executor) {
+ return executor(resolve_, reject_);
+ }
});
- this.resolve = promiseResolve;
- this.reject = promiseReject;
+
+ this._done = false;
+ this._resolve = resolve;
+ this._reject = reject;
+ }
+
+ /**
+ * Return whether the promise is done (resolved or rejected).
+ */
+ get done() {
+ return this._done;
+ }
+
+ /**
+ * Resolve the promise.
+ */
+ resolve(...args) {
+ this._done = true;
+ return this._resolve(...args);
+ }
+
+ /**
+ * Reject the promise.
+ */
+ reject(...args) {
+ this._done = true;
+ return this._reject(...args);
}
}
@@ -526,3 +611,23 @@ function findTransceiverForSender(pc, sender) {
}
return null;
}
+
+// Contains a set of values and will yell at you if you try to add a value twice.
+class UniqueSet extends Set {
+ constructor(items) {
+ super();
+ if (items !== undefined) {
+ for (const item of items) {
+ this.add(item);
+ }
+ }
+ }
+
+ add(value, message) {
+ if (message === undefined) {
+ message = `Value '${value}' needs to be unique but it is already in the set`;
+ }
+ assert_true(!this.has(value), message);
+ super.add(value);
+ }
+}
diff --git a/webrtc/tools/.eslintrc.js b/webrtc/tools/.eslintrc.js
index 1778f8505e04cf..914a09dc32c741 100644
--- a/webrtc/tools/.eslintrc.js
+++ b/webrtc/tools/.eslintrc.js
@@ -105,7 +105,7 @@ module.exports = {
createDataChannelPair: true,
awaitMessage: true,
blobToArrayBuffer: true,
- assert_equals_array_buffer: true,
+ assert_equals_typed_array: true,
generateMediaStreamTrack: true,
getTrackFromUserMedia: true,
getUserMediaTracksAndStreams: true,