summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc-extensions
diff options
context:
space:
mode:
Diffstat (limited to '')
-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.html48
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-maxFramerate.html101
-rw-r--r--testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-playoutDelayHint.html113
-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.html219
-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
13 files changed, 1136 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..a0cc989c13
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpParameters-adaptivePtime.html
@@ -0,0 +1,48 @@
+<!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';
+
+ function getFirstEncoding(param) {
+ const { encodings } = param;
+ assert_equals(encodings.length, 1);
+ return encodings[0];
+ }
+
+ 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 = getFirstEncoding(param);
+
+ assert_true(encoding.adaptivePtime);
+
+ encoding.adaptivePtime = false;
+ await sender.setParameters(param);
+ param = sender.getParameters();
+ encoding = getFirstEncoding(param);
+
+ 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 = getFirstEncoding(param);
+
+ assert_false(encoding.adaptivePtime);
+
+ }, `adaptivePtime should be default false`);
+
+</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-playoutDelayHint.html b/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-playoutDelayHint.html
new file mode 100644
index 0000000000..29dfc19a6b
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpReceiver-playoutDelayHint.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests for RTCRtpReceiver-playoutDelayHint attribute</title>
+<link rel="help" href="https://henbos.github.io/webrtc-extensions/#dom-rtcrtpreceiver-playoutdelayhint">
+<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.playoutDelayHint, null);
+}, 'audio playoutDelayHint is null by default');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.5;
+ assert_equals(receiver.playoutDelayHint, 0.5);
+}, 'audio playoutDelayHint accepts posititve values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ receiver.playoutDelayHint = 20.5;
+ assert_equals(receiver.playoutDelayHint, 20.5);
+}, 'audio playoutDelayHint accepts large positive values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.7
+ assert_throws_js(TypeError, () => {
+ receiver.playoutDelayHint = -0.5;
+ }, 'audio playoutDelayHint doesn\'t accept negative values');
+ assert_equals(receiver.playoutDelayHint, 0.7);
+}, 'audio playoutDelayHint returns last valid value on throw');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.0;
+ assert_equals(receiver.playoutDelayHint, 0.0);
+}, 'audio playoutDelayHint allows zero value');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('audio', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.5;
+ receiver.playoutDelayHint = null;
+ assert_equals(receiver.playoutDelayHint, null);
+}, 'audio playoutDelayHint 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.playoutDelayHint, null);
+}, 'video playoutDelayHint is null by default');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.5;
+ assert_equals(receiver.playoutDelayHint, 0.5);
+}, 'video playoutDelayHint accepts posititve values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ receiver.playoutDelayHint = 20.5;
+ assert_equals(receiver.playoutDelayHint, 20.5);
+}, 'video playoutDelayHint accepts large posititve values');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.7
+ assert_throws_js(TypeError, () => {
+ receiver.playoutDelayHint = -0.5;
+ }, 'video playoutDelayHint doesn\'t accept negative values');
+ assert_equals(receiver.playoutDelayHint, 0.7);
+}, 'video playoutDelayHint returns last valid value');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.0;
+ assert_equals(receiver.playoutDelayHint, 0.0);
+}, 'video playoutDelayHint allows zero value');
+
+test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const {receiver} = pc.addTransceiver('video', {direction:'recvonly'});
+ receiver.playoutDelayHint = 0.5;
+ receiver.playoutDelayHint = null;
+ assert_equals(receiver.playoutDelayHint, null);
+}, 'video playoutDelayHint 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..e823bd830c
--- /dev/null
+++ b/testing/web-platform/tests/webrtc-extensions/RTCRtpTransceiver-headerExtensionControl.html
@@ -0,0 +1,219 @@
+<!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';
+
+async function negotiate(pc1, pc2) {
+ const offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(offer);
+ const answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(answer);
+}
+
+test(function(t) {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('video');
+ const capabilities = transceiver.headerExtensionsToOffer;
+ let capability = capabilities.find((capability) => {
+ return capability.uri == "urn:ietf:params:rtp-hdrext:sdes:mid" &&
+ capability.direction != "stopped";
+ });
+ assert_not_equals(capability, undefined);
+ capability = capabilities.find((capability) => {
+ return capability.uri == "urn:3gpp:video-orientation" &&
+ capability.direction != "stopped";
+ });
+ assert_not_equals(capability, undefined);
+}, `the video transceiver.headerExtensionsToOffer() includes mandatory extensions`);
+
+test(function(t) {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.headerExtensionsToOffer;
+ let capability = capabilities.find((capability) => {
+ return capability.uri == "urn:ietf:params:rtp-hdrext:sdes:mid" &&
+ capability.direction != "stopped";
+ });
+ assert_not_equals(capability, undefined);
+}, `the audio transceiver.headerExtensionsToOffer() includes mandatory extensions`);
+
+test(function(t) {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.headerExtensionsToOffer;
+ capabilities[0].uri = "";
+ assert_throws_js(TypeError, () => {
+ transceiver.setOfferedRtpHeaderExtensions(capabilities);
+ }, 'transceiver should throw TypeError when setting an empty URI');
+}, `setOfferedRtpHeaderExtensions throws TypeError on encountering missing URI`);
+
+test(function(t) {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.headerExtensionsToOffer;
+ capabilities[0].uri = "4711";
+ assert_throws_dom("NotSupportedError", () => {
+ transceiver.setOfferedRtpHeaderExtensions(capabilities);
+ }, 'transceiver should throw NotSupported when setting an unknown URI');
+}, `setOfferedRtpHeaderExtensions throws NotSupported on encountering unknown URI`);
+
+test(function(t) {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ const capabilities = transceiver.headerExtensionsToOffer;
+ 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.setOfferedRtpHeaderExtensions(capabilities);
+ }, `transceiver should throw InvalidModificationError when setting a mandatory header extension\'s direction to ${direction}`);
+ });
+}, `setOfferedRtpHeaderExtensions throws InvalidModificationError when setting a mandatory header extension\'s direction to something else than "sendrecv"`);
+
+test(function(t) {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('audio');
+ let capabilities = transceiver.headerExtensionsToOffer;
+ 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.headerExtensionsToOffer;
+ 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 setOfferedRtpHeaderExtensions is visible in headerExtensionsOffered`);
+
+promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const transceiver = pc.addTransceiver('video');
+ const capabilities = transceiver.headerExtensionsToOffer;
+ const offer = await pc.createOffer();
+ const extmaps = offer
+ .sdp
+ .split("\n")
+ .filter(line => { return line.includes("a=extmap"); })
+ .join("\n");
+ for (const capability of capabilities) {
+ if (capability.direction == "stopped") {
+ assert_false(extmaps.includes(capability.uri));
+ } else {
+ assert_true(extmaps.includes(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.headerExtensionsToOffer;
+ const selected_capability = capabilities.find((capability) => {
+ return capability.direction == "sendrecv" &&
+ capability.uri != "urn:ietf:params:rtp-hdrext:sdes:mid" &&
+ capability.uri != "urn:3gpp:video-orientation";
+ });
+ selected_capability.direction = "stopped";
+ transceiver.setOfferedRtpHeaderExtensions(capabilities);
+ const offer = await pc.createOffer();
+ const extmaps = offer
+ .sdp
+ .split("\n")
+ .filter(line => { return line.includes("a=extmap"); })
+ .join("\n");
+ for (const capability of capabilities) {
+ if (capability.direction == "stopped") {
+ assert_false(extmaps.includes(capability.uri));
+ } else {
+ assert_true(extmaps.includes(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.headerExtensionsToOffer;
+ const selected_capability = capabilities.find((capability) => {
+ return capability.direction == "sendrecv" &&
+ capability.uri != "urn:ietf:params:rtp-hdrext:sdes:mid" &&
+ capability.uri != "urn:3gpp:video-orientation";
+ });
+ selected_capability.direction = "stopped";
+ transceiver.setOfferedRtpHeaderExtensions(capabilities);
+
+ await negotiate(pc1, pc2);
+ const negotiated_capabilites = transceiver.headerExtensionsNegotiated;
+
+ // Attempt enabling the extension.
+ selected_capability.direction = "sendrecv";
+
+ // The enabled extension should not be part of the negotiated set.
+ transceiver.setOfferedRtpHeaderExtensions(capabilities);
+ await negotiate(pc1, pc2);
+ assert_not_equals(
+ transceiver.headerExtensionsNegotiated.find(capability => {
+ return capability.uri == selected_capability.uri &&
+ capability.direction == "sendrecv";
+ }), undefined);
+}, `the set of negotiated extensions grows with subsequent 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.headerExtensionsToOffer;
+ const selected_capability = capabilities.find((capability) => {
+ return capability.direction == "sendrecv" &&
+ capability.uri != "urn:ietf:params:rtp-hdrext:sdes:mid" &&
+ capability.uri != "urn:3gpp:video-orientation";
+ });
+ selected_capability.direction = "stopped";
+ transceiver.setOfferedRtpHeaderExtensions(capabilities);
+
+ await negotiate(pc1, pc2);
+ const negotiated_capabilites = transceiver.headerExtensionsNegotiated;
+
+ for (const capability of negotiated_capabilites) {
+ assert_not_equals(capabilities.find((cap) => {
+ return cap.uri == capability.uri && cap.direction != "stopped";
+ }), undefined);
+ }
+ for (const capability of capabilities) {
+ if (capability.direction != "stopped") {
+ assert_not_equals(negotiated_capabilites.find((cap) => {
+ return cap.uri == capability.uri;
+ }), undefined);
+ }
+ }
+}, `the set of negotiated extensions is the set of unstopped 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>