summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-extensions
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/webrtc-extensions
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webrtc-extensions')
-rw-r--r--testing/web-platform/tests/webrtc-extensions/META.yml3
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCOAuthCredential.html44
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-adaptivePtime.html42
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html422
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-maxFramerate.html101
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget-stats.html96
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget.html130
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html94
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-helper.js140
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-senderCaptureTimeOffset.html92
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpTransceiver-headerExtensionControl.html295
-rw-r--r--testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.https.html83
-rw-r--r--testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.js15
-rw-r--r--testing/web-platform/tests/webrtc-extensions/transfer-datachannel-worker.js19
-rw-r--r--testing/web-platform/tests/webrtc-extensions/transfer-datachannel.html165
15 files changed, 1741 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webrtc-extensions/META.yml b/testing/web-platform/tests/webrtc-extensions/META.yml
new file mode 100644
index 0000000000..be8cb028f0
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/META.yml
@@ -0,0 +1,3 @@
+spec: https://w3c.github.io/webrtc-extensions/
+suggested_reviewers:
+ - hbos
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCOAuthCredential.html b/testing/web-platform/tests/webrtc-extensions/RTCOAuthCredential.html
new file mode 100644
index 0000000000..63e92c6d08
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCOAuthCredential.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCConfiguration iceServers with OAuth credentials</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='../webrtc/RTCConfiguration-helper.js'></script>
+<script>
+ 'use strict';
+
+// These tests are based on
+// https://w3c.github.io/webrtc-extensions/#rtcoauthcredential-dictionary
+
+/*
+ 4.3.2. To set a configuration
+ 11.6. If scheme name is turn or turns, and server.credentialType is "oauth",
+ and server.credential is not an RTCOAuthCredential, then throw an
+ InvalidAccessError and abort these steps.
+*/
+config_test(makePc => {
+ assert_throws_dom('InvalidAccessError', () =>
+ makePc({ iceServers: [{
+ urls: 'turns:turn.example.org',
+ credentialType: 'oauth',
+ username: 'user',
+ credential: 'cred'
+ }] }));
+}, 'with turns server, credentialType oauth, and string credential should throw InvalidAccessError');
+
+config_test(makePc => {
+ const pc = makePc({ iceServers: [{
+ urls: 'turns:turn2.example.net',
+ username: '22BIjxU93h/IgwEb',
+ credential: {
+ macKey: 'WmtzanB3ZW9peFhtdm42NzUzNG0=',
+ accessToken: 'AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ5VhNDgeMR3+ZlZ35byg972fW8QjpEl7bx91YLBPFsIhsxloWcXPhA=='
+ },
+ credentialType: 'oauth'
+ }]});
+ const { iceServers } = pc.getConfiguration();
+ const server = iceServers[0];
+ assert_equals(server.credentialType, 'oauth');
+}, 'with turns server, credential type and credential from spec should not throw');
+
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-adaptivePtime.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
new file mode 100644
index 0000000000..8a7a8b6ba6
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>RTCRtpEncodingParameters adaptivePtime property</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ 'use strict';
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const { sender } = pc.addTransceiver('audio', {
+ sendEncodings: [{adaptivePtime: true}],
+ });
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assert_true(encoding.adaptivePtime);
+
+ encoding.adaptivePtime = false;
+ await sender.setParameters(param);
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assert_false(encoding.adaptivePtime);
+
+ }, `Setting adaptivePtime should be accepted`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const { sender } = pc.addTransceiver('audio', { sendEncodings: [{}] });
+
+ const param = sender.getParameters();
+ const encoding = param.encodings[0];
+
+ assert_false(encoding.adaptivePtime);
+
+ }, `adaptivePtime should be default false`);
+
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html
new file mode 100644
index 0000000000..5c81349b15
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-codec.html
@@ -0,0 +1,422 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>RTCRtpEncodingParameters codec property</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../webrtc/RTCPeerConnection-helper.js"></script>
+<script src="../webrtc/third_party/sdp/sdp.js"></script>
+<script src="../webrtc/simulcast/simulcast.js"></script>
+<script>
+ 'use strict';
+
+ function findFirstCodec(name) {
+ return RTCRtpSender.getCapabilities(name.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(name, undefined, { sensitivity: 'base' }) === 0)[0];
+ }
+
+ function codecsNotMatching(mimeType) {
+ return RTCRtpSender.getCapabilities(mimeType.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(mimeType, undefined, {sensitivity: 'base'}) !== 0);
+ }
+
+ function assertCodecEquals(a, b) {
+ assert_equals(a.mimeType, b.mimeType);
+ assert_equals(a.clockRate, b.clockRate);
+ assert_equals(a.channels, b.channels);
+ assert_equals(a.sdpFmtpLine, b.sdpFmtpLine);
+ }
+
+ async function codecsForSender(sender) {
+ const rids = sender.getParameters().encodings.map(e => e.rid);
+ const stats = await sender.getStats();
+ const codecs = [...stats]
+ .filter(([k, v]) => v.type === 'outbound-rtp')
+ .sort(([k, v], [k2, v2]) => rids.indexOf(v.rid) - rids.indexOf(v2.rid))
+ .map(([k, v]) => stats.get(v.codecId).mimeType);
+ return codecs;
+ }
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const { sender } = pc.addTransceiver('audio');
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assert_equals(encoding.codec, undefined);
+ }, `Codec should be undefined by default on audio encodings`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const { sender } = pc.addTransceiver('video');
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assert_equals(encoding.codec, undefined);
+ }, `Codec should be undefined by default on video encodings`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const opus = findFirstCodec('audio/opus');
+
+ const { sender } = pc.addTransceiver('audio', {
+ sendEncodings: [{codec: opus}],
+ });
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assertCodecEquals(opus, encoding.codec);
+ }, `Creating an audio sender with addTransceiver and codec should work`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const vp8 = findFirstCodec('video/VP8');
+
+ const { sender } = pc.addTransceiver('video', {
+ sendEncodings: [{codec: vp8}],
+ });
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assertCodecEquals(vp8, encoding.codec);
+ }, `Creating a video sender with addTransceiver and codec should work`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const opus = findFirstCodec('audio/opus');
+
+ const { sender } = pc.addTransceiver('audio');
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = opus;
+ await sender.setParameters(param);
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assertCodecEquals(opus, encoding.codec);
+
+ delete encoding.codec;
+ await sender.setParameters(param);
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assert_equals(encoding.codec, undefined);
+ }, `Setting codec on an audio sender with setParameters should work`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const vp8 = findFirstCodec('video/VP8');
+
+ const { sender } = pc.addTransceiver('video');
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = vp8;
+ await sender.setParameters(param);
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assertCodecEquals(vp8, encoding.codec);
+
+ delete encoding.codec;
+ await sender.setParameters(param);
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assert_equals(encoding.codec, undefined);
+ }, `Setting codec on a video sender with setParameters should work`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const newCodec = {
+ mimeType: "audio/newCodec",
+ clockRate: 90000,
+ channel: 2,
+ };
+
+ assert_throws_dom('OperationError', () => pc.addTransceiver('video', {
+ sendEncodings: [{codec: newCodec}],
+ }));
+ }, `Creating an audio sender with addTransceiver and non-existing codec should throw OperationError`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const newCodec = {
+ mimeType: "video/newCodec",
+ clockRate: 90000,
+ };
+
+ assert_throws_dom('OperationError', () => pc.addTransceiver('video', {
+ sendEncodings: [{codec: newCodec}],
+ }));
+ }, `Creating a video sender with addTransceiver and non-existing codec should throw OperationError`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const newCodec = {
+ mimeType: "audio/newCodec",
+ clockRate: 90000,
+ channel: 2,
+ };
+
+ const { sender } = pc.addTransceiver('audio');
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = newCodec;
+ await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
+ }, `Setting a non-existing codec on an audio sender with setParameters should throw InvalidModificationError`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const newCodec = {
+ mimeType: "video/newCodec",
+ clockRate: 90000,
+ };
+
+ const { sender } = pc.addTransceiver('video');
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = newCodec;
+ await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
+ }, `Setting a non-existing codec on a video sender with setParameters should throw InvalidModificationError`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const opus = findFirstCodec('audio/opus');
+ const nonOpus = codecsNotMatching(opus.mimeType);
+
+ const transceiver = pc.addTransceiver('audio');
+ const sender = transceiver.sender;
+
+ transceiver.setCodecPreferences(nonOpus);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = opus;
+ await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
+ }, `Setting a non-preferred codec on an audio sender with setParameters should throw InvalidModificationError`);
+
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const vp8 = findFirstCodec('video/VP8');
+ const nonVP8 = codecsNotMatching(vp8.mimeType);
+
+ const transceiver = pc.addTransceiver('video');
+ const sender = transceiver.sender;
+
+ transceiver.setCodecPreferences(nonVP8);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = vp8;
+ await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
+ }, `Setting a non-preferred codec on a video sender with setParameters should throw InvalidModificationError`);
+
+ promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const opus = findFirstCodec('audio/opus');
+ const nonOpus = codecsNotMatching(opus.mimeType);
+
+ const transceiver = pc1.addTransceiver('audio');
+ const sender = transceiver.sender;
+
+ transceiver.setCodecPreferences(nonOpus);
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = opus;
+ await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
+ }, `Setting a non-negotiated codec on an audio sender with setParameters should throw InvalidModificationError`);
+
+ promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const vp8 = findFirstCodec('video/VP8');
+ const nonVP8 = codecsNotMatching(vp8.mimeType);
+
+ const transceiver = pc1.addTransceiver('video');
+ const sender = transceiver.sender;
+
+ transceiver.setCodecPreferences(nonVP8);
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ encoding.codec = vp8;
+ await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
+ }, `Setting a non-negotiated codec on a video sender with setParameters should throw InvalidModificationError`);
+
+ promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const opus = findFirstCodec('audio/opus');
+ const nonOpus = codecsNotMatching(opus.mimeType);
+
+ const transceiver = pc1.addTransceiver('audio', {
+ sendEncodings: [{codec: opus}],
+ });
+ const sender = transceiver.sender;
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assertCodecEquals(opus, encoding.codec);
+
+ transceiver.setCodecPreferences(nonOpus);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assert_equals(encoding.codec, undefined);
+ }, `Codec should be undefined after negotiating away the currently set codec on an audio sender`);
+
+ promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+
+ const vp8 = findFirstCodec('video/VP8');
+ const nonVP8 = codecsNotMatching(vp8.mimeType);
+
+ const transceiver = pc1.addTransceiver('video', {
+ sendEncodings: [{codec: vp8}],
+ });
+ const sender = transceiver.sender;
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+
+ assertCodecEquals(vp8, encoding.codec);
+
+ transceiver.setCodecPreferences(nonVP8);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ param = sender.getParameters();
+ encoding = param.encodings[0];
+
+ assert_equals(encoding.codec, undefined);
+ }, `Codec should be undefined after negotiating away the currently set codec on a video sender`);
+
+ promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ const stream = await getNoiseStream({audio:true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+
+ const opus = findFirstCodec('audio/opus');
+ const nonOpus = codecsNotMatching(opus.mimeType);
+
+ const transceiver = pc1.addTransceiver(stream.getTracks()[0]);
+ const sender = transceiver.sender;
+
+ transceiver.setCodecPreferences(nonOpus.concat([opus]));
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ let codecs = await codecsForSender(sender);
+ assert_not_equals(codecs[0], opus.mimeType);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+ encoding.codec = opus;
+
+ await sender.setParameters(param);
+
+ codecs = await codecsForSender(sender);
+ assert_array_equals(codecs, [opus.mimeType]);
+ }, `Stats output-rtp should match the selected codec in non-simulcast usecase on an audio sender`);
+
+ promise_test(async (t) => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ t.add_cleanup(() => pc2.close());
+ const stream = await getNoiseStream({video:true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+
+ const vp8 = findFirstCodec('video/VP8');
+ const nonVP8 = codecsNotMatching(vp8.mimeType);
+
+ const transceiver = pc1.addTransceiver(stream.getTracks()[0]);
+ const sender = transceiver.sender;
+
+ transceiver.setCodecPreferences(nonVP8.concat([vp8]));
+
+ exchangeIceCandidates(pc1, pc2);
+ await exchangeOfferAnswer(pc1, pc2);
+
+ let codecs = await codecsForSender(sender);
+ assert_not_equals(codecs[0], vp8.mimeType);
+
+ let param = sender.getParameters();
+ let encoding = param.encodings[0];
+ encoding.codec = vp8;
+
+ await sender.setParameters(param);
+
+ codecs = await codecsForSender(sender);
+ assert_array_equals(codecs, [vp8.mimeType]);
+ }, `Stats output-rtp should match the selected codec in non-simulcast usecase on a video sender`);
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-maxFramerate.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-maxFramerate.html
new file mode 100644
index 0000000000..3e348f0d14
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-maxFramerate.html
@@ -0,0 +1,101 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCRtpParameters encodings</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/webrtc/dictionary-helper.js"></script>
+<script src="/webrtc/RTCRtpParameters-helper.js"></script>
+<script>
+'use strict';
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ assert_throws_js(RangeError, () => pc.addTransceiver('video', {
+ sendEncodings: [{
+ maxFramerate: -10
+ }]
+ }));
+}, `addTransceiver() with sendEncoding.maxFramerate field set to less than 0 should reject with RangeError`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ let {sender} = pc.addTransceiver('audio', {
+ sendEncodings: [{
+ maxFramerate: -10
+ }]
+ });
+ let encodings = sender.getParameters().encodings;
+ assert_equals(encodings.length, 1);
+ assert_not_own_property(encodings[0], "maxFramerate");
+
+ sender = pc.addTransceiver('audio', {
+ sendEncodings: [{
+ maxFramerate: 10
+ }]
+ }).sender;
+ encodings = sender.getParameters().encodings;
+ assert_equals(encodings.length, 1);
+ assert_not_own_property(encodings[0], "maxFramerate");
+}, `addTransceiver('audio') with sendEncoding.maxFramerate should succeed, but remove the maxFramerate, even if it is invalid`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {sender} = pc.addTransceiver('audio');
+ let params = sender.getParameters();
+ assert_equals(params.encodings.length, 1);
+ params.encodings[0].maxFramerate = 20;
+ await sender.setParameters(params);
+ const {encodings} = sender.getParameters();
+ assert_equals(encodings.length, 1);
+ assert_not_own_property(encodings[0], "maxFramerate");
+}, `setParameters with maxFramerate on an audio sender should succeed, but remove the maxFramerate`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {sender} = pc.addTransceiver('audio');
+ let params = sender.getParameters();
+ assert_equals(params.encodings.length, 1);
+ params.encodings[0].maxFramerate = -1;
+ await sender.setParameters(params);
+ const {encodings} = sender.getParameters();
+ assert_equals(encodings.length, 1);
+ assert_not_own_property(encodings[0], "maxFramerate");
+}, `setParameters with an invalid maxFramerate on an audio sender should succeed, but remove the maxFramerate`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const { sender } = pc.addTransceiver('video');
+ await doOfferAnswerExchange(t, pc);
+
+ const param = sender.getParameters();
+ const encoding = param.encodings[0];
+ assert_not_own_property(encoding, "maxFramerate");
+
+ encoding.maxFramerate = -10;
+ return promise_rejects_js(t, RangeError,
+ sender.setParameters(param));
+}, `setParameters() with encoding.maxFramerate field set to less than 0 should reject with RangeError`);
+
+// It would be great if we could test to see whether maxFramerate is actually
+// honored.
+test_modified_encoding('video', 'maxFramerate', 24, 16,
+ 'setParameters() with maxFramerate 24->16 should succeed');
+
+test_modified_encoding('video', 'maxFramerate', undefined, 16,
+ 'setParameters() with maxFramerate undefined->16 should succeed');
+
+test_modified_encoding('video', 'maxFramerate', 24, undefined,
+ 'setParameters() with maxFramerate 24->undefined should succeed');
+
+test_modified_encoding('video', 'maxFramerate', 0, 16,
+ 'setParameters() with maxFramerate 0->16 should succeed');
+
+test_modified_encoding('video', 'maxFramerate', 24, 0,
+ 'setParameters() with maxFramerate 24->0 should succeed');
+
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget-stats.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget-stats.html
new file mode 100644
index 0000000000..33f71800bd
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget-stats.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>Tests RTCRtpReceiver-jitterBufferTarget verified with stats</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/webrtc/RTCPeerConnection-helper.js"></script>
+<body>
+<script>
+'use strict'
+
+function async_promise_test(func, name, properties) {
+ async_test(t => {
+ Promise.resolve(func(t))
+ .catch(t.step_func(e => { throw e; }))
+ .then(() => t.done());
+ }, name, properties);
+}
+
+async_promise_test(t => applyJitterBufferTarget(t, "video", 4000),
+ "measure raising and lowering video jitterBufferTarget");
+async_promise_test(t => applyJitterBufferTarget(t, "audio", 4000),
+ "measure raising and lowering audio jitterBufferTarget");
+
+async function applyJitterBufferTarget(t, kind, target) {
+ const caller = new RTCPeerConnection();
+ t.add_cleanup(() => caller.close());
+ const callee = new RTCPeerConnection();
+ t.add_cleanup(() => callee.close());
+
+ const stream = await getNoiseStream({[kind]:true});
+ t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+ caller.addTransceiver(stream.getTracks()[0], {streams: [stream]});
+ caller.addTransceiver(stream.getTracks()[0], {streams: [stream]});
+
+ exchangeIceCandidates(caller, callee);
+ await exchangeOffer(caller, callee);
+ const [unconstrainedReceiver, constrainedReceiver] = callee.getReceivers();
+ const haveRtp = Promise.all([
+ new Promise(r => constrainedReceiver.track.onunmute = r),
+ new Promise(r => unconstrainedReceiver.track.onunmute = r)
+ ]);
+ await exchangeAnswer(caller, callee);
+ const chromeTimeout = new Promise(r => t.step_timeout(r, 1000)); // crbug.com/1295295
+ await Promise.race([haveRtp, chromeTimeout]);
+
+ // Allow some data to be processed to let the jitter buffer to stabilize a bit before measuring
+ await new Promise(r => t.step_timeout(r, 5000));
+
+ t.step(() => assert_equals(constrainedReceiver.jitterBufferTarget, null,
+ `jitterBufferTarget supported for ${kind}`));
+
+ constrainedReceiver.jitterBufferTarget = target;
+ t.step(() => assert_equals(constrainedReceiver.jitterBufferTarget, target,
+ `jitterBufferTarget increase target for ${kind}`));
+
+ const [increased, base] = await Promise.all([
+ measureDelayFromStats(t, constrainedReceiver, 20),
+ measureDelayFromStats(t, unconstrainedReceiver, 20)
+ ]);
+
+ t.step(() => assert_greater_than(increased , base,
+ `${kind} increased delay ${increased} ` +
+ ` greater than base delay ${base}`));
+
+ constrainedReceiver.jitterBufferTarget = 0;
+
+ // Allow the jitter buffer to stabilize a bit before measuring
+ await new Promise(r => t.step_timeout(r, 5000));
+ t.step(() => assert_equals(constrainedReceiver.jitterBufferTarget, 0,
+ `jitterBufferTarget decrease target for ${kind}`));
+
+ const decreased = await measureDelayFromStats(t, constrainedReceiver, 20);
+
+ t.step(() => assert_less_than(decreased, increased,
+ `${kind} decreasedDelay ${decreased} ` +
+ `less than increased delay ${increased}`));
+}
+
+async function measureDelayFromStats(t, receiver, cycles) {
+
+ let statsReport = await receiver.getStats();
+ const oldInboundStats = [...statsReport.values()].find(({type}) => type == "inbound-rtp");
+
+ await new Promise(r => t.step_timeout(r, 1000 * cycles));
+
+ statsReport = await receiver.getStats();
+ const inboundStats = [...statsReport.values()].find(({type}) => type == "inbound-rtp");
+
+ const delay = ((inboundStats.jitterBufferDelay - oldInboundStats.jitterBufferDelay) /
+ (inboundStats.jitterBufferEmittedCount - oldInboundStats.jitterBufferEmittedCount) * 1000);
+
+ return delay;
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget.html
new file mode 100644
index 0000000000..448162d3a2
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-jitterBufferTarget.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests for RTCRtpReceiver-jitterBufferTarget attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+'use strict'
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+}, 'audio jitterBufferTarget is null by default');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 500;
+ assert_equals(receiver.jitterBufferTarget, 500);
+}, 'audio jitterBufferTarget accepts posititve values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 4000;
+ assert_throws_js(RangeError, () => {
+ receiver.jitterBufferTarget = 4001;
+ }, 'audio jitterBufferTarget doesn\'t accept values greater than 4000 milliseconds');
+ assert_equals(receiver.jitterBufferTarget, 4000);
+}, 'audio jitterBufferTarget accepts values up to 4000 milliseconds');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 700;
+ assert_throws_js(RangeError, () => {
+ receiver.jitterBufferTarget = -500;
+ }, 'audio jitterBufferTarget doesn\'t accept negative values');
+ assert_equals(receiver.jitterBufferTarget, 700);
+}, 'audio jitterBufferTarget returns last valid value on throw');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 0;
+ assert_equals(receiver.jitterBufferTarget, 0);
+}, 'audio jitterBufferTarget allows zero value');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 500;
+ assert_equals(receiver.jitterBufferTarget, 500);
+ receiver.jitterBufferTarget = null;
+ assert_equals(receiver.jitterBufferTarget, null);
+}, 'audio jitterBufferTarget allows to reset value to null');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+}, 'video jitterBufferTarget is null by default');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 500;
+ assert_equals(receiver.jitterBufferTarget, 500);
+}, 'video jitterBufferTarget accepts posititve values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 4000;
+ assert_throws_js(RangeError, () => {
+ receiver.jitterBufferTarget = 4001;
+ }, 'video jitterBufferTarget doesn\'t accept values greater than 4000 milliseconds');
+ assert_equals(receiver.jitterBufferTarget, 4000);
+}, 'video jitterBufferTarget accepts values up to 4000 milliseconds');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 700;
+ assert_throws_js(RangeError, () => {
+ receiver.jitterBufferTarget = -500;
+ }, 'video jitterBufferTarget doesn\'t accept negative values');
+ assert_equals(receiver.jitterBufferTarget, 700);
+}, 'video jitterBufferTarget returns last valid value');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 0;
+ assert_equals(receiver.jitterBufferTarget, 0);
+}, 'video jitterBufferTarget allows zero value');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ assert_equals(receiver.jitterBufferTarget, null);
+ receiver.jitterBufferTarget = 500;
+ assert_equals(receiver.jitterBufferTarget, 500);
+ receiver.jitterBufferTarget = null;
+ assert_equals(receiver.jitterBufferTarget, null);
+}, 'video jitterBufferTarget allows to reset value to null');
+</script>
+</body>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html
new file mode 100644
index 0000000000..60b4ed0a74
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html
@@ -0,0 +1,94 @@
+<!doctype html>
+<meta charset=utf-8>
+<!-- This file contains a test that waits for 2 seconds. -->
+<meta name="timeout" content="long">
+<title>captureTimestamp attribute in RTCRtpSynchronizationSource</title>
+<div><video id="remote" width="124" height="124" autoplay></video></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/webrtc/RTCPeerConnection-helper.js"></script>
+<script src="/webrtc/RTCStats-helper.js"></script>
+<script src="/webrtc-extensions/RTCRtpSynchronizationSource-helper.js"></script>
+<script>
+'use strict';
+
+function listenForCaptureTimestamp(t, receiver) {
+ return new Promise((resolve) => {
+ function listen() {
+ const ssrcs = receiver.getSynchronizationSources();
+ assert_true(ssrcs != undefined);
+ if (ssrcs.length > 0) {
+ assert_equals(ssrcs.length, 1);
+ if (ssrcs[0].captureTimestamp != undefined) {
+ resolve(ssrcs[0].captureTimestamp);
+ return true;
+ }
+ }
+ return false;
+ };
+ t.step_wait(listen, 'No abs-capture-time capture time header extension.');
+ });
+}
+
+// Passes if `getSynchronizationSources()` contains `captureTimestamp` if and
+// only if expected.
+for (const kind of ['audio', 'video']) {
+ promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{[kind]: true}, /* absCaptureTimeOffered= */false,
+ /* absCaptureTimeAnswered= */false);
+ const receiver = callee.getReceivers()[0];
+
+ for (const ssrc of await listenForSSRCs(t, receiver)) {
+ assert_equals(typeof ssrc.captureTimestamp, 'undefined');
+ }
+ }, '[' + kind + '] getSynchronizationSources() should not contain ' +
+ 'captureTimestamp if absolute capture time RTP header extension is not ' +
+ 'offered');
+
+ promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{[kind]: true}, /* absCaptureTimeOffered= */false,
+ /* absCaptureTimeAnswered= */false);
+ const receiver = callee.getReceivers()[0];
+
+ for (const ssrc of await listenForSSRCs(t, receiver)) {
+ assert_equals(typeof ssrc.captureTimestamp, 'undefined');
+ }
+ }, '[' + kind + '] getSynchronizationSources() should not contain ' +
+ 'captureTimestamp if absolute capture time RTP header extension is ' +
+ 'offered, but not answered');
+
+ promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{[kind]: true}, /* absCaptureTimeOffered= */true,
+ /* absCaptureTimeAnswered= */true);
+ const receiver = callee.getReceivers()[0];
+ await listenForCaptureTimestamp(t, receiver);
+ }, '[' + kind + '] getSynchronizationSources() should contain ' +
+ 'captureTimestamp if absolute capture time RTP header extension is ' +
+ 'negotiated');
+}
+
+// Passes if `captureTimestamp` for audio and video are comparable, which is
+// expected since the test creates a local peer connection between `caller` and
+// `callee`.
+promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{audio: true, video: true},
+ /* absCaptureTimeOffered= */true, /* absCaptureTimeAnswered= */true);
+ const receivers = callee.getReceivers();
+ assert_equals(receivers.length, 2);
+
+ let captureTimestamps = [undefined, undefined];
+ const t0 = performance.now();
+ for (let i = 0; i < 2; ++i) {
+ captureTimestamps[i] = await listenForCaptureTimestamp(t, receivers[i]);
+ }
+ const t1 = performance.now();
+ assert_less_than(Math.abs(captureTimestamps[0] - captureTimestamps[1]),
+ t1 - t0);
+}, 'Audio and video RTCRtpSynchronizationSource.captureTimestamp are ' +
+ 'comparable');
+
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-helper.js b/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-helper.js
new file mode 100644
index 0000000000..10cfd65155
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-helper.js
@@ -0,0 +1,140 @@
+'use strict';
+
+// This file depends on `webrtc/RTCPeerConnection-helper.js`
+// which should be loaded from the main HTML file.
+
+var kAbsCaptureTime =
+ 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time';
+
+function addHeaderExtensionToSdp(sdp, uri) {
+ const extmap = new RegExp('a=extmap:(\\d+)');
+ let sdpLines = sdp.split('\r\n');
+
+ // This assumes at most one audio m= section and one video m= section.
+ // If more are present, only the first section of each kind is munged.
+ for (const section of ['audio', 'video']) {
+ let found_section = false;
+ let maxId = undefined;
+ let maxIdLine = undefined;
+ let extmapAllowMixed = false;
+
+ // find the largest header extension id for section.
+ for (let i = 0; i < sdpLines.length; ++i) {
+ if (!found_section) {
+ if (sdpLines[i].startsWith('m=' + section)) {
+ found_section = true;
+ }
+ continue;
+ } else {
+ if (sdpLines[i].startsWith('m=')) {
+ // end of section
+ break;
+ }
+ }
+
+ if (sdpLines[i] === 'a=extmap-allow-mixed') {
+ extmapAllowMixed = true;
+ }
+ let result = sdpLines[i].match(extmap);
+ if (result && result.length === 2) {
+ if (maxId == undefined || result[1] > maxId) {
+ maxId = parseInt(result[1]);
+ maxIdLine = i;
+ }
+ }
+ }
+
+ if (maxId == 14 && !extmapAllowMixed) {
+ // Reaching the limit of one byte header extension. Adding two byte header
+ // extension support.
+ sdpLines.splice(maxIdLine + 1, 0, 'a=extmap-allow-mixed');
+ }
+ if (maxIdLine !== undefined) {
+ sdpLines.splice(maxIdLine + 1, 0,
+ 'a=extmap:' + (maxId + 1).toString() + ' ' + uri);
+ }
+ }
+ return sdpLines.join('\r\n');
+}
+
+// TODO(crbug.com/1051821): Use RTP header extension API instead of munging
+// when the RTP header extension API is implemented.
+async function addAbsCaptureTimeAndExchangeOffer(caller, callee) {
+ let offer = await caller.createOffer();
+
+ // Absolute capture time header extension may not be offered by default,
+ // in such case, munge the SDP.
+ offer.sdp = addHeaderExtensionToSdp(offer.sdp, kAbsCaptureTime);
+
+ await caller.setLocalDescription(offer);
+ return callee.setRemoteDescription(offer);
+}
+
+// TODO(crbug.com/1051821): Use RTP header extension API instead of munging
+// when the RTP header extension API is implemented.
+async function checkAbsCaptureTimeAndExchangeAnswer(caller, callee,
+ absCaptureTimeAnswered) {
+ let answer = await callee.createAnswer();
+
+ const extmap = new RegExp('a=extmap:\\d+ ' + kAbsCaptureTime + '\r\n', 'g');
+ if (answer.sdp.match(extmap) == null) {
+ // We expect that absolute capture time RTP header extension is answered.
+ // But if not, there is no need to proceed with the test.
+ assert_false(absCaptureTimeAnswered, 'Absolute capture time RTP ' +
+ 'header extension is not answered');
+ } else {
+ if (!absCaptureTimeAnswered) {
+ // We expect that absolute capture time RTP header extension is not
+ // answered, but it is, then we munge the answer to remove it.
+ answer.sdp = answer.sdp.replace(extmap, '');
+ }
+ }
+
+ await callee.setLocalDescription(answer);
+ return caller.setRemoteDescription(answer);
+}
+
+async function exchangeOfferAndListenToOntrack(t, caller, callee,
+ absCaptureTimeOffered) {
+ const ontrackPromise = addEventListenerPromise(t, callee, 'track');
+ // Absolute capture time header extension is expected not offered by default,
+ // and thus munging is needed to enable it.
+ await absCaptureTimeOffered
+ ? addAbsCaptureTimeAndExchangeOffer(caller, callee)
+ : exchangeOffer(caller, callee);
+ return ontrackPromise;
+}
+
+async function initiateSingleTrackCall(t, cap, absCaptureTimeOffered,
+ absCaptureTimeAnswered) {
+ const caller = new RTCPeerConnection();
+ t.add_cleanup(() => caller.close());
+ const callee = new RTCPeerConnection();
+ t.add_cleanup(() => callee.close());
+
+ const stream = await getNoiseStream(cap);
+ stream.getTracks().forEach(track => {
+ caller.addTrack(track, stream);
+ t.add_cleanup(() => track.stop());
+ });
+
+ // TODO(crbug.com/988432): `getSynchronizationSources() on the audio side
+ // needs a hardware sink for the returned dictionary entries to get updated.
+ const remoteVideo = document.getElementById('remote');
+
+ callee.ontrack = e => {
+ remoteVideo.srcObject = e.streams[0];
+ }
+
+ exchangeIceCandidates(caller, callee);
+
+ await exchangeOfferAndListenToOntrack(t, caller, callee,
+ absCaptureTimeOffered);
+
+ // Exchange answer and check whether the absolute capture time RTP header
+ // extension is answered.
+ await checkAbsCaptureTimeAndExchangeAnswer(caller, callee,
+ absCaptureTimeAnswered);
+
+ return [caller, callee];
+}
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-senderCaptureTimeOffset.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-senderCaptureTimeOffset.html
new file mode 100644
index 0000000000..63ad9bf888
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpSynchronizationSource-senderCaptureTimeOffset.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<meta charset=utf-8>
+<!-- This file contains a test that waits for 2 seconds. -->
+<meta name="timeout" content="long">
+<title>senderCaptureTimeOffset attribute in RTCRtpSynchronizationSource</title>
+<div><video id="remote" width="124" height="124" autoplay></video></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/webrtc/RTCPeerConnection-helper.js"></script>
+<script src="/webrtc/RTCStats-helper.js"></script>
+<script src="/webrtc-extensions/RTCRtpSynchronizationSource-helper.js"></script>
+<script>
+'use strict';
+
+function listenForSenderCaptureTimeOffset(t, receiver) {
+ return new Promise((resolve) => {
+ function listen() {
+ const ssrcs = receiver.getSynchronizationSources();
+ assert_true(ssrcs != undefined);
+ if (ssrcs.length > 0) {
+ assert_equals(ssrcs.length, 1);
+ if (ssrcs[0].captureTimestamp != undefined) {
+ resolve(ssrcs[0].senderCaptureTimeOffset);
+ return true;
+ }
+ }
+ return false;
+ };
+ t.step_wait(listen, 'No abs-capture-time capture time header extension.');
+ });
+}
+
+// Passes if `getSynchronizationSources()` contains `senderCaptureTimeOffset` if
+// and only if expected.
+for (const kind of ['audio', 'video']) {
+ promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{[kind]: true}, /* absCaptureTimeOffered= */false,
+ /* absCaptureTimeAnswered= */false);
+ const receiver = callee.getReceivers()[0];
+
+ for (const ssrc of await listenForSSRCs(t, receiver)) {
+ assert_equals(typeof ssrc.senderCaptureTimeOffset, 'undefined');
+ }
+ }, '[' + kind + '] getSynchronizationSources() should not contain ' +
+ 'senderCaptureTimeOffset if absolute capture time RTP header extension ' +
+ 'is not offered');
+
+ promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{[kind]: true}, /* absCaptureTimeOffered= */false,
+ /* absCaptureTimeAnswered= */false);
+ const receiver = callee.getReceivers()[0];
+
+ for (const ssrc of await listenForSSRCs(t, receiver)) {
+ assert_equals(typeof ssrc.senderCaptureTimeOffset, 'undefined');
+ }
+ }, '[' + kind + '] getSynchronizationSources() should not contain ' +
+ 'senderCaptureTimeOffset if absolute capture time RTP header extension ' +
+ 'is offered, but not answered');
+
+ promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{[kind]: true}, /* absCaptureTimeOffered= */true,
+ /* absCaptureTimeAnswered= */true);
+ const receiver = callee.getReceivers()[0];
+ let senderCaptureTimeOffset = await listenForSenderCaptureTimeOffset(
+ t, receiver);
+ assert_true(senderCaptureTimeOffset != undefined);
+ }, '[' + kind + '] getSynchronizationSources() should contain ' +
+ 'senderCaptureTimeOffset if absolute capture time RTP header extension ' +
+ 'is negotiated');
+}
+
+// Passes if `senderCaptureTimeOffset` is zero, which is expected since the test
+// creates a local peer connection between `caller` and `callee`.
+promise_test(async t => {
+ const [caller, callee] = await initiateSingleTrackCall(
+ t, /* caps= */{audio: true, video: true},
+ /* absCaptureTimeOffered= */true, /* absCaptureTimeAnswered= */true);
+ const receivers = callee.getReceivers();
+ assert_equals(receivers.length, 2);
+
+ for (let i = 0; i < 2; ++i) {
+ let senderCaptureTimeOffset = await listenForSenderCaptureTimeOffset(
+ t, receivers[i]);
+ assert_equals(senderCaptureTimeOffset, 0);
+ }
+}, 'Audio and video RTCRtpSynchronizationSource.senderCaptureTimeOffset must ' +
+ 'be zero');
+
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/RTCRtpTransceiver-headerExtensionControl.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpTransceiver-headerExtensionControl.html
new file mode 100644
index 0000000000..79eba02727
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpTransceiver-headerExtensionControl.html
@@ -0,0 +1,295 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCRtpParameters encodings</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/webrtc/dictionary-helper.js"></script>
+<script src="/webrtc/RTCRtpParameters-helper.js"></script>
+<script src="/webrtc/third_party/sdp/sdp.js"></script>
+<script>
+'use strict';
+
+async function negotiate(pc1, pc2) {
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+}
+
+['audio', 'video'].forEach(kind => {
+ test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver(kind);
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ const capability = capabilities.find((capability) => {
+ return capability.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ assert_not_equals(capability, undefined);
+ assert_equals(capability.direction, 'sendrecv');
+ }, `the ${kind} transceiver.getHeaderExtensionsToNegotiate() includes mandatory extensions`);
+});
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ capabilities[0].uri = '';
+ assert_throws_js(TypeError, () => {
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ }, 'transceiver should throw TypeError when setting an empty URI');
+}, `setHeaderExtensionsToNegotiate throws TypeError on encountering missing URI`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ capabilities[0].direction = '';
+ assert_throws_js(TypeError, () => {
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ }, 'transceiver should throw TypeError when setting an empty direction');
+}, `setHeaderExtensionsToNegotiate throws TypeError on encountering missing direction`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ capabilities[0].uri = '4711';
+ assert_throws_dom('InvalidModificationError', () => {
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ }, 'transceiver should throw InvalidModificationError when setting an unknown URI');
+}, `setHeaderExtensionsToNegotiate throws InvalidModificationError on encountering unknown URI`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('video');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate().filter(capability => {
+ return capability.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ assert_throws_dom('InvalidModificationError', () => {
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ }, 'transceiver should throw InvalidModificationError when removing elements from the list');
+}, `setHeaderExtensionsToNegotiate throws InvalidModificationError when removing elements from the list`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('video');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ capabilities.push({
+ uri: '4711',
+ direction: 'recvonly',
+ });
+ assert_throws_dom('InvalidModificationError', () => {
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ }, 'transceiver should throw InvalidModificationError when adding elements to the list');
+}, `setHeaderExtensionsToNegotiate throws InvalidModificationError when adding elements to the list`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ let capability = capabilities.find((capability) => {
+ return capability.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ ['sendonly', 'recvonly', 'inactive', 'stopped'].map(direction => {
+ capability.direction = direction;
+ assert_throws_dom('InvalidModificationError', () => {
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ }, `transceiver should throw InvalidModificationError when setting a mandatory header extension\'s direction to ${direction}`);
+ });
+}, `setHeaderExtensionsToNegotiate throws InvalidModificationError when setting a mandatory header extension\'s direction to something else than "sendrecv"`);
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ let capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ let selected_capability = capabilities.find((capability) => {
+ return capability.direction === 'sendrecv' &&
+ capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ selected_capability.direction = 'stopped';
+ const offered_capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ let altered_capability = capabilities.find((capability) => {
+ return capability.uri === selected_capability.uri &&
+ capability.direction === 'stopped';
+ });
+ assert_not_equals(altered_capability, undefined);
+}, `modified direction set by setHeaderExtensionsToNegotiate is visible in subsequent getHeaderExtensionsToNegotiate`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('video');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ const offer = await pc.createOffer();
+ const extensions = SDPUtils.matchPrefix(SDPUtils.splitSections(offer.sdp)[1], 'a=extmap:')
+ .map(line => SDPUtils.parseExtmap(line));
+ for (const capability of capabilities) {
+ if (capability.direction === 'stopped') {
+ assert_equals(undefined, extensions.find(e => e.uri === capability.uri));
+ } else {
+ assert_not_equals(undefined, extensions.find(e => e.uri === capability.uri));
+ }
+ }
+}, `Unstopped extensions turn up in offer`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('video');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ const selected_capability = capabilities.find((capability) => {
+ return capability.direction === 'sendrecv' &&
+ capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ selected_capability.direction = 'stopped';
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ const offer = await pc.createOffer();
+ const extensions = SDPUtils.matchPrefix(SDPUtils.splitSections(offer.sdp)[1], 'a=extmap:')
+ .map(line => SDPUtils.parseExtmap(line));
+ for (const capability of capabilities) {
+ if (capability.direction === 'stopped') {
+ assert_equals(undefined, extensions.find(e => e.uri === capability.uri));
+ } else {
+ assert_not_equals(undefined, extensions.find(e => e.uri === capability.uri));
+ }
+ }
+}, `Stopped extensions do not turn up in offers`);
+
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ // Disable a non-mandatory extension before first negotiation.
+ const transceiver = pc1.addTransceiver('video');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ const selected_capability = capabilities.find((capability) => {
+ return capability.direction === 'sendrecv' &&
+ capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ selected_capability.direction = 'stopped';
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+
+ await negotiate(pc1, pc2);
+ const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();
+
+ assert_equals(capabilities.length, negotiated_capabilites.length);
+}, `The set of negotiated extensions has the same size as the set of extensions to negotiate`);
+
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ // Disable a non-mandatory extension before first negotiation.
+ const transceiver = pc1.addTransceiver('video');
+ const capabilities = transceiver.getHeaderExtensionsToNegotiate();
+ const selected_capability = capabilities.find((capability) => {
+ return capability.direction === 'sendrecv' &&
+ capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid';
+ });
+ selected_capability.direction = 'stopped';
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+
+ await negotiate(pc1, pc2);
+ const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();
+
+ // Attempt enabling the extension.
+ selected_capability.direction = 'sendrecv';
+
+ // The enabled extension should not be part of the negotiated set.
+ transceiver.setHeaderExtensionsToNegotiate(capabilities);
+ await negotiate(pc1, pc2);
+ assert_not_equals(
+ transceiver.getNegotiatedHeaderExtensions().find(capability => {
+ return capability.uri === selected_capability.uri &&
+ capability.direction === 'sendrecv';
+ }), undefined);
+}, `Header extensions can be reactivated in subsequent offers`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const t1 = pc.addTransceiver('video');
+ const t2 = pc.addTransceiver('video');
+ const extensionUri = 'urn:3gpp:video-orientation';
+
+ assert_true(!!t1.getHeaderExtensionsToNegotiate().find(ext => ext.uri === extensionUri));
+ const ext1 = t1.getHeaderExtensionsToNegotiate();
+ ext1.find(ext => ext.uri === extensionUri).direction = 'stopped';
+ t1.setHeaderExtensionsToNegotiate(ext1);
+
+ assert_true(!!t2.getHeaderExtensionsToNegotiate().find(ext => ext.uri === extensionUri));
+ const ext2 = t2.getHeaderExtensionsToNegotiate();
+ ext2.find(ext => ext.uri === extensionUri).direction = 'sendrecv';
+ t2.setHeaderExtensionsToNegotiate(ext2);
+
+ const offer = await pc.createOffer();
+ const sections = SDPUtils.splitSections(offer.sdp);
+ sections.shift();
+ const extensions = sections.map(section => {
+ return SDPUtils.matchPrefix(section, 'a=extmap:')
+ .map(SDPUtils.parseExtmap);
+ });
+ assert_equals(extensions.length, 2);
+ assert_false(!!extensions[0].find(extension => extension.uri === extensionUri));
+ assert_true(!!extensions[1].find(extension => extension.uri === extensionUri));
+}, 'Header extensions can be deactivated on a per-mline basis');
+
+promise_test(async t => {
+ const pc1 = new RTCPeerConnection();
+ t.add_cleanup(() => pc1.close());
+ const pc2 = new RTCPeerConnection();
+ t.add_cleanup(() => pc2.close());
+
+ const t1 = pc1.addTransceiver('video');
+
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ // Get the transceiver after it is created by SRD.
+ const t2 = pc2.getTransceivers()[0];
+ const t2_capabilities = t2.getHeaderExtensionsToNegotiate();
+ const t2_capability_to_stop = t2_capabilities
+ .find(capability => capability.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid');
+ assert_not_equals(undefined, t2_capability_to_stop);
+ t2_capability_to_stop.direction = 'stopped';
+ t2.setHeaderExtensionsToNegotiate(t2_capabilities);
+
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ const t1_negotiated = t1.getNegotiatedHeaderExtensions()
+ .find(extension => extension.uri === t2_capability_to_stop.uri);
+ assert_not_equals(undefined, t1_negotiated);
+ assert_equals(t1_negotiated.direction, 'stopped');
+ const t1_capability = t1.getHeaderExtensionsToNegotiate()
+ .find(extension => extension.uri === t2_capability_to_stop.uri);
+ assert_not_equals(undefined, t1_capability);
+ assert_equals(t1_capability.direction, 'sendrecv');
+}, 'Extensions not negotiated by the peer are `stopped` in getNegotiatedHeaderExtensions');
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+
+ const transceiver = pc.addTransceiver('video');
+ const negotiated_capabilites = transceiver.getNegotiatedHeaderExtensions();
+ assert_equals(negotiated_capabilites.length,
+ transceiver.getHeaderExtensionsToNegotiate().length);
+ for (const capability of negotiated_capabilites) {
+ assert_equals(capability.direction, 'stopped');
+ }
+}, 'Prior to negotiation, getNegotiatedHeaderExtensions() returns `stopped` for all extensions.');
+
+</script>
diff --git a/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.https.html b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.https.html
new file mode 100644
index 0000000000..625fee4fe1
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.https.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script src="../service-workers/service-worker/resources/test-helpers.sub.js"></script>
+ <script>
+async function createConnections(test, firstConnectionCallback, secondConnectionCallback)
+{
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ test.add_cleanup(() => pc1.close());
+ test.add_cleanup(() => pc2.close());
+
+ pc1.onicecandidate = (e) => pc2.addIceCandidate(e.candidate);
+ pc2.onicecandidate = (e) => pc1.addIceCandidate(e.candidate);
+
+ firstConnectionCallback(pc1);
+
+ const offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+
+ secondConnectionCallback(pc2);
+
+ const answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+}
+
+async function waitForMessage(receiver, data)
+{
+ while (true) {
+ const received = await new Promise(resolve => receiver.onmessage = (event) => resolve(event.data));
+ if (data === received)
+ return;
+ }
+}
+
+promise_test(async (test) => {
+ let frame;
+ const scope = 'resources/';
+ const script = 'transfer-datachannel-service-worker.js';
+
+ await service_worker_unregister(test, scope);
+ const registration = await navigator.serviceWorker.register(script, {scope});
+ test.add_cleanup(async () => {
+ return service_worker_unregister(test, scope);
+ });
+ const worker = registration.installing;
+
+ const messageChannel = new MessageChannel();
+
+ let localChannel;
+ let remoteChannel;
+
+ await new Promise((resolve, reject) => {
+ createConnections(test, (firstConnection) => {
+ localChannel = firstConnection.createDataChannel('sendDataChannel');
+ worker.postMessage({channel: localChannel, port: messageChannel.port2}, [localChannel, messageChannel.port2]);
+ }, (secondConnection) => {
+ secondConnection.ondatachannel = (event) => {
+ remoteChannel = event.channel;
+ remoteChannel.onopen = resolve;
+ };
+ });
+ });
+
+ const promise = waitForMessage(messageChannel.port1, "OK");
+ remoteChannel.send("OK");
+ await promise;
+
+ const data = new Promise(resolve => remoteChannel.onmessage = (event) => resolve(event.data));
+ messageChannel.port1.postMessage({message: "OK2"});
+ assert_equals(await data, "OK2");
+}, "offerer data channel in service worker");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.js b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.js
new file mode 100644
index 0000000000..c1919d0b9a
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-service-worker.js
@@ -0,0 +1,15 @@
+let channel;
+let port;
+onmessage = (e) => {
+ if (e.data.port) {
+ port = e.data.port;
+ port.onmessage = (event) => channel.send(event.data.message);
+ }
+ if (e.data.channel) {
+ channel = e.data.channel;
+ channel.onopen = () => port.postMessage("opened");
+ channel.onerror = () => port.postMessage("errored");
+ channel.onclose = () => port.postMessage("closed");
+ channel.onmessage = (event) => port.postMessage(event.data);
+ }
+};
diff --git a/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-worker.js b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-worker.js
new file mode 100644
index 0000000000..10d71f68f0
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel-worker.js
@@ -0,0 +1,19 @@
+let channel;
+onmessage = (event) => {
+ if (event.data.channel) {
+ channel = event.data.channel;
+ channel.onopen = () => self.postMessage("opened");
+ channel.onerror = () => self.postMessage("errored");
+ channel.onclose = () => self.postMessage("closed");
+ channel.onmessage = event => self.postMessage(event.data);
+ }
+ if (event.data.message) {
+ if (channel)
+ channel.send(event.data.message);
+ }
+ if (event.data.close) {
+ if (channel)
+ channel.close();
+ }
+};
+self.postMessage("registered");
diff --git a/testing/web-platform/tests/webrtc-extensions/transfer-datachannel.html b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel.html
new file mode 100644
index 0000000000..9759a67a24
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/transfer-datachannel.html
@@ -0,0 +1,165 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script src="../service-workers/service-worker/resources/test-helpers.sub.js"></script>
+ <script>
+async function createConnections(test, firstConnectionCallback, secondConnectionCallback)
+{
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ test.add_cleanup(() => pc1.close());
+ test.add_cleanup(() => pc2.close());
+
+ pc1.onicecandidate = (e) => pc2.addIceCandidate(e.candidate);
+ pc2.onicecandidate = (e) => pc1.addIceCandidate(e.candidate);
+
+ firstConnectionCallback(pc1);
+
+ const offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+
+ secondConnectionCallback(pc2);
+
+ const answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+}
+
+async function waitForMessage(receiver, data)
+{
+ while (true) {
+ const received = await new Promise(resolve => receiver.onmessage = (event) => resolve(event.data));
+ if (data === received)
+ return;
+ }
+}
+
+promise_test(async (test) => {
+ let localChannel;
+ let remoteChannel;
+
+ const worker = new Worker('transfer-datachannel-worker.js');
+ let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ assert_equals(await data, "registered");
+
+ await new Promise((resolve, reject) => {
+ createConnections(test, (firstConnection) => {
+ localChannel = firstConnection.createDataChannel('sendDataChannel');
+ worker.postMessage({channel: localChannel}, [localChannel]);
+ data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ }, (secondConnection) => {
+ secondConnection.ondatachannel = (event) => {
+ remoteChannel = event.channel;
+ remoteChannel.onopen = resolve;
+ };
+ });
+ });
+
+ assert_equals(await data, "opened");
+
+ data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ remoteChannel.send("OK");
+ assert_equals(await data, "OK");
+
+ data = new Promise(resolve => remoteChannel.onmessage = (event) => resolve(event.data));
+ worker.postMessage({message: "OK2"});
+ assert_equals(await data, "OK2");
+}, "offerer data channel in workers");
+
+
+promise_test(async (test) => {
+ let localChannel;
+ let remoteChannel;
+
+ const worker = new Worker('transfer-datachannel-worker.js');
+ let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ assert_equals(await data, "registered");
+
+ data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ await new Promise((resolve, reject) => {
+ createConnections(test, (firstConnection) => {
+ localChannel = firstConnection.createDataChannel('sendDataChannel');
+ localChannel.onopen = resolve;
+ }, (secondConnection) => {
+ secondConnection.ondatachannel = (event) => {
+ remoteChannel = event.channel;
+ worker.postMessage({channel: remoteChannel}, [remoteChannel]);
+ };
+ });
+ });
+ assert_equals(await data, "opened");
+
+ data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ localChannel.send("OK");
+ assert_equals(await data, "OK");
+
+ data = new Promise(resolve => localChannel.onmessage = (event) => resolve(event.data));
+ worker.postMessage({message: "OK2"});
+ assert_equals(await data, "OK2");
+}, "answerer data channel in workers");
+
+promise_test(async (test) => {
+ let localChannel;
+ let remoteChannel;
+
+ const worker = new Worker('transfer-datachannel-worker.js');
+ let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ assert_equals(await data, "registered");
+
+ data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ await new Promise((resolve, reject) => {
+ createConnections(test, (firstConnection) => {
+ localChannel = firstConnection.createDataChannel('sendDataChannel');
+ worker.postMessage({channel: localChannel}, [localChannel]);
+
+ }, (secondConnection) => {
+ secondConnection.ondatachannel = (event) => {
+ remoteChannel = event.channel;
+ remoteChannel.onopen = resolve;
+ };
+ });
+ });
+ assert_equals(await data, "opened");
+
+ data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ remoteChannel.close();
+ assert_equals(await data, "closed");
+
+}, "data channel close event in worker");
+
+promise_test(async (test) => {
+ let localChannel;
+ let remoteChannel;
+
+ const worker = new Worker('transfer-datachannel-worker.js');
+ let data = new Promise(resolve => worker.onmessage = (event) => resolve(event.data));
+ assert_equals(await data, "registered");
+
+ await new Promise((resolve, reject) => {
+ createConnections(test, (firstConnection) => {
+ localChannel = firstConnection.createDataChannel('sendDataChannel');
+ }, (secondConnection) => {
+ secondConnection.ondatachannel = (event) => {
+ remoteChannel = event.channel;
+ test.step_timeout(() => {
+ try {
+ worker.postMessage({channel: remoteChannel}, [remoteChannel]);
+ reject("postMessage ok");
+ } catch(e) {
+ resolve();
+ }
+ }, 0);
+ };
+ });
+ });
+}, "Failing to transfer a data channel");
+ </script>
+ </body>
+</html>