mirror of
https://github.com/servo/servo.git
synced 2025-08-24 22:58:21 +01:00
Update web-platform-tests to revision 8a2ceb5f18911302b7a5c1cd2791f4ab50ad4326
This commit is contained in:
parent
462c272380
commit
1f531f66ea
5377 changed files with 174916 additions and 84369 deletions
|
@ -69,7 +69,7 @@
|
|||
// Helper function that takes in an RTCDtlsFingerprint
|
||||
// and return an a=fingerprint SDP line
|
||||
function fingerprintToSdpLine(fingerprint) {
|
||||
return `\r\na=fingerprint ${fingerprint.algorithm} ${fingerprint.value.toUpperCase()}\r\n`;
|
||||
return `\r\na=fingerprint:${fingerprint.algorithm} ${fingerprint.value.toUpperCase()}\r\n`;
|
||||
}
|
||||
|
||||
// Assert that an SDP string has fingerprint line for all the cert's fingerprints
|
||||
|
@ -159,7 +159,7 @@
|
|||
promise_test(t => {
|
||||
return generateCertificate()
|
||||
.then(cert => {
|
||||
assert_own_property(cert, 'getFingerprints');
|
||||
assert_idl_attribute(cert, 'getFingerprints');
|
||||
|
||||
const fingerprints = cert.getFingerprints();
|
||||
assert_true(Array.isArray(fingerprints),
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCConfiguration bundlePolicy</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
...
|
||||
RTCConfiguration getConfiguration();
|
||||
void setConfiguration(RTCConfiguration configuration);
|
||||
};
|
||||
|
||||
4.2.1. RTCConfiguration Dictionary
|
||||
dictionary RTCConfiguration {
|
||||
RTCBundlePolicy bundlePolicy = "balanced";
|
||||
...
|
||||
};
|
||||
|
||||
4.2.6. RTCBundlePolicy Enum
|
||||
enum RTCBundlePolicy {
|
||||
"balanced",
|
||||
"max-compat",
|
||||
"max-bundle"
|
||||
};
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_equals(pc.getConfiguration().bundlePolicy, 'balanced');
|
||||
}, 'Default bundlePolicy should be balanced');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: undefined });
|
||||
assert_equals(pc.getConfiguration().bundlePolicy, 'balanced');
|
||||
}, `new RTCPeerConnection({ bundlePolicy: undefined }) should have bundlePolicy balanced`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'balanced' });
|
||||
assert_equals(pc.getConfiguration().bundlePolicy, 'balanced');
|
||||
}, `new RTCPeerConnection({ bundlePolicy: 'balanced' }) should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'max-compat' });
|
||||
assert_equals(pc.getConfiguration().bundlePolicy, 'max-compat');
|
||||
}, `new RTCPeerConnection({ bundlePolicy: 'max-compat' }) should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
|
||||
assert_equals(pc.getConfiguration().bundlePolicy, 'max-bundle');
|
||||
}, `new RTCPeerConnection({ bundlePolicy: 'max-bundle' }) should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.setConfiguration({});
|
||||
}, 'setConfiguration({}) with initial default bundlePolicy balanced should succeed');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'balanced' });
|
||||
pc.setConfiguration({});
|
||||
}, 'setConfiguration({}) with initial bundlePolicy balanced should succeed');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.setConfiguration({ bundlePolicy: 'balanced' });
|
||||
}, 'setConfiguration({ bundlePolicy: balanced }) with initial default bundlePolicy balanced should succeed');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'balanced' });
|
||||
pc.setConfiguration({ bundlePolicy: 'balanced' });
|
||||
}, `setConfiguration({ bundlePolicy: 'balanced' }) with initial bundlePolicy balanced should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'max-compat' });
|
||||
pc.setConfiguration({ bundlePolicy: 'max-compat' });
|
||||
}, `setConfiguration({ bundlePolicy: 'max-compat' }) with initial bundlePolicy max-compat should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
|
||||
pc.setConfiguration({ bundlePolicy: 'max-bundle' });
|
||||
}, `setConfiguration({ bundlePolicy: 'max-bundle' }) with initial bundlePolicy max-bundle should succeed`);
|
||||
|
||||
test(() => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
new RTCPeerConnection({ bundlePolicy: null }));
|
||||
}, `new RTCPeerConnection({ bundlePolicy: null }) should throw TypeError`);
|
||||
|
||||
test(() => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
new RTCPeerConnection({ bundlePolicy: 'invalid' }));
|
||||
}, `new RTCPeerConnection({ bundlePolicy: 'invalid' }) should throw TypeError`);
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
To set a configuration
|
||||
5. If configuration.bundlePolicy is set and its value differs from the
|
||||
connection's bundle policy, throw an InvalidModificationError.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
|
||||
assert_idl_attribute(pc, 'setConfiguration');
|
||||
|
||||
assert_throws('InvalidModificationError', () =>
|
||||
pc.setConfiguration({ bundlePolicy: 'max-compat' }));
|
||||
}, `setConfiguration({ bundlePolicy: 'max-compat' }) with initial bundlePolicy max-bundle should throw InvalidModificationError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
|
||||
assert_idl_attribute(pc, 'setConfiguration');
|
||||
|
||||
// the default value for bundlePolicy is balanced
|
||||
assert_throws('InvalidModificationError', () =>
|
||||
pc.setConfiguration({}));
|
||||
}, `setConfiguration({}) with initial bundlePolicy max-bundle should throw InvalidModificationError`);
|
||||
|
||||
/*
|
||||
Coverage Report
|
||||
Tested 2
|
||||
Total 2
|
||||
*/
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
// Run a test function as two test cases.
|
||||
// The first test case test the configuration by passing a given config
|
||||
// to the constructor.
|
||||
// The second test case create an RTCPeerConnection object with default
|
||||
// configuration, then call setConfiguration with the provided config.
|
||||
// The test function is given a constructor function to create
|
||||
// a new instance of RTCPeerConnection with given config,
|
||||
// either directly as constructor parameter or through setConfiguration.
|
||||
function config_test(test_func, desc) {
|
||||
test(() => {
|
||||
test_func(config => new RTCPeerConnection(config));
|
||||
}, `new RTCPeerConnection(config) - ${desc}`);
|
||||
|
||||
test(() => {
|
||||
test_func(config => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, 'setConfiguration');
|
||||
pc.setConfiguration(config);
|
||||
return pc;
|
||||
})
|
||||
}, `setConfiguration(config) - ${desc}`);
|
||||
}
|
|
@ -26,6 +26,7 @@ dictionary RTCConfiguration {
|
|||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, "getConfiguration");
|
||||
assert_equals(pc.getConfiguration().iceCandidatePoolSize, 0);
|
||||
}, "Initialize a new RTCPeerConnection with no iceCandidatePoolSize");
|
||||
|
||||
|
@ -33,6 +34,7 @@ test(() => {
|
|||
const pc = new RTCPeerConnection({
|
||||
iceCandidatePoolSize: 0
|
||||
});
|
||||
assert_idl_attribute(pc, "getConfiguration");
|
||||
assert_equals(pc.getConfiguration().iceCandidatePoolSize, 0);
|
||||
}, "Initialize a new RTCPeerConnection with iceCandidatePoolSize: 0");
|
||||
|
||||
|
@ -40,6 +42,7 @@ test(() => {
|
|||
const pc = new RTCPeerConnection({
|
||||
iceCandidatePoolSize: 255
|
||||
});
|
||||
assert_idl_attribute(pc, "getConfiguration");
|
||||
assert_equals(pc.getConfiguration().iceCandidatePoolSize, 255);
|
||||
}, "Initialize a new RTCPeerConnection with iceCandidatePoolSize: 255");
|
||||
|
||||
|
@ -63,9 +66,11 @@ test(() => {
|
|||
/*
|
||||
Reconfiguration
|
||||
*/
|
||||
const pc = new RTCPeerConnection({});
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, "getConfiguration");
|
||||
assert_idl_attribute(pc, "setConfiguration");
|
||||
pc.setConfiguration({
|
||||
iceCandidatePoolSize: 0
|
||||
});
|
||||
|
@ -73,6 +78,9 @@ test(() => {
|
|||
}, "Reconfigure RTCPeerConnection instance iceCandidatePoolSize to 0");
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, "getConfiguration");
|
||||
assert_idl_attribute(pc, "setConfiguration");
|
||||
pc.setConfiguration({
|
||||
iceCandidatePoolSize: 255
|
||||
});
|
||||
|
@ -88,8 +96,8 @@ been implemented). Without this check, these tests will pass incorrectly.
|
|||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_equals(typeof pc.setConfiguration, "function", "RTCPeerConnection.prototype.setConfiguration is not implemented");
|
||||
|
||||
assert_throws(new TypeError(), () => {
|
||||
pc.setConfiguration({
|
||||
iceCandidatePoolSize: -1
|
||||
|
@ -98,8 +106,8 @@ test(() => {
|
|||
}, "Reconfigure RTCPeerConnection instance iceCandidatePoolSize to -1 (Out Of Range)");
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_equals(typeof pc.setConfiguration, "function", "RTCPeerConnection.prototype.setConfiguration is not implemented");
|
||||
|
||||
assert_throws(new TypeError(), () => {
|
||||
pc.setConfiguration({
|
||||
iceCandidatePoolSize: 256
|
||||
|
|
|
@ -0,0 +1,570 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCConfiguration iceServers</title>
|
||||
<script src='/resources/testharness.js'></script>
|
||||
<script src='/resources/testharnessreport.js'></script>
|
||||
<script src='RTCConfiguration-helper.js'></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor's draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper function is called from
|
||||
// RTCConfiguration-helper.js:
|
||||
// config_test
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
...
|
||||
};
|
||||
|
||||
4.2.1. RTCConfiguration Dictionary
|
||||
dictionary RTCConfiguration {
|
||||
sequence<RTCIceServer> iceServers;
|
||||
...
|
||||
};
|
||||
|
||||
4.2.2. RTCIceCredentialType Enum
|
||||
enum RTCIceCredentialType {
|
||||
"password",
|
||||
"oauth"
|
||||
};
|
||||
|
||||
4.2.3. RTCOAuthCredential Dictionary
|
||||
dictionary RTCOAuthCredential {
|
||||
required DOMString macKey;
|
||||
required DOMString accessToken;
|
||||
};
|
||||
|
||||
4.2.4. RTCIceServer Dictionary
|
||||
dictionary RTCIceServer {
|
||||
required (DOMString or sequence<DOMString>) urls;
|
||||
DOMString username;
|
||||
(DOMString or RTCOAuthCredential) credential;
|
||||
RTCIceCredentialType credentialType = "password";
|
||||
};
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_equals(pc.getConfiguration().iceServers, undefined);
|
||||
}, 'new RTCPeerConnection() should have default configuration.iceServers of undefined');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: null }));
|
||||
}, '{ iceServers: null } should throw TypeError');
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: undefined });
|
||||
assert_equals(pc.getConfiguration().iceServers, undefined);
|
||||
}, '{ iceServers: undefined } should succeed');
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [] });
|
||||
assert_array_equals(pc.getConfiguration().iceServers, []);
|
||||
}, '{ iceServers: [] } should succeed');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: [null] }));
|
||||
}, '{ iceServers: [null] } should throw TypeError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: [undefined] }));
|
||||
}, '{ iceServers: [undefined] } should throw TypeError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: [{}] }));
|
||||
}, '{ iceServers: [{}] } should throw TypeError');
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: []
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, []);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
}, 'with empty list urls should succeed');
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: 'stun:stun1.example.net'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['stun:stun1.example.net']);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
|
||||
}, `with stun server should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: ['stun:stun1.example.net']
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['stun:stun1.example.net']);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
|
||||
}, `with stun server array should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: ['stun:stun1.example.net', 'stun:stun2.example.net']
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['stun:stun1.example.net', 'stun:stun2.example.net']);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
|
||||
}, `with 2 stun servers should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = new RTCPeerConnection({ iceServers: [{
|
||||
urls: 'turn:turn.example.org',
|
||||
username: 'user',
|
||||
credential: 'cred'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['turn:turn.example.org']);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
assert_equals(server.username, 'user');
|
||||
assert_equals(server.credential, 'cred');
|
||||
|
||||
}, `with turn server, username, credential should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: 'turns:turn.example.org',
|
||||
username: '',
|
||||
credential: ''
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['turns:turn.example.org']);
|
||||
assert_equals(server.username, '');
|
||||
assert_equals(server.credential, '');
|
||||
|
||||
}, `with turns server and empty string username, credential should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: 'turn:turn.example.org',
|
||||
username: '',
|
||||
credential: ''
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['turn:turn.example.org']);
|
||||
assert_equals(server.username, '');
|
||||
assert_equals(server.credential, '');
|
||||
|
||||
}, `with turn server and empty string username, credential should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: ['turns:turn.example.org', 'turn:turn.example.net'],
|
||||
username: 'user',
|
||||
credential: 'cred'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['turns:turn.example.org', 'turn:turn.example.net']);
|
||||
assert_equals(server.username, 'user');
|
||||
assert_equals(server.credential, 'cred');
|
||||
|
||||
}, `with one turns server, one turn server, username, credential should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: 'stun:stun1.example.net',
|
||||
credentialType: 'password'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['stun:stun1.example.net']);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
|
||||
}, `with stun server and credentialType password should succeed`);
|
||||
|
||||
/*
|
||||
4.3.2. To set a configuration
|
||||
11.4. If scheme name is turn or turns, and either of server.username or
|
||||
server.credential are omitted, then throw an InvalidAccessError.
|
||||
*/
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turn:turn.example.net'
|
||||
}] }));
|
||||
}, 'with turn server and no credentials should throw InvalidAccessError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turn:turn.example.net',
|
||||
username: 'user'
|
||||
}] }));
|
||||
}, 'with turn server and only username should throw InvalidAccessError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turn:turn.example.net',
|
||||
credential: 'cred'
|
||||
}] }));
|
||||
}, 'with turn server and only credential should throw InvalidAccessError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turns:turn.example.net'
|
||||
}] }));
|
||||
}, 'with turns server and no credentials should throw InvalidAccessError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turns:turn.example.net',
|
||||
username: 'user'
|
||||
}] }));
|
||||
}, 'with turns server and only username should throw InvalidAccessError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turns:turn.example.net',
|
||||
credential: 'cred'
|
||||
}] }));
|
||||
}, 'with turns server and only credential should throw InvalidAccessError');
|
||||
|
||||
/*
|
||||
4.3.2. To set a configuration
|
||||
11.3. For each url in server.urls parse url and obtain scheme name.
|
||||
- If the scheme name is not implemented by the browser, throw a SyntaxError.
|
||||
- or if parsing based on the syntax defined in [ RFC7064] and [RFC7065] fails,
|
||||
throw a SyntaxError.
|
||||
|
||||
[RFC7064] URI Scheme for the Session Traversal Utilities for NAT (STUN) Protocol
|
||||
3.1. URI Scheme Syntax
|
||||
stunURI = scheme ":" host [ ":" port ]
|
||||
scheme = "stun" / "stuns"
|
||||
|
||||
[RFC7065] Traversal Using Relays around NAT (TURN) Uniform Resource Identifiers
|
||||
3.1. URI Scheme Syntax
|
||||
turnURI = scheme ":" host [ ":" port ]
|
||||
[ "?transport=" transport ]
|
||||
scheme = "turn" / "turns"
|
||||
transport = "udp" / "tcp" / transport-ext
|
||||
transport-ext = 1*unreserved
|
||||
*/
|
||||
config_test(makePc => {
|
||||
assert_throws('SyntaxError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'relative-url'
|
||||
}] }));
|
||||
}, 'with relative url should throw SyntaxError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('SyntaxError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'http://example.com'
|
||||
}] }));
|
||||
}, 'with http url should throw SyntaxError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('SyntaxError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turn://example.org/foo?x=y'
|
||||
}] }));
|
||||
}, 'with invalid turn url should throw SyntaxError');
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws('SyntaxError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'stun://example.org/foo?x=y'
|
||||
}] }));
|
||||
}, 'with invalid stun url should throw SyntaxError');
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: [],
|
||||
credentialType: 'password'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, []);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
}, `with empty urls and credentialType password should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: [],
|
||||
credentialType: 'oauth'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, []);
|
||||
assert_equals(server.credentialType, 'oauth');
|
||||
}, `with empty urls and credentialType oauth should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: [],
|
||||
credentialType: 'invalid'
|
||||
}] }));
|
||||
}, 'with invalid credentialType should throw TypeError');
|
||||
|
||||
// token credentialType was removed from the spec since 20170508
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: [],
|
||||
credentialType: 'token'
|
||||
}] }));
|
||||
}, 'with credentialType token should throw TypeError');
|
||||
|
||||
// Blink and Gecko fall back to url, but it's not in the spec.
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceServers: [{
|
||||
url: 'stun:stun1.example.net'
|
||||
}] }));
|
||||
}, 'with url field should throw TypeError');
|
||||
|
||||
/*
|
||||
4.3.2. To set a configuration
|
||||
11.5. If scheme name is turn or turns, and server.credentialType is "password",
|
||||
and server.credential is not a DOMString, then throw an InvalidAccessError
|
||||
and abort these steps.
|
||||
*/
|
||||
config_test(makePc => {
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
makePc({ iceServers: [{
|
||||
urls: 'turns:turn.example.org',
|
||||
credentialType: 'password',
|
||||
username: 'user',
|
||||
credential: {
|
||||
macKey: '',
|
||||
accessToken: ''
|
||||
}
|
||||
}] }));
|
||||
}, 'with turns server, credentialType password, and RTCOauthCredential credential should throw InvalidAccessError');
|
||||
|
||||
/*
|
||||
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('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:turn.example.org',
|
||||
credentialType: 'oauth',
|
||||
username: 'user',
|
||||
credential: {
|
||||
macKey: 'mac',
|
||||
accessToken: 'token'
|
||||
}
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['turns:turn.example.org']);
|
||||
assert_equals(server.credentialType, 'oauth');
|
||||
assert_equals(server.username, 'user');
|
||||
|
||||
const { credential } = server;
|
||||
assert_equals(credential.macKey, 'mac');
|
||||
assert_equals(credential.accessToken, 'token');
|
||||
|
||||
}, `with turns server, credentialType oauth and RTCOAuthCredential credential should succeed`);
|
||||
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: ['turns:turn.example.org', 'stun:stun1.example.net'],
|
||||
credentialType: 'oauth',
|
||||
username: 'user',
|
||||
credential: {
|
||||
macKey: 'mac',
|
||||
accessToken: 'token'
|
||||
}
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['turns:turn.example.org', 'stun:stun1.example.net']);
|
||||
assert_equals(server.credentialType, 'oauth');
|
||||
assert_equals(server.username, 'user');
|
||||
|
||||
const { credential } = server;
|
||||
assert_equals(credential.macKey, 'mac');
|
||||
assert_equals(credential.accessToken, 'token');
|
||||
|
||||
}, `with both turns and stun server, credentialType oauth and RTCOAuthCredential credential should succeed`);
|
||||
|
||||
// credential type validation is ignored when scheme name is stun
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: 'stun:stun1.example.net',
|
||||
credentialType: 'oauth',
|
||||
username: 'user',
|
||||
credential: 'cred'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
const server = iceServers[0];
|
||||
|
||||
assert_array_equals(server.urls, ['stun:stun1.example.net']);
|
||||
assert_equals(server.credentialType, 'oauth');
|
||||
assert_equals(server.username, 'user');
|
||||
assert_equals(server.credential, 'cred');
|
||||
|
||||
}, 'with stun server, credentialType oauth, and string credential should succeed');
|
||||
|
||||
// credential type validation is ignored when scheme name is stun
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: 'stun:stun1.example.net',
|
||||
credentialType: 'password',
|
||||
username: 'user',
|
||||
credential: {
|
||||
macKey: '',
|
||||
accessToken: ''
|
||||
}
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, ['stun:stun1.example.net']);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
assert_equals(server.username, 'user');
|
||||
|
||||
const { credential } = server;
|
||||
assert_equals(credential.macKey, '');
|
||||
assert_equals(credential.accessToken, '');
|
||||
|
||||
}, 'with stun server, credentialType password, and RTCOAuthCredential credential should succeed');
|
||||
|
||||
// credential type validation is ignored when urls is empty and there is no scheme name
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: [],
|
||||
credentialType: 'oauth',
|
||||
username: 'user',
|
||||
credential: 'cred'
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
const server = iceServers[0];
|
||||
|
||||
assert_array_equals(server.urls, []);
|
||||
assert_equals(server.credentialType, 'oauth');
|
||||
assert_equals(server.username, 'user');
|
||||
assert_equals(server.credential, 'cred');
|
||||
|
||||
}, 'with empty urls list, credentialType oauth, and string credential should succeed');
|
||||
|
||||
// credential type validation is ignored when urls is empty and there is no scheme name
|
||||
config_test(makePc => {
|
||||
const pc = makePc({ iceServers: [{
|
||||
urls: [],
|
||||
credentialType: 'password',
|
||||
username: 'user',
|
||||
credential: {
|
||||
macKey: '',
|
||||
accessToken: ''
|
||||
}
|
||||
}] });
|
||||
|
||||
const { iceServers } = pc.getConfiguration();
|
||||
assert_equals(iceServers.length, 1);
|
||||
|
||||
const server = iceServers[0];
|
||||
assert_array_equals(server.urls, []);
|
||||
assert_equals(server.credentialType, 'password');
|
||||
assert_equals(server.username, 'user');
|
||||
|
||||
const { credential } = server;
|
||||
assert_equals(credential.macKey, '');
|
||||
assert_equals(credential.accessToken, '');
|
||||
|
||||
}, 'with empty urls list, credentialType password, and RTCOAuthCredential credential should succeed');
|
||||
|
||||
/*
|
||||
Tested
|
||||
4.3.2. To set a configuration
|
||||
11.1-6.
|
||||
|
||||
Untestable
|
||||
4.3.2. To set a configuration
|
||||
11.7. Append server to validatedServers.
|
||||
|
||||
Coverage Report
|
||||
Tested 9
|
||||
Not Tested 0
|
||||
Untestable 1
|
||||
Total 10
|
||||
*/
|
||||
|
||||
</script>
|
|
@ -0,0 +1,119 @@
|
|||
<!doctype html>
|
||||
<title>RTCConfiguration iceTransportPolicy</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCConfiguration-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper function is called from RTCConfiguration-helper.js:
|
||||
// config_test
|
||||
|
||||
/*
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
RTCConfiguration getConfiguration();
|
||||
void setConfiguration(RTCConfiguration configuration);
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCConfiguration {
|
||||
sequence<RTCIceServer> iceServers;
|
||||
RTCIceTransportPolicy iceTransportPolicy = "all";
|
||||
};
|
||||
|
||||
enum RTCIceTransportPolicy {
|
||||
"relay",
|
||||
"all"
|
||||
};
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
}, `new RTCPeerConnection() should have default iceTransportPolicy all`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransportPolicy: undefined });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
}, `new RTCPeerConnection({ iceTransportPolicy: undefined }) should have default iceTransportPolicy all`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransportPolicy: 'all' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
}, `new RTCPeerConnection({ iceTransportPolicy: 'all' }) should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransportPolicy: 'relay' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'relay');
|
||||
}, `new RTCPeerConnection({ iceTransportPolicy: 'relay' }) should succeed`);
|
||||
|
||||
/*
|
||||
4.3.2. Set a configuration
|
||||
8. Set the ICE Agent's ICE transports setting to the value of
|
||||
configuration.iceTransportPolicy. As defined in [JSEP] (section 4.1.16.),
|
||||
if the new ICE transports setting changes the existing setting, no action
|
||||
will be taken until the next gathering phase. If a script wants this to
|
||||
happen immediately, it should do an ICE restart.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransportPolicy: 'all' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
|
||||
pc.setConfiguration({ iceTransportPolicy: 'relay' });
|
||||
assert_equals(pc.getConfiguration(), iceTransportPolicy, 'relay');
|
||||
}, `setConfiguration({ iceTransportPolicy: 'relay' }) with initial iceTransportPolicy all should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransportPolicy: 'relay' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'relay');
|
||||
|
||||
pc.setConfiguration({ iceTransportPolicy: 'all' });
|
||||
assert_equals(pc.getConfiguration(), iceTransportPolicy, 'all');
|
||||
}, `setConfiguration({ iceTransportPolicy: 'all' }) with initial iceTransportPolicy relay should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransportPolicy: 'relay' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'relay');
|
||||
|
||||
// default value for iceTransportPolicy is all
|
||||
pc.setConfiguration({});
|
||||
assert_equals(pc.getConfiguration(), iceTransportPolicy, 'all');
|
||||
}, `setConfiguration({}) with initial iceTransportPolicy relay should set new value to all`);
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceTransportPolicy: 'invalid' }));
|
||||
}, `with invalid iceTransportPolicy should throw TypeError`);
|
||||
|
||||
// "none" is in Blink and Gecko's IDL, but not in the spec.
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceTransportPolicy: 'none' }));
|
||||
}, `with none iceTransportPolicy should throw TypeError`);
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ iceTransportPolicy: null }));
|
||||
}, `with null iceTransportPolicy should throw TypeError`);
|
||||
|
||||
// iceTransportPolicy is called iceTransports in Blink.
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransports: 'relay' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
}, `new RTCPeerConnection({ iceTransports: 'relay' }) should have no effect`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransports: 'invalid' });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
}, `new RTCPeerConnection({ iceTransports: 'invalid' }) should have no effect`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ iceTransports: null });
|
||||
assert_equals(pc.getConfiguration().iceTransportPolicy, 'all');
|
||||
}, `new RTCPeerConnection({ iceTransports: null }) should have no effect`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,143 @@
|
|||
<!doctype html>
|
||||
<title>RTCConfiguration rtcpMuxPolicy</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCConfiguration-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper function is called from RTCConfiguration-helper.js:
|
||||
// config_test
|
||||
|
||||
/*
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
RTCConfiguration getConfiguration();
|
||||
void setConfiguration(RTCConfiguration configuration);
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCConfiguration {
|
||||
RTCRtcpMuxPolicy rtcpMuxPolicy = "require";
|
||||
...
|
||||
};
|
||||
|
||||
enum RTCRtcpMuxPolicy {
|
||||
"negotiate",
|
||||
"require"
|
||||
};
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
|
||||
}, `new RTCPeerConnection() should have default rtcpMuxPolicy require`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ rtcpMuxPolicy: undefined });
|
||||
assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
|
||||
}, `new RTCPeerConnection({ rtcpMuxPolicy: undefined }) should have default rtcpMuxPolicy require`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ rtcpMuxPolicy: 'require' });
|
||||
assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'require');
|
||||
}, `new RTCPeerConnection({ rtcpMuxPolicy: 'require' }) should succeed`);
|
||||
|
||||
/*
|
||||
4.3.1.1. Constructor
|
||||
3. If configuration.rtcpMuxPolicy is negotiate, and the user agent does not
|
||||
implement non-muxed RTCP, throw a NotSupportedError.
|
||||
*/
|
||||
test(() => {
|
||||
let pc;
|
||||
try {
|
||||
pc = new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' });
|
||||
} catch(err) {
|
||||
// NotSupportedError is a DOMException with code 9
|
||||
if(err.code === 9 && err.name === 'NotSupportedError') {
|
||||
// ignore error and pass test if negotiate is not supported
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
assert_equals(pc.getConfiguration().rtcpMuxPolicy, 'negotiate');
|
||||
|
||||
}, `new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' }) may succeed or throw NotSupportedError`);
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ rtcpMuxPolicy: null }));
|
||||
}, `with { rtcpMuxPolicy: null } should throw TypeError`);
|
||||
|
||||
config_test(makePc => {
|
||||
assert_throws(new TypeError(), () =>
|
||||
makePc({ rtcpMuxPolicy: 'invalid' }));
|
||||
}, `with { rtcpMuxPolicy: 'invalid' } should throw TypeError`);
|
||||
|
||||
/*
|
||||
4.3.2. Set a configuration
|
||||
6. If configuration.rtcpMuxPolicy is set and its value differs from the
|
||||
connection's rtcpMux policy, throw an InvalidModificationError.
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection({ rtcpMuxPolicy: 'require' });
|
||||
assert_idl_attribute(pc, 'setConfiguration');
|
||||
assert_throws('InvalidModificationError', () =>
|
||||
pc.setConfiguration({ rtcpMuxPolicy: 'negotiate' }));
|
||||
|
||||
}, `setConfiguration({ rtcpMuxPolicy: 'negotiate' }) with initial rtcpMuxPolicy require should throw InvalidModificationError`);
|
||||
|
||||
test(() => {
|
||||
let pc;
|
||||
try {
|
||||
pc = new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' });
|
||||
} catch(err) {
|
||||
// NotSupportedError is a DOMException with code 9
|
||||
if(err.code === 9 && err.name === 'NotSupportedError') {
|
||||
// ignore error and pass test if negotiate is not supported
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
assert_idl_attribute(pc, 'setConfiguration');
|
||||
assert_throws('InvalidModificationError', () =>
|
||||
pc.setConfiguration({ rtcpMuxPolicy: 'require' }));
|
||||
|
||||
}, `setConfiguration({ rtcpMuxPolicy: 'require' }) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError`);
|
||||
|
||||
test(() => {
|
||||
let pc;
|
||||
try {
|
||||
pc = new RTCPeerConnection({ rtcpMuxPolicy: 'negotiate' });
|
||||
} catch(err) {
|
||||
// NotSupportedError is a DOMException with code 9
|
||||
if(err.code === 9 && err.name === 'NotSupportedError') {
|
||||
// ignore error and pass test if negotiate is not supported
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
assert_idl_attribute(pc, 'setConfiguration');
|
||||
// default value for rtcpMuxPolicy is require
|
||||
assert_throws('InvalidModificationError', () =>
|
||||
pc.setConfiguration({}));
|
||||
|
||||
}, `setConfiguration({}) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError`);
|
||||
|
||||
/*
|
||||
Coverage Report
|
||||
|
||||
Tested 2
|
||||
Total 2
|
||||
*/
|
||||
</script>
|
114
tests/wpt/web-platform-tests/webrtc/RTCDTMFSender-helper.js
Normal file
114
tests/wpt/web-platform-tests/webrtc/RTCDTMFSender-helper.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// Code using this helper should also include RTCPeerConnection-helper.js
|
||||
// in the main HTML file
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// getTrackFromUserMedia
|
||||
|
||||
// Create a RTCDTMFSender using getUserMedia()
|
||||
function createDtmfSender(pc = new RTCPeerConnection()) {
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
const sender = pc.addTrack(track, mediaStream);
|
||||
const dtmfSender = sender.dtmf;
|
||||
|
||||
assert_true(dtmfSender instanceof RTCDTMFSender,
|
||||
'Expect audio sender.dtmf to be set to a RTCDTMFSender');
|
||||
|
||||
return dtmfSender;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Create an RTCDTMFSender and test tonechange events on it.
|
||||
testFunc
|
||||
Test function that is going to manipulate the DTMFSender.
|
||||
It will be called with:
|
||||
t - the test object
|
||||
sender - the created RTCDTMFSender
|
||||
pc - the associated RTCPeerConnection as second argument.
|
||||
toneChanges
|
||||
Array of expected tonechange events fired. The elements
|
||||
are array of 3 items:
|
||||
expectedTone
|
||||
The expected character in event.tone
|
||||
expectedToneBuffer
|
||||
The expected new value of dtmfSender.toneBuffer
|
||||
expectedDuration
|
||||
The rough time since beginning or last tonechange event
|
||||
was fired.
|
||||
desc
|
||||
Test description.
|
||||
*/
|
||||
function test_tone_change_events(testFunc, toneChanges, desc) {
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
createDtmfSender(pc)
|
||||
.then(dtmfSender => {
|
||||
let lastEventTime = Date.now();
|
||||
|
||||
const onToneChange = t.step_func(ev => {
|
||||
assert_true(ev instanceof RTCDTMFToneChangeEvent,
|
||||
'Expect tone change event object to be an RTCDTMFToneChangeEvent');
|
||||
|
||||
const { tone } = ev;
|
||||
assert_equals(typeof tone, 'string',
|
||||
'Expect event.tone to be the tone string');
|
||||
|
||||
assert_greater_than(toneChanges.length, 0,
|
||||
'More tonechange event is fired than expected');
|
||||
|
||||
const [
|
||||
expectedTone, expectedToneBuffer, expectedDuration
|
||||
] = toneChanges.shift();
|
||||
|
||||
assert_equals(tone, expectedTone,
|
||||
`Expect current event.tone to be ${expectedTone}`);
|
||||
|
||||
assert_equals(dtmfSender.toneBuffer, expectedToneBuffer,
|
||||
`Expect dtmfSender.toneBuffer to be updated to ${expectedToneBuffer}`);
|
||||
|
||||
const now = Date.now();
|
||||
const duration = now - lastEventTime;
|
||||
|
||||
assert_approx_equals(duration, expectedDuration, 50,
|
||||
`Expect tonechange event for "${tone}" to be fired approximately after ${expectedDuration} seconds`);
|
||||
|
||||
lastEventTime = now;
|
||||
|
||||
if(toneChanges.length === 0) {
|
||||
// Wait for same duration as last expected duration + 100ms
|
||||
// before passing test in case there are new tone events fired,
|
||||
// in which case the test should fail.
|
||||
t.step_timeout(
|
||||
t.step_func(() => {
|
||||
t.done();
|
||||
pc.close();
|
||||
}), expectedDuration + 100);
|
||||
}
|
||||
});
|
||||
|
||||
dtmfSender.addEventListener('tonechange', onToneChange);
|
||||
|
||||
testFunc(t, dtmfSender, pc);
|
||||
})
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached(`Unexpected promise rejection: ${err}`);
|
||||
}));
|
||||
}, desc);
|
||||
}
|
||||
|
||||
// Get the one and only tranceiver from pc.getTransceivers().
|
||||
// Assumes that there is only one tranceiver in pc.
|
||||
function getTransceiver(pc) {
|
||||
const transceivers = pc.getTransceivers();
|
||||
assert_equals(transceivers.length, 1,
|
||||
'Expect there to be only one tranceiver in pc');
|
||||
|
||||
return transceivers[0];
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCDTMFSender.prototype.insertDTMF</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script src="RTCDTMFSender-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js
|
||||
// generateAnswer
|
||||
|
||||
// The following helper functions are called from RTCDTMFSender-helper.js
|
||||
// createDtmfSender
|
||||
// test_tone_change_events
|
||||
// getTransceiver
|
||||
|
||||
/*
|
||||
7. Peer-to-peer DTMF
|
||||
partial interface RTCRtpSender {
|
||||
readonly attribute RTCDTMFSender? dtmf;
|
||||
};
|
||||
|
||||
interface RTCDTMFSender : EventTarget {
|
||||
void insertDTMF(DOMString tones,
|
||||
optional unsigned long duration = 100,
|
||||
optional unsigned long interToneGap = 70);
|
||||
attribute EventHandler ontonechange;
|
||||
readonly attribute DOMString toneBuffer;
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
The tones parameter is treated as a series of characters.
|
||||
|
||||
The characters 0 through 9, A through D, #, and * generate the associated
|
||||
DTMF tones.
|
||||
|
||||
The characters a to d MUST be normalized to uppercase on entry and are
|
||||
equivalent to A to D.
|
||||
|
||||
As noted in [RTCWEB-AUDIO] Section 3, support for the characters 0 through 9,
|
||||
A through D, #, and * are required.
|
||||
|
||||
The character ',' MUST be supported, and indicates a delay of 2 seconds
|
||||
before processing the next character in the tones parameter.
|
||||
|
||||
All other characters (and only those other characters) MUST be considered
|
||||
unrecognized.
|
||||
*/
|
||||
promise_test(t => {
|
||||
return createDtmfSender()
|
||||
.then(dtmfSender => {
|
||||
dtmfSender.insertDTMF('');
|
||||
dtmfSender.insertDTMF('012345689');
|
||||
dtmfSender.insertDTMF('ABCD');
|
||||
dtmfSender.insertDTMF('abcd');
|
||||
dtmfSender.insertDTMF('#*');
|
||||
dtmfSender.insertDTMF(',');
|
||||
dtmfSender.insertDTMF('0123456789ABCDabcd#*,');
|
||||
});
|
||||
}, 'insertDTMF() should succeed if tones contains valid DTMF characters');
|
||||
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
6. If tones contains any unrecognized characters, throw an
|
||||
InvalidCharacterError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
return createDtmfSender()
|
||||
.then(dtmfSender => {
|
||||
assert_throws('InvalidCharacterError', () =>
|
||||
// 'F' is invalid
|
||||
dtmfSender.insertDTMF('123FFABC'));
|
||||
|
||||
assert_throws('InvalidCharacterError', () =>
|
||||
// 'E' is invalid
|
||||
dtmfSender.insertDTMF('E'));
|
||||
|
||||
assert_throws('InvalidCharacterError', () =>
|
||||
// ' ' is invalid
|
||||
dtmfSender.insertDTMF('# *'));
|
||||
});
|
||||
}, 'insertDTMF() should throw InvalidCharacterError if tones contains invalid DTMF characters');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
3. If transceiver.stopped is true, throw an InvalidStateError.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const dtmfSender = transceiver.sender.dtmf;
|
||||
|
||||
transceiver.stop();
|
||||
assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
|
||||
|
||||
}, 'insertDTMF() should throw InvalidStateError if transceiver is stopped');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
4. If transceiver.currentDirection is recvonly or inactive, throw an InvalidStateError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', {
|
||||
direction: 'recvonly'
|
||||
});
|
||||
const dtmfSender = transceiver.sender.dtmf;
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(() => {
|
||||
assert_equals(transceiver.currentDirection, 'inactive');
|
||||
assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
|
||||
});
|
||||
}, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', {
|
||||
direction: 'inactive'
|
||||
});
|
||||
const dtmfSender = transceiver.sender.dtmf;
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(() => {
|
||||
assert_equals(transceiver.currentDirection, 'inactive');
|
||||
assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
|
||||
});
|
||||
}, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
The characters a to d MUST be normalized to uppercase on entry and are
|
||||
equivalent to A to D.
|
||||
|
||||
7. Set the object's toneBuffer attribute to tones.
|
||||
*/
|
||||
promise_test(() => {
|
||||
return createDtmfSender()
|
||||
.then(dtmfSender => {
|
||||
dtmfSender.insertDTMF('123');
|
||||
assert_equals(dtmfSender.toneBuffer, '123');
|
||||
|
||||
dtmfSender.insertDTMF('ABC');
|
||||
assert_equals(dtmfSender.toneBuffer, 'ABC');
|
||||
|
||||
dtmfSender.insertDTMF('bcd');
|
||||
assert_equals(dtmfSender.toneBuffer, 'BCD');
|
||||
});
|
||||
}, 'insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,50 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<meta name="timeout" content="long">
|
||||
<title>RTCDTMFSender.prototype.ontonechange (Long Timeout)</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script src="RTCDTMFSender-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCDTMFSender-helper.js
|
||||
// test_tone_change_events
|
||||
|
||||
/*
|
||||
7. Peer-to-peer DTMF
|
||||
partial interface RTCRtpSender {
|
||||
readonly attribute RTCDTMFSender? dtmf;
|
||||
};
|
||||
|
||||
interface RTCDTMFSender : EventTarget {
|
||||
void insertDTMF(DOMString tones,
|
||||
optional unsigned long duration = 100,
|
||||
optional unsigned long interToneGap = 70);
|
||||
attribute EventHandler ontonechange;
|
||||
readonly attribute DOMString toneBuffer;
|
||||
};
|
||||
|
||||
[Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)]
|
||||
interface RTCDTMFToneChangeEvent : Event {
|
||||
readonly attribute DOMString tone;
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
8. If the value of the duration parameter is less than 40, set it to 40.
|
||||
If, on the other hand, the value is greater than 6000, set it to 6000.
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.insertDTMF('A', 8000, 70);
|
||||
}, [
|
||||
['A', '', 0],
|
||||
['', '', 6070]
|
||||
],'insertDTMF with duration greater than 6000 should be clamped to 6000');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,285 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCDTMFSender.prototype.ontonechange</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script src="RTCDTMFSender-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js
|
||||
// generateAnswer
|
||||
|
||||
// The following helper functions are called from RTCDTMFSender-helper.js
|
||||
// test_tone_change_events
|
||||
// getTransceiver
|
||||
|
||||
/*
|
||||
7. Peer-to-peer DTMF
|
||||
partial interface RTCRtpSender {
|
||||
readonly attribute RTCDTMFSender? dtmf;
|
||||
};
|
||||
|
||||
interface RTCDTMFSender : EventTarget {
|
||||
void insertDTMF(DOMString tones,
|
||||
optional unsigned long duration = 100,
|
||||
optional unsigned long interToneGap = 70);
|
||||
attribute EventHandler ontonechange;
|
||||
readonly attribute DOMString toneBuffer;
|
||||
};
|
||||
|
||||
[Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)]
|
||||
interface RTCDTMFToneChangeEvent : Event {
|
||||
readonly attribute DOMString tone;
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
11. If a Playout task is scheduled to be run; abort these steps; otherwise queue
|
||||
a task that runs the following steps (Playout task):
|
||||
3. If toneBuffer is an empty string, fire an event named tonechange with an
|
||||
empty string at the RTCDTMFSender object and abort these steps.
|
||||
4. Remove the first character from toneBuffer and let that character be tone.
|
||||
6. Queue a task to be executed in duration + interToneGap ms from now that
|
||||
runs the steps labelled Playout task.
|
||||
7. Fire an event named tonechange with a string consisting of tone at the
|
||||
RTCDTMFSender object.
|
||||
*/
|
||||
test_tone_change_events(dtmfSender => {
|
||||
dtmfSender.insertDTMF('123');
|
||||
}, [
|
||||
['1', '23', 0],
|
||||
['2', '3', 170],
|
||||
['3', '', 170],
|
||||
['', '', 170]
|
||||
], 'insertDTMF() with default duration and intertoneGap should fire tonechange events at the expected time');
|
||||
|
||||
test_tone_change_events(dtmfSender => {
|
||||
dtmfSender.insertDTMF('abc', 100, 70);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 170],
|
||||
['C', '', 170],
|
||||
['', '', 170]
|
||||
], 'insertDTMF() with explicit duration and intertoneGap should fire tonechange events at the expected time');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
10. If toneBuffer is an empty string, abort these steps.
|
||||
*/
|
||||
async_test(t => {
|
||||
createDtmfSender()
|
||||
.then(dtmfSender => {
|
||||
dtmfSender.addEventListener('tonechange',
|
||||
t.unreached_func('Expect no tonechange event to be fired'));
|
||||
|
||||
dtmfSender.insertDTMF('', 100, 70);
|
||||
|
||||
t.step_timeout(t.step_func_done(), 300);
|
||||
})
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached(`Unexpected promise rejection: ${err}`);
|
||||
}));
|
||||
}, `insertDTMF('') should not fire any tonechange event, including for '' tone`);
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
8. If the value of the duration parameter is less than 40, set it to 40.
|
||||
If, on the other hand, the value is greater than 6000, set it to 6000.
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.insertDTMF('ABC', 10, 70);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 110],
|
||||
['C', '', 110],
|
||||
['', '', 110]
|
||||
], 'insertDTMF() with duration less than 40 should be clamped to 40');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
9. If the value of the interToneGap parameter is less than 30, set it to 30.
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.insertDTMF('ABC', 100, 10);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 130],
|
||||
['C', '', 130],
|
||||
['', '', 130]
|
||||
],
|
||||
'insertDTMF() with interToneGap less than 30 should be clamped to 30');
|
||||
|
||||
/*
|
||||
[w3c/webrtc-pc#1373]
|
||||
This step is added to handle the "," character correctly. "," supposed to delay the next
|
||||
tonechange event by 2000ms.
|
||||
|
||||
7.2. insertDTMF
|
||||
11.5. If tone is "," delay sending tones for 2000 ms on the associated RTP media
|
||||
stream, and queue a task to be executed in 2000 ms from now that runs the
|
||||
steps labelled Playout task.
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.insertDTMF('A,B', 100, 70);
|
||||
|
||||
}, [
|
||||
['A', ',B', 0],
|
||||
[',', 'B', 170],
|
||||
['B', '', 2000],
|
||||
['', '', 170]
|
||||
], 'insertDTMF with comma should delay next tonechange event for a constant 2000ms');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
11.1. If transceiver.stopped is true, abort these steps.
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender, pc) => {
|
||||
const transceiver = getTransceiver(pc);
|
||||
dtmfSender.addEventListener('tonechange', ev => {
|
||||
if(ev.tone === 'B') {
|
||||
transceiver.stop();
|
||||
}
|
||||
});
|
||||
|
||||
dtmfSender.insertDTMF('ABC', 100, 70);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 170]
|
||||
], 'insertDTMF() with transceiver stopped in the middle should stop future tonechange events from firing');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
3. If a Playout task is scheduled to be run, abort these steps;
|
||||
otherwise queue a task that runs the following steps (Playout task):
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.addEventListener('tonechange', ev => {
|
||||
if(ev.tone === 'B') {
|
||||
// Set a timeout to make sure the toneBuffer
|
||||
// is changed after the tonechange event listener
|
||||
// by test_tone_change_events is called.
|
||||
// This is to correctly test the expected toneBuffer.
|
||||
t.step_timeout(() => {
|
||||
dtmfSender.insertDTMF('12', 100, 70);
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
|
||||
dtmfSender.insertDTMF('ABC', 100, 70);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 170],
|
||||
['1', '2', 170],
|
||||
['2', '', 170],
|
||||
['', '', 170]
|
||||
], 'Calling insertDTMF() in the middle of tonechange events should cause future tonechanges to be updated to new tones');
|
||||
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
3. If a Playout task is scheduled to be run, abort these steps;
|
||||
otherwise queue a task that runs the following steps (Playout task):
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.addEventListener('tonechange', ev => {
|
||||
if(ev.tone === 'B') {
|
||||
t.step_timeout(() => {
|
||||
dtmfSender.insertDTMF('12', 100, 70);
|
||||
dtmfSender.insertDTMF('34', 100, 70);
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
|
||||
dtmfSender.insertDTMF('ABC', 100, 70);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 170],
|
||||
['3', '4', 170],
|
||||
['4', '', 170],
|
||||
['', '', 170]
|
||||
], 'Calling insertDTMF() multiple times in the middle of tonechange events should cause future tonechanges to be updated the last provided tones');
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
3. If a Playout task is scheduled to be run, abort these steps;
|
||||
otherwise queue a task that runs the following steps (Playout task):
|
||||
*/
|
||||
test_tone_change_events((t, dtmfSender) => {
|
||||
dtmfSender.addEventListener('tonechange', ev => {
|
||||
if(ev.tone === 'B') {
|
||||
t.step_timeout(() => {
|
||||
dtmfSender.insertDTMF('');
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
|
||||
dtmfSender.insertDTMF('ABC', 100, 70);
|
||||
}, [
|
||||
['A', 'BC', 0],
|
||||
['B', 'C', 170],
|
||||
['', '', 170]
|
||||
], `Calling insertDTMF('') in the middle of tonechange events should stop future tonechange events from firing`);
|
||||
|
||||
/*
|
||||
7.2. insertDTMF
|
||||
11.2. If transceiver.currentDirection is recvonly or inactive, abort these steps.
|
||||
*/
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', { direction: 'sendrecv' });
|
||||
const dtmfSender = transceiver.sender.dtmf;
|
||||
|
||||
// Since setRemoteDescription happens in parallel with tonechange event,
|
||||
// We use a flag and allow tonechange events to be fired as long as
|
||||
// the promise returned by setRemoteDescription is not yet resolved.
|
||||
let remoteDescriptionIsSet = false;
|
||||
|
||||
// We only do basic tone verification and not check timing here
|
||||
let expectedTones = ['A', 'B', 'C', 'D', ''];
|
||||
|
||||
const onToneChange = t.step_func(ev => {
|
||||
assert_false(remoteDescriptionIsSet,
|
||||
'Expect no tonechange event to be fired after currentDirection is changed to recvonly');
|
||||
|
||||
const { tone } = ev;
|
||||
const expectedTone = expectedTones.shift();
|
||||
assert_equals(tone, expectedTone,
|
||||
`Expect fired event.tone to be ${expectedTone}`);
|
||||
|
||||
// Only change transceiver.currentDirection after the first
|
||||
// tonechange event, to make sure that tonechange is triggered
|
||||
// then stopped
|
||||
if(tone === 'A') {
|
||||
transceiver.setDirection('recvonly');
|
||||
|
||||
pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer => pc.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
assert_equals(transceiver.currentDirection, 'recvonly');
|
||||
remoteDescriptionIsSet = true;
|
||||
|
||||
// Pass the test if no further tonechange event is
|
||||
// fired in the next 300ms
|
||||
t.step_timeout(t.step_func_done(), 300);
|
||||
})
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached(`Unexpected promise rejection: ${err}`);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
dtmfSender.addEventListener('tonechange', onToneChange);
|
||||
dtmfSender.insertDTMF('ABCD', 100, 70);
|
||||
|
||||
}, `Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,105 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>RTCDtlsTransport.prototype.getRemoteCertificates</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// exchangeIceCandidates
|
||||
// doSignalingHandshake
|
||||
|
||||
/*
|
||||
5.5. RTCDtlsTransport Interface
|
||||
interface RTCDtlsTransport : EventTarget {
|
||||
readonly attribute RTCDtlsTransportState state;
|
||||
sequence<ArrayBuffer> getRemoteCertificates();
|
||||
attribute EventHandler onstatechange;
|
||||
attribute EventHandler onerror;
|
||||
...
|
||||
};
|
||||
|
||||
enum RTCDtlsTransportState {
|
||||
"new",
|
||||
"connecting",
|
||||
"connected",
|
||||
"closed",
|
||||
"failed"
|
||||
};
|
||||
|
||||
getRemoteCertificates
|
||||
Returns the certificate chain in use by the remote side, with each certificate
|
||||
encoded in binary Distinguished Encoding Rules (DER) [X690].
|
||||
getRemoteCertificates() will return an empty list prior to selection of the
|
||||
remote certificate, which will be completed by the time RTCDtlsTransportState
|
||||
transitions to "connected".
|
||||
*/
|
||||
async_test(t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
pc1.createDataChannel('test');
|
||||
exchangeIceCandidates(pc1, pc2);
|
||||
|
||||
doSignalingHandshake(pc1, pc2)
|
||||
.then(t.step_func(() => {
|
||||
// pc.sctp is set when set*Description(answer) is called
|
||||
const sctpTransport1 = pc1.sctp;
|
||||
const sctpTransport2 = pc2.sctp;
|
||||
|
||||
assert_true(sctpTransport1 instanceof RTCSctpTransport,
|
||||
'Expect pc.sctp to be set to valid RTCSctpTransport');
|
||||
|
||||
assert_true(sctpTransport2 instanceof RTCSctpTransport,
|
||||
'Expect pc.sctp to be set to valid RTCSctpTransport');
|
||||
|
||||
const dtlsTransport1 = sctpTransport1.transport;
|
||||
const dtlsTransport2 = sctpTransport2.transport;
|
||||
|
||||
const testedTransports = new Set();
|
||||
|
||||
// Callback function that test the respective DTLS transports
|
||||
// when they become connected.
|
||||
const onConnected = t.step_func(dtlsTransport => {
|
||||
const certs = dtlsTransport.getRemoteCertificates();
|
||||
|
||||
assert_greater_than(certs.length, 0,
|
||||
'Expect DTLS transport to have at least one remote certificate when connected');
|
||||
|
||||
for(const cert of certs) {
|
||||
assert_true(cert instanceof ArrayBuffer,
|
||||
'Expect certificate elements be instance of ArrayBuffer');
|
||||
}
|
||||
|
||||
testedTransports.add(dtlsTransport);
|
||||
|
||||
// End the test if both dtlsTransports are tested.
|
||||
if(testedTransports.has(dtlsTransport1) && testedTransports.has(dtslTransport2)) {
|
||||
t.done();
|
||||
}
|
||||
})
|
||||
|
||||
for(const dtlsTransport of [dtlsTransport1, dtlsTransport2]) {
|
||||
if(dtlsTransport.state === 'connected') {
|
||||
onConnected(dtlsTransport);
|
||||
} else {
|
||||
assert_array_equals(dtlsTransport.getCertificates(), [],
|
||||
'Expect DTLS certificates be initially empty until become connected');
|
||||
|
||||
dtlsTransport.addEventListener('statechange', t.step_func(() => {
|
||||
if(dtlsTransport.state === 'connected') {
|
||||
onConnected(dtlsTransport);
|
||||
}
|
||||
}));
|
||||
|
||||
dtlsTransport.addEventListener('error', t.step_func(err => {
|
||||
assert_unreached(`Unexpected error during DTLS handshake: ${err}`);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
</script>
|
190
tests/wpt/web-platform-tests/webrtc/RTCIceTransport.html
Normal file
190
tests/wpt/web-platform-tests/webrtc/RTCIceTransport.html
Normal file
|
@ -0,0 +1,190 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCIceTransport</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// createDataChannelPair
|
||||
// awaitMessage
|
||||
|
||||
/*
|
||||
5.6. RTCIceTransport Interface
|
||||
interface RTCIceTransport {
|
||||
readonly attribute RTCIceRole role;
|
||||
readonly attribute RTCIceComponent component;
|
||||
readonly attribute RTCIceTransportState state;
|
||||
readonly attribute RTCIceGathererState gatheringState;
|
||||
sequence<RTCIceCandidate> getLocalCandidates();
|
||||
sequence<RTCIceCandidate> getRemoteCandidates();
|
||||
RTCIceCandidatePair? getSelectedCandidatePair();
|
||||
RTCIceParameters? getLocalParameters();
|
||||
RTCIceParameters? getRemoteParameters();
|
||||
...
|
||||
};
|
||||
|
||||
getLocalCandidates
|
||||
Returns a sequence describing the local ICE candidates gathered for this
|
||||
RTCIceTransport and sent in onicecandidate
|
||||
|
||||
getRemoteCandidates
|
||||
Returns a sequence describing the remote ICE candidates received by this
|
||||
RTCIceTransport via addIceCandidate()
|
||||
|
||||
getSelectedCandidatePair
|
||||
Returns the selected candidate pair on which packets are sent, or null if
|
||||
there is no such pair.
|
||||
|
||||
getLocalParameters
|
||||
Returns the local ICE parameters received by this RTCIceTransport via
|
||||
setLocalDescription , or null if the parameters have not yet been received.
|
||||
|
||||
getRemoteParameters
|
||||
Returns the remote ICE parameters received by this RTCIceTransport via
|
||||
setRemoteDescription or null if the parameters have not yet been received.
|
||||
*/
|
||||
function getIceTransportFromSctp(pc) {
|
||||
const sctpTransport = pc.sctp;
|
||||
assert_true(sctpTransport instanceof RTCSctpTransport,
|
||||
'Expect pc.sctp to be instantiated from RTCSctpTransport');
|
||||
|
||||
const dtlsTransport = sctpTransport.transport;
|
||||
assert_true(dtlsTransport instanceof RTCDtlsTransport,
|
||||
'Expect sctp.transport to be an RTCDtlsTransport');
|
||||
|
||||
const iceTransport = dtlsTransport.transport;
|
||||
assert_true(iceTransport instanceof RTCIceTransport,
|
||||
'Expect dtlsTransport.transport to be an RTCIceTransport');
|
||||
|
||||
return iceTransport;
|
||||
}
|
||||
|
||||
function validateCandidates(candidates) {
|
||||
assert_greater_than(candidates.length, 0,
|
||||
'Expect at least one ICE candidate returned from get*Candidates()');
|
||||
|
||||
for(const candidate of candidates) {
|
||||
assert_true(candidate instanceof RTCIceCandidate,
|
||||
'Expect candidate elements to be instance of RTCIceCandidate');
|
||||
}
|
||||
}
|
||||
|
||||
function validateCandidateParameter(param) {
|
||||
assert_not_equals(param, null,
|
||||
'Expect candidate parameter to be non-null after data channels are connected');
|
||||
|
||||
assert_equals(typeof param.usernameFragment, 'string',
|
||||
'Expect param.usernameFragment to be set with string value');
|
||||
|
||||
assert_equals(typeof param.password, 'string',
|
||||
'Expect param.password to be set with string value');
|
||||
}
|
||||
|
||||
function validateConnectedIceTransport(iceTransport) {
|
||||
const { state, gatheringState, role, component } = iceTransport;
|
||||
|
||||
assert_true(role === 'controlling' || role === 'controlled',
|
||||
'Expect RTCIceRole to be either controlling or controlled');
|
||||
|
||||
assert_true(component === 'rtp' || component === 'rtcp',
|
||||
'Expect RTCIceComponent to be either rtp or rtcp');
|
||||
|
||||
assert_true(state === 'connected' || state === 'completed',
|
||||
'Expect ICE transport to be in connected or completed state after data channels are connected');
|
||||
|
||||
assert_true(gatheringState === 'gathering' || gatheringState === 'completed',
|
||||
'Expect ICE transport to be in gathering or completed gatheringState after data channels are connected');
|
||||
|
||||
validateCandidates(iceTransport.getLocalCandidates());
|
||||
validateCandidates(iceTransport.getRemoteCandidates());
|
||||
|
||||
const candidatePair = iceTransport.getSelectedCandidatePair();
|
||||
assert_not_equals(candidatePair, null,
|
||||
'Expect selected candidate pair to be non-null after ICE transport is connected');
|
||||
|
||||
assert_true(candidatePair.local instanceof RTCIceCandidate,
|
||||
'Expect candidatePair.local to be instance of RTCIceCandidate');
|
||||
|
||||
assert_true(candidatePair.remote instanceof RTCIceCandidate,
|
||||
'Expect candidatePair.remote to be instance of RTCIceCandidate');
|
||||
|
||||
validateCandidateParameter(iceTransport.getLocalParameters());
|
||||
validateCandidateParameter(iceTransport.getRemoteParameters());
|
||||
}
|
||||
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
return createDataChannelPair(pc1, pc2)
|
||||
.then(([channel1, channel2]) => {
|
||||
// Send a ping message and wait for it just to make sure
|
||||
// that the connection is fully working before testing
|
||||
channel1.send('ping');
|
||||
return awaitMessage(channel2);
|
||||
})
|
||||
.then(() => {
|
||||
const iceTransport1 = getIceTransportFromSctp(pc1);
|
||||
const iceTransport2 = getIceTransportFromSctp(pc2);
|
||||
|
||||
validateConnectedIceTransport(iceTransport1);
|
||||
validateConnectedIceTransport(iceTransport2);
|
||||
|
||||
assert_equals(
|
||||
iceTransport1.getLocalCandidates().length,
|
||||
iceTransport2.getRemoteCandidates().length,
|
||||
`Expect iceTransport1 to have same number of local candidate as iceTransport2's remote candidates`);
|
||||
|
||||
assert_equals(
|
||||
iceTransport1.getRemoteCandidates().length,
|
||||
iceTransport2.getLocalCandidates().length,
|
||||
`Expect iceTransport1 to have same number of remote candidate as iceTransport2's local candidates`);
|
||||
|
||||
const candidatePair1 = iceTransport1.getSelectedCandidatePair();
|
||||
const candidatePair2 = iceTransport2.getSelectedCandidatePair();
|
||||
|
||||
assert_equals(candidatePair1.local.candidate, candidatePair2.remote.candidate,
|
||||
'Expect selected local candidate of one pc is the selected remote candidate or another');
|
||||
|
||||
assert_equals(candidatePair1.remote.candidate, candidatePair2.local.candidate,
|
||||
'Expect selected local candidate of one pc is the selected remote candidate or another');
|
||||
|
||||
assert_equals(iceTransport1.role, 'controlling',
|
||||
`Expect offerer's iceTransport to take the controlling role`);
|
||||
|
||||
assert_equals(iceTransport2.role, 'controlled',
|
||||
`Expect answerer's iceTransport to take the controlled role`);
|
||||
});
|
||||
}, 'Two connected iceTransports should has matching local/remote candidates returned');
|
||||
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
pc1.createDataChannel('');
|
||||
|
||||
// setRemoteDescription(answer) without the other peer
|
||||
// setting answer it's localDescription
|
||||
return pc1.createOffer()
|
||||
.then(offer =>
|
||||
pc1.setLocalDescription(offer)
|
||||
.then(() => pc2.setRemoteDescription(offer))
|
||||
.then(() => pc2.createAnswer()))
|
||||
.then(answer => pc1.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
const iceTransport = getIceTransportFromSctp(pc1);
|
||||
|
||||
assert_array_equals(iceTransport.getRemoteCandidates(), [],
|
||||
'Expect iceTransport to not have any remote candidate');
|
||||
|
||||
assert_equals(iceTransport.getSelectedCandidatePair(), null,
|
||||
'Expect selectedCandidatePair to be null');
|
||||
});
|
||||
}, 'Unconnected iceTransport should have empty remote candidates and selected pair');
|
||||
|
||||
</script>
|
|
@ -5,6 +5,32 @@
|
|||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.htm
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
...
|
||||
Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate);
|
||||
};
|
||||
|
||||
interface RTCIceCandidate {
|
||||
readonly attribute DOMString candidate;
|
||||
readonly attribute DOMString? sdpMid;
|
||||
readonly attribute unsigned short? sdpMLineIndex;
|
||||
readonly attribute DOMString? ufrag;
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCIceCandidateInit {
|
||||
DOMString candidate = "";
|
||||
DOMString? sdpMid = null;
|
||||
unsigned short? sdpMLineIndex = null;
|
||||
DOMString ufrag;
|
||||
};
|
||||
*/
|
||||
|
||||
// SDP copied from JSEP Example 7.1
|
||||
// It contains two media streams with different ufrags
|
||||
// to test if candidate is added to the correct stream
|
||||
|
@ -110,6 +136,24 @@ a=rtcp-rsize
|
|||
`Expect candidate line to be found after media line ${beforeMediaLine}`);
|
||||
}
|
||||
|
||||
// Reject because WebIDL for addIceCandidate does not allow null argument
|
||||
// null can be accidentally passed from onicecandidate event handler
|
||||
// when null is used to indicate end of candidate
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, new TypeError(),
|
||||
pc.addIceCandidate(null)));
|
||||
}, 'Add null candidate should reject with TypeError');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4. Return the result of enqueuing the following steps:
|
||||
1. If remoteDescription is null return a promise rejected with a
|
||||
newly created InvalidStateError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -120,6 +164,9 @@ a=rtcp-rsize
|
|||
}));
|
||||
}, 'Add ICE candidate before setting remote description should reject with InvalidStateError');
|
||||
|
||||
/*
|
||||
Success cases
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -140,6 +187,28 @@ a=rtcp-rsize
|
|||
})));
|
||||
}, 'Add ICE candidate with RTCIceCandidate should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => pc.addIceCandidate({ sdpMid }));
|
||||
}, 'Add candidate with only valid sdpMid should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => pc.addIceCandidate({ sdpMLineIndex }));
|
||||
}, 'Add candidate with only valid sdpMLineIndex should succeed');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4.6.2. If candidate is applied successfully, the user agent MUST queue
|
||||
a task that runs the following steps:
|
||||
2. Let remoteDescription be connection's pendingRemoteDescription
|
||||
if not null, otherwise connection's currentRemoteDescription.
|
||||
3. Add candidate to remoteDescription.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -152,7 +221,7 @@ a=rtcp-rsize
|
|||
assert_candidate_line_between(pc.remoteDescription.sdp,
|
||||
mediaLine1, candidateLine1, mediaLine2);
|
||||
});
|
||||
}, 'Added candidate should be found in first media stream');
|
||||
}, 'addIceCandidate with first sdpMid and sdpMLineIndex add candidate to first media stream');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -168,7 +237,22 @@ a=rtcp-rsize
|
|||
assert_candidate_line_after(pc.remoteDescription.sdp,
|
||||
mediaLine2, candidateLine2);
|
||||
});
|
||||
}, 'Added candidate should be found in second media stream');
|
||||
}, 'addIceCandidate with second sdpMid and sdpMLineIndex should add candidate to second media stream');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid, sdpMLineIndex,
|
||||
ufrag: null
|
||||
}))
|
||||
.then(() => {
|
||||
assert_candidate_line_between(pc.remoteDescription.sdp,
|
||||
mediaLine1, candidateLine1, mediaLine2);
|
||||
});
|
||||
}, 'Add candidate for first media stream with null ufrag should add candidate to first media stream');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -191,8 +275,19 @@ a=rtcp-rsize
|
|||
assert_candidate_line_after(pc.remoteDescription.sdp,
|
||||
mediaLine2, candidateLine2);
|
||||
});
|
||||
}, 'Multiple candidates should be added to their corresponding media stream');
|
||||
}, 'Adding multiple candidates should add candidates to their corresponding media stream');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4.6. If candidate.candidate is an empty string, process candidate as an
|
||||
end-of-candidates indication for the corresponding media description
|
||||
and ICE candidate generation.
|
||||
2. If candidate is applied successfully, the user agent MUST queue
|
||||
a task that runs the following steps:
|
||||
2. Let remoteDescription be connection's pendingRemoteDescription
|
||||
if not null, otherwise connection's currentRemoteDescription.
|
||||
3. Add candidate to remoteDescription.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -215,18 +310,11 @@ a=rtcp-rsize
|
|||
});
|
||||
}, 'Add with empty candidate string (end of candidate) should succeed');
|
||||
|
||||
// Reject because WebIDL for addIceCandidate does not allow null argument
|
||||
// null can be accidentally passed from onicecandidate event handler
|
||||
// when null is used to indicate end of candidate
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, new TypeError(),
|
||||
pc.addIceCandidate(null)));
|
||||
}, 'Add null candidate should reject withTypeError');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
3. If both sdpMid and sdpMLineIndex are null, return a promise rejected
|
||||
with a newly created TypeError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -245,12 +333,11 @@ a=rtcp-rsize
|
|||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, 'OperationError',
|
||||
promise_rejects(t, new TypeError(),
|
||||
pc.addIceCandidate({
|
||||
candidate: invalidCandidateStr,
|
||||
sdpMid, sdpMLineIndex, ufrag
|
||||
candidate: candidateStr1
|
||||
})));
|
||||
}, 'Add candidate with invalid candidate string should reject with OperationError');
|
||||
}, 'Add candidate with only valid candidate string should reject with TypeError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -265,17 +352,6 @@ a=rtcp-rsize
|
|||
})));
|
||||
}, 'Add candidate with invalid candidate string and both sdpMid and sdpMLineIndex null should reject with TypeError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, new TypeError(),
|
||||
pc.addIceCandidate({
|
||||
candidate: candidateStr1
|
||||
})));
|
||||
}, 'Add candidate with only valid candidate string should reject with TypeError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -299,20 +375,13 @@ a=rtcp-rsize
|
|||
})));
|
||||
}, 'Add candidate with manually filled default values should reject with TypeError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => pc.addIceCandidate({ sdpMid }));
|
||||
}, 'Add candidate with only valid sdpMid should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => pc.addIceCandidate({ sdpMLineIndex }));
|
||||
}, 'Add candidate with only valid sdpMLineIndex should succeed');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4.3. If candidate.sdpMid is not null, run the following steps:
|
||||
1. If candidate.sdpMid is not equal to the mid of any media
|
||||
description in remoteDescription , reject p with a newly
|
||||
created OperationError and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -325,6 +394,14 @@ a=rtcp-rsize
|
|||
})));
|
||||
}, 'Add candidate with invalid sdpMid should reject with OperationError');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4.4. Else, if candidate.sdpMLineIndex is not null, run the following
|
||||
steps:
|
||||
1. If candidate.sdpMLineIndex is equal to or larger than the
|
||||
number of media descriptions in remoteDescription , reject p
|
||||
with a newly created OperationError and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -352,34 +429,6 @@ a=rtcp-rsize
|
|||
}));
|
||||
}, 'Invalid sdpMLineIndex should be ignored if valid sdpMid is provided');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, 'OperationError',
|
||||
pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid, sdpMLineIndex,
|
||||
ufrag: 'invalid'
|
||||
})));
|
||||
}, 'Add candidate with invalid ufrag should reject with OperationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid, sdpMLineIndex,
|
||||
ufrag: null
|
||||
}))
|
||||
.then(() => {
|
||||
assert_candidate_line_between(pc.remoteDescription.sdp,
|
||||
mediaLine1, candidateLine1, mediaLine2);
|
||||
});
|
||||
}, 'Add candidate for media stream 1 with null ufrag should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -396,6 +445,45 @@ a=rtcp-rsize
|
|||
});
|
||||
}, 'Add candidate for media stream 2 with null ufrag should succeed');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4.5. If candidate.ufrag is neither undefined nor null, and is not equal
|
||||
to any ufrag present in the corresponding media description of an
|
||||
applied remote description, reject p with a newly created
|
||||
OperationError and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, 'OperationError',
|
||||
pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid, sdpMLineIndex,
|
||||
ufrag: 'invalid'
|
||||
})));
|
||||
}, 'Add candidate with invalid ufrag should reject with OperationError');
|
||||
|
||||
/*
|
||||
4.3.2. addIceCandidate
|
||||
4.6.1. If candidate could not be successfully added the user agent MUST
|
||||
queue a task that runs the following steps:
|
||||
2. Reject p with a DOMException object whose name attribute has
|
||||
the value OperationError and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() =>
|
||||
promise_rejects(t, 'OperationError',
|
||||
pc.addIceCandidate({
|
||||
candidate: invalidCandidateStr,
|
||||
sdpMid, sdpMLineIndex, ufrag
|
||||
})));
|
||||
}, 'Add candidate with invalid candidate string should reject with OperationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
|
@ -410,115 +498,48 @@ a=rtcp-rsize
|
|||
})));
|
||||
}, 'Add candidate with sdpMid belonging to different ufrag should reject with OperationError');
|
||||
|
||||
// The check for sdpMid and sdpMLineIndex being null is done outside
|
||||
// of enqueuing task, so it rejects even when pc is closed.
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
/*
|
||||
TODO
|
||||
4.3.2. addIceCandidate
|
||||
4.6. In parallel, add the ICE candidate candidate as described in [JSEP]
|
||||
(section 4.1.17.). Use candidate.ufrag to identify the ICE generation;
|
||||
|
||||
return pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => {
|
||||
const promise = pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid: null,
|
||||
sdpMLineIndex: null
|
||||
});
|
||||
If the ufrag is null, process the candidate for the most recent ICE
|
||||
generation.
|
||||
|
||||
pc.close();
|
||||
return promise_rejects(t, new TypeError(), promise);
|
||||
});
|
||||
}, 'Add candidate with both sdpMid and sdpMLineIndex null should still reject with TypeError after pc is closed');
|
||||
- Call with candidate string containing partial malformed syntax, i.e. malformed IP.
|
||||
Some browsers may ignore the syntax error and add it to the SDP regardless.
|
||||
|
||||
function assert_never_resolves(t, promise) {
|
||||
promise.then(
|
||||
t.step_func(result => {
|
||||
assert_unreached(`Pending promise should never be resolved. Instead it is fulfilled with: ${result}`);
|
||||
}),
|
||||
t.step_func(err => {
|
||||
assert_unreached(`Pending promise should never be resolved. Instead it is rejected with: ${err}`);
|
||||
}));
|
||||
Non-Testable
|
||||
4.3.2. addIceCandidate
|
||||
4.6. (The steps are non-testable because the abort step in enqueue operation
|
||||
steps in before they can reach here):
|
||||
1. If candidate could not be successfully added the user agent MUST
|
||||
queue a task that runs the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, then abort
|
||||
these steps.
|
||||
|
||||
t.step_timeout(t.step_func_done(), 100);
|
||||
}
|
||||
2. If candidate is applied successfully, the user agent MUST queue
|
||||
a task that runs the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, then abort these steps.
|
||||
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
Issues
|
||||
w3c/webrtc-pc#1213
|
||||
addIceCandidate end of candidates woes
|
||||
|
||||
pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => {
|
||||
assert_never_resolves(t,
|
||||
pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid, sdpMLineIndex, ufrag
|
||||
}));
|
||||
w3c/webrtc-pc#1216
|
||||
Clarify addIceCandidate behavior when adding candidate after end of candidate
|
||||
|
||||
pc.close();
|
||||
w3c/webrtc-pc#1227
|
||||
addIceCandidate may add ice candidate to the wrong remote description
|
||||
|
||||
// When pc is closed, the remote description is not modified
|
||||
// even if succeed
|
||||
t.step_timeout(t.step_func(() => {
|
||||
assert_false(pc.remoteDescription.sdp.includes(candidateLine1),
|
||||
'Candidate should not be added to SDP because pc is closed');
|
||||
}), 80);
|
||||
});
|
||||
}, 'Add valid candidate should never resolve when pc is closed');
|
||||
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_never_resolves(t, pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid, sdpMLineIndex, ufrag
|
||||
}));
|
||||
|
||||
pc.close();
|
||||
}, 'Add candidate when remote description is null should never resolve when pc is closed');
|
||||
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => {
|
||||
assert_never_resolves(t, pc.addIceCandidate({
|
||||
candidate: invalidCandidateStr,
|
||||
sdpMid, sdpMLineIndex, ufrag
|
||||
}));
|
||||
pc.close();
|
||||
});
|
||||
}, 'Add candidate with invalid candidate string should never resolve when pc is closed');
|
||||
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => {
|
||||
assert_never_resolves(t, pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMid: 'invalid',
|
||||
sdpMLineIndex, ufrag
|
||||
}));
|
||||
pc.close();
|
||||
});
|
||||
}, 'Add candidate with invalid sdpMid should never resolve when pc is closed');
|
||||
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
pc.setRemoteDescription(sessionDesc)
|
||||
.then(() => {
|
||||
assert_never_resolves(t, pc.addIceCandidate({
|
||||
candidate: candidateStr1,
|
||||
sdpMLineIndex: 2,
|
||||
ufrag
|
||||
}));
|
||||
pc.close();
|
||||
});
|
||||
}, 'Add candidate with invalid sdpMLineIndex should never resolve when pc is closed');
|
||||
|
||||
// TODO: More tests need to be added:
|
||||
// - Set pc with both current and pending remote descriptions with different ufrags,
|
||||
// Check that addIceCandidate with specific ufrag adds candidate to the correct
|
||||
// remote description.
|
||||
// - Call with candidate string containing partial malformed syntax, i.e. malformed IP.
|
||||
// Some browsers may ignore the syntax error and add it to the SDP regardless.
|
||||
w3c/webrtc-pc#1345
|
||||
Make promise rejection/enqueing consistent
|
||||
|
||||
Coverage Report
|
||||
Total: 23
|
||||
Tested: 19
|
||||
Not Tested: 2
|
||||
Non-Testable: 2
|
||||
*/
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.addTrack</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateMediaStreamTrack
|
||||
|
||||
/*
|
||||
5.1. RTCPeerConnection Interface Extensions
|
||||
partial interface RTCPeerConnection {
|
||||
...
|
||||
sequence<RTCRtpSender> getSenders();
|
||||
sequence<RTCRtpReceiver> getReceivers();
|
||||
sequence<RTCRtpTransceiver> getTransceivers();
|
||||
RTCRtpSender addTrack(MediaStreamTrack track,
|
||||
MediaStream... streams);
|
||||
RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
|
||||
optional RTCRtpTransceiverInit init);
|
||||
};
|
||||
|
||||
Note
|
||||
While addTrack checks if the MediaStreamTrack given as an argument is
|
||||
already being sent to avoid sending the same MediaStreamTrack twice,
|
||||
the other ways do not, allowing the same MediaStreamTrack to be sent
|
||||
several times simultaneously.
|
||||
*/
|
||||
|
||||
/*
|
||||
5.1. addTrack
|
||||
4. If connection's [[isClosed]] slot is true, throw an InvalidStateError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
|
||||
pc.close();
|
||||
assert_throws('InvalidStateError', () => pc.addTrack(track, mediaStream))
|
||||
});
|
||||
}, 'addTrack when pc is closed should throw InvalidStateError');
|
||||
|
||||
/*
|
||||
5.1. addTrack
|
||||
8. If sender is null, run the following steps:
|
||||
1. Create an RTCRtpSender with track and streams and let sender be
|
||||
the result.
|
||||
2. Create an RTCRtpReceiver with track.kind as kind and let receiver
|
||||
be the result.
|
||||
3. Create an RTCRtpTransceiver with sender and receiver and let
|
||||
transceiver be the result.
|
||||
4. Add transceiver to connection's set of transceivers.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const sender = pc.addTrack(track);
|
||||
|
||||
assert_true(sender instanceof RTCRtpSender,
|
||||
'Expect sender to be instance of RTCRtpSender');
|
||||
|
||||
assert_equals(sender.track, track,
|
||||
`Expect sender's track to be the added track`);
|
||||
|
||||
const transceivers = pc.getTransceivers();
|
||||
assert_equals(transceivers.length, 1,
|
||||
'Expect only one transceiver with sender added');
|
||||
|
||||
const [transceiver] = transceivers;
|
||||
assert_equals(transceiver.sender, sender);
|
||||
|
||||
assert_array_equals([sender], pc.getSenders(),
|
||||
'Expect only one sender with given track added');
|
||||
|
||||
const { receiver } = transceiver;
|
||||
assert_equals(receiver.track.kind, 'audio');
|
||||
assert_array_equals([transceiver.receiver], pc.getReceivers(),
|
||||
'Expect only one receiver associated with transceiver added');
|
||||
});
|
||||
}, 'addTrack with single track argument and no mediaStream should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const sender = pc.addTrack(track, mediaStream);
|
||||
|
||||
assert_true(sender instanceof RTCRtpSender,
|
||||
'Expect sender to be instance of RTCRtpSender');
|
||||
|
||||
assert_equals(sender.track, track,
|
||||
`Expect sender's track to be the added track`);
|
||||
});
|
||||
}, 'addTrack with single track argument and single mediaStream should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const mediaStream2 = new MediaStream([track]);
|
||||
const sender = pc.addTrack(track, mediaStream, mediaStream2);
|
||||
|
||||
assert_true(sender instanceof RTCRtpSender,
|
||||
'Expect sender to be instance of RTCRtpSender');
|
||||
|
||||
assert_equals(sender.track, track,
|
||||
`Expect sender's track to be the added track`);
|
||||
});
|
||||
}, 'addTrack with single track argument and multiple mediaStreams should succeed');
|
||||
|
||||
/*
|
||||
5.1. addTrack
|
||||
5. Let senders be the result of executing the CollectSenders algorithm.
|
||||
If an RTCRtpSender for track already exists in senders, throw an
|
||||
InvalidAccessError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
|
||||
pc.addTrack(track, mediaStream);
|
||||
assert_throws('InvalidAccessError', () => pc.addTrack(track, mediaStream));
|
||||
});
|
||||
}, 'Adding the same track multiple times should throw InvalidAccessError');
|
||||
|
||||
/*
|
||||
5.1. addTrack
|
||||
6. The steps below describe how to determine if an existing sender can
|
||||
be reused.
|
||||
|
||||
If any RTCRtpSender object in senders matches all the following
|
||||
criteria, let sender be that object, or null otherwise:
|
||||
- The sender's track is null.
|
||||
- The transceiver kind of the RTCRtpTransceiver, associated with
|
||||
the sender, matches track's kind.
|
||||
- The sender has never been used to send. More precisely, the
|
||||
RTCRtpTransceiver associated with the sender has never had a
|
||||
currentDirection of sendrecv or sendonly.
|
||||
7. If sender is not null, run the following steps to use that sender:
|
||||
1. Set sender.track to track.
|
||||
3. Enable sending direction on the RTCRtpTransceiver associated
|
||||
with sender.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
const transceiver = pc.addTransceiver('audio', { direction: 'recvonly' });
|
||||
assert_equals(transceiver.sender.track, null);
|
||||
assert_equals(transceiver.direction, 'recvonly');
|
||||
|
||||
const track = generateMediaStreamTrack('audio');
|
||||
const sender = pc.addTrack(track);
|
||||
|
||||
assert_equals(sender, transceiver.sender);
|
||||
assert_equals(sender.track, track);
|
||||
assert_equals(transceiver.direction, 'sendrecv');
|
||||
assert_array_equals([sender], pc.getSenders());
|
||||
|
||||
}, 'addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender');
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
assert_equals(transceiver.sender.track, null);
|
||||
assert_equals(transceiver.direction, 'sendrecv');
|
||||
|
||||
const track = generateMediaStreamTrack('audio');
|
||||
const sender = pc.addTrack(track);
|
||||
|
||||
assert_equals(sender.track, track);
|
||||
assert_not_equals(sender, transceiver.sender);
|
||||
|
||||
const senders = pc.getSenders();
|
||||
assert_equals(senders.length, 2,
|
||||
'Expect 2 senders added to connection');
|
||||
|
||||
assert_true(senders.includes(sender),
|
||||
'Expect senders list to include sender');
|
||||
|
||||
assert_true(senders.includes(transceiver.sender),
|
||||
`Expect senders list to include first transceiver's sender`);
|
||||
|
||||
}, 'addTrack with existing sender with null track, same kind, and sendrecv direction should create new sender');
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
const transceiver = pc.addTransceiver('video', { direction: 'recvonly' });
|
||||
assert_equals(transceiver.sender.track, null);
|
||||
assert_equals(transceiver.direction, 'recvonly');
|
||||
|
||||
const track = generateMediaStreamTrack('audio');
|
||||
const sender = pc.addTrack(track);
|
||||
|
||||
assert_equals(sender.track, track);
|
||||
assert_not_equals(sender, transceiver.sender);
|
||||
|
||||
const senders = pc.getSenders();
|
||||
assert_equals(senders.length, 2,
|
||||
'Expect 2 senders added to connection');
|
||||
|
||||
assert_true(senders.includes(sender),
|
||||
'Expect senders list to include sender');
|
||||
|
||||
assert_true(senders.includes(transceiver.sender),
|
||||
`Expect senders list to include first transceiver's sender`);
|
||||
|
||||
}, 'addTrack with existing sender with null track, different kind, and recvonly direction should create new sender');
|
||||
|
||||
/*
|
||||
TODO
|
||||
5.1. addTrack
|
||||
3. Let streams be a list of MediaStream objects constructed from the
|
||||
method's remaining arguments, or an empty list if the method was
|
||||
called with a single argument.
|
||||
6. The steps below describe how to determine if an existing sender can
|
||||
be reused. Doing so will cause future calls to createOffer and
|
||||
createAnswer to mark the corresponding media description as sendrecv
|
||||
or sendonly and add the MSID of the track added, as defined in [JSEP]
|
||||
(section 5.2.2. and section 5.3.2.).
|
||||
9. A track could have contents that are inaccessible to the application.
|
||||
This can be due to being marked with a peerIdentity option or anything
|
||||
that would make a track CORS cross-origin. These tracks can be supplied
|
||||
to the addTrack method, and have an RTCRtpSender created for them, but
|
||||
content must not be transmitted, unless they are also marked with
|
||||
peerIdentity and they meet the requirements for sending (see isolated
|
||||
streams and RTCPeerConnection).
|
||||
|
||||
All other tracks that are not accessible to the application must not be
|
||||
sent to the peer, with silence (audio), black frames (video) or
|
||||
equivalently absent content being sent in place of track content.
|
||||
|
||||
Note that this property can change over time.
|
||||
|
||||
Non-Testable
|
||||
5.1. addTrack
|
||||
7. If sender is not null, run the following steps to use that sender:
|
||||
2. Set sender's [[associated MediaStreams]] to streams.
|
||||
|
||||
Tested in RTCPeerConnection-onnegotiationneeded.html:
|
||||
5.1. addTrack
|
||||
10. Update the negotiation-needed flag for connection.
|
||||
|
||||
*/
|
||||
</script>
|
|
@ -3,63 +3,130 @@
|
|||
<title>RTCPeerConnection.prototype.addTransceiver</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
|
||||
// https://rawgit.com/w3c/webrtc-pc/cc8d80f455b86c8041d63bceb8b457f45c72aa89/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateMediaStreamTrack()
|
||||
|
||||
/*
|
||||
* 5.1. RTCPeerConnection Interface Extensions
|
||||
* partial interface RTCPeerConnection {
|
||||
* sequence<RTCRtpSender> getSenders();
|
||||
* sequence<RTCRtpReceiver> getReceivers();
|
||||
* sequence<RTCRtpTransceiver> getTransceivers();
|
||||
* RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
|
||||
* optional RTCRtpTransceiverInit init);
|
||||
* ...
|
||||
* };
|
||||
*
|
||||
* dictionary RTCRtpTransceiverInit {
|
||||
* RTCRtpTransceiverDirection direction = "sendrecv";
|
||||
* sequence<MediaStream> streams;
|
||||
* sequence<RTCRtpEncodingParameters> sendEncodings;
|
||||
* };
|
||||
5.1. RTCPeerConnection Interface Extensions
|
||||
|
||||
partial interface RTCPeerConnection {
|
||||
sequence<RTCRtpSender> getSenders();
|
||||
sequence<RTCRtpReceiver> getReceivers();
|
||||
sequence<RTCRtpTransceiver> getTransceivers();
|
||||
RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
|
||||
optional RTCRtpTransceiverInit init);
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCRtpTransceiverInit {
|
||||
RTCRtpTransceiverDirection direction = "sendrecv";
|
||||
sequence<MediaStream> streams;
|
||||
sequence<RTCRtpEncodingParameters> sendEncodings;
|
||||
};
|
||||
|
||||
enum RTCRtpTransceiverDirection {
|
||||
"sendrecv",
|
||||
"sendonly",
|
||||
"recvonly",
|
||||
"inactive"
|
||||
};
|
||||
|
||||
5.2. RTCRtpSender Interface
|
||||
|
||||
interface RTCRtpSender {
|
||||
readonly attribute MediaStreamTrack? track;
|
||||
...
|
||||
};
|
||||
|
||||
5.3. RTCRtpReceiver Interface
|
||||
|
||||
interface RTCRtpReceiver {
|
||||
readonly attribute MediaStreamTrack track;
|
||||
...
|
||||
};
|
||||
|
||||
5.4. RTCRtpTransceiver Interface
|
||||
|
||||
interface RTCRtpTransceiver {
|
||||
readonly attribute DOMString? mid;
|
||||
[SameObject]
|
||||
readonly attribute RTCRtpSender sender;
|
||||
[SameObject]
|
||||
readonly attribute RTCRtpReceiver receiver;
|
||||
readonly attribute boolean stopped;
|
||||
readonly attribute RTCRtpTransceiverDirection direction;
|
||||
readonly attribute RTCRtpTransceiverDirection? currentDirection;
|
||||
...
|
||||
};
|
||||
|
||||
Note
|
||||
While addTrack checks if the MediaStreamTrack given as an argument is
|
||||
already being sent to avoid sending the same MediaStreamTrack twice,
|
||||
the other ways do not, allowing the same MediaStreamTrack to be sent
|
||||
several times simultaneously.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 5.1. addTransceiver
|
||||
* The initial value of mid is null.
|
||||
*
|
||||
* 3. If the first argument is a string, let it be kind and run the following steps:
|
||||
* 2. Let track be null.
|
||||
* 5. Create an RTCRtpSender with track, streams and sendEncodings and let sender
|
||||
* be the result.
|
||||
* 6. Create an RTCRtpReceiver with kind and let receiver be the result.
|
||||
* 7. Create an RTCRtpTransceiver with sender and receiver and let transceiver
|
||||
* be the result.
|
||||
* 8. Add transceiver to connection's set of transceivers.
|
||||
*
|
||||
* 5.3. RTCRtpReceiver Interface
|
||||
* Create an RTCRtpReceiver
|
||||
* 2. Let track be a new MediaStreamTrack object [GETUSERMEDIA]. The source of
|
||||
* track is a remote source provided by receiver.
|
||||
* 3. Initialize track.kind to kind.
|
||||
* 5. Initialize track.label to the result of concatenating the string "remote "
|
||||
* with kind.
|
||||
* 6. Initialize track.readyState to live.
|
||||
* 7. Initialize track.muted to true.
|
||||
*
|
||||
* 5.4. RTCRtpTransceiver Interface
|
||||
* Create an RTCRtpTransceiver
|
||||
* 2. Set transceiver.sender to sender.
|
||||
* 3. Set transceiver.receiver to receiver.
|
||||
* 4. Set transceiver.stopped to false.
|
||||
5.1. addTransceiver
|
||||
3. If the first argument is a string, let it be kind and run the following steps:
|
||||
1. If kind is not a legal MediaStreamTrack kind, throw a TypeError.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, 'addTransceiver');
|
||||
assert_throws(new TypeError(), () => pc.addTransceiver('invalid'));
|
||||
}, 'addTransceiver() with string argument as invalid kind should throw TypeError');
|
||||
|
||||
/*
|
||||
5.1. addTransceiver
|
||||
The initial value of mid is null.
|
||||
|
||||
3. If the dictionary argument is present, let direction be the value of the
|
||||
direction member. Otherwise let direction be sendrecv.
|
||||
4. If the first argument is a string, let it be kind and run the following steps:
|
||||
2. Let track be null.
|
||||
8. Create an RTCRtpSender with track, streams and sendEncodings and let
|
||||
sender be the result.
|
||||
9. Create an RTCRtpReceiver with kind and let receiver be the result.
|
||||
10. Create an RTCRtpTransceiver with sender, receiver and direction, and let
|
||||
transceiver be the result.
|
||||
11. Add transceiver to connection's set of transceivers.
|
||||
|
||||
5.2. RTCRtpSender Interface
|
||||
Create an RTCRtpSender
|
||||
2. Set sender.track to track.
|
||||
|
||||
5.3. RTCRtpReceiver Interface
|
||||
Create an RTCRtpReceiver
|
||||
2. Let track be a new MediaStreamTrack object [GETUSERMEDIA]. The source of
|
||||
track is a remote source provided by receiver.
|
||||
3. Initialize track.kind to kind.
|
||||
5. Initialize track.label to the result of concatenating the string "remote "
|
||||
with kind.
|
||||
6. Initialize track.readyState to live.
|
||||
7. Initialize track.muted to true.
|
||||
8. Set receiver.track to track.
|
||||
|
||||
5.4. RTCRtpTransceiver Interface
|
||||
Create an RTCRtpTransceiver
|
||||
2. Set transceiver.sender to sender.
|
||||
3. Set transceiver.receiver to receiver.
|
||||
4. Let transceiver have a [[Direction]] internal slot, initialized to direction.
|
||||
5. Let transceiver have a [[CurrentDirection]] internal slot, initialized
|
||||
to null.
|
||||
6. Set transceiver.stopped to false.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_own_property(pc, 'addTransceiver');
|
||||
assert_idl_attribute(pc, 'addTransceiver');
|
||||
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
assert_true(transceiver instanceof RTCRtpTransceiver,
|
||||
|
@ -68,6 +135,7 @@
|
|||
assert_equals(transceiver.mid, null);
|
||||
assert_equals(transceiver.stopped, false);
|
||||
assert_equals(transceiver.direction, 'sendrecv');
|
||||
assert_equals(transceiver.currentDirection, null);
|
||||
|
||||
assert_array_equals([transceiver], pc.getTransceivers(),
|
||||
`Expect added transceiver to be the only element in connection's list of transceivers`);
|
||||
|
@ -86,7 +154,7 @@
|
|||
assert_true(receiver instanceof RTCRtpReceiver,
|
||||
'Expect receiver to be instance of RTCRtpReceiver');
|
||||
|
||||
const track = receiver.track
|
||||
const track = receiver.track;
|
||||
assert_true(track instanceof MediaStreamTrack,
|
||||
'Expect receiver.track to be instance of MediaStreamTrack');
|
||||
|
||||
|
@ -103,7 +171,7 @@
|
|||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_own_property(pc, 'addTransceiver');
|
||||
assert_idl_attribute(pc, 'addTransceiver');
|
||||
|
||||
const transceiver = pc.addTransceiver('video');
|
||||
assert_true(transceiver instanceof RTCRtpTransceiver,
|
||||
|
@ -130,7 +198,7 @@
|
|||
assert_true(receiver instanceof RTCRtpReceiver,
|
||||
'Expect receiver to be instance of RTCRtpReceiver');
|
||||
|
||||
const track = receiver.track
|
||||
const track = receiver.track;
|
||||
assert_true(track instanceof MediaStreamTrack,
|
||||
'Expect receiver.track to be instance of MediaStreamTrack');
|
||||
|
||||
|
@ -144,14 +212,264 @@
|
|||
|
||||
}, `addTransceiver('video') should return a video transceiver`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', { direction: 'sendonly' });
|
||||
assert_equals(transceiver.direction, 'sendonly');
|
||||
}, `addTransceiver() with direction sendonly should have result transceiver.direction be the same`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', { direction: 'inactive' });
|
||||
assert_equals(transceiver.direction, 'inactive');
|
||||
}, `addTransceiver() with direction inactive should have result transceiver.direction be the same`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, 'addTransceiver');
|
||||
assert_throws(new TypeError(), () =>
|
||||
pc.addTransceiver('audio', { direction: 'invalid' }));
|
||||
}, `addTransceiver() with invalid direction should throw TypeError`);
|
||||
|
||||
/*
|
||||
* 5.1. addTransceiver
|
||||
* 3.1. If kind is not a legal MediaStreamTrack kind, throw a TypeError.
|
||||
5.1. addTransceiver
|
||||
5. If the first argument is a MediaStreamTrack , let it be track and let
|
||||
kind be track.kind.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_own_property(pc, 'addTransceiver');
|
||||
assert_throws(new TypeError(), () => pc.addTransceiver('invalid'));
|
||||
}, 'addTransceiver() with string argument as invalid kind should throw TypeError');
|
||||
const track = generateMediaStreamTrack('audio');
|
||||
|
||||
const transceiver = pc.addTransceiver(track);
|
||||
const { sender, receiver } = transceiver;
|
||||
|
||||
assert_true(sender instanceof RTCRtpSender,
|
||||
'Expect sender to be instance of RTCRtpSender');
|
||||
|
||||
assert_true(receiver instanceof RTCRtpReceiver,
|
||||
'Expect receiver to be instance of RTCRtpReceiver');
|
||||
|
||||
assert_equals(sender.track, track,
|
||||
'Expect sender.track should be the track that is added');
|
||||
|
||||
const receiverTrack = receiver.track;
|
||||
assert_true(receiverTrack instanceof MediaStreamTrack,
|
||||
'Expect receiver.track to be instance of MediaStreamTrack');
|
||||
|
||||
assert_equals(receiverTrack.kind, 'audio',
|
||||
`receiver.track should have the same kind as added track's kind`);
|
||||
|
||||
assert_equals(receiverTrack.label, 'remote audio');
|
||||
assert_equals(receiverTrack.readyState, 'live');
|
||||
assert_equals(receiverTrack.muted, true);
|
||||
|
||||
assert_array_equals([transceiver], pc.getTransceivers(),
|
||||
`Expect added transceiver to be the only element in connection's list of transceivers`);
|
||||
|
||||
assert_array_equals([sender], pc.getSenders(),
|
||||
`Expect added sender to be the only element in connection's list of senders`);
|
||||
|
||||
assert_array_equals([receiver], pc.getReceivers(),
|
||||
`Expect added receiver to be the only element in connection's list of receivers`);
|
||||
|
||||
}, 'addTransceiver(track) should have result with sender.track be given track');
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const track = generateMediaStreamTrack('audio');
|
||||
|
||||
const transceiver1 = pc.addTransceiver(track);
|
||||
const transceiver2 = pc.addTransceiver(track);
|
||||
|
||||
assert_not_equals(transceiver1, transceiver2);
|
||||
|
||||
const sender1 = transceiver1.sender;
|
||||
const sender2 = transceiver2.sender;
|
||||
|
||||
assert_not_equals(sender1, sender2);
|
||||
assert_equals(transceiver1.sender.track, track);
|
||||
assert_equals(transceiver2.sender.track, track);
|
||||
|
||||
const transceivers = pc.getTransceivers();
|
||||
assert_equals(transceivers.length, 2);
|
||||
assert_true(transceivers.includes(transceiver1));
|
||||
assert_true(transceivers.includes(transceiver2));
|
||||
|
||||
const senders = pc.getSenders();
|
||||
assert_equals(senders.length, 2);
|
||||
assert_true(senders.includes(sender1));
|
||||
assert_true(senders.includes(sender2));
|
||||
|
||||
}, 'addTransceiver(track) multiple times should create multiple transceivers');
|
||||
|
||||
|
||||
/*
|
||||
5.1. addTransceiver
|
||||
6. Verify that each rid value in sendEncodings is composed only of
|
||||
case-sensitive alphanumeric characters (a-z, A-Z, 0-9) up to a maximum
|
||||
of 16 characters. If one of the RIDs does not meet these requirements,
|
||||
throw a TypeError.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, 'addTransceiver');
|
||||
|
||||
assert_throws(new TypeError(), () =>
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
rid: '@Invalid!'
|
||||
}]
|
||||
}));
|
||||
}, 'addTransceiver() with rid containing invalid non-alphanumeric characters should throw TypeError');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_idl_attribute(pc, 'addTransceiver');
|
||||
|
||||
assert_throws(new TypeError(), () =>
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
rid: 'a'.repeat(17)
|
||||
}]
|
||||
}));
|
||||
}, 'addTransceiver() with rid longer than 16 characters should throw TypeError');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
rid: 'foo'
|
||||
}]
|
||||
});
|
||||
}, `addTransceiver() with valid rid value should succeed`);
|
||||
|
||||
/*
|
||||
5.1. addTransceiver
|
||||
7. If any RTCRtpEncodingParameters dictionary in sendEncodings contains a
|
||||
read-only parameter other than rid, throw an InvalidAccessError.
|
||||
|
||||
- The sendEncodings argument can be used to specify the number of offered
|
||||
simulcast encodings, and optionally their RIDs and encoding parameters.
|
||||
Aside from rid , all read-only parameters in the RTCRtpEncodingParameters
|
||||
dictionaries, such as ssrc, must be left unset, or an error will be thrown.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
ssrc: 2
|
||||
}]
|
||||
}));
|
||||
}, `addTransceiver() with readonly ssrc set should throw InvalidAccessError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
rtx: {
|
||||
ssrc: 2
|
||||
}
|
||||
}]
|
||||
}));
|
||||
}, `addTransceiver() with readonly rtx set should throw InvalidAccessError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_throws('InvalidAccessError', () =>
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
fec: {
|
||||
ssrc: 2
|
||||
}
|
||||
}]
|
||||
}));
|
||||
}, `addTransceiver() with readonly fec set should throw InvalidAccessError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
dtx: 'enabled',
|
||||
active: false,
|
||||
priority: 'low',
|
||||
ptime: 5,
|
||||
maxBitrate: 8,
|
||||
maxFramerate: 25,
|
||||
rid: 'foo'
|
||||
}]
|
||||
});
|
||||
}, `addTransceiver() with valid sendEncodings should succeed`);
|
||||
|
||||
/*
|
||||
TODO
|
||||
5.1. addTransceiver
|
||||
- Adding a transceiver will cause future calls to createOffer to add a media
|
||||
description for the corresponding transceiver, as defined in [JSEP]
|
||||
(section 5.2.2.).
|
||||
|
||||
- Setting a new RTCSessionDescription may change mid to a non-null value,
|
||||
as defined in [JSEP] (section 5.5. and section 5.6.).
|
||||
|
||||
1. If the dictionary argument is present, and it has a streams member, let
|
||||
streams be that list of MediaStream objects.
|
||||
|
||||
5.2. RTCRtpSender Interface
|
||||
Create an RTCRtpSender
|
||||
3. Let sender have an [[associated MediaStreams]] internal slot, representing
|
||||
a list of MediaStream objects that the MediaStreamTrack object of this
|
||||
sender is associated with.
|
||||
|
||||
4. Set sender's [[associated MediaStreams]] slot to streams.
|
||||
|
||||
5. Let sender have a [[send encodings]] internal slot, representing a list
|
||||
of RTCRtpEncodingParameters dictionaries.
|
||||
|
||||
6. If sendEncodings is given as input to this algorithm, and is non-empty,
|
||||
set the [[send encodings]] slot to sendEncodings. Otherwise, set it to a
|
||||
list containing a single RTCRtpEncodingParameters with active set to true.
|
||||
|
||||
5.3. RTCRtpReceiver Interface
|
||||
Create an RTCRtpReceiver
|
||||
4. If an id string, id, was given as input to this algorithm, initialize
|
||||
track.id to id. (Otherwise the value generated when track was created
|
||||
will be used.)
|
||||
|
||||
Tested in RTCPeerConnection-onnegotiationneeded.html
|
||||
5.1. addTransceiver
|
||||
12. Update the negotiation-needed flag for connection.
|
||||
|
||||
Out of Scope
|
||||
5.1. addTransceiver
|
||||
8. If sendEncodings is set, then subsequent calls to createOffer will be
|
||||
configured to send multiple RTP encodings as defined in [JSEP]
|
||||
(section 5.2.2. and section 5.2.1.).
|
||||
|
||||
When setRemoteDescription is called with a corresponding remote
|
||||
description that is able to receive multiple RTP encodings as defined
|
||||
in [JSEP] (section 3.7.), the RTCRtpSender may send multiple RTP
|
||||
encodings and the parameters retrieved via the transceiver's
|
||||
sender.getParameters() will reflect the encodings negotiated.
|
||||
|
||||
9. This specification does not define how to configure createOffer to
|
||||
receive multiple RTP encodings. However when setRemoteDescription is
|
||||
called with a corresponding remote description that is able to send
|
||||
multiple RTP encodings as defined in [JSEP], the RTCRtpReceiver may
|
||||
receive multiple RTP encodings and the parameters retrieved via the
|
||||
transceiver's receiver.getParameters() will reflect the encodings
|
||||
negotiated.
|
||||
|
||||
Coverage Report
|
||||
Tested Not-Tested Non-Testable Total
|
||||
addTransceiver 14 1 3 18
|
||||
Create Sender 3 4 0 7
|
||||
Create Receiver 8 1 0 9
|
||||
Create Transceiver 7 0 0 7
|
||||
|
||||
Total 32 6 3 41
|
||||
*/
|
||||
</script>
|
||||
|
|
|
@ -23,68 +23,6 @@ const testArgs = {
|
|||
'undefined': false,
|
||||
'{}': false,
|
||||
|
||||
// iceServers
|
||||
'{ iceServers: null }': new TypeError,
|
||||
'{ iceServers: undefined }': false,
|
||||
'{ iceServers: [] }': false,
|
||||
'{ iceServers: [{}] }': new TypeError,
|
||||
'{ iceServers: [null] }': new TypeError,
|
||||
'{ iceServers: [undefined] }': new TypeError,
|
||||
'{ iceServers: [{ urls: "stun:stun1.example.net" }] }': false,
|
||||
'{ iceServers: [{ urls: [] }] }': false,
|
||||
'{ iceServers: [{ urls: ["stun:stun1.example.net"] }] }': false,
|
||||
'{ iceServers: [{ urls: ["stun:stun1.example.net", "stun:stun2.example.net"] }] }': false,
|
||||
// username and password required for turn: and turns:
|
||||
'{ iceServers: [{ urls: "turns:turn.example.org", username: "user", credential: "cred" }] }': false,
|
||||
'{ iceServers: [{ urls: "turn:turn.example.net", username: "user", credential: "cred" }] }': false,
|
||||
'{ iceServers: [{ urls: "turns:turn.example.org", username: "", credential: "" }] }': false,
|
||||
'{ iceServers: [{ urls: "turn:turn.example.net", username: "", credential: "" }] }': false,
|
||||
'{ iceServers: [{ urls: ["turns:turn.example.org", "turn:turn.example.net"], username: "user", credential: "cred" }] }': false,
|
||||
'{ iceServers: [{ urls: "stun:stun1.example.net", credentialType: "password" }] }': false,
|
||||
'{ iceServers: [{ urls: "stun:stun1.example.net", credentialType: "token" }] }': false,
|
||||
'{ iceServers: [{ urls: "turn:turn.example.net" }] }': 'InvalidAccessError',
|
||||
'{ iceServers: [{ urls: "turn:turn.example.net", username: "user" }] }': 'InvalidAccessError',
|
||||
'{ iceServers: [{ urls: "turn:turn.example.net", credential: "cred" }] }': 'InvalidAccessError',
|
||||
'{ iceServers: [{ urls: "turns:turn.example.org" }] }': 'InvalidAccessError',
|
||||
'{ iceServers: [{ urls: "turns:turn.example.org", username: "user" }] }': 'InvalidAccessError',
|
||||
'{ iceServers: [{ urls: "turns:turn.example.org", credential: "cred" }] }': 'InvalidAccessError',
|
||||
'{ iceServers: [{ urls: "relative-url" }] }': 'SyntaxError',
|
||||
'{ iceServers: [{ urls: "http://example.com" }] }': 'SyntaxError',
|
||||
// credentialType
|
||||
'{ iceServers: [{ urls: [] }] }': false,
|
||||
'{ iceServers: [{ urls: [], credentialType: "password" }] }': false,
|
||||
'{ iceServers: [{ urls: [], credentialType: "token" }] }': false,
|
||||
'{ iceServers: [{ urls: [], credentialType: "invalid" }] }': new TypeError,
|
||||
// Blink and Gecko fall back to url, but it's not in the spec.
|
||||
'{ iceServers: [{ url: "stun:stun1.example.net" }] }': new TypeError,
|
||||
|
||||
// iceTransportPolicy
|
||||
'{ iceTransportPolicy: null }': new TypeError,
|
||||
'{ iceTransportPolicy: undefined }': false,
|
||||
'{ iceTransportPolicy: "relay" }': false,
|
||||
'{ iceTransportPolicy: "all" }': false,
|
||||
'{ iceTransportPolicy: "invalid" }': new TypeError,
|
||||
// "none" is in Blink and Gecko's IDL, but not in the spec.
|
||||
'{ iceTransportPolicy: "none" }': new TypeError,
|
||||
// iceTransportPolicy is called iceTransports in Blink.
|
||||
'{ iceTransports: "invalid" }': false,
|
||||
'{ iceTransports: "none" }': false,
|
||||
|
||||
// bundlePolicy
|
||||
'{ bundlePolicy: null }': new TypeError,
|
||||
'{ bundlePolicy: undefined }': false,
|
||||
'{ bundlePolicy: "balanced" }': false,
|
||||
'{ bundlePolicy: "max-compat" }': false,
|
||||
'{ bundlePolicy: "max-bundle" }': false,
|
||||
'{ bundlePolicy: "invalid" }': new TypeError,
|
||||
|
||||
// rtcpMuxPolicy
|
||||
'{ rtcpMuxPolicy: null }': new TypeError,
|
||||
'{ rtcpMuxPolicy: undefined }': false,
|
||||
'{ rtcpMuxPolicy: "negotiate" }': false,
|
||||
'{ rtcpMuxPolicy: "require" }': false,
|
||||
'{ rtcpMuxPolicy: "invalid" }': new TypeError,
|
||||
|
||||
// peerIdentity
|
||||
'{ peerIdentity: toStringThrows }': new Error,
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer()
|
||||
// generateAnswer()
|
||||
// test_never_resolve()
|
||||
|
||||
/*
|
||||
* 4.3.2. createAnswer()
|
||||
|
@ -37,7 +36,7 @@
|
|||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer({ video: true })
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer => {
|
||||
|
@ -52,7 +51,7 @@
|
|||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer({ data: true })
|
||||
return generateOffer({ pc, data: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => {
|
||||
pc.close();
|
||||
|
@ -61,18 +60,6 @@
|
|||
});
|
||||
}, 'createAnswer() when connection is closed reject with InvalidStateError');
|
||||
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer({ data: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => {
|
||||
const promise = pc.createAnswer();
|
||||
pc.close();
|
||||
return promise;
|
||||
});
|
||||
}, 'createAnswer() when connection is closed in parallel should never resolve');
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* 4.3.2 createAnswer
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
// https://rawgit.com/w3c/webrtc-pc/cc8d80f455b86c8041d63bceb8b457f45c72aa89/webrtc.html
|
||||
|
||||
/*
|
||||
6.1. RTCPeerConnection Interface Extensions
|
||||
|
@ -19,6 +19,19 @@
|
|||
};
|
||||
|
||||
6.2. RTCDataChannel
|
||||
|
||||
interface RTCDataChannel : EventTarget {
|
||||
readonly attribute USVString label;
|
||||
readonly attribute boolean ordered;
|
||||
readonly attribute unsigned short? maxPacketLifeTime;
|
||||
readonly attribute unsigned short? maxRetransmits;
|
||||
readonly attribute USVString protocol;
|
||||
readonly attribute boolean negotiated;
|
||||
readonly attribute unsigned short? id;
|
||||
readonly attribute RTCPriorityType priority;
|
||||
readonly attribute RTCDataChannelState readyState;
|
||||
};
|
||||
|
||||
dictionary RTCDataChannelInit {
|
||||
boolean ordered = true;
|
||||
unsigned short maxPacketLifeTime;
|
||||
|
@ -29,6 +42,15 @@
|
|||
unsigned short id;
|
||||
RTCPriorityType priority = "low";
|
||||
};
|
||||
|
||||
4.9.1. RTCPriorityType Enum
|
||||
|
||||
enum RTCPriorityType {
|
||||
"very-low",
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
};
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
|
@ -49,33 +71,85 @@ test(() => {
|
|||
}, 'createDataChannel with closed connection should throw InvalidStateError');
|
||||
|
||||
/*
|
||||
6.2. createDataChannel
|
||||
4. Initialize channel's label attribute to the value of the first argument.
|
||||
5. Set channel's ordered , maxPacketLifeTime , maxRetransmits , protocol,
|
||||
negotiated , id and priority attributes to the values of the corresponding
|
||||
members of the dataChannelDict argument, using a value of null if the
|
||||
corresponding dictionary member is missing.
|
||||
6.1. createDataChannel
|
||||
4. Let channel have a [[Label]] internal slot initialized to the value of the
|
||||
first argument.
|
||||
5. Let options be the second argument.
|
||||
6. Let channel have an [[MaxPacketLifeTime]] internal slot initialized to
|
||||
option's maxPacketLifeTime member, if present, otherwise null.
|
||||
7. Let channel have an [[MaxRetransmits]] internal slot initialized to
|
||||
option's maxRetransmits member, if present, otherwise null.
|
||||
8. Let channel have an [[DataChannelId]] internal slot initialized to
|
||||
option's id member, if present, otherwise null.
|
||||
9. Let channel have an [[Ordered]] internal slot initialized to option's
|
||||
ordered member.
|
||||
10. Let channel have an [[Protocol]] internal slot initialized to option's
|
||||
protocol member.
|
||||
11. Let channel have an [[Negotiated]] internal slot initialized to option's
|
||||
negotiated member.
|
||||
12. Let channel have an [[DataChannelPriority]] internal slot initialized
|
||||
to option's priority member.
|
||||
|
||||
6.2. RTCDataChannel
|
||||
|
||||
A RTCDataChannel, created with createDataChannel or dispatched via a
|
||||
RTCDataChannelEvent, MUST initially be in the connecting state
|
||||
|
||||
binaryType
|
||||
When a RTCDataChannel object is created, the binaryType attribute MUST
|
||||
be initialized to the string "blob".
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const channel = pc.createDataChannel('');
|
||||
assert_true(channel instanceof RTCDataChannel, 'is RTCDataChannel');
|
||||
assert_equals(channel.label, '', 'label');
|
||||
assert_equals(channel.ordered, true, 'ordered');
|
||||
assert_equals(channel.maxPacketLifeTime, null, 'maxPacketLifeTime');
|
||||
assert_equals(channel.maxRetransmits, null, 'maxRetransmits');
|
||||
assert_equals(channel.protocol, '', 'protocol');
|
||||
assert_equals(channel.negotiated, false, 'negotiated');
|
||||
assert_equals(channel.label, '');
|
||||
assert_equals(channel.ordered, true);
|
||||
assert_equals(channel.maxPacketLifeTime, null);
|
||||
assert_equals(channel.maxRetransmits, null);
|
||||
assert_equals(channel.protocol, '');
|
||||
assert_equals(channel.negotiated, false);
|
||||
|
||||
// Since no offer/answer exchange has occurred yet, the DTLS role is unknown
|
||||
// and so the ID should be null.
|
||||
assert_equals(channel.id, null, 'id');
|
||||
assert_equals(channel.priority, 'low', 'priority');
|
||||
assert_equals(channel.id, null);
|
||||
assert_equals(channel.priority, 'low');
|
||||
|
||||
assert_equals(channel.readyState, 'connecting');
|
||||
assert_equals(channel.binaryType, 'blob');
|
||||
|
||||
}, 'createDataChannel attribute default values');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const channel = pc.createDataChannel('test', {
|
||||
ordered: false,
|
||||
maxPacketLifeTime: null,
|
||||
maxRetransmits: 1,
|
||||
protocol: 'custom',
|
||||
negotiated: true,
|
||||
id: 3,
|
||||
priority: 'high'
|
||||
});
|
||||
|
||||
assert_true(channel instanceof RTCDataChannel, 'is RTCDataChannel');
|
||||
assert_equals(channel.label, 'test');
|
||||
assert_equals(channel.ordered, false);
|
||||
assert_equals(channel.maxPacketLifeTime, null);
|
||||
assert_equals(channel.maxRetransmits, 1);
|
||||
assert_equals(channel.protocol, 'custom');
|
||||
assert_equals(channel.negotiated, true);
|
||||
assert_equals(channel.id, 3);
|
||||
assert_equals(channel.priority, 'high');
|
||||
assert_equals(channel.readyState, 'connecting');
|
||||
assert_equals(channel.binaryType, 'blob');
|
||||
|
||||
}, 'createDataChannel with provided parameters should initialize attributes to provided values');
|
||||
|
||||
/*
|
||||
6.2. createDataChannel
|
||||
4. Initialize channel's label attribute to the value of the first argument.
|
||||
4. Let channel have a [[Label]] internal slot initialized to the value of the
|
||||
first argument.
|
||||
|
||||
[ECMA262] 7.1.12. ToString(argument)
|
||||
undefined -> "undefined"
|
||||
|
@ -99,10 +173,9 @@ for (const [description, label, expected] of labels) {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
boolean ordered = true;
|
||||
...
|
||||
}
|
||||
createDataChannel
|
||||
9. Let channel have an [[Ordered]] internal slot initialized to option's
|
||||
ordered member.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -122,10 +195,9 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
unsigned short maxPacketLifeTime;
|
||||
...
|
||||
}
|
||||
createDataChannel
|
||||
6. Let channel have an [[MaxPacketLifeTime]] internal slot initialized to
|
||||
option's maxPacketLifeTime member, if present, otherwise null.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection;
|
||||
|
@ -135,10 +207,9 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
unsigned short maxRetransmits;
|
||||
...
|
||||
}
|
||||
createDataChannel
|
||||
7. Let channel have an [[MaxRetransmits]] internal slot initialized to
|
||||
option's maxRetransmits member, if present, otherwise null.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection;
|
||||
|
@ -148,12 +219,12 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. createDataChannel
|
||||
8. If both the maxPacketLifeTime and maxRetransmits attributes are set
|
||||
(not null), throw a SyntaxError.
|
||||
15. If both [[MaxPacketLifeTime]] and [[MaxRetransmits]] attributes are set
|
||||
(not null), throw a TypeError.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection;
|
||||
assert_throws('SyntaxError', () => pc.createDataChannel('', {
|
||||
assert_throws(new TypeError(), () => pc.createDataChannel('', {
|
||||
maxPacketLifeTime: 0,
|
||||
maxRetransmits: 0
|
||||
}));
|
||||
|
@ -161,10 +232,9 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
USVString protocol = "";
|
||||
...
|
||||
}
|
||||
createDataChannel
|
||||
10. Let channel have an [[Protocol]] internal slot initialized to option's
|
||||
protocol member.
|
||||
*/
|
||||
const protocols = [
|
||||
['"foo"', 'foo', 'foo'],
|
||||
|
@ -182,10 +252,9 @@ for (const [description, protocol, expected] of protocols) {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
boolean negotiated = false;
|
||||
...
|
||||
}
|
||||
createDataChannel
|
||||
11. Let channel have an [[Negotiated]] internal slot initialized to option's
|
||||
negotiated member.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection;
|
||||
|
@ -196,12 +265,6 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
[EnforceRange]
|
||||
unsigned short id;
|
||||
...
|
||||
}
|
||||
|
||||
createDataChannel
|
||||
10. If id is equal to 65535, which is greater than the maximum allowed ID
|
||||
of 65534 but still qualifies as an unsigned short, throw a TypeError.
|
||||
|
@ -223,18 +286,10 @@ for (const id of [-1, 65535, 65536]) {
|
|||
|
||||
/*
|
||||
6.2. RTCDataChannel
|
||||
dictionary RTCDataChannelInit {
|
||||
RTCPriorityType priority = "low";
|
||||
...
|
||||
}
|
||||
createDataChannel
|
||||
12. Let channel have an [[DataChannelPriority]] internal slot initialized
|
||||
to option's priority member.
|
||||
|
||||
4.9.1. RTCPriorityType Enum
|
||||
enum RTCPriorityType {
|
||||
"very-low",
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
};
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -250,9 +305,8 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. createDataChannel
|
||||
6. If negotiated is false and label is longer than 65535 bytes long,
|
||||
throw a TypeError.
|
||||
*/
|
||||
13. If [[Negotiated]] is false and [[Label]] is longer than 65535 bytes
|
||||
long, throw a TypeError. */
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
assert_throws(new TypeError(), () =>
|
||||
|
@ -264,7 +318,7 @@ test(() => {
|
|||
|
||||
/*
|
||||
6.2. createDataChannel
|
||||
7. If negotiated is false and protocol is longer than 65535 bytes long,
|
||||
14. If [[Negotiated]] is false and [[Protocol]] is longer than 65535 bytes long,
|
||||
throw a TypeError.
|
||||
*/
|
||||
test(() => {
|
||||
|
@ -278,29 +332,106 @@ test(() => {
|
|||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel('', {
|
||||
label: ' '.repeat(65536),
|
||||
const label = ' '.repeat(65536)
|
||||
|
||||
const channel = pc.createDataChannel('', {
|
||||
label,
|
||||
protocol: ' '.repeat(65536),
|
||||
negotiated: true
|
||||
});
|
||||
|
||||
assert_equals(channel.label, label);
|
||||
}, 'createDataChannel with negotiated true and long label and long protocol should succeed');
|
||||
|
||||
/*
|
||||
TODO
|
||||
4.4.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.6. If description is of type "answer" or "pranswer", then run the
|
||||
following steps:
|
||||
1. If description initiates the establishment of a new SCTP association,
|
||||
as defined in [SCTP-SDP], Sections 10.3 and 10.4, set the value of
|
||||
connection's [[sctpTransport]] internal slot to a newly created RTCSctpTransport.
|
||||
2. If description negotiates the DTLS role of the SCTP transport, and
|
||||
there is an RTCDataChannel with a null id, then generate an ID according
|
||||
to [RTCWEB-DATA-PROTOCOL].
|
||||
|
||||
6.2. createDataChannel
|
||||
11. If the id attribute is null (due to no ID being passed into
|
||||
18. If the [[DataChannelId]] slot is null (due to no ID being passed into
|
||||
createDataChannel), and the DTLS role of the SCTP transport has already
|
||||
been negotiated, then initialize id to a value generated by the user
|
||||
agent, according to [RTCWEB-DATA-PROTOCOL], and skip to the next step.
|
||||
If no available ID could be generated, or if the value of the id member
|
||||
of the dictionary is taken by an existing RTCDataChannel , throw a
|
||||
ResourceInUse exception.
|
||||
been negotiated, then initialize [[DataChannelId]] to a value generated
|
||||
by the user agent, according to [RTCWEB-DATA-PROTOCOL].
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const channel1 = pc.createDataChannel('channel');
|
||||
assert_equals(channel1.id, null,
|
||||
'Expect initial id to be null');
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer => pc.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
assert_not_equals(channel1.id, null,
|
||||
'Expect channel1.id to be assigned');
|
||||
|
||||
assert_greater_than_equals(channel1.id, 0,
|
||||
'Expect channel1.id to be set to valid unsigned short');
|
||||
|
||||
assert_less_than(channel1.id, 65535,
|
||||
'Expect channel1.id to be set to valid unsigned short');
|
||||
|
||||
const channel2 = pc.createDataChannel('channel');
|
||||
|
||||
assert_not_equals(channel2.id, null,
|
||||
'Expect channel2.id to be assigned');
|
||||
|
||||
assert_greater_than_equals(channel2.id, 0,
|
||||
'Expect channel2.id to be set to valid unsigned short');
|
||||
|
||||
assert_less_than(channel2.id, 65535,
|
||||
'Expect channel2.id to be set to valid unsigned short');
|
||||
|
||||
assert_not_equals(channel2, channel1,
|
||||
'Expect channels created from same label to be different');
|
||||
|
||||
assert_equals(channel2.label, channel1.label,
|
||||
'Expect different channnels can have the same label but different id');
|
||||
|
||||
assert_not_equals(channel2.id, channel1.id,
|
||||
'Expect different channnels can have the same label but different id');
|
||||
});
|
||||
}, 'Channels created after SCTP transport is established should have id assigned');
|
||||
|
||||
/*
|
||||
TODO
|
||||
6.1. createDataChannel
|
||||
18. If no available ID could be generated, or if the value of the
|
||||
id member of the dictionary is taken by an existing RTCDataChannel, throw
|
||||
a ResourceInUse exception.
|
||||
|
||||
Untestable
|
||||
6.2. createDataChannel
|
||||
9. If an attribute, either maxPacketLifeTime or maxRetransmits , has been
|
||||
set to indicate unreliable mode, and that value exceeds the maximum
|
||||
value supported by the user agent, the value must be set to the user
|
||||
agents maximum value.
|
||||
*/
|
||||
6.1. createDataChannel
|
||||
16. If a setting, either [[MaxPacketLifeTime]] or [[MaxRetransmits]], has
|
||||
been set to indicate unreliable mode, and that value exceeds the maximum
|
||||
value supported by the user agent, the value MUST be set to the user
|
||||
agents maximum value.
|
||||
|
||||
20. Create channel's associated underlying data transport and configure
|
||||
it according to the relevant properties of channel.
|
||||
|
||||
Tested in RTCPeerConnection-onnegotiationneeded.html
|
||||
21. If channel was the first RTCDataChannel created on connection, update
|
||||
the negotiation-needed flag for connection.
|
||||
|
||||
Issues
|
||||
w3c/webrtc-pc#1412
|
||||
ResourceInUse exception is not defined
|
||||
|
||||
Coverage Report
|
||||
Tested 22
|
||||
Not Tested 1
|
||||
Untestable 2
|
||||
Total 25
|
||||
*/
|
||||
</script>
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
// countAudioLine()
|
||||
// countVideoLine()
|
||||
// test_state_change_event()
|
||||
// test_never_resolve()
|
||||
// assert_session_desc_equals()
|
||||
|
||||
/*
|
||||
|
@ -49,7 +48,7 @@
|
|||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(offer => {
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer);
|
||||
|
@ -65,15 +64,6 @@
|
|||
pc.createOffer());
|
||||
}, 'createOffer() after connection is closed should reject with InvalidStateError');
|
||||
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const promise = pc.createOffer();
|
||||
|
||||
pc.close();
|
||||
return promise;
|
||||
|
||||
}, 'createOffer() when connection is closed halfway should never resolve');
|
||||
|
||||
/*
|
||||
* Final steps to create an offer
|
||||
* 2. If connection was modified in such a way that additional inspection of the
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.getDefaultIceServers</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
static sequence<RTCIceServer> getDefaultIceServers();
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCIceServer {
|
||||
required (DOMString or sequence<DOMString>) urls;
|
||||
DOMString username;
|
||||
(DOMString or RTCOAuthCredential) credential;
|
||||
RTCIceCredentialType credentialType = "password";
|
||||
};
|
||||
|
||||
dictionary RTCOAuthCredential {
|
||||
required DOMString macKey;
|
||||
required DOMString accessToken;
|
||||
};
|
||||
|
||||
enum RTCIceCredentialType {
|
||||
"password",
|
||||
"oauth"
|
||||
};
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const iceServers = RTCPeerConnection.getDefaultIceServers();
|
||||
|
||||
assert_true(Array.isArray(iceServers),
|
||||
'Expect iceServers to be an array');
|
||||
|
||||
// dictionary IDL cannot be tested automatically using idlharness
|
||||
for(const server of iceServers) {
|
||||
const { urls, username, credential, credentialType } = server;
|
||||
|
||||
if(Array.isArray(urls)) {
|
||||
for(const url of urls) {
|
||||
assert_equals(typeof url, 'string',
|
||||
'Expect elements in urls array to be string');
|
||||
}
|
||||
} else {
|
||||
assert_equals(typeof urls, 'string',
|
||||
'Expect urls to be either string or array');
|
||||
}
|
||||
|
||||
if(username !== undefined) {
|
||||
assert_equals(typeof username, 'string',
|
||||
'Expect username to be either undefined or string');
|
||||
}
|
||||
|
||||
assert_true(credentialType === 'password' || credentialType === 'oauth',
|
||||
'Expect credentialType to be either password or oauth')
|
||||
|
||||
if(credential) {
|
||||
if(typeof(credential) === 'object') {
|
||||
const { macKey, accessToken } = credential;
|
||||
assert_equals(typeof macKey, 'string',
|
||||
'Expect macKey to be string');
|
||||
|
||||
assert_equals(typeof accessToken, 'string',
|
||||
'Expect accessToken to be string');
|
||||
|
||||
} else {
|
||||
assert_equals(typeof credential, 'string',
|
||||
'Expect credential to be either undefined, string, or RTCOauthCredential dictionary');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expect default ice servers to be accepted as valid configuration
|
||||
const pc = new RTCPeerConnection({ iceServers });
|
||||
|
||||
// Only make sure there are same number of ice servers configured
|
||||
// and not do any deep equality checking
|
||||
assert_equals(pc.getConfiguration().iceServers.length, iceServers.length);
|
||||
|
||||
}, 'RTCPeerConnection.getDefaultIceServers() should return array of RTCIceServer');
|
||||
|
||||
/*
|
||||
Coverage Report
|
||||
Since there is no steps involved and we are only checking basic call,
|
||||
This is counted as 1 trivial test coverage.
|
||||
|
||||
Tested 1
|
||||
Total 1
|
||||
*/
|
||||
</script>
|
|
@ -0,0 +1,400 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.getIdentityAssertion</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="identity-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The tests here interacts with the mock identity provider located at
|
||||
// /.well-known/idp-proxy/mock-idp.js
|
||||
|
||||
// The following helper functions are called from identity-helper.js
|
||||
// parseAssertionResult
|
||||
// getIdpDomains
|
||||
// assert_rtcerror_rejection
|
||||
// hostString
|
||||
|
||||
/*
|
||||
9.6. RTCPeerConnection Interface Extensions
|
||||
partial interface RTCPeerConnection {
|
||||
void setIdentityProvider(DOMString provider,
|
||||
optional RTCIdentityProviderOptions options);
|
||||
Promise<DOMString> getIdentityAssertion();
|
||||
readonly attribute Promise<RTCIdentityAssertion> peerIdentity;
|
||||
readonly attribute DOMString? idpLoginUrl;
|
||||
readonly attribute DOMString? idpErrorInfo;
|
||||
};
|
||||
|
||||
dictionary RTCIdentityProviderOptions {
|
||||
DOMString protocol = "default";
|
||||
DOMString usernameHint;
|
||||
DOMString peerIdentity;
|
||||
};
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const port = window.location.port;
|
||||
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
pc.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js?foo=bar',
|
||||
usernameHint: `alice@${idpDomain}`,
|
||||
peerIdentity: 'bob@example.org'
|
||||
});
|
||||
|
||||
return pc.getIdentityAssertion()
|
||||
.then(assertionResultStr => {
|
||||
const { idp, assertion } = parseAssertionResult(assertionResultStr);
|
||||
|
||||
assert_equals(idp.domain, idpHost,
|
||||
'Expect mock-idp.js to construct domain from its location.host');
|
||||
|
||||
assert_equals(idp.protocol, 'mock-idp.js',
|
||||
'Expect mock-idp.js to return protocol of itself with no query string');
|
||||
|
||||
const {
|
||||
watermark,
|
||||
args,
|
||||
env,
|
||||
query,
|
||||
} = assertion;
|
||||
|
||||
assert_equals(watermark, 'mock-idp.js.watermark',
|
||||
'Expect assertion result to contain watermark left by mock-idp.js');
|
||||
|
||||
assert_equals(args.origin, window.origin,
|
||||
'Expect args.origin argument to be the origin of this window');
|
||||
|
||||
assert_equals(env.location,
|
||||
`https://${idpHost}/.well-known/idp-proxy/idp-test.js?foo=bar`,
|
||||
'Expect IdP proxy to be loaded with full well-known URL constructed from provider and protocol');
|
||||
|
||||
assert_equals(env.origin, `https://${idpHost}`,
|
||||
'Expect IdP to have its own origin');
|
||||
|
||||
assert_equals(args.options.protocol, 'idp-test.js?foo=bar',
|
||||
'Expect options.protocol to be the same value as being passed from here');
|
||||
|
||||
assert_equals(args.options.usernameHint, `alice@${idpDomain}`,
|
||||
'Expect options.usernameHint to be the same value as being passed from here');
|
||||
|
||||
assert_equals(args.options.peerIdentity, 'bob@example.org',
|
||||
'Expect options.peerIdentity to be the same value as being passed from here');
|
||||
|
||||
assert_equals(query.foo, 'bar',
|
||||
'Expect query string to be parsed by mock-idp.js and returned back');
|
||||
});
|
||||
}, 'getIdentityAssertion() should load IdP proxy and return assertion generated');
|
||||
|
||||
// When generating assertion, the RTCPeerConnection doesn't care if the returned assertion
|
||||
// represents identity of different domain
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const port = window.location.port;
|
||||
|
||||
const [idpDomain1, idpDomain2] = getIdpDomains();
|
||||
assert_not_equals(idpDomain1, idpDomain2,
|
||||
'Sanity check two idpDomains are different');
|
||||
|
||||
// Ask mock-idp.js to return a custom domain idpDomain2 and custom protocol foo
|
||||
pc.setIdentityProvider(hostString(idpDomain1, port), {
|
||||
protocol: `mock-idp.js?generatorAction=return-custom-idp&domain=${idpDomain2}&protocol=foo`,
|
||||
usernameHint: `alice@${idpDomain2}`,
|
||||
});
|
||||
|
||||
return pc.getIdentityAssertion()
|
||||
.then(assertionResultStr => {
|
||||
const { idp, assertion } = parseAssertionResult(assertionResultStr);
|
||||
assert_equals(idp.domain, idpDomain2);
|
||||
assert_equals(idp.protocol, 'foo');
|
||||
assert_equals(assertion.options.usernameHint, `alice@${idpDomain2}`);
|
||||
});
|
||||
}, 'getIdentityAssertion() should succeed if mock-idp.js return different domain and protocol in assertion');
|
||||
|
||||
/*
|
||||
9.3. Requesting Identity Assertions
|
||||
4. If the IdP proxy produces an error or returns a promise that does not resolve to
|
||||
a valid RTCIdentityValidationResult (see 9.5 IdP Error Handling), then identity
|
||||
validation fails.
|
||||
|
||||
9.5. IdP Error Handling
|
||||
- If an identity provider throws an exception or returns a promise that is ultimately
|
||||
rejected, then the procedure that depends on the IdP MUST also fail. These types of
|
||||
errors will cause an IdP failure with an RTCError with errorDetail set to
|
||||
"idp-execution-failure".
|
||||
|
||||
9.6. RTCPeerConnection Interface Extensions
|
||||
idpErrorInfo
|
||||
An attribute that the IdP can use to pass additional information back to the
|
||||
applications about the error. The format of this string is defined by the IdP and
|
||||
may be JSON.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_equals(pc.idpErrorInfo, null,
|
||||
'Expect initial pc.idpErrorInfo to be null');
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
|
||||
// Ask mock-idp.js to throw an error with err.errorInfo set to bar
|
||||
pc.setIdentityProvider(hostString(idpDomain, port), {
|
||||
protocol: `mock-idp.js?generatorAction=throw-error&errorInfo=bar`,
|
||||
usernameHint: `alice@${idpDomain}`,
|
||||
});
|
||||
|
||||
return assert_rtcerror_rejection('idp-execution-failure',
|
||||
pc.getIdentityAssertion())
|
||||
.then(() => {
|
||||
assert_equals(pc.idpErrorInfo, 'bar',
|
||||
'Expect pc.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js');
|
||||
});
|
||||
}, `getIdentityAssertion() should reject with RTCError('idp-execution-failure') if mock-idp.js throws error`);
|
||||
|
||||
/*
|
||||
9.5. IdP Error Handling
|
||||
- If the script loaded from the identity provider is not valid JavaScript or does
|
||||
not implement the correct interfaces, it causes an IdP failure with an RTCError
|
||||
with errorDetail set to "idp-bad-script-failure".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
|
||||
// Ask mock-idp.js to not register its callback to the
|
||||
// RTCIdentityProviderRegistrar
|
||||
pc.setIdentityProvider(hostString(idpDomain, port), {
|
||||
protocol: `mock-idp.js?action=do-not-register`,
|
||||
usernameHint: `alice@${idpDomain}`,
|
||||
});
|
||||
|
||||
return assert_rtcerror_rejection('idp-bad-script-failure',
|
||||
pc.getIdentityAssertion());
|
||||
|
||||
}, `getIdentityAssertion() should reject with RTCError('idp-bad-script-failure') if IdP proxy script do not register its callback`);
|
||||
|
||||
/*
|
||||
9.3. Requesting Identity Assertions
|
||||
4. If the IdP proxy produces an error or returns a promise that does not resolve
|
||||
to a valid RTCIdentityAssertionResult (see 9.5 IdP Error Handling), then assertion
|
||||
generation fails.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
|
||||
// Ask mock-idp.js to return an invalid result that is not proper
|
||||
// RTCIdentityAssertionResult
|
||||
pc.setIdentityProvider(hostString(idpDomain, port), {
|
||||
protocol: `mock-idp.js?generatorAction=return-invalid-result`,
|
||||
usernameHint: `alice@${idpDomain}`,
|
||||
});
|
||||
|
||||
return promise_rejects(t, 'OperationError',
|
||||
pc.getIdentityAssertion());
|
||||
}, `getIdentityAssertion() should reject with OperationError if mock-idp.js return invalid result`);
|
||||
|
||||
/*
|
||||
9.5. IdP Error Handling
|
||||
- A RTCPeerConnection might be configured with an identity provider, but loading of
|
||||
the IdP URI fails. Any procedure that attempts to invoke such an identity provider
|
||||
and cannot load the URI fails with an RTCError with errorDetail set to
|
||||
"idp-load-failure" and the httpRequestStatusCode attribute of the error set to the
|
||||
HTTP status code of the response.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
pc.setIdentityProvider('nonexistent-origin.web-platform.test', {
|
||||
protocol: `non-existent`,
|
||||
usernameHint: `alice@example.org`,
|
||||
});
|
||||
|
||||
return assert_rtcerror_rejection('idp-load-failure',
|
||||
pc.getIdentityAssertion());
|
||||
}, `getIdentityAssertion() should reject with RTCError('idp-load-failure') if IdP cannot be loaded`);
|
||||
|
||||
/*
|
||||
9.3.1. User Login Procedure
|
||||
Rejecting the promise returned by generateAssertion will cause the error to
|
||||
propagate to the application. Login errors are indicated by rejecting the
|
||||
promise with an RTCError with errorDetail set to "idp-need-login".
|
||||
|
||||
The URL to login at will be passed to the application in the idpLoginUrl
|
||||
attribute of the RTCPeerConnection.
|
||||
|
||||
9.5. IdP Error Handling
|
||||
- If the identity provider requires the user to login, the operation will fail
|
||||
RTCError with errorDetail set to "idp-need-login" and the idpLoginUrl attribute
|
||||
of the error set to the URL that can be used to login.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_equals(pc.idpLoginUrl, null,
|
||||
'Expect initial pc.idpLoginUrl to be null');
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
pc.setIdentityProvider(idpHost, {
|
||||
protocol: `mock-idp.js?generatorAction=require-login`,
|
||||
usernameHint: `alice@${idpDomain}`,
|
||||
});
|
||||
|
||||
return assert_rtcerror_rejection('idp-need-login',
|
||||
pc.getIdentityAssertion())
|
||||
.then(err => {
|
||||
assert_equals(err.idpLoginUrl, `https://${idpHost}/login`,
|
||||
'Expect err.idpLoginUrl to be set to url set by mock-idp.js');
|
||||
|
||||
assert_equals(pc.idpLoginUrl, `https://${idpHost}/login`,
|
||||
'Expect pc.idpLoginUrl to be set to url set by mock-idp.js');
|
||||
|
||||
assert_equals(pc.idpErrorInfo, 'login required',
|
||||
'Expect pc.idpErrorInfo to be set to info set by mock-idp.js');
|
||||
});
|
||||
}, `getIdentityAssertion() should reject with RTCError('idp-need-login') when mock-idp.js requires login`);
|
||||
|
||||
/*
|
||||
RTCIdentityProviderOptions Members
|
||||
peerIdentity
|
||||
The identity of the peer. For identity providers that bind their assertions to a
|
||||
particular pair of communication peers, this allows them to generate an assertion
|
||||
that includes both local and remote identities. If this value is omitted, but a
|
||||
value is provided for the peerIdentity member of RTCConfiguration, the value from
|
||||
RTCConfiguration is used.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection({
|
||||
peerIdentity: 'bob@example.net'
|
||||
});
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
pc.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js'
|
||||
});
|
||||
|
||||
return pc.getIdentityAssertion()
|
||||
.then(assertionResultStr => {
|
||||
const { assertion } = parseAssertionResult(assertionResultStr);
|
||||
assert_equals(assertion.args.options.peerIdentity, 'bob@example.net');
|
||||
});
|
||||
}, 'setIdentityProvider() with no peerIdentity provided should use peerIdentity value from getConfiguration()');
|
||||
|
||||
/*
|
||||
9.6. setIdentityProvider
|
||||
3. If any identity provider value has changed, discard any stored identity assertion.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
pc.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js?mark=first'
|
||||
});
|
||||
|
||||
return pc.getIdentityAssertion()
|
||||
.then(assertionResultStr => {
|
||||
const { assertion } = parseAssertionResult(assertionResultStr);
|
||||
assert_equals(assertion.query.mark, 'first');
|
||||
|
||||
pc.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js?mark=second'
|
||||
});
|
||||
|
||||
return pc.getIdentityAssertion();
|
||||
})
|
||||
.then(assertionResultStr => {
|
||||
const { assertion } = parseAssertionResult(assertionResultStr);
|
||||
assert_equals(assertion.query.mark, 'second',
|
||||
'Expect generated assertion is from second IdP config');
|
||||
});
|
||||
}, `Calling setIdentityProvider() multiple times should reset identity assertions`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
|
||||
pc.setIdentityProvider(hostString(idpDomain, port), {
|
||||
protocol: 'mock-idp.js',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return pc.getIdentityAssertion()
|
||||
.then(assertionResultStr =>
|
||||
pc.createOffer()
|
||||
.then(offer => {
|
||||
assert_true(offer.sdp.includes(`\r\na=identity:${assertionResultStr}`,
|
||||
'Expect SDP to have a=identity line containing assertion string'));
|
||||
}));
|
||||
}, 'createOffer() should return SDP containing identity assertion string if identity provider is set');
|
||||
|
||||
/*
|
||||
4.4.2. Steps to create an offer
|
||||
1. If the need for an identity assertion was identified when createOffer was
|
||||
invoked, wait for the identity assertion request process to complete.
|
||||
2. If the identity provider was unable to produce an identity assertion, reject p
|
||||
with a newly created NotReadableError and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
|
||||
pc.setIdentityProvider(hostString(idpDomain, port), {
|
||||
protocol: 'mock-idp.js?generatorAction=throw-error',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return promise_rejects(t, 'NotReadableError',
|
||||
pc.createOffer());
|
||||
}, 'createOffer() should reject with NotReadableError if identitity assertion request fails');
|
||||
|
||||
/*
|
||||
4.4.2. Steps to create an answer
|
||||
1. If the need for an identity assertion was identified when createAnswer was
|
||||
invoked, wait for the identity assertion request process to complete.
|
||||
|
||||
2. If the identity provider was unable to produce an identity assertion, reject p
|
||||
with a newly created NotReadableError and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
|
||||
pc.setIdentityProvider(hostString(idpDomain, port), {
|
||||
protocol: 'mock-idp.js?generatorAction=throw-error',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return new RTCPeerConnection()
|
||||
.createOffer()
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() =>
|
||||
promise_rejects(t, 'NotReadableError',
|
||||
pc.createAnswer()));
|
||||
|
||||
}, 'createAnswer() should reject with NotReadableError if identitity assertion request fails');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,187 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.getStats</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCStats-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
// https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
|
||||
|
||||
// The following helper function is called from RTCPeerConnection-helper.js
|
||||
// getTrackFromUserMedia
|
||||
|
||||
// The following helper function is called from RTCStats-helper.js
|
||||
// validateStatsReport
|
||||
// assert_stats_report_has_stats
|
||||
|
||||
/*
|
||||
8.2. RTCPeerConnection Interface Extensions
|
||||
partial interface RTCPeerConnection {
|
||||
Promise<RTCStatsReport> getStats(optional MediaStreamTrack? selector = null);
|
||||
};
|
||||
|
||||
8.3. RTCStatsReport Object
|
||||
interface RTCStatsReport {
|
||||
readonly maplike<DOMString, object>;
|
||||
};
|
||||
|
||||
8.4. RTCStats Dictionary
|
||||
dictionary RTCStats {
|
||||
DOMHighResTimeStamp timestamp;
|
||||
RTCStatsType type;
|
||||
DOMString id;
|
||||
};
|
||||
|
||||
id
|
||||
Two RTCStats objects, extracted from two different RTCStatsReport objects, MUST
|
||||
have the same id if they were produced by inspecting the same underlying object.
|
||||
|
||||
8.2. getStats
|
||||
1. Let selectorArg be the method's first argument.
|
||||
2. Let connection be the RTCPeerConnection object on which the method was invoked.
|
||||
3. If selectorArg is neither null nor a valid MediaStreamTrack, return a promise
|
||||
rejected with a newly created TypeError.
|
||||
5. Let p be a new promise.
|
||||
6. Run the following steps in parallel:
|
||||
1. Gather the stats indicated by selector according to the stats selection algorithm.
|
||||
2. Resolve p with the resulting RTCStatsReport object, containing the gathered stats.
|
||||
*/
|
||||
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.getStats();
|
||||
}, 'getStats() with no argument should succeed');
|
||||
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.getStats(null);
|
||||
}, 'getStats(null) should succeed');
|
||||
|
||||
/*
|
||||
8.2. getStats
|
||||
4. Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track
|
||||
member matches selectorArg. If no such sender or receiver exists, return a promise
|
||||
rejected with a newly created InvalidAccessError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
return promise_rejects(t, 'InvalidAccessError', pc.getStats(track));
|
||||
});
|
||||
}, 'getStats() with track not added to connection should reject with InvalidAccessError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
pc.addTrack(track, mediaStream);
|
||||
return pc.getStats(track);
|
||||
});
|
||||
}, 'getStats() with track added via addTrack should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const track = generateMediaStreamTrack();
|
||||
pc.addTransceiver(track);
|
||||
|
||||
return pc.getStats(track);
|
||||
}, 'getStats() with track added via addTransceiver should succeed');
|
||||
|
||||
/*
|
||||
8.2. getStats
|
||||
4. Let selector be a RTCRtpSender or RTCRtpReceiver on connection which track
|
||||
member matches selectorArg. If more than one sender or receiver fit this criteria,
|
||||
return a promise rejected with a newly created InvalidAccessError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
// addTransceiver allows adding same track multiple times
|
||||
const transceiver1 = pc.addTransceiver(track);
|
||||
const transceiver2 = pc.addTransceiver(track);
|
||||
|
||||
assert_not_equals(transceiver1, transceiver2);
|
||||
assert_not_equals(transceiver1.sender, transceiver2.sender);
|
||||
assert_equals(transceiver1.sender.track, transceiver2.sender.track);
|
||||
|
||||
return promise_rejects(t, 'InvalidAccessError', pc.getStats(track));
|
||||
});
|
||||
}, `getStats() with track associated with more than one sender should reject with InvalidAccessError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver1 = pc.addTransceiver('audio');
|
||||
|
||||
// Create another transceiver that resends what
|
||||
// is being received, kind of like echo
|
||||
const transceiver2 = pc.addTransceiver(transceiver1.receiver.track);
|
||||
assert_equals(transceiver1.receiver.track, transceiver2.sender.track);
|
||||
|
||||
return promise_rejects(t, 'InvalidAccessError', pc.getStats(transceiver1.receiver.track));
|
||||
}, 'getStats() with track associated with both sender and receiver should reject with InvalidAccessError');
|
||||
|
||||
/*
|
||||
8.5. The stats selection algorithm
|
||||
2. If selector is null, gather stats for the whole connection, add them to result,
|
||||
return result, and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.getStats()
|
||||
.then(statsReport => {
|
||||
validateStatsReport(statsReport);
|
||||
assert_stats_report_has_stats(statsReport, ['peer-connection']);
|
||||
});
|
||||
}, 'getStats() with no argument should return stats report containing peer-connection stats');
|
||||
|
||||
/*
|
||||
8.5. The stats selection algorithm
|
||||
3. If selector is an RTCRtpSender, gather stats for and add the following objects
|
||||
to result:
|
||||
- All RTCOutboundRTPStreamStats objects corresponding to selector.
|
||||
- All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
|
||||
objects added.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
pc.addTrack(track, mediaStream);
|
||||
|
||||
return pc.getStats(track)
|
||||
.then(statsReport => {
|
||||
validateStatsReport(statsReport);
|
||||
assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
|
||||
});
|
||||
});
|
||||
}, `getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats`);
|
||||
|
||||
|
||||
/*
|
||||
8.5. The stats selection algorithm
|
||||
4. If selector is an RTCRtpReceiver, gather stats for and add the following objects
|
||||
to result:
|
||||
- All RTCInboundRTPStreamStats objects corresponding to selector.
|
||||
- All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
|
||||
added.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
|
||||
return pc.getStats(transceiver.receiver.track)
|
||||
.then(statsReport => {
|
||||
validateStatsReport(statsReport);
|
||||
assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
|
||||
});
|
||||
}, `getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats`);
|
||||
|
||||
</script>
|
|
@ -7,7 +7,7 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
* 5.1. RTCPeerConnection Interface Extensions
|
||||
|
@ -22,15 +22,15 @@
|
|||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_own_property(pc, 'getSenders');
|
||||
assert_idl_attribute(pc, 'getSenders');
|
||||
const senders = pc.getSenders();
|
||||
assert_array_equals([], senders, 'Expect senders to be empty array');
|
||||
|
||||
assert_own_property(pc, 'getReceivers');
|
||||
assert_idl_attribute(pc, 'getReceivers');
|
||||
const receivers = pc.getReceivers();
|
||||
assert_array_equals([], receivers, 'Expect receivers to be empty array');
|
||||
|
||||
assert_own_property(pc, 'getTransceivers');
|
||||
assert_idl_attribute(pc, 'getTransceivers');
|
||||
const transceivers = pc.getTransceivers();
|
||||
assert_array_equals([], transceivers, 'Expect transceivers to be empty array');
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ function assert_is_session_description(sessionDesc) {
|
|||
}
|
||||
|
||||
assert_not_equals(sessionDesc, undefined,
|
||||
'Expect session description to be defined, but got undefined');
|
||||
'Expect session description to be defined');
|
||||
|
||||
assert_true(typeof(sessionDesc) === 'object',
|
||||
'Expect sessionDescription to be either a RTCSessionDescription or an object');
|
||||
|
@ -69,7 +69,7 @@ function assert_is_session_description(sessionDesc) {
|
|||
assert_true(typeof(sessionDesc.type) === 'string',
|
||||
'Expect sessionDescription.type to be a string');
|
||||
|
||||
assert_true(typeof(sessionDesc.type) === 'string',
|
||||
assert_true(typeof(sessionDesc.sdp) === 'string',
|
||||
'Expect sessionDescription.sdp to be a string');
|
||||
}
|
||||
|
||||
|
@ -107,21 +107,27 @@ function assert_session_desc_not_equals(sessionDesc1, sessionDesc2) {
|
|||
// object with any audio, video, data media lines present
|
||||
function generateOffer(options={}) {
|
||||
const {
|
||||
audio=false,
|
||||
video=false,
|
||||
data=false
|
||||
audio = false,
|
||||
video = false,
|
||||
data = false,
|
||||
pc,
|
||||
} = options;
|
||||
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
if(data) {
|
||||
if (data) {
|
||||
pc.createDataChannel('test');
|
||||
}
|
||||
|
||||
return pc.createOffer({
|
||||
offerToReceiveAudio: audio,
|
||||
offerToReceiveVideo: video
|
||||
}).then(offer => {
|
||||
const setup = {};
|
||||
|
||||
if (audio) {
|
||||
setup.offerToReceiveAudio = true;
|
||||
}
|
||||
|
||||
if (video) {
|
||||
setup.offerToReceiveVideo = true;
|
||||
}
|
||||
|
||||
return pc.createOffer(setup).then(offer => {
|
||||
// Guard here to ensure that the generated offer really
|
||||
// contain the number of media lines we want
|
||||
const { sdp } = offer;
|
||||
|
@ -251,10 +257,11 @@ function doSignalingHandshake(localPc, remotePc) {
|
|||
// It does the heavy lifting of performing signaling handshake,
|
||||
// ICE candidate exchange, and waiting for data channel at two
|
||||
// end points to open.
|
||||
function createDataChannelPair() {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
const channel1 = pc1.createDataChannel('test');
|
||||
function createDataChannelPair(
|
||||
pc1=new RTCPeerConnection(),
|
||||
pc2=new RTCPeerConnection())
|
||||
{
|
||||
const channel1 = pc1.createDataChannel('');
|
||||
|
||||
exchangeIceCandidates(pc1, pc2);
|
||||
|
||||
|
@ -278,7 +285,7 @@ function createDataChannelPair() {
|
|||
}
|
||||
|
||||
function onDataChannel(event) {
|
||||
channel2 = event.channel
|
||||
channel2 = event.channel;
|
||||
channel2.addEventListener('error', reject);
|
||||
const { readyState } = channel2;
|
||||
|
||||
|
@ -360,7 +367,7 @@ function assert_equals_array_buffer(buffer1, buffer2) {
|
|||
function generateMediaStreamTrack(kind) {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_own_property(pc, 'addTransceiver',
|
||||
assert_idl_attribute(pc, 'addTransceiver',
|
||||
'Expect pc to have addTransceiver() method');
|
||||
|
||||
const transceiver = pc.addTransceiver(kind);
|
||||
|
@ -372,3 +379,18 @@ function generateMediaStreamTrack(kind) {
|
|||
|
||||
return track;
|
||||
}
|
||||
|
||||
// Obtain a MediaStreamTrack of kind using getUserMedia.
|
||||
// Return Promise of pair of track and associated mediaStream.
|
||||
// Assumes that there is at least one available device
|
||||
// to generate the track.
|
||||
function getTrackFromUserMedia(kind) {
|
||||
return navigator.mediaDevices.getUserMedia({ [kind]: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
`Expect getUserMedia to return at least one track of kind ${kind}`);
|
||||
const [ track ] = tracks;
|
||||
return [track, mediaStream];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,13 +3,16 @@
|
|||
<title>Test RTCPeerConnection.prototype.onnegotiationneeded</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/* Helper Functions */
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateAnswer
|
||||
// test_never_resolve
|
||||
|
||||
// Listen to the negotiationneeded event on a peer connection
|
||||
// Returns a promise that resolves when the first event is fired.
|
||||
|
@ -59,33 +62,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Run a test function that return a promise that should
|
||||
// never be resolved. For lack of better options,
|
||||
// we wait for a time out and pass the test if the
|
||||
// promise doesn't resolve within that time.
|
||||
function test_never_resolve(testFunc, testName) {
|
||||
async_test(t => {
|
||||
testFunc(t)
|
||||
.then(
|
||||
t.step_func(result => {
|
||||
assert_unreached(`Pending promise should never be resolved. Instead it is fulfilled with: ${result}`);
|
||||
}),
|
||||
t.step_func(err => {
|
||||
assert_unreached(`Pending promise should never be resolved. Instead it is rejected with: ${err}`);
|
||||
}));
|
||||
|
||||
t.step_timeout(t.step_func_done(), 200)
|
||||
}, testName);
|
||||
}
|
||||
|
||||
// Helper function to generate answer based on given offer using a freshly
|
||||
// created RTCPeerConnection object
|
||||
function generateAnswer(offer) {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer());
|
||||
}
|
||||
|
||||
/*
|
||||
4.7.3. Updating the Negotiation-Needed flag
|
||||
|
||||
|
@ -106,39 +82,18 @@
|
|||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const promise = awaitNegotiation(pc);
|
||||
const negotiated = awaitNegotiation(pc);
|
||||
|
||||
pc.createDataChannel('test');
|
||||
return promise;
|
||||
return negotiated;
|
||||
}, 'Creating first data channel should fire negotiationneeded event');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel('test');
|
||||
// Attaching the event handler after the negotiation-needed steps
|
||||
// are performed should still receive the event, because the event
|
||||
// firing is queued as a task
|
||||
return awaitNegotiation(pc);
|
||||
}, 'task for negotiationneeded event should be enqueued for next tick');
|
||||
|
||||
/*
|
||||
4.7.3. Updating the Negotiation-Needed flag
|
||||
|
||||
To update the negotiation-needed flag
|
||||
6. Queue a task that runs the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
*/
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel('test');
|
||||
pc.close();
|
||||
return awaitNegotiation(pc);
|
||||
}, 'negotiationneeded event should not fire if connection is closed');
|
||||
const negotiated = awaitNegotiation(pc);
|
||||
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel('foo');
|
||||
|
||||
return awaitNegotiation(pc)
|
||||
return negotiated
|
||||
.then(({nextPromise}) => {
|
||||
pc.createDataChannel('bar');
|
||||
return nextPromise;
|
||||
|
@ -159,8 +114,10 @@
|
|||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const negotiated = awaitNegotiation(pc);
|
||||
|
||||
pc.addTransceiver('audio');
|
||||
return awaitNegotiation(pc);
|
||||
return negotiated;
|
||||
}, 'addTransceiver() should fire negotiationneeded event');
|
||||
|
||||
/*
|
||||
|
@ -170,8 +127,10 @@
|
|||
*/
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const negotiated = awaitNegotiation(pc);
|
||||
|
||||
pc.addTransceiver('audio');
|
||||
return awaitNegotiation(pc)
|
||||
return negotiated
|
||||
.then(({nextPromise}) => {
|
||||
pc.addTransceiver('video');
|
||||
return nextPromise;
|
||||
|
@ -185,8 +144,10 @@
|
|||
*/
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const negotiated = awaitNegotiation(pc);
|
||||
|
||||
pc.createDataChannel('test');
|
||||
return awaitNegotiation(pc)
|
||||
return negotiated
|
||||
.then(({nextPromise}) => {
|
||||
pc.addTransceiver('video');
|
||||
return nextPromise;
|
||||
|
@ -200,34 +161,32 @@
|
|||
*/
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const promise = awaitNegotiation(pc);
|
||||
const negotiated = awaitNegotiation(pc);
|
||||
|
||||
return pc.createOffer()
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => {
|
||||
.then(() => negotiated)
|
||||
.then(({nextPromise}) => {
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
pc.createDataChannel('test');
|
||||
return promise;
|
||||
return nextPromise;
|
||||
});
|
||||
}, 'negotiationneeded event should not fire if signaling state is not stable');
|
||||
|
||||
/*
|
||||
4.3.1. RTCPeerConnection Operation
|
||||
To set an RTCSessionDescription description
|
||||
10. If connection's signaling state is now stable, update the
|
||||
negotiation-needed flag. If connection's [[needNegotiation]] slot
|
||||
was true both before and after this update, queue a task that runs
|
||||
the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
2. If connection's [[needNegotiation]] slot is false, abort these steps.
|
||||
3. Fire a simple event named negotiationneeded at connection.
|
||||
4.4.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.10. If connection's signaling state is now stable, update the negotiation-needed
|
||||
flag. If connection's [[NegotiationNeeded]] slot was true both before and after
|
||||
this update, queue a task that runs the following steps:
|
||||
2. If connection's [[NegotiationNeeded]] slot is false, abort these steps.
|
||||
3. Fire a simple event named negotiationneeded at connection.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return assert_first_promise_fulfill_after_second(
|
||||
awaitNegotiation(pc),
|
||||
pc.createOffer()
|
||||
pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => {
|
||||
|
@ -243,7 +202,6 @@
|
|||
4.7.3. Updating the Negotiation-Needed flag
|
||||
|
||||
To update the negotiation-needed flag
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
3. If the result of checking if negotiation is needed is "false",
|
||||
clear the negotiation-needed flag by setting connection's
|
||||
[[needNegotiation]] slot to false, and abort these steps.
|
||||
|
@ -293,6 +251,12 @@
|
|||
|
||||
stop
|
||||
11. Update the negotiation-needed flag for connection.
|
||||
|
||||
Untestable
|
||||
4.7.3. Updating the Negotiation-Needed flag
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
6. Queue a task that runs the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
*/
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.ontrack</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// getTrackFromUserMedia
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.8. If description is set as a remote description, then run the following
|
||||
steps for each media description in description:
|
||||
3. Set transceiver's mid value to the mid of the corresponding media
|
||||
description. If the media description has no MID, and transceiver's
|
||||
mid is unset, generate a random value as described in [JSEP] (section 5.9.).
|
||||
4. If the direction of the media description is sendrecv or sendonly, and
|
||||
transceiver.receiver.track has not yet been fired in a track event,
|
||||
process the remote track for the media description, given transceiver.
|
||||
|
||||
5.1.1. Processing Remote MediaStreamTracks
|
||||
To process the remote track for an incoming media description [JSEP]
|
||||
(section 5.9.) given RTCRtpTransceiver transceiver, the user agent MUST
|
||||
run the following steps:
|
||||
|
||||
1. Let connection be the RTCPeerConnection object associated with transceiver.
|
||||
2. Let streams be a list of MediaStream objects that the media description
|
||||
indicates the MediaStreamTrack belongs to.
|
||||
3. Add track to all MediaStream objects in streams.
|
||||
4. Queue a task to fire an event named track with transceiver, track, and
|
||||
streams at the connection object.
|
||||
|
||||
5.7. RTCTrackEvent
|
||||
[Constructor(DOMString type, RTCTrackEventInit eventInitDict)]
|
||||
interface RTCTrackEvent : Event {
|
||||
readonly attribute RTCRtpReceiver receiver;
|
||||
readonly attribute MediaStreamTrack track;
|
||||
[SameObject]
|
||||
readonly attribute FrozenArray<MediaStream> streams;
|
||||
readonly attribute RTCRtpTransceiver transceiver;
|
||||
};
|
||||
|
||||
[mediacapture-main]
|
||||
4.2. MediaStream
|
||||
interface MediaStream : EventTarget {
|
||||
readonly attribute DOMString id;
|
||||
sequence<MediaStreamTrack> getTracks();
|
||||
...
|
||||
};
|
||||
|
||||
[mediacapture-main]
|
||||
4.3. MediaStreamTrack
|
||||
interface MediaStreamTrack : EventTarget {
|
||||
readonly attribute DOMString kind;
|
||||
readonly attribute DOMString id;
|
||||
...
|
||||
};
|
||||
*/
|
||||
|
||||
function validateTrackEvent(trackEvent) {
|
||||
const { receiver, track, streams, transceiver } = trackEvent;
|
||||
|
||||
assert_true(track instanceof MediaStreamTrack,
|
||||
'Expect track to be instance of MediaStreamTrack');
|
||||
|
||||
assert_true(Array.isArray(streams),
|
||||
'Expect streams to be an array');
|
||||
|
||||
for(const mediaStream of streams) {
|
||||
assert_true(mediaStream instanceof MediaStream,
|
||||
'Expect elements in streams to be instance of MediaStream');
|
||||
|
||||
assert_true(mediaStream.getTracks().includes(track),
|
||||
'Expect each mediaStream to have track as one of their tracks');
|
||||
}
|
||||
|
||||
assert_true(receiver instanceof RTCRtpReceiver,
|
||||
'Expect trackEvent.receiver to be defined and is instance of RTCRtpReceiver');
|
||||
|
||||
assert_equals(receiver.track, track,
|
||||
'Expect trackEvent.receiver.track to be the same as trackEvent.track');
|
||||
|
||||
assert_true(transceiver instanceof RTCRtpTransceiver,
|
||||
'Expect trackEvent.transceiver to be defined and is instance of RTCRtpTransceiver');
|
||||
|
||||
assert_equals(transceiver.receiver, receiver,
|
||||
'Expect trackEvent.transceiver.receiver to be the same as trackEvent.receiver');
|
||||
}
|
||||
|
||||
// tests that ontrack is called and parses the msid information from the SDP and creates
|
||||
// the streams with matching identifiers.
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
// Fail the test if the ontrack event handler is not implemented
|
||||
assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
|
||||
|
||||
const sdp = `v=0
|
||||
o=- 166855176514521964 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
a=msid-semantic:WMS *
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:someufrag
|
||||
a=ice-pwd:somelongpwdwithenoughrandomness
|
||||
a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
|
||||
a=setup:actpass
|
||||
a=rtcp-mux
|
||||
a=mid:mid1
|
||||
a=sendonly
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=msid:stream1 track1
|
||||
a=ssrc:1001 cname:some
|
||||
`;
|
||||
|
||||
pc.ontrack = t.step_func(trackEvent => {
|
||||
const { streams, track, transceiver } = trackEvent;
|
||||
|
||||
assert_equals(streams.length, 1,
|
||||
'the track belongs to one MediaStream');
|
||||
|
||||
const [stream] = streams;
|
||||
assert_equals(stream.id, 'stream1',
|
||||
'Expect stream.id to be the same as specified in the a=msid line');
|
||||
|
||||
assert_equals(track.kind, 'audio',
|
||||
'Expect track.kind to be audio');
|
||||
|
||||
validateTrackEvent(trackEvent);
|
||||
|
||||
assert_equals(transceiver.direction, 'recvonly',
|
||||
'Expect transceiver.direction to be reverse of sendonly (recvonly)');
|
||||
|
||||
t.done();
|
||||
});
|
||||
|
||||
pc.setRemoteDescription({ type: 'offer', sdp })
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached('Error ' + err.name + ': ' + err.message);
|
||||
}));
|
||||
}, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.');
|
||||
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
assert_idl_attribute(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
|
||||
|
||||
const sdp = `v=0
|
||||
o=- 166855176514521964 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
a=msid-semantic:WMS *
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:someufrag
|
||||
a=ice-pwd:somelongpwdwithenoughrandomness
|
||||
a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
|
||||
a=setup:actpass
|
||||
a=rtcp-mux
|
||||
a=mid:mid1
|
||||
a=recvonly
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=msid:stream1 track1
|
||||
a=ssrc:1001 cname:some
|
||||
`;
|
||||
|
||||
pc.ontrack = t.unreached_func('ontrack event should not fire for track with recvonly direction');
|
||||
|
||||
pc.setRemoteDescription({ type: 'offer', sdp })
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached('Error ' + err.name + ': ' + err.message);
|
||||
}))
|
||||
.then(t.step_func(() => {
|
||||
t.step_timeout(t.step_func_done(), 100);
|
||||
}));
|
||||
|
||||
}, 'setRemoteDescription() with m= line of recvonly direction should not trigger track event');
|
||||
|
||||
async_test(t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
pc2.ontrack = t.step_func(trackEvent => {
|
||||
const { track } = trackEvent;
|
||||
|
||||
assert_equals(track.kind, 'audio',
|
||||
'Expect track.kind to be audio');
|
||||
|
||||
validateTrackEvent(trackEvent);
|
||||
|
||||
t.done();
|
||||
});
|
||||
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
pc1.addTrack(track, mediaStream);
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => pc2.setRemoteDescription(offer));
|
||||
})
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached('Error ' + err.name + ': ' + err.message);
|
||||
}));
|
||||
|
||||
}, 'addTrack() should cause remote connection to fire ontrack when setRemoteDescription()');
|
||||
|
||||
async_test(t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
pc2.ontrack = t.step_func(trackEvent => {
|
||||
const { track } = trackEvent;
|
||||
|
||||
assert_equals(track.kind, 'video',
|
||||
'Expect track.kind to be video');
|
||||
|
||||
validateTrackEvent(trackEvent);
|
||||
|
||||
t.done();
|
||||
});
|
||||
|
||||
pc1.addTransceiver('video');
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => pc2.setRemoteDescription(offer))
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached('Error ' + err.name + ': ' + err.message);
|
||||
}));
|
||||
|
||||
}, `addTransceiver('video') should cause remote connection to fire ontrack when setRemoteDescription()`);
|
||||
|
||||
async_test(t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
pc2.ontrack = t.step_func(trackEvent => {
|
||||
const { track } = trackEvent;
|
||||
|
||||
assert_equals(track.kind, 'video',
|
||||
'Expect track.kind to be video');
|
||||
|
||||
validateTrackEvent(trackEvent);
|
||||
|
||||
t.done();
|
||||
});
|
||||
|
||||
pc1.addTransceiver('audio', { direction: 'inactive' });
|
||||
pc2.ontrack = t.unreached_func('ontrack event should not fire for track with inactive direction');
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => pc2.setRemoteDescription(offer))
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached('Error ' + err.name + ': ' + err.message);
|
||||
}))
|
||||
.then(t.step_func(() => {
|
||||
t.step_timeout(t.step_func_done(), 100);
|
||||
}));
|
||||
|
||||
}, `addTransceiver() with inactive direction should not cause remote connection to fire ontrack when setRemoteDescription()`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,318 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.peerIdentity</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="identity-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The tests here interacts with the mock identity provider located at
|
||||
// /.well-known/idp-proxy/mock-idp.js
|
||||
|
||||
// The following helper functions are called from identity-helper.js
|
||||
// parseAssertionResult
|
||||
// getIdpDomains
|
||||
// assert_rtcerror_rejection
|
||||
// hostString
|
||||
|
||||
/*
|
||||
9.6. RTCPeerConnection Interface Extensions
|
||||
partial interface RTCPeerConnection {
|
||||
void setIdentityProvider(DOMString provider,
|
||||
optional RTCIdentityProviderOptions options);
|
||||
Promise<DOMString> getIdentityAssertion();
|
||||
readonly attribute Promise<RTCIdentityAssertion> peerIdentity;
|
||||
readonly attribute DOMString? idpLoginUrl;
|
||||
readonly attribute DOMString? idpErrorInfo;
|
||||
};
|
||||
|
||||
dictionary RTCIdentityProviderOptions {
|
||||
DOMString protocol = "default";
|
||||
DOMString usernameHint;
|
||||
DOMString peerIdentity;
|
||||
};
|
||||
|
||||
[Constructor(DOMString idp, DOMString name)]
|
||||
interface RTCIdentityAssertion {
|
||||
attribute DOMString idp;
|
||||
attribute DOMString name;
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.2. setRemoteDescription
|
||||
If an a=identity attribute is present in the session description, the browser
|
||||
validates the identity assertion..
|
||||
|
||||
If the "peerIdentity" configuration is applied to the RTCPeerConnection, this
|
||||
establishes a target peer identity of the provided value. Alternatively, if the
|
||||
RTCPeerConnection has previously authenticated the identity of the peer (that
|
||||
is, there is a current value for peerIdentity ), then this also establishes a
|
||||
target peer identity.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
pc1.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => pc2.setRemoteDescription(offer))
|
||||
.then(() => pc2.peerIdentity)
|
||||
.then(identityAssertion => {
|
||||
const { idp, name } = identityAssertion;
|
||||
assert_equals(idp, idpDomain, `Expect IdP domain to be ${idpDomain}`);
|
||||
assert_equals(identityAssertion, `alice@${idpDomain}`,
|
||||
`Expect validated identity from mock-idp.js to be same as specified in usernameHint`);
|
||||
});
|
||||
}, 'setRemoteDescription() on offer with a=identity should establish peerIdentity');
|
||||
|
||||
/*
|
||||
4.3.2. setRemoteDescription
|
||||
The target peer identity cannot be changed once set. Once set, if a different
|
||||
value is provided, the user agent MUST reject the returned promise with a newly
|
||||
created InvalidModificationError and abort this operation. The RTCPeerConnection
|
||||
MUST be closed if the validated peer identity does not match the target peer
|
||||
identity.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection({
|
||||
peerIdentity: `bob@${idpDomain}`
|
||||
});
|
||||
|
||||
pc1.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidModificationError',
|
||||
pc2.setRemoteDescription(offer)))
|
||||
.then(() => {
|
||||
assert_true(pc2.signalingState, 'closed',
|
||||
'Expect peer connection to be closed after mismatch peer identity');
|
||||
});
|
||||
}, 'setRemoteDescription() on offer with a=identity that resolve to value different from target peer identity should reject with InvalidModificationError');
|
||||
|
||||
/*
|
||||
9.4. Verifying Identity Assertions
|
||||
8. The RTCPeerConnection decodes the contents and validates that it contains a
|
||||
fingerprint value for every a=fingerprint attribute in the session description.
|
||||
This ensures that the certificate used by the remote peer for communications
|
||||
is covered by the identity assertion.
|
||||
|
||||
If identity validation fails, the peerIdentity promise is rejected with a newly
|
||||
created OperationError.
|
||||
|
||||
If identity validation fails and there is a target peer identity for the
|
||||
RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected
|
||||
with the same DOMException.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection({
|
||||
peerIdentity: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
// Ask mockidp.js to return custom contents in validation result
|
||||
pc1.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js?validatorAction=return-custom-contents&contents=bogus',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
const peerIdentityPromise = pc2.peerIdentity;
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => Promise.all([
|
||||
promise_rejects(t, 'OperationError',
|
||||
pc2.setRemoteDescription(offer)),
|
||||
promise_rejects(t, 'OperationError',
|
||||
peerIdentityPromise)
|
||||
]));
|
||||
}, 'setRemoteDescription() with peerIdentity set and with IdP proxy that return validationAssertion with mismatch contents should reject with OperationError');
|
||||
|
||||
/*
|
||||
9.4. Verifying Identity Assertions
|
||||
9. The RTCPeerConnection validates that the domain portion of the identity matches
|
||||
the domain of the IdP as described in [RTCWEB-SECURITY-ARCH]. If this check
|
||||
fails then the identity validation fails.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const port = window.location.port;
|
||||
const [idpDomain1, idpDomain2] = getIdpDomains();
|
||||
assert_not_equals(idpDomain1, idpDomain2,
|
||||
'Sanity check two idpDomains are different');
|
||||
|
||||
const idpHost1 = hostString(idpDomain1, port);
|
||||
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection({
|
||||
peerIdentity: `alice@${idpDomain2}`
|
||||
});
|
||||
|
||||
// mock-idp.js will return assertion of domain2 identity
|
||||
// with domain1 in the idp.domain field
|
||||
pc1.setIdentityProvider(idpHost1, {
|
||||
protocol: 'mock-idp.js',
|
||||
usernameHint: `alice@${idpDomain2}`
|
||||
});
|
||||
|
||||
return pc1.getIdentityAssertion()
|
||||
.then(assertionResultStr => {
|
||||
const { idp, assertion } = parseAssertionResult(assertionResultStr);
|
||||
|
||||
assert_equals(idp.domain, idpDomain1,
|
||||
'Sanity check domain of assertion is domain1');
|
||||
|
||||
assert_equals(assertion.options.usernameHint, `alice@${idpDomain2}`,
|
||||
'Sanity check domain1 is going to validate a domain2 identity');
|
||||
|
||||
return pc1.createOffer();
|
||||
})
|
||||
.then(offer => Promise.all([
|
||||
promise_rejects(t, 'OperationError',
|
||||
pc2.setRemoteDescription(offer)),
|
||||
promise_rejects(t, 'OperationError',
|
||||
pc2.peerIdentity)
|
||||
]));
|
||||
}, 'setRemoteDescription() and peerIdentity should reject with OperationError if IdP return validated identity that is different from its own domain');
|
||||
|
||||
/*
|
||||
9.4 Verifying Identity Assertions
|
||||
If identity validation fails and there is a target peer identity for the
|
||||
RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected
|
||||
with the same DOMException.
|
||||
|
||||
9.5 IdP Error Handling
|
||||
- If an identity provider throws an exception or returns a promise that is ultimately
|
||||
rejected, then the procedure that depends on the IdP MUST also fail. These types of
|
||||
errors will cause an IdP failure with an RTCError with errorDetail set to
|
||||
"idp-execution-failure".
|
||||
|
||||
Any error generated by the IdP MAY provide additional information in the
|
||||
idpErrorInfo attribute. The information in this string is defined by the
|
||||
IdP in use.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection({
|
||||
peerIdentity: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
// Ask mock-idp.js to throw error during validation,
|
||||
// i.e. during pc2.setRemoteDescription()
|
||||
pc1.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js?validatorAction=throw-error&errorInfo=bar',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => Promise.all([
|
||||
assert_rtcerror_rejection('idp-execution-failure',
|
||||
pc2.setRemoteDescription(offer)),
|
||||
assert_rtcerror_rejection('idp-execution-failure',
|
||||
pc2.peerIdentity)
|
||||
]))
|
||||
.then(() => {
|
||||
assert_equals(pc2.idpErrorInfo, 'bar',
|
||||
'Expect pc2.idpErrorInfo to be set to the err.idpErrorInfo thrown by mock-idp.js');
|
||||
});
|
||||
}, `When IdP throws error and pc has target peer identity, setRemoteDescription() and peerIdentity rejected with RTCError('idp-execution-error')`);
|
||||
|
||||
/*
|
||||
4.3.2. setRemoteDescription
|
||||
If there is no target peer identity, then setRemoteDescription does not await the
|
||||
completion of identity validation.
|
||||
|
||||
9.5. IdP Error Handling
|
||||
- If an identity provider throws an exception or returns a promise that is
|
||||
ultimately rejected, then the procedure that depends on the IdP MUST also fail.
|
||||
These types of errors will cause an IdP failure with an RTCError with errorDetail
|
||||
set to "idp-execution-failure".
|
||||
|
||||
9.4. Verifying Identity Assertions
|
||||
If identity validation fails and there is no a target peer identity, the value of
|
||||
the peerIdentity MUST be set to a new, unresolved promise instance. This permits
|
||||
the use of renegotiation (or a subsequent answer, if the session description was
|
||||
a provisional answer) to resolve or reject the identity.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
const port = window.location.port;
|
||||
const [idpDomain] = getIdpDomains();
|
||||
const idpHost = hostString(idpDomain, port);
|
||||
|
||||
// Ask mock-idp.js to throw error during validation,
|
||||
// i.e. during pc2.setRemoteDescription()
|
||||
pc1.setIdentityProvider(idpHost, {
|
||||
protocol: 'mock-idp.js?validatorAction=throw-error',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
const peerIdentityPromise1 = pc2.peerIdentity;
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer =>
|
||||
// setRemoteDescription should succeed because there is no target peer identity set
|
||||
pc2.setRemoteDescription(offer))
|
||||
.then(() =>
|
||||
assert_rtcerror_rejection('idp-execution-failure',
|
||||
peerIdentityPromise,
|
||||
`Expect first peerIdentity promise to be rejected with RTCError('idp-execution-failure')`))
|
||||
.then(() => {
|
||||
const peerIdentityPromise2 = pc2.peerIdentity;
|
||||
assert_not_equals(peerIdentityPromise2, peerIdentityPromise1,
|
||||
'Expect pc2.peerIdentity to be replaced with a fresh unresolved promise');
|
||||
|
||||
// regenerate an identity assertion with no test option to throw error
|
||||
pc1.setIdentityProvider(idpHost, {
|
||||
protocol: 'idp-test.js',
|
||||
usernameHint: `alice@${idpDomain}`
|
||||
});
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => pc2.setRemoteDescription(offer))
|
||||
.then(peerIdentityPromise2)
|
||||
.then(identityAssertion => {
|
||||
const { idp, name } = identityAssertion;
|
||||
|
||||
assert_equals(idp, idpDomain,
|
||||
`Expect IdP domain to be ${idpDomain}`);
|
||||
|
||||
assert_equals(name, `alice@${idpDomain}`,
|
||||
`Expect validated identity to be alice@${idpDomain}`);
|
||||
|
||||
assert_equals(pc2.peeridentity, peerIdentityPromise2,
|
||||
'Expect pc2.peerIdentity to stay fixed after identity is validated');
|
||||
});
|
||||
});
|
||||
}, 'IdP failure with no target peer identity should have following setRemoteDescription() succeed and replace pc.peerIdentity with a new promise');
|
||||
|
||||
</script>
|
|
@ -24,6 +24,13 @@
|
|||
};
|
||||
*/
|
||||
|
||||
// Before calling removeTrack can be tested, one needs to add MediaStreamTracks to
|
||||
// a peer connection. There are two ways for adding MediaStreamTrack: addTrack and
|
||||
// addTransceiver. addTransceiver is a newer API while addTrack has been implemented
|
||||
// in current browsers for some time. As a result some of the removeTrack tests have
|
||||
// two versions so that removeTrack can be partially tested without addTransceiver
|
||||
// and the transceiver APIs being implemented.
|
||||
|
||||
/*
|
||||
5.1. removeTrack
|
||||
3. If connection's [[isClosed]] slot is true, throw an InvalidStateError.
|
||||
|
@ -37,7 +44,24 @@
|
|||
pc.close();
|
||||
assert_throws('InvalidStateError', () => pc.removeTrack(sender));
|
||||
|
||||
}, 'Calling removeTrack when connection is closed should throw InvalidStateError');
|
||||
}, 'addTransceiver - Calling removeTrack when connection is closed should throw InvalidStateError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const sender = pc.addTrack(track, mediaStream);
|
||||
|
||||
pc.close();
|
||||
assert_throws('InvalidStateError', () => pc.removeTrack(sender));
|
||||
});
|
||||
}, 'addTrack - Calling removeTrack when connection is closed should throw InvalidStateError');
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -49,7 +73,25 @@
|
|||
pc2.close();
|
||||
assert_throws('InvalidStateError', () => pc2.removeTrack(sender));
|
||||
|
||||
}, 'Calling removeTrack on different connection that is closed should throw InvalidStateError');
|
||||
}, 'addTransceiver - Calling removeTrack on different connection that is closed should throw InvalidStateError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const sender = pc.addTrack(track, mediaStream);
|
||||
|
||||
const pc2 = new RTCPeerConnection();
|
||||
pc2.close();
|
||||
assert_throws('InvalidStateError', () => pc2.removeTrack(sender));
|
||||
});
|
||||
}, 'addTrack - Calling removeTrack on different connection that is closed should throw InvalidStateError');
|
||||
|
||||
/*
|
||||
5.1. removeTrack
|
||||
|
@ -64,7 +106,24 @@
|
|||
const pc2 = new RTCPeerConnection();
|
||||
assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
|
||||
|
||||
}, 'Calling removeTrack on different connection should throw InvalidAccessError');
|
||||
}, 'addTransceiver - Calling removeTrack on different connection should throw InvalidAccessError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const sender = pc.addTrack(track, mediaStream);
|
||||
|
||||
const pc2 = new RTCPeerConnection();
|
||||
assert_throws('InvalidAccessError', () => pc2.removeTrack(sender));
|
||||
});
|
||||
}, 'addTrack - Calling removeTrack on different connection should throw InvalidAccessError')
|
||||
|
||||
/*
|
||||
5.1. removeTrack
|
||||
|
@ -85,7 +144,26 @@
|
|||
assert_equals(transceiver.direction, 'sendrecv',
|
||||
'direction should not be altered');
|
||||
|
||||
}, 'Calling removeTrack with valid sender should set sender.track to null');
|
||||
}, 'addTransceiver - Calling removeTrack with valid sender should set sender.track to null');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect getUserMedia to return at least one audio track');
|
||||
|
||||
const track = tracks[0];
|
||||
const sender = pc.addTrack(track, mediaStream);
|
||||
|
||||
assert_equals(sender.track, track);
|
||||
|
||||
pc.removeTrack(sender);
|
||||
assert_equals(sender.track, null);
|
||||
});
|
||||
}, 'addTrack - Calling removeTrack with valid sender should set sender.track to null');
|
||||
|
||||
/*
|
||||
5.1. removeTrack
|
|
@ -0,0 +1,267 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection Set Session Description - Transceiver Tests</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateAnswer
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setLocalDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
|
||||
5.4. RTCRtpTransceiver Interface
|
||||
|
||||
interface RTCRtpTransceiver {
|
||||
readonly attribute DOMString? mid;
|
||||
[SameObject]
|
||||
readonly attribute RTCRtpSender sender;
|
||||
[SameObject]
|
||||
readonly attribute RTCRtpReceiver receiver;
|
||||
readonly attribute RTCRtpTransceiverDirection direction;
|
||||
readonly attribute RTCRtpTransceiverDirection? currentDirection;
|
||||
...
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
7. If description is set as a local description, then run the following steps for
|
||||
each media description in description that is not yet associated with an
|
||||
RTCRtpTransceiver object:
|
||||
1. Let transceiver be the RTCRtpTransceiver used to create the media
|
||||
description.
|
||||
2. Set transceiver's mid value to the mid of the corresponding media
|
||||
description.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
assert_equals(transceiver.mid, null);
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => {
|
||||
assert_equals(transceiver.mid, null,
|
||||
'Expect transceiver.mid to still be null after createOffer');
|
||||
|
||||
return pc.setLocalDescription(offer)
|
||||
.then(() => {
|
||||
assert_equals(typeof transceiver.mid, 'string',
|
||||
'Expect transceiver.mid to set to valid string value');
|
||||
|
||||
assert_equals(offer.sdp.includes(`\r\na=mid:${transceiver.mid}`), true,
|
||||
'Expect transceiver mid to be found in offer SDP');
|
||||
});
|
||||
});
|
||||
}, 'setLocalDescription(offer) with m= section should assign mid to corresponding transceiver');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
8. If description is set as a remote description, then run the following steps
|
||||
for each media description in description:
|
||||
2. If no suitable transceiver is found (transceiver is unset), run the following
|
||||
steps:
|
||||
1. Create an RTCRtpSender, sender, from the media description.
|
||||
2. Create an RTCRtpReceiver, receiver, from the media description.
|
||||
3. Create an RTCRtpTransceiver with sender, receiver and direction, and let
|
||||
transceiver be the result.
|
||||
3. Set transceiver's mid value to the mid of the corresponding media description.
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
const transceiver1 = pc1.addTransceiver('audio');
|
||||
assert_array_equals(pc1.getTransceivers(), [transceiver1]);
|
||||
assert_array_equals(pc2.getTransceivers(), []);
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => {
|
||||
return Promise.all([
|
||||
pc1.setLocalDescription(offer),
|
||||
pc2.setRemoteDescrption(offer)
|
||||
])
|
||||
.then(() => {
|
||||
const transceivers = pc2.getTransceivers();
|
||||
assert_equals(transceivers.length, 1,
|
||||
'Expect new transceiver added to pc2 after setRemoteDescription');
|
||||
|
||||
const [ transceiver2 ] = transceivers;
|
||||
|
||||
assert_equals(typeof transceiver2.mid, 'string',
|
||||
'Expect transceiver2.mid to be set');
|
||||
|
||||
assert_equals(transceiver1.mid, transceiver2.mid,
|
||||
'Expect transceivers of both side to have the same mid');
|
||||
|
||||
assert_equals(offer.sdp.includes(`\r\na=mid:${transceiver2.mid}`), true,
|
||||
'Expect transceiver mid to be found in offer SDP');
|
||||
});
|
||||
});
|
||||
}, 'setRemoteDescription(offer) with m= section and no existing transceiver should create corresponding transceiver');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
9. If description is of type "rollback", then run the following steps:
|
||||
1. If the mid value of an RTCRtpTransceiver was set to a non-null value by
|
||||
the RTCSessionDescription that is being rolled back, set the mid value
|
||||
of that transceiver to null, as described by [JSEP] (section 4.1.8.2.).
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
assert_equals(transceiver.mid, null);
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => {
|
||||
assert_equals(transceiver.mid, null);
|
||||
return pc.setLocalDescription(offer);
|
||||
})
|
||||
.then(() => {
|
||||
assert_not_equals(transceiver.mid, null);
|
||||
return pc.setLocalDescription({ type: 'rollback' });
|
||||
})
|
||||
.then(() => {
|
||||
assert_equals(transceiver.mid, null,
|
||||
'Expect transceiver.mid to become null again after rollback');
|
||||
});
|
||||
}, 'setLocalDescription(rollback) should unset transceiver.mid');
|
||||
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver1 = pc.addTransceiver('audio');
|
||||
assert_equals(transceiver1.mid, null);
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer => pc.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
// pc is back to stable state
|
||||
// create another transceiver
|
||||
const transceiver2 = pc.addTransceiver('video');
|
||||
|
||||
assert_not_equals(transceiver1.mid, null);
|
||||
assert_equals(transceiver2.mid, null);
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => {
|
||||
assert_not_equals(transceiver1.mid, null);
|
||||
assert_not_equals(transceiver2.mid, null,
|
||||
'Expect transceiver2.mid to become set');
|
||||
|
||||
return pc.setLocalDescription({ type: 'rollback' });
|
||||
})
|
||||
.then(() => {
|
||||
assert_not_equals(transceiver1.mid, null,
|
||||
'Expect transceiver1.mid to stay set');
|
||||
|
||||
assert_equals(transceiver2.mid, null,
|
||||
'Expect transceiver2.mid to be rolled back to null');
|
||||
});
|
||||
})
|
||||
}, 'setLocalDescription(rollback) should only unset transceiver mids associated with current round');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
9. If description is of type "rollback", then run the following steps:
|
||||
2. If an RTCRtpTransceiver was created by applying the RTCSessionDescription
|
||||
that is being rolled back, and a track has not been attached to it via
|
||||
addTrack, remove that transceiver from connection's set of transceivers,
|
||||
as described by [JSEP] (section 4.1.8.2.).
|
||||
*/
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
pc1.addTransceiver('audio');
|
||||
|
||||
return pc1.createOffer()
|
||||
.then(offer => pc2.setRemoteDescription(offer))
|
||||
.then(() => {
|
||||
const transceivers = pc2.getTransceivers();
|
||||
assert_equals(transceivers.length, 1);
|
||||
const [ transceiver ] = transceivers;
|
||||
|
||||
assert_equals(typeof transceiver.mid, 'string',
|
||||
'Expect transceiver.mid to be set');
|
||||
|
||||
return pc2.setRemoteDescription({ type: 'rollback' })
|
||||
.then(() => {
|
||||
assert_equals(transceiver.mid, null,
|
||||
'Expect transceiver.mid to be unset');
|
||||
|
||||
assert_array_equals(pc2.getTransceivers(), [],
|
||||
`Expect transceiver to be removed from pc2's transceiver list`);
|
||||
});
|
||||
});
|
||||
}, 'setRemoteDescription(rollback) should remove newly created transceiver from transceiver list');
|
||||
|
||||
/*
|
||||
TODO
|
||||
- Steps for transceiver direction is added to tip of tree draft, but not yet
|
||||
published as editor's draft
|
||||
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
8. If description is set as a remote description, then run the following steps
|
||||
for each media description in description:
|
||||
1. As described by [JSEP] (section 5.9.), attempt to find an existing
|
||||
RTCRtpTransceiver object, transceiver, to represent the media description.
|
||||
3. If the media description has no MID, and transceiver's mid is unset, generate
|
||||
a random value as described in [JSEP] (section 5.9.).
|
||||
4. If the direction of the media description is sendrecv or sendonly, and
|
||||
transceiver.receiver.track has not yet been fired in a track event, process
|
||||
the remote track for the media description, given transceiver.
|
||||
5. If the media description is rejected, and transceiver is not already stopped,
|
||||
stop the RTCRtpTransceiver transceiver.
|
||||
|
||||
[JSEP]
|
||||
5.9. Applying a Remote Description
|
||||
- If the m= section is not associated with any RtpTransceiver
|
||||
(possibly because it was dissociated in the previous step),
|
||||
either find an RtpTransceiver or create one according to the
|
||||
following steps:
|
||||
|
||||
- If the m= section is sendrecv or recvonly, and there are
|
||||
RtpTransceivers of the same type that were added to the
|
||||
PeerConnection by addTrack and are not associated with any
|
||||
m= section and are not stopped, find the first (according to
|
||||
the canonical order described in Section 5.2.1) such
|
||||
RtpTransceiver.
|
||||
|
||||
- If no RtpTransceiver was found in the previous step, create
|
||||
one with a recvonly direction.
|
||||
*/
|
||||
</script>
|
|
@ -0,0 +1,164 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setLocalDescription</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer
|
||||
// generateAnswer
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.2. If description is set as a local description, then run one of the following
|
||||
steps:
|
||||
|
||||
- If description is of type "answer", then this completes an offer answer
|
||||
negotiation.
|
||||
|
||||
Set connection's currentLocalDescription to description and
|
||||
currentRemoteDescription to the value of pendingRemoteDescription.
|
||||
|
||||
Set both pendingRemoteDescription and pendingLocalDescription to null.
|
||||
|
||||
Finally set connection's signaling state to stable.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-remote-offer', 'stable']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer =>
|
||||
pc.setLocalDescription(answer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_session_desc_equals(pc.localDescription, answer);
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, answer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, offer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
})));
|
||||
}, 'setLocalDescription() with valid answer should succeed');
|
||||
|
||||
/*
|
||||
4.3.2. setLocalDescription
|
||||
3. Let lastAnswer be the result returned by the last call to createAnswer.
|
||||
4. If description.sdp is null and description.type is answer, set description.sdp
|
||||
to lastAnswer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer =>
|
||||
pc.setLocalDescription({ type: 'answer' })
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_session_desc_equals(pc.localDescription, answer);
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, answer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, offer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
})));
|
||||
}, 'setLocalDescription() with type answer and null sdp should use lastAnswer generated from createAnswer');
|
||||
|
||||
/*
|
||||
4.3.2. setLocalDescription
|
||||
3. Let lastAnswer be the result returned by the last call to createAnswer.
|
||||
7. If description.type is answer and description.sdp does not match lastAnswer,
|
||||
reject the promise with a newly created InvalidModificationError and abort these
|
||||
steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer =>
|
||||
promise_rejects(t, 'InvalidModificationError',
|
||||
pc.setLocalDescription(answer))));
|
||||
}, 'setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps.
|
||||
|
||||
[jsep]
|
||||
5.5. If the type is "pranswer" or "answer", the PeerConnection
|
||||
state MUST be either "have-remote-offer" or "have-local-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription({ type: 'answer', sdp: offer.sdp })));
|
||||
|
||||
}, 'Calling setLocalDescription(answer) from stable state should reject with InvalidStateError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription(answer)));
|
||||
}, 'Calling setLocalDescription(answer) from have-local-offer state should reject with InvalidStateError');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,147 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setLocalDescription</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer
|
||||
// assert_session_desc_not_equals
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.2. setLocalDescription
|
||||
2. Let lastOffer be the result returned by the last call to createOffer.
|
||||
5. If description.sdp is null and description.type is offer, set description.sdp
|
||||
to lastOffer.
|
||||
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.2. If description is set as a local description, then run one of the following
|
||||
steps:
|
||||
- If description is of type "offer", set connection.pendingLocalDescription
|
||||
to description and signaling state to have-local-offer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-local-offer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
}));
|
||||
}, 'setLocalDescription with valid offer should succeed');
|
||||
|
||||
/*
|
||||
4.3.2. setLocalDescription
|
||||
2. Let lastOffer be the result returned by the last call to createOffer.
|
||||
5. If description.sdp is null and description.type is offer, set description.sdp
|
||||
to lastOffer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription({ type: 'offer' })
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
}));
|
||||
}, 'setLocalDescription with type offer and null sdp should use lastOffer generated from createOffer');
|
||||
|
||||
/*
|
||||
4.3.2. setLocalDescription
|
||||
2. Let lastOffer be the result returned by the last call to createOffer.
|
||||
6. If description.type is offer and description.sdp does not match lastOffer,
|
||||
reject the promise with a newly created InvalidModificationError and abort
|
||||
these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer({ pc, data: true })
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidModificationError',
|
||||
pc.setLocalDescription(offer)));
|
||||
}, 'setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
// Create first offer with audio line, then second offer with
|
||||
// both audio and video line. Since the second offer is the
|
||||
// last offer, setLocalDescription would reject when setting
|
||||
// with the first offer
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer1 =>
|
||||
pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer2 => {
|
||||
assert_session_desc_not_equals(offer1, offer2);
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
pc.setLocalDescription(offer1));
|
||||
}));
|
||||
}, 'Set created offer other than last offer should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
// Only one state change event should be fired
|
||||
test_state_change_event(t, pc, ['have-local-offer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer1 =>
|
||||
pc.setLocalDescription(offer1)
|
||||
.then(() =>
|
||||
pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer2 =>
|
||||
pc.setLocalDescription(offer2)
|
||||
.then(offer2 => {
|
||||
assert_session_desc_not_equals(offer1, offer2);
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_session_desc_equals(pc.localDescription, offer2);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer2);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
}))));
|
||||
}, 'Creating and setting offer multiple times should succeed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,153 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setLocalDescription pranswer</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer
|
||||
// generateAnswer
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setLocalDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? localDescription;
|
||||
readonly attribute RTCSessionDescription? currentLocalDescription;
|
||||
readonly attribute RTCSessionDescription? pendingLocalDescription;
|
||||
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps.
|
||||
|
||||
[jsep]
|
||||
5.5. If the type is "pranswer" or "answer", the PeerConnection
|
||||
state MUST be either "have-remote-offer" or "have-local-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription({ type: 'pranswer', sdp: offer.sdp })));
|
||||
|
||||
}, 'setLocalDescription(pranswer) from stable state should reject with InvalidStateError');
|
||||
|
||||
/*
|
||||
4.3.1.6 Set the RTCSessionSessionDescription
|
||||
2.2.2. If description is set as a local description, then run one of the
|
||||
following steps:
|
||||
- If description is of type "pranswer", then set
|
||||
connection.pendingLocalDescription to description and signaling state to
|
||||
have-local-pranswer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-remote-offer', 'have-local-pranswer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer => {
|
||||
const pranswer = { type: 'pranswer', sdp: answer.sdp };
|
||||
|
||||
return pc.setLocalDescription(pranswer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-local-pranswer');
|
||||
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, offer);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
|
||||
assert_session_desc_equals(pc.localDescription, pranswer);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, pranswer);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
});
|
||||
}));
|
||||
}, 'setLocalDescription(pranswer) should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-remote-offer', 'have-local-pranswer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer => {
|
||||
const pranswer = { type: 'pranswer', sdp: answer.sdp };
|
||||
|
||||
return pc.setLocalDescription(pranswer)
|
||||
.then(() => pc.setLocalDescription(pranswer));
|
||||
}));
|
||||
}, 'setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-remote-offer', 'have-local-pranswer', 'stable']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer => {
|
||||
const pranswer = { type: 'pranswer', sdp: answer.sdp };
|
||||
|
||||
return pc.setLocalDescription(pranswer)
|
||||
.then(() => pc.setLocalDescription(answer))
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_session_desc_equals(pc.localDescription, answer);
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, answer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, offer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
});
|
||||
}));
|
||||
}, 'setLocalDescription(answer) from have-local-pranswer state should succeed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,123 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setLocalDescription rollback</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setLocalDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? localDescription;
|
||||
readonly attribute RTCSessionDescription? currentLocalDescription;
|
||||
readonly attribute RTCSessionDescription? pendingLocalDescription;
|
||||
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.2. If description is set as a local description, then run one of the
|
||||
following steps:
|
||||
- If description is of type "rollback", then this is a rollback. Set
|
||||
connection.pendingLocalDescription to null and signaling state to stable.
|
||||
*/
|
||||
promise_test(t=> {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
test_state_change_event(t, pc, ['have-local-offer', 'stable']);
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_not_equals(pc.localDescription, null);
|
||||
assert_not_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
|
||||
return pc.setLocalDescription({ type: 'rollback' });
|
||||
})
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_equals(pc.localDescription, null);
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
});
|
||||
}, 'setLocalDescription(rollback) from have-local-offer state should reset back to stable state');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps. Note that this implies that once the answerer has performed
|
||||
setLocalDescription with his answer, this cannot be rolled back.
|
||||
|
||||
[jsep]
|
||||
4.1.8.2. Rollback
|
||||
- Rollback can only be used to cancel proposed changes;
|
||||
there is no support for rolling back from a stable state to a
|
||||
previous stable state
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription({ type: 'rollback' }));
|
||||
}, `setLocalDescription(rollback) from stable state should reject with InvalidStateError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer()))
|
||||
.then(answer => pc.setLocalDescription(answer))
|
||||
.then(() => {
|
||||
return promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription({ type: 'rollback' }));
|
||||
});
|
||||
}, `setLocalDescription(rollback) after setting answer description should reject with InvalidStateError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer()
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => pc.setLocalDescription({
|
||||
type: 'rollback',
|
||||
sdp: '!<Invalid SDP Content>;'
|
||||
}));
|
||||
}, `setLocalDescription(rollback) should ignore invalid sdp content and succeed`);
|
||||
</script>
|
|
@ -8,179 +8,40 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer()
|
||||
// generateAnswer()
|
||||
// assert_session_desc_not_equals()
|
||||
// assert_session_desc_equals()
|
||||
// test_state_change_event()
|
||||
// test_never_resolve()
|
||||
|
||||
// generateOffer
|
||||
// assert_session_desc_not_equals
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
* 4.3.2. setLocalDescription(offer)
|
||||
*/
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
/*
|
||||
* 5. If description.sdp is null and description.type is offer, set description.sdp
|
||||
* to lastOffer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription({ type: 'offer' })
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
}));
|
||||
}, 'setLocalDescription with type offer and null sdp should use lastOffer generated from createOffer');
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
/*
|
||||
* 6. If description.type is offer and description.sdp does not match lastOffer,
|
||||
* reject the promise with a newly created InvalidModificationError and abort
|
||||
* these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
return generateOffer({ data: true })
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidModificationError',
|
||||
pc.setLocalDescription(offer)));
|
||||
}, 'setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError');
|
||||
|
||||
/*
|
||||
* 6. If description.type is offer and description.sdp does not match lastOffer,
|
||||
* reject the promise with a newly created InvalidModificationError and abort
|
||||
* these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
// Create first offer with audio line, then second offer with
|
||||
// both audio and video line. Since the second offer is the
|
||||
// last offer, setLocalDescription would reject when setting
|
||||
// with the first offer
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer1 =>
|
||||
pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer2 => {
|
||||
assert_session_desc_not_equals(offer1, offer2);
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
pc.setLocalDescription(offer1));
|
||||
}));
|
||||
}, 'Set created offer other than last offer should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
// Only one state change event should be fired
|
||||
test_state_change_event(t, pc, ['have-local-offer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer1 =>
|
||||
pc.setLocalDescription(offer1)
|
||||
.then(() =>
|
||||
pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer2 =>
|
||||
pc.setLocalDescription(offer2)
|
||||
.then(offer2 => {
|
||||
assert_session_desc_not_equals(offer1, offer2);
|
||||
assert_equals(pc.signalingState, 'have-local-offer');
|
||||
assert_session_desc_equals(pc.localDescription, offer2);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer2);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
}))));
|
||||
}, 'Creating and setting offer multiple times should succeed');
|
||||
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => {
|
||||
const promise = pc.setLocalDescription(offer);
|
||||
pc.close();
|
||||
return promise;
|
||||
});
|
||||
}, 'setLocalDescription(offer) should never resolve if connection is closed in parallel')
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* 4.3.1. Setting an RTCSessionDescription
|
||||
* 2.2.2. If description is set as a local description, then run one of
|
||||
* the following steps:
|
||||
* - If description is of type "rollback", then this is a rollback. Set
|
||||
* connection.pendingLocalDescription to null and signaling state to stable.
|
||||
* - If description is of type "pranswer", then set connection.pendingLocalDescription
|
||||
* to description and signaling state to have-local-pranswer.
|
||||
*/
|
||||
|
||||
|
||||
/* setLocalDescription(answer) */
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-remote-offer', 'stable']);
|
||||
|
||||
return generateOffer({ video: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer =>
|
||||
pc.setLocalDescription(answer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_session_desc_equals(pc.localDescription, answer);
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, answer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, offer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
})));
|
||||
}, 'setLocalDescription() with valid answer should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer({ video: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer =>
|
||||
pc.setLocalDescription({ type: 'answer' })
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_session_desc_equals(pc.localDescription, answer);
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, answer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, offer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
})));
|
||||
}, 'setLocalDescription() with type answer and null sdp should use lastAnswer generated from createAnswer');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer({ video: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer =>
|
||||
promise_rejects(t, 'InvalidModificationError',
|
||||
pc.setLocalDescription(answer))));
|
||||
}, 'setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError');
|
||||
|
||||
/*
|
||||
* Operations after returning to stable state
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
promise_test(t => {
|
||||
|
@ -212,7 +73,7 @@
|
|||
test_state_change_event(t, pc,
|
||||
['have-remote-offer', 'stable', 'have-local-offer']);
|
||||
|
||||
return generateOffer({ data: true })
|
||||
return generateOffer({ pc, data: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer =>
|
||||
|
@ -230,41 +91,13 @@
|
|||
}, 'Switching role from answerer to offerer after going back to stable state should succeed');
|
||||
|
||||
/*
|
||||
* InvalidStateError
|
||||
* [webrtc-pc] 4.3.1. Setting the RTCSessionDescription
|
||||
* 2.3. If the description's type is invalid for the current signaling state
|
||||
* of connection, then reject p with a newly created InvalidStateError
|
||||
* and abort these steps.
|
||||
TODO
|
||||
4.3.2. setLocalDescription
|
||||
4. If description.sdp is null and description.type is pranswer, set description.sdp
|
||||
to lastAnswer.
|
||||
7. If description.type is pranswer and description.sdp does not match lastAnswer,
|
||||
reject the promise with a newly created InvalidModificationError and abort these
|
||||
steps.
|
||||
*/
|
||||
|
||||
/*
|
||||
* [jsep] 5.5. If the type is "pranswer" or "answer", the PeerConnection
|
||||
* state MUST be either "have-remote-offer" or "have-local-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription({ type: 'answer', sdp: offer.sdp })));
|
||||
|
||||
}, 'Calling setLocalDescription(answer) from stable state should reject with InvalidStateError');
|
||||
|
||||
/*
|
||||
* [jsep] 5.5. If the type is "pranswer" or "answer", the PeerConnection
|
||||
* state MUST be either "have-remote-offer" or "have-local-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setLocalDescription(answer)));
|
||||
}, 'Calling setLocalDescription(answer) from have-local-offer state should reject with InvalidStateError');
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setRemoteDescription - answer</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer()
|
||||
// generateAnswer()
|
||||
// assert_session_desc_equals()
|
||||
// test_state_change_event()
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.3. Otherwise, if description is set as a remote description, then run one of
|
||||
the following steps:
|
||||
- If description is of type "answer", then this completes an offer answer
|
||||
negotiation.
|
||||
|
||||
Set connection's currentRemoteDescription to description and
|
||||
currentLocalDescription to the value of pendingLocalDescription.
|
||||
|
||||
Set both pendingRemoteDescription and pendingLocalDescription to null.
|
||||
|
||||
Finally setconnection's signaling state to stable.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-local-offer', 'stable']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer =>
|
||||
pc.setRemoteDescription(answer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.remoteDescription, answer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, offer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, answer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
})));
|
||||
}, 'setRemoteDescription() with valid state and answer should succeed');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.1.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps.
|
||||
|
||||
[JSEP]
|
||||
5.6. If the type is "answer", the PeerConnection state MUST be either
|
||||
"have-local-offer" or "have-remote-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription({ type: 'answer', sdp: offer.sdp })));
|
||||
}, 'Calling setRemoteDescription(answer) from stable state should reject with InvalidStateError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription(answer)));
|
||||
|
||||
}, 'Calling setRemoteDescription(answer) from have-remote-offer state should reject with InvalidStateError');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,135 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setRemoteDescription - offer</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer()
|
||||
// assert_session_desc_equals()
|
||||
// test_state_change_event()
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.3. Otherwise, if description is set as a remote description, then run one of
|
||||
the following steps:
|
||||
- If description is of type "offer", set connection.pendingRemoteDescription
|
||||
attribute to description and signaling state to have-remote-offer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
test_state_change_event(t, pc, ['have-remote-offer']);
|
||||
|
||||
return generateOffer({ pc, data: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-remote-offer');
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, offer);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
}));
|
||||
}, 'setRemoteDescription with valid offer should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
// have-remote-offer event should only fire once
|
||||
test_state_change_event(t, pc, ['have-remote-offer']);
|
||||
|
||||
return Promise.all([
|
||||
pc.createOffer({ offerToReceiveAudio: true }),
|
||||
generateOffer({ pc, data: true })
|
||||
]).then(([offer1, offer2]) =>
|
||||
pc.setRemoteDescription(offer1)
|
||||
.then(() => pc.setRemoteDescription(offer2))
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-remote-offer');
|
||||
assert_session_desc_equals(pc.remoteDescription, offer2);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
}));
|
||||
}, 'Setting remote description multiple times with different offer should succeed');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.1.4. If the content of description is not valid SDP syntax, then reject p with
|
||||
an RTCError (with errorDetail set to "sdp-syntax-error" and the
|
||||
sdpLineNumber attribute set to the line number in the SDP where the syntax
|
||||
error was detected) and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription({
|
||||
type: 'offer',
|
||||
sdp: 'Invalid SDP'
|
||||
})
|
||||
.then(() => {
|
||||
assert_unreached('Expect promise to be rejected');
|
||||
}, err => {
|
||||
assert_equals(err.errorDetail, 'sdp-syntax-error',
|
||||
'Expect error detail field to set to sdp-syntax-error');
|
||||
|
||||
assert_true(err instanceof RTCError,
|
||||
'Expect err to be instance of RTCError');
|
||||
});
|
||||
}, 'setRemoteDescription(offer) with invalid SDP should reject with RTCError');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.1.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps.
|
||||
|
||||
[JSEP]
|
||||
5.6. If the type is "offer", the PeerConnection state MUST be either "stable" or
|
||||
"have-remote-offer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => pc.createOffer())
|
||||
.then(offer2 =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription(offer2)));
|
||||
}, 'Calling setRemoteDescription(offer) from have-local-offer state should reject with InvalidStateError');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,150 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setRemoteDescription pranswer</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer
|
||||
// generateAnswer
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setLocalDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? localDescription;
|
||||
readonly attribute RTCSessionDescription? currentLocalDescription;
|
||||
readonly attribute RTCSessionDescription? pendingLocalDescription;
|
||||
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.1.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps.
|
||||
|
||||
[JSEP]
|
||||
5.6. If the type is "pranswer" or "answer", the PeerConnection state MUST be either
|
||||
"have-local-offer" or "have-remote-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription({ type: 'pranswer', sdp: offer.sdp })));
|
||||
}, 'setRemoteDescription(pranswer) from stable state should reject with InvalidStateError');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.3. Otherwise, if description is set as a remote description, then run one
|
||||
of the following steps:
|
||||
- If description is of type "pranswer", then set
|
||||
connection.pendingRemoteDescription to description and signaling state
|
||||
to have-remote-pranswer.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-local-offer', 'have-remote-pranswer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer => {
|
||||
const pranswer = { type: 'pranswer', sdp: answer.sdp };
|
||||
|
||||
return pc.setRemoteDescription(pranswer)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-remote-pranswer');
|
||||
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingLocalDescription, offer);
|
||||
assert_equals(pc.currentLocalDescription, null);
|
||||
|
||||
assert_session_desc_equals(pc.remoteDescription, pranswer);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, pranswer);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
});
|
||||
}));
|
||||
}, 'setRemoteDescription(pranswer) from have-local-offer state should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-local-offer', 'have-remote-pranswer']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer => {
|
||||
const pranswer = { type: 'pranswer', sdp: answer.sdp };
|
||||
|
||||
return pc.setRemoteDescription(pranswer)
|
||||
.then(() => pc.setRemoteDescription(pranswer));
|
||||
}));
|
||||
}, 'setRemoteDescription(pranswer) multiple times should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-local-offer', 'have-remote-pranswer', 'stable']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer => {
|
||||
const pranswer = { type: 'pranswer', sdp: answer.sdp };
|
||||
|
||||
return pc.setRemoteDescription(pranswer)
|
||||
.then(() => pc.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.currentLocalDescription, offer);
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
|
||||
assert_session_desc_equals(pc.remoteDescription, answer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, answer);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
});
|
||||
}));
|
||||
}, 'setRemoteDescription(answer) from have-remote-pranswer state should succeed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,111 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCPeerConnection.prototype.setRemoteDescription rollback</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer
|
||||
// assert_session_desc_equals
|
||||
// test_state_change_event
|
||||
|
||||
/*
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setLocalDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? localDescription;
|
||||
readonly attribute RTCSessionDescription? currentLocalDescription;
|
||||
readonly attribute RTCSessionDescription? pendingLocalDescription;
|
||||
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.3. Otherwise, if description is set as a remote description, then run one
|
||||
of the following steps:
|
||||
- If description is of type "rollback", then this is a rollback.
|
||||
Set connection.pendingRemoteDescription to null and signaling state to stable.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
test_state_change_event(t, pc, ['have-remote-offer', 'stable']);
|
||||
|
||||
return generateOffer({ data: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-remote-offer');
|
||||
assert_not_equals(pc.remoteDescription, null);
|
||||
assert_not_equals(pc.pendingRemoteDescription, null);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
|
||||
return pc.setRemoteDescription({ type: 'rollback' });
|
||||
})
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'stable');
|
||||
assert_equals(pc.remoteDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
});
|
||||
}, 'setRemoteDescription(rollback) in have-remote-offer state should revert to stable state');
|
||||
|
||||
/*
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.3. If the description's type is invalid for the current signaling state of
|
||||
connection, then reject p with a newly created InvalidStateError and abort
|
||||
these steps.
|
||||
|
||||
[jsep]
|
||||
4.1.8.2. Rollback
|
||||
- Rollback can only be used to cancel proposed changes;
|
||||
there is no support for rolling back from a stable state to a
|
||||
previous stable state
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription({ type: 'rollback' }));
|
||||
}, `setRemoteDescription(rollback) from stable state should reject with InvalidStateError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
return pc.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => pc.setRemoteDescription({
|
||||
type: 'rollback',
|
||||
sdp: '!<Invalid SDP Content>;'
|
||||
}));
|
||||
}, `setRemoteDescription(rollback) should ignore invalid sdp content and succeed`);
|
||||
|
||||
</script>
|
|
@ -8,7 +8,7 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer()
|
||||
|
@ -16,86 +16,37 @@
|
|||
// assert_session_desc_not_equals()
|
||||
// assert_session_desc_equals()
|
||||
// test_state_change_event()
|
||||
// test_never_resolve()
|
||||
|
||||
/*
|
||||
* 4.3.2. setRemoteDescription(offer)
|
||||
4.3.2. Interface Definition
|
||||
[Constructor(optional RTCConfiguration configuration)]
|
||||
interface RTCPeerConnection : EventTarget {
|
||||
Promise<void> setRemoteDescription(
|
||||
RTCSessionDescriptionInit description);
|
||||
|
||||
readonly attribute RTCSessionDescription? remoteDescription;
|
||||
readonly attribute RTCSessionDescription? currentRemoteDescription;
|
||||
readonly attribute RTCSessionDescription? pendingRemoteDescription;
|
||||
...
|
||||
};
|
||||
|
||||
4.6.2. RTCSessionDescription Class
|
||||
dictionary RTCSessionDescriptionInit {
|
||||
required RTCSdpType type;
|
||||
DOMString sdp = "";
|
||||
};
|
||||
|
||||
4.6.1. RTCSdpType
|
||||
enum RTCSdpType {
|
||||
"offer",
|
||||
"pranswer",
|
||||
"answer",
|
||||
"rollback"
|
||||
};
|
||||
*/
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
test_state_change_event(t, pc, ['have-remote-offer']);
|
||||
|
||||
return generateOffer({ data: true })
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(offer => {
|
||||
assert_equals(pc.signalingState, 'have-remote-offer');
|
||||
assert_session_desc_equals(pc.remoteDescription, offer);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, offer);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
}));
|
||||
}, 'setRemoteDescription with valid offer should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
// have-remote-offer event should only fire once
|
||||
test_state_change_event(t, pc, ['have-remote-offer']);
|
||||
|
||||
return Promise.all([
|
||||
generateOffer({ audio: true }),
|
||||
generateOffer({ data: true })
|
||||
]).then(([offer1, offer2]) =>
|
||||
pc.setRemoteDescription(offer1)
|
||||
.then(() => pc.setRemoteDescription(offer2))
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-remote-offer');
|
||||
assert_session_desc_equals(pc.remoteDescription, offer2);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
|
||||
assert_equals(pc.currentRemoteDescription, null);
|
||||
}));
|
||||
}, 'Setting remote description multiple times with different offer should succeed');
|
||||
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer()
|
||||
.then(offer => {
|
||||
const promise = pc.setRemoteDescription(offer);
|
||||
pc.close();
|
||||
return promise;
|
||||
});
|
||||
}, 'setRemoteDescription(offer) should never resolve if connection is closed in parallel')
|
||||
|
||||
/*
|
||||
* 4.3.1. Setting an RTCSessionDescription
|
||||
* 2.4. If the content of description is not valid SDP syntax, then reject p
|
||||
* with an RTCError (with errorDetail set to "sdp-syntax-error" and the
|
||||
* sdpLineNumber attribute set to the line number in the SDP where the
|
||||
* syntax error was detected) and abort these steps.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.setRemoteDescription({
|
||||
type: 'offer',
|
||||
sdp: 'Invalid SDP'
|
||||
})
|
||||
.then(() => {
|
||||
assert_unreached('Expect promise to be rejected');
|
||||
}, err => {
|
||||
assert_equals(err.errorDetail, 'sdp-syntax-error',
|
||||
'Expect error detail field to set to sdp-syntax-error');
|
||||
|
||||
assert_true(err instanceof RTCError,
|
||||
'Expect err to be instance of RTCError');
|
||||
});
|
||||
}, 'setRemoteDescription(offer) with invalid SDP should reject with RTCError');
|
||||
|
||||
/*
|
||||
* 4.6.1. enum RTCSdpType
|
||||
4.6.1. enum RTCSdpType
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -106,7 +57,7 @@
|
|||
type: 'bogus',
|
||||
sdp: 'bogus'
|
||||
}));
|
||||
}, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError')
|
||||
}, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -117,71 +68,9 @@
|
|||
type: 'answer',
|
||||
sdp: 'invalid'
|
||||
}));
|
||||
}, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError')
|
||||
}, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError');
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* Setting an RTCSessionDescription
|
||||
* 2.1.5. If the content of description is invalid, then reject p with
|
||||
* a newly created InvalidAccessError and abort these steps.
|
||||
* 2.2.5-10
|
||||
* setRemoteDescription(rollback)
|
||||
* setRemoteDescription(pranswer)
|
||||
*
|
||||
* Non-testable
|
||||
* Setting an RTCSessionDescription
|
||||
* 6. For all other errors, for example if description cannot be
|
||||
* applied at the media layer, reject p with a newly created OperationError.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 4.3.2. setRemoteDescription(answer)
|
||||
*/
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
test_state_change_event(t, pc, ['have-local-offer', 'stable']);
|
||||
|
||||
return pc.createOffer({ offerToReceiveVideo: true })
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer))
|
||||
.then(answer =>
|
||||
pc.setRemoteDescription(answer)
|
||||
.then(() => {
|
||||
assert_session_desc_equals(pc.localDescription, offer);
|
||||
assert_session_desc_equals(pc.remoteDescription, answer);
|
||||
|
||||
assert_session_desc_equals(pc.currentLocalDescription, offer);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, answer);
|
||||
|
||||
assert_equals(pc.pendingLocalDescription, null);
|
||||
assert_equals(pc.pendingRemoteDescription, null);
|
||||
})));
|
||||
}, 'setRemoteDescription() with valid state and answer should succeed');
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* 4.3.2 setRemoteDescription
|
||||
* If an a=identity attribute is present in the session description,
|
||||
* the browser validates the identity assertion.
|
||||
*
|
||||
* If the "peerIdentity" configuration is applied to the RTCPeerConnection,
|
||||
* this establishes a target peer identity of the provided value. Alternatively,
|
||||
* if the RTCPeerConnection has previously authenticated the identity of the
|
||||
* peer (that is, there is a current value for peerIdentity ), then this also
|
||||
* establishes a target peer identity.
|
||||
*
|
||||
* The target peer identity cannot be changed once set. Once set,
|
||||
* if a different value is provided, the user agent must reject
|
||||
* the returned promise with a newly created InvalidModificationError
|
||||
* and abort this operation. The RTCPeerConnection must be closed if
|
||||
* the validated peer identity does not match the target peer identity.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Operations after returning to stable state
|
||||
*/
|
||||
/* Operations after returning to stable state */
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
@ -190,21 +79,22 @@
|
|||
test_state_change_event(t, pc,
|
||||
['have-remote-offer', 'stable', 'have-remote-offer']);
|
||||
|
||||
return generateOffer({ audio: true })
|
||||
return pc2.createOffer({ offerToReceiveAudio: true })
|
||||
.then(offer1 =>
|
||||
pc.setRemoteDescription(offer1)
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer => pc.setLocalDescription(answer))
|
||||
.then(() => generateOffer({ data: true }))
|
||||
.then(offer2 =>
|
||||
pc.setRemoteDescription(offer2)
|
||||
.then(() => pc2.createOffer({ offerToReceiveVideo: true }))
|
||||
.then(offer2 => {
|
||||
return pc.setRemoteDescription(offer2)
|
||||
.then(() => {
|
||||
assert_equals(pc.signalingState, 'have-remote-offer');
|
||||
assert_session_desc_not_equals(offer1, offer2);
|
||||
assert_session_desc_equals(pc.remoteDescription, offer2);
|
||||
assert_session_desc_equals(pc.currentRemoteDescription, offer1);
|
||||
assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
|
||||
})));
|
||||
});
|
||||
}));
|
||||
}, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
|
@ -219,7 +109,7 @@
|
|||
.then(() => generateAnswer(offer)))
|
||||
.then(answer =>
|
||||
pc.setRemoteDescription(answer)
|
||||
.then(() => generateOffer({ data: true }))
|
||||
.then(() => generateOffer({ pc, data: true }))
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => {
|
||||
|
@ -231,97 +121,10 @@
|
|||
}, 'Switching role from offerer to answerer after going back to stable state should succeed');
|
||||
|
||||
/*
|
||||
* InvalidStateError
|
||||
* [webrtc-pc] 4.3.1. Setting the RTCSessionDescription
|
||||
* 2.3. If the description's type is invalid for the current signaling state
|
||||
* of connection, then reject p with a newly created InvalidStateError
|
||||
* and abort these steps.
|
||||
TODO
|
||||
4.3.2. setRemoteDescription
|
||||
- If an a=identity attribute is present in the session description, the browser
|
||||
validates the identity assertion.
|
||||
*/
|
||||
|
||||
/*
|
||||
* [jsep] 5.6. If the type is "pranswer" or "answer", the PeerConnection
|
||||
* state MUST be either "have-local-offer" or "have-remote-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer()
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription({ type: 'answer', sdp: offer.sdp })));
|
||||
}, 'Calling setRemoteDescription(answer) from stable state should reject with InvalidStateError');
|
||||
|
||||
|
||||
/*
|
||||
* [jsep] 5.6. If the type is "offer", the PeerConnection state
|
||||
* MUST be either "stable" or "have-remote-offer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => generateOffer())
|
||||
.then(offer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription(offer)));
|
||||
}, 'Calling setRemoteDescription(offer) from have-local-offer state should reject with InvalidStateError');
|
||||
|
||||
/*
|
||||
* [jsep] 5.6. If the type is "pranswer" or "answer", the PeerConnection
|
||||
* state MUST be either "have-local-offer" or "have-remote-pranswer".
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
return generateOffer()
|
||||
.then(offer =>
|
||||
pc.setRemoteDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer =>
|
||||
promise_rejects(t, 'InvalidStateError',
|
||||
pc.setRemoteDescription(answer)));
|
||||
|
||||
}, 'Calling setRemoteDescription(answer) from have-remote-offer state should reject with InvalidStateError');
|
||||
|
||||
// tests that ontrack is called and parses the msid information from the SDP and creates
|
||||
// the streams with matching identifiers.
|
||||
async_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
|
||||
// Fail the test if the ontrack event handler is not implemented
|
||||
assert_own_property(pc, 'ontrack', 'Expect pc to have ontrack event handler attribute');
|
||||
|
||||
const sdp = `v=0
|
||||
o=- 166855176514521964 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
a=msid-semantic:WMS *
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:someufrag
|
||||
a=ice-pwd:somelongpwdwithenoughrandomness
|
||||
a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4
|
||||
a=setup:actpass
|
||||
a=rtcp-mux
|
||||
a=mid:mid1
|
||||
a=sendonly
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=msid:stream1 track1
|
||||
a=ssrc:1001 cname:some
|
||||
`;
|
||||
|
||||
pc.ontrack = t.step_func(event => {
|
||||
assert_equals(event.streams.length, 1, 'the track belongs to one MediaStream');
|
||||
assert_equals(event.streams[0].id, 'stream1', 'the stream name is parsed from the MSID line');
|
||||
t.done();
|
||||
});
|
||||
|
||||
pc.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp}))
|
||||
.catch(t.step_func(err => {
|
||||
assert_unreached('Error ' + err.name + ': ' + err.message);
|
||||
}));
|
||||
}, 'setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.');
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
'use strict'
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// This file depends on dictionary-helper.js which should
|
||||
// be loaded from the main HTML file.
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
dictionary RTCRtpCapabilities {
|
||||
sequence<RTCRtpCodecCapability> codecs;
|
||||
sequence<RTCRtpHeaderExtensionCapability> headerExtensions;
|
||||
};
|
||||
|
||||
dictionary RTCRtpCodecCapability {
|
||||
DOMString mimeType;
|
||||
unsigned long clockRate;
|
||||
unsigned short channels;
|
||||
DOMString sdpFmtpLine;
|
||||
};
|
||||
|
||||
dictionary RTCRtpHeaderExtensionCapability {
|
||||
DOMString uri;
|
||||
};
|
||||
*/
|
||||
|
||||
function validateRtpCapabilities(capabilities) {
|
||||
assert_array_field(capabilities, 'codecs');
|
||||
for(const codec of capabilities.codecs) {
|
||||
validateCodecCapability(codec);
|
||||
}
|
||||
|
||||
assert_greater_than(capabilities.codec, 0,
|
||||
'Expect at least one codec capability available');
|
||||
|
||||
assert_array_field(capabilities, 'headerExtensions');
|
||||
for(const headerExt of capabilities.headerExtensions) {
|
||||
validateHeaderExtensionCapability(headerExt);
|
||||
}
|
||||
}
|
||||
|
||||
function validateCodecCapability(codec) {
|
||||
assert_optional_string_field(codec, 'mimeType');
|
||||
assert_optional_unsigned_int_field(codec, 'clockRate');
|
||||
assert_optional_unsigned_int_field(codec, 'channels');
|
||||
assert_optional_string_field(codec, 'sdpFmtpLine');
|
||||
}
|
||||
|
||||
function validateHeaderExtensionCapability(headerExt) {
|
||||
assert_optional_string_field(uri);
|
||||
}
|
233
tests/wpt/web-platform-tests/webrtc/RTCRtpParameters-codecs.html
Normal file
233
tests/wpt/web-platform-tests/webrtc/RTCRtpParameters-codecs.html
Normal file
|
@ -0,0 +1,233 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpParameters codecs</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateSenderRtpParameters
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<void> setParameters(optional RTCRtpParameters parameters);
|
||||
RTCRtpParameters getParameters();
|
||||
};
|
||||
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
dictionary RTCRtpCodecParameters {
|
||||
[readonly]
|
||||
unsigned short payloadType;
|
||||
|
||||
[readonly]
|
||||
DOMString mimeType;
|
||||
|
||||
[readonly]
|
||||
unsigned long clockRate;
|
||||
|
||||
[readonly]
|
||||
unsigned short channels;
|
||||
|
||||
[readonly]
|
||||
DOMString sdpFmtpLine;
|
||||
};
|
||||
|
||||
getParameters
|
||||
- The codecs sequence is populated based on the codecs that have been negotiated
|
||||
for sending, and which the user agent is currently capable of sending.
|
||||
|
||||
If setParameters has removed or reordered codecs, getParameters MUST return
|
||||
the shortened/reordered list. However, every time codecs are renegotiated by
|
||||
a new offer/answer exchange, the list of codecs MUST be restored to the full
|
||||
negotiated set, in the priority order indicated by the remote description,
|
||||
in effect discarding the effects of setParameters.
|
||||
|
||||
codecs
|
||||
- When using the setParameters method, the codecs sequence from the corresponding
|
||||
call to getParameters can be reordered and entries can be removed, but entries
|
||||
cannot be added, and the RTCRtpCodecParameters dictionary members cannot be modified.
|
||||
*/
|
||||
|
||||
// Get the first codec from param.codecs.
|
||||
// Assert that param.codecs has at least one element
|
||||
function getFirstCodec(param) {
|
||||
const { codecs } = param;
|
||||
assert_greater_than(codecs.length, 0);
|
||||
return codecs[0];
|
||||
}
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
7. If parameters.encodings.length is different from N, or if any parameter
|
||||
in the parameters argument, marked as a Read-only parameter, has a value
|
||||
that is different from the corresponding parameter value returned from
|
||||
sender.getParameters(), abort these steps and return a promise rejected
|
||||
with a newly created InvalidModificationError. Note that this also applies
|
||||
to transactionId.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const codec = getFirstCodec(param);
|
||||
|
||||
if(codec.payloadType === undefined) {
|
||||
codec.payloadType = 8;
|
||||
} else {
|
||||
codec.payloadType += 1;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, 'setParameters() with codec.payloadType modified should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const codec = getFirstCodec(param);
|
||||
|
||||
if(codec.mimeType === undefined) {
|
||||
codec.mimeType = 'audio/piedpiper';
|
||||
} else {
|
||||
codec.mimeType = `${codec.mimeType}-modified`;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, 'setParameters() with codec.mimeType modified should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const codec = getFirstCodec(param);
|
||||
|
||||
if(codec.clockRate === undefined) {
|
||||
codec.clockRate = 8000;
|
||||
} else {
|
||||
codec.clockRate += 1;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, 'setParameters() with codec.clockRate modified should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const codec = getFirstCodec(param);
|
||||
|
||||
if(codec.channels === undefined) {
|
||||
codec.channels = 6;
|
||||
} else {
|
||||
codec.channels += 1;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, 'setParameters() with codec.channels modified should reject with InvalidModificationError');
|
||||
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const codec = getFirstCodec(param);
|
||||
|
||||
if(codec.sdpFmtpLine === undefined) {
|
||||
codec.sdpFmtpLine = 'a=fmtp:98 0-15';
|
||||
} else {
|
||||
codec.sdpFmtpLine = `${codec.sdpFmtpLine};foo=1`;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, 'setParameters() with codec.sdpFmtpLine modified should reject with InvalidModificationError');
|
||||
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { codecs } = param;
|
||||
|
||||
codecs.push({
|
||||
payloadType: 2,
|
||||
mimeType: 'audio/piedpiper',
|
||||
clockRate: 1000,
|
||||
channels: 2
|
||||
});
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, 'setParameters() with new codecs inserted should reject with InvalidModificationError');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { codecs } = param;
|
||||
|
||||
// skip and pass test if there is less than 2 codecs
|
||||
if(codecs.length >= 2) {
|
||||
const tmp = codecs[0];
|
||||
codecs[0] = codecs[1];
|
||||
codecs[1] = tmp;
|
||||
}
|
||||
|
||||
return sender.setParameters(param);
|
||||
|
||||
}, 'setParameters with reordered codecs should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { codecs } = param;
|
||||
|
||||
param.codecs = codecs.slice(1);
|
||||
|
||||
return sender.setParameters(param);
|
||||
|
||||
}, 'setParameters with dropped codec should succeed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,85 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpParameters degradationPreference</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateSenderRtpParameters
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<void> setParameters(optional RTCRtpParameters parameters);
|
||||
RTCRtpParameters getParameters();
|
||||
};
|
||||
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
enum RTCDegradationPreference {
|
||||
"maintain-framerate",
|
||||
"maintain-resolution",
|
||||
"balanced"
|
||||
};
|
||||
|
||||
- degradationPreference is set to the last value passed into setParameters,
|
||||
or the default value of "balanced" if setParameters hasn't been called.
|
||||
*/
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
assert_equals(param.degradationPreference, 'balanced',
|
||||
'Expect initial param.degradationPreference to be balanced');
|
||||
|
||||
param.degradationPreference = 'maintain-framerate';
|
||||
|
||||
return pc.setParameters(param)
|
||||
.then(() => {
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
assert_equals(param.degradationPreference, 'maintain-framerate');
|
||||
});
|
||||
}, 'setParameters with degradationPreference set should succeed');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
assert_equals(param.degradationPreference, 'balanced',
|
||||
'Expect initial param.degradationPreference to be balanced');
|
||||
|
||||
param.degradationPreference = undefined;
|
||||
|
||||
return pc.setParameters(param)
|
||||
.then(() => {
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
assert_equals(param.degradationPreference, undefined);
|
||||
});
|
||||
}, 'setParameters with degradationPreference unset should succeed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,396 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpParameters encodings</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateSenderRtpParameters
|
||||
|
||||
/*
|
||||
5.1. RTCPeerConnection Interface Extensions
|
||||
partial interface RTCPeerConnection {
|
||||
RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
|
||||
optional RTCRtpTransceiverInit init);
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCRtpTransceiverInit {
|
||||
RTCRtpTransceiverDirection direction = "sendrecv";
|
||||
sequence<MediaStream> streams;
|
||||
sequence<RTCRtpEncodingParameters> sendEncodings;
|
||||
};
|
||||
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<void> setParameters(optional RTCRtpParameters parameters);
|
||||
RTCRtpParameters getParameters();
|
||||
};
|
||||
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
dictionary RTCRtpEncodingParameters {
|
||||
[readonly]
|
||||
unsigned long ssrc;
|
||||
|
||||
[readonly]
|
||||
RTCRtpRtxParameters rtx;
|
||||
|
||||
[readonly]
|
||||
RTCRtpFecParameters fec;
|
||||
|
||||
RTCDtxStatus dtx;
|
||||
boolean active;
|
||||
RTCPriorityType priority;
|
||||
unsigned long ptime;
|
||||
unsigned long maxBitrate;
|
||||
double maxFramerate;
|
||||
|
||||
[readonly]
|
||||
DOMString rid;
|
||||
|
||||
double scaleResolutionDownBy;
|
||||
};
|
||||
|
||||
dictionary RTCRtpRtxParameters {
|
||||
[readonly]
|
||||
unsigned long ssrc;
|
||||
};
|
||||
|
||||
dictionary RTCRtpFecParameters {
|
||||
[readonly]
|
||||
unsigned long ssrc;
|
||||
};
|
||||
|
||||
enum RTCDtxStatus {
|
||||
"disabled",
|
||||
"enabled"
|
||||
};
|
||||
|
||||
enum RTCPriorityType {
|
||||
"very-low",
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
};
|
||||
|
||||
getParameters
|
||||
- encodings is set to the value of the [[send encodings]] internal slot.
|
||||
*/
|
||||
|
||||
// Get the first encoding in param.encodings.
|
||||
// Asserts that param.encodings has at least one element.
|
||||
function getFirstEncoding(param) {
|
||||
const { encodings } = param;
|
||||
assert_equals(encodings.length, 1);
|
||||
return encodings[0];
|
||||
}
|
||||
|
||||
/*
|
||||
5.1. addTransceiver
|
||||
7. Create an RTCRtpSender with track, streams and sendEncodings and let sender
|
||||
be the result.
|
||||
|
||||
5.2. create an RTCRtpSender
|
||||
5. Let sender have a [[send encodings]] internal slot, representing a list
|
||||
of RTCRtpEncodingParameters dictionaries.
|
||||
6. If sendEncodings is given as input to this algorithm, and is non-empty,
|
||||
set the [[send encodings]] slot to sendEncodings.
|
||||
|
||||
Otherwise, set it to a list containing a single RTCRtpEncodingParameters
|
||||
with active set to true.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const param = transceiver.sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const { encodings } = param;
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
assert_equals(encoding.active, true);
|
||||
}, 'addTransceiver() with undefined sendEncodings should have default encoding parameter with active set to true');
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', { sendEncodings: [] });
|
||||
|
||||
const param = transceiver.sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const { encodings } = param;
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
assert_equals(encoding.active, true);
|
||||
}, 'addTransceiver() with empty list sendEncodings should have default encoding parameter with active set to true');
|
||||
|
||||
/*
|
||||
5.2. create an RTCRtpSender
|
||||
To create an RTCRtpSender with a MediaStreamTrack , track, a list of MediaStream
|
||||
objects, streams, and optionally a list of RTCRtpEncodingParameters objects,
|
||||
sendEncodings, run the following steps:
|
||||
5. Let sender have a [[send encodings]] internal slot, representing a list
|
||||
of RTCRtpEncodingParameters dictionaries.
|
||||
|
||||
6. If sendEncodings is given as input to this algorithm, and is non-empty,
|
||||
set the [[send encodings]] slot to sendEncodings.
|
||||
|
||||
5.2. getParameters
|
||||
- encodings is set to the value of the [[send encodings]] internal slot.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
dtx: 'enabled',
|
||||
active: false,
|
||||
priority: 'low',
|
||||
ptime: 5,
|
||||
maxBitrate: 8,
|
||||
maxFramerate: 25,
|
||||
rid: 'foo'
|
||||
}]
|
||||
});
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
assert_equals(encoding.dtx, 'enabled');
|
||||
assert_equals(encoding.active, false);
|
||||
assert_equals(encoding.priority, 'low');
|
||||
assert_equals(encoding.ptime, 5);
|
||||
assert_equals(encoding.maxBitrate, 8);
|
||||
assert_equals(encoding.maxFramerate, 25);
|
||||
assert_equals(encoding.rid, 'foo');
|
||||
|
||||
}, `sender.getParameters() should return sendEncodings set by addTransceiver()`);
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
3. Let N be the number of RTCRtpEncodingParameters stored in sender's internal
|
||||
[[send encodings]] slot.
|
||||
7. If parameters.encodings.length is different from N, or if any parameter
|
||||
in the parameters argument, marked as a Read-only parameter, has a value
|
||||
that is different from the corresponding parameter value returned from
|
||||
sender.getParameters(), abort these steps and return a promise rejected
|
||||
with a newly created InvalidModificationError. Note that this also applies
|
||||
to transactionId.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { encodings } = param;
|
||||
assert_equals(encodings.length, 1);
|
||||
|
||||
// {} is valid RTCRtpEncodingParameters because all fields are optional
|
||||
encodings.push({});
|
||||
assert_equals(param.encodings.length, 2);
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `sender.setParameters() with mismatch number of encodings should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
param.encodings = undefined;
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `sender.setParameters() with encodings unset should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
const { ssrc } = encoding;
|
||||
|
||||
// ssrc may not be set since it is optional
|
||||
if(ssrc === undefined) {
|
||||
encoding.ssrc = 2;
|
||||
} else {
|
||||
// If it is set, increase the number by 1 to make it different from original
|
||||
encoding.ssrc = ssrc + 1;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified encoding.ssrc field should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
const { rtx } = encoding;
|
||||
|
||||
if(rtx === undefined) {
|
||||
encoding.rtx = { ssrc: 2 };
|
||||
} else if(rtx.ssrc === undefined) {
|
||||
rtx.ssrc = 2;
|
||||
} else {
|
||||
rtx.ssrc += 1;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified encoding.rtx field should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
const { fec } = encoding;
|
||||
|
||||
if(fec === undefined) {
|
||||
encoding.fec = { ssrc: 2 };
|
||||
} else if(fec.ssrc === undefined) {
|
||||
fec.ssrc = 2;
|
||||
} else {
|
||||
fec.ssrc += 1;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified encoding.fec field should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio', {
|
||||
sendEncodings: [{ rid: 'foo' }],
|
||||
});
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
assert_equals(encoding.rid, 'foo');
|
||||
|
||||
encoding.rid = 'bar';
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified encoding.rid field should reject with InvalidModificationError`);
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
8. If the scaleResolutionDownBy parameter in the parameters argument has a
|
||||
value less than 1.0, abort these steps and return a promise rejected with
|
||||
a newly created RangeError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
encoding.scaleResolutionDownBy = 0.5;
|
||||
return promise_rejects(t, 'RangeError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with encoding.scaleResolutionDownBy field set to less than 1.0 should reject with RangeError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
encoding.scaleResolutionDownBy = 1.5;
|
||||
return sender.setParameters(param)
|
||||
.then(() => {
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
assert_approx_equals(encoding.scaleResolutionDownBy, 1.5, 0.01);
|
||||
});
|
||||
|
||||
}, `setParameters() with encoding.scaleResolutionDownBy field set to greater than 1.0 should succeed`);
|
||||
|
||||
// Helper function to test that modifying an encoding field should succeed
|
||||
function test_modified_encoding(field, value1, value2, desc) {
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio', {
|
||||
sendEncodings: [{
|
||||
[field]: value1
|
||||
}]
|
||||
});
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
|
||||
assert_equals(encoding[field], value1);
|
||||
encoding[field] = value2;
|
||||
|
||||
return sender.setParameters(param)
|
||||
.then(() => {
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
const encoding = getFirstEncoding(param);
|
||||
assert_equals(encoding[field], value2);
|
||||
});
|
||||
}, desc);
|
||||
}
|
||||
|
||||
test_modified_encoding('dtx', 'enabled', 'disabled',
|
||||
'setParameters() with modified encoding.dtx should succeed');
|
||||
|
||||
test_modified_encoding('dtx', 'enabled', undefined,
|
||||
'setParameters() with unset encoding.dtx should succeed');
|
||||
|
||||
test_modified_encoding('active', true, false,
|
||||
'setParameters() with modified encoding.active should succeed');
|
||||
|
||||
test_modified_encoding('priority', 'very-low', 'high',
|
||||
'setParameters() with modified encoding.priority should succeed');
|
||||
|
||||
test_modified_encoding('ptime', 2, 4,
|
||||
'setParameters() with modified encoding.ptime should succeed');
|
||||
|
||||
test_modified_encoding('maxBitrate', 2, 4,
|
||||
'setParameters() with modified encoding.maxBitrate should succeed');
|
||||
|
||||
test_modified_encoding('maxBitrate', 24, 16,
|
||||
'setParameters() with modified encoding.maxFramerate should succeed');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,75 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpParameters headerExtensions</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateSenderRtpParameters
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<void> setParameters(optional RTCRtpParameters parameters);
|
||||
RTCRtpParameters getParameters();
|
||||
};
|
||||
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
dictionary RTCRtpHeaderExtensionParameters {
|
||||
[readonly]
|
||||
DOMString uri;
|
||||
|
||||
[readonly]
|
||||
unsigned short id;
|
||||
|
||||
[readonly]
|
||||
boolean encrypted;
|
||||
};
|
||||
|
||||
getParameters
|
||||
- The headerExtensions sequence is populated based on the header extensions
|
||||
that have been negotiated for sending.
|
||||
*/
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
7. If parameters.encodings.length is different from N, or if any parameter
|
||||
in the parameters argument, marked as a Read-only parameter, has a value
|
||||
that is different from the corresponding parameter value returned from
|
||||
sender.getParameters(), abort these steps and return a promise rejected
|
||||
with a newly created InvalidModificationError. Note that this also applies
|
||||
to transactionId.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
param.headerExtensions = [{
|
||||
uri: 'non-existent.example.org',
|
||||
id: 404,
|
||||
encrypted: false
|
||||
}];
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified headerExtensions should reject with InvalidModificationError`);
|
||||
|
||||
</script>
|
262
tests/wpt/web-platform-tests/webrtc/RTCRtpParameters-helper.js
Normal file
262
tests/wpt/web-platform-tests/webrtc/RTCRtpParameters-helper.js
Normal file
|
@ -0,0 +1,262 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// Helper function for testing RTCRtpParameters dictionary fields
|
||||
|
||||
// This file depends on dictionary-helper.js which should
|
||||
// be loaded from the main HTML file.
|
||||
|
||||
/*
|
||||
Validates the RTCRtpParameters returned from RTCRtpSender.prototype.getParameters
|
||||
|
||||
5.2. RTCRtpSender Interface
|
||||
getParameters
|
||||
- transactionId is set to a new unique identifier, used to match this getParameters
|
||||
call to a setParameters call that may occur later.
|
||||
|
||||
- encodings is set to the value of the [[SendEncodings]] internal slot.
|
||||
|
||||
- The headerExtensions sequence is populated based on the header extensions that
|
||||
have been negotiated for sending.
|
||||
|
||||
- The codecs sequence is populated based on the codecs that have been negotiated
|
||||
for sending, and which the user agent is currently capable of sending. If
|
||||
setParameters has removed or reordered codecs, getParameters MUST return the
|
||||
shortened/reordered list. However, every time codecs are renegotiated by a
|
||||
new offer/answer exchange, the list of codecs MUST be restored to the full
|
||||
negotiated set, in the priority order indicated by the remote description,
|
||||
in effect discarding the effects of setParameters.
|
||||
|
||||
- rtcp.cname is set to the CNAME of the associated RTCPeerConnection. rtcp.reducedSize
|
||||
is set to true if reduced-size RTCP has been negotiated for sending, and false otherwise.
|
||||
|
||||
- degradationPreference is set to the last value passed into setParameters, or the
|
||||
default value of "balanced" if setParameters hasn't been called.
|
||||
*/
|
||||
function validateSenderRtpParameters(param) {
|
||||
validateRtpParameters(param);
|
||||
|
||||
assert_not_equals(param.transactionId, undefined,
|
||||
'Expect sender param.transactionId to be set');
|
||||
|
||||
assert_not_equals(param.rtcp.cname, undefined,
|
||||
'Expect sender param.rtcp.cname to be set');
|
||||
|
||||
assert_not_equals(param.rtcp.reducedSize, undefined,
|
||||
'Expect sender param.rtcp.reducedSize to be set to either true or false');
|
||||
}
|
||||
|
||||
/*
|
||||
Validates the RTCRtpParameters returned from RTCRtpReceiver.prototype.getParameters
|
||||
|
||||
5.3. RTCRtpReceiver Interface
|
||||
getParameters
|
||||
When getParameters is called, the RTCRtpParameters dictionary is constructed
|
||||
as follows:
|
||||
|
||||
- encodings is populated based on SSRCs and RIDs present in the current remote
|
||||
description, including SSRCs used for RTX and FEC, if signaled. Every member
|
||||
of the RTCRtpEncodingParameters dictionaries other than the SSRC and RID fields
|
||||
is left undefined.
|
||||
|
||||
- The headerExtensions sequence is populated based on the header extensions that
|
||||
the receiver is currently prepared to receive.
|
||||
|
||||
- The codecs sequence is populated based on the codecs that the receiver is currently
|
||||
prepared to receive.
|
||||
|
||||
- rtcp.reducedSize is set to true if the receiver is currently prepared to receive
|
||||
reduced-size RTCP packets, and false otherwise. rtcp.cname is left undefined.
|
||||
|
||||
- transactionId and degradationPreference are left undefined.
|
||||
*/
|
||||
function validateReceiverRtpParameters(param) {
|
||||
validateRtpParameters(param);
|
||||
|
||||
assert_equals(param.transactionId, undefined,
|
||||
'Expect receiver param.transactionId to be unset');
|
||||
|
||||
assert_not_equals(param.rtcp.reducedSize, undefined,
|
||||
'Expect receiver param.rtcp.reducedSize to be set');
|
||||
|
||||
assert_equals(param.rtcp.cname, undefined,
|
||||
'Expect receiver param.rtcp.cname to be unset');
|
||||
|
||||
assert_equals(param.degradationPreference, undefined,
|
||||
'Expect receiver param.degradationPreference to be unset');
|
||||
}
|
||||
|
||||
/*
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
enum RTCDegradationPreference {
|
||||
"maintain-framerate",
|
||||
"maintain-resolution",
|
||||
"balanced"
|
||||
};
|
||||
*/
|
||||
function validateRtpParameters(param) {
|
||||
assert_optional_string_field(param, 'transactionId');
|
||||
|
||||
assert_array_field(param, 'encodings');
|
||||
for(const encoding of param.encodings) {
|
||||
validateEncodingParameters(encoding);
|
||||
}
|
||||
|
||||
assert_array_field(param, 'headerExtensions');
|
||||
for(const headerExt of param.headerExtensions) {
|
||||
validateHeaderExtensionParameters(headerExt);
|
||||
}
|
||||
|
||||
assert_dict_field(param, 'rtcp');
|
||||
validateRtcpParameters(param.rtcp);
|
||||
|
||||
assert_array_field(param, 'codecs');
|
||||
for(const codec of param.codecs) {
|
||||
validateCodecParameters(codec);
|
||||
}
|
||||
|
||||
assert_optional_enum_field(param, 'degradationPreference',
|
||||
['maintain-framerate', 'maintain-resolution', 'balanced']);
|
||||
}
|
||||
|
||||
/*
|
||||
dictionary RTCRtpEncodingParameters {
|
||||
[readonly]
|
||||
unsigned long ssrc;
|
||||
|
||||
[readonly]
|
||||
RTCRtpRtxParameters rtx;
|
||||
|
||||
[readonly]
|
||||
RTCRtpFecParameters fec;
|
||||
|
||||
RTCDtxStatus dtx;
|
||||
boolean active;
|
||||
RTCPriorityType priority;
|
||||
unsigned long ptime;
|
||||
unsigned long maxBitrate;
|
||||
double maxFramerate;
|
||||
|
||||
[readonly]
|
||||
DOMString rid;
|
||||
|
||||
double scaleResolutionDownBy;
|
||||
};
|
||||
|
||||
dictionary RTCRtpRtxParameters {
|
||||
[readonly]
|
||||
unsigned long ssrc;
|
||||
};
|
||||
|
||||
dictionary RTCRtpFecParameters {
|
||||
[readonly]
|
||||
unsigned long ssrc;
|
||||
};
|
||||
|
||||
enum RTCDtxStatus {
|
||||
"disabled",
|
||||
"enabled"
|
||||
};
|
||||
|
||||
enum RTCPriorityType {
|
||||
"very-low",
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
};
|
||||
*/
|
||||
function validateEncodingParameters(encoding) {
|
||||
assert_optional_unsigned_int_field(encoding, 'ssrc');
|
||||
|
||||
assert_optional_dict_field(encoding, 'rtx');
|
||||
if(encoding.rtx) {
|
||||
assert_unsigned_int_field(encoding.rtx, 'ssrc');
|
||||
}
|
||||
|
||||
assert_optional_dict_field(encoding, 'fec');
|
||||
if(encoding.fec) {
|
||||
assert_unsigned_int_field(encoding.fec, 'ssrc');
|
||||
}
|
||||
|
||||
assert_optional_enum_field(encoding, 'dtx',
|
||||
['disabled', 'enabled']);
|
||||
|
||||
assert_optional_boolean_field(encoding, 'active');
|
||||
assert_optional_enum_field(encoding, 'priority',
|
||||
['very-low', 'low', 'medium', 'high']);
|
||||
|
||||
assert_optional_unsigned_int_field(encoding, 'ptime');
|
||||
assert_optional_unsigned_int_field(encoding, 'maxBitrate');
|
||||
assert_optional_number_field(encoding, 'maxFramerate');
|
||||
|
||||
assert_optional_string_field(encoding, 'rid');
|
||||
assert_optional_number_field(encoding, 'scaleResolutionDownBy');
|
||||
}
|
||||
|
||||
/*
|
||||
dictionary RTCRtcpParameters {
|
||||
[readonly]
|
||||
DOMString cname;
|
||||
|
||||
[readonly]
|
||||
boolean reducedSize;
|
||||
};
|
||||
*/
|
||||
function validateRtcpParameters(rtcp) {
|
||||
assert_optional_string_field(rtcp, 'cname');
|
||||
assert_optional_boolean_field(rtcp, 'reducedSize');
|
||||
}
|
||||
|
||||
/*
|
||||
dictionary RTCRtpHeaderExtensionParameters {
|
||||
[readonly]
|
||||
DOMString uri;
|
||||
|
||||
[readonly]
|
||||
unsigned short id;
|
||||
|
||||
[readonly]
|
||||
boolean encrypted;
|
||||
};
|
||||
*/
|
||||
function validateHeaderExtensionParameters(headerExt) {
|
||||
assert_optional_string_field(headerExt, 'uri');
|
||||
assert_optional_unsigned_int_field(headerExt, 'id');
|
||||
assert_optional_boolean_field(headerExt, 'encrypted');
|
||||
}
|
||||
|
||||
/*
|
||||
dictionary RTCRtpCodecParameters {
|
||||
[readonly]
|
||||
unsigned short payloadType;
|
||||
|
||||
[readonly]
|
||||
DOMString mimeType;
|
||||
|
||||
[readonly]
|
||||
unsigned long clockRate;
|
||||
|
||||
[readonly]
|
||||
unsigned short channels;
|
||||
|
||||
[readonly]
|
||||
DOMString sdpFmtpLine;
|
||||
};
|
||||
*/
|
||||
function validateCodecParameters(codec) {
|
||||
assert_optional_unsigned_int_field(codec, 'payloadType');
|
||||
assert_optional_string_field(codec, 'mimeType');
|
||||
assert_optional_unsigned_int_field(codec, 'clockRate');
|
||||
assert_optional_unsigned_int_field(codec, 'channels');
|
||||
assert_optional_string_field(codec, 'sdpFmtpLine');
|
||||
}
|
105
tests/wpt/web-platform-tests/webrtc/RTCRtpParameters-rtcp.html
Normal file
105
tests/wpt/web-platform-tests/webrtc/RTCRtpParameters-rtcp.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpParameters rtcp</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateSenderRtpParameters
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<void> setParameters(optional RTCRtpParameters parameters);
|
||||
RTCRtpParameters getParameters();
|
||||
};
|
||||
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
dictionary RTCRtcpParameters {
|
||||
[readonly]
|
||||
DOMString cname;
|
||||
|
||||
[readonly]
|
||||
boolean reducedSize;
|
||||
};
|
||||
|
||||
getParameters
|
||||
- rtcp.cname is set to the CNAME of the associated RTCPeerConnection.
|
||||
|
||||
rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated for
|
||||
sending, and false otherwise.
|
||||
*/
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
7. If parameters.encodings.length is different from N, or if any parameter
|
||||
in the parameters argument, marked as a Read-only parameter, has a value
|
||||
that is different from the corresponding parameter value returned from
|
||||
sender.getParameters(), abort these steps and return a promise rejected
|
||||
with a newly created InvalidModificationError. Note that this also applies
|
||||
to transactionId.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { rtcp } = param;
|
||||
|
||||
if(rtcp === undefined) {
|
||||
param.rtcp = { cname: 'foo' };
|
||||
|
||||
} else if(rtcp.cname === undefined) {
|
||||
rtcp.cname = 'foo';
|
||||
|
||||
} else {
|
||||
rtcp.cname = `${rtcp.cname}-modified`;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified rtcp.cname should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { rtcp } = param;
|
||||
|
||||
if(rtcp === undefined) {
|
||||
param.rtcp = { reducedSize: true };
|
||||
|
||||
} else if(rtcp.reducedSize === undefined) {
|
||||
rtcp.reducedSize = true;
|
||||
|
||||
} else {
|
||||
rtcp.reducedSize = !rtcp.reducedSize;
|
||||
}
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() with modified rtcp.reducedSize should reject with InvalidModificationError`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,146 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpParameters transactionId</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateSenderRtpParameters
|
||||
|
||||
/*
|
||||
5.1. RTCPeerConnection Interface Extensions
|
||||
partial interface RTCPeerConnection {
|
||||
RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind,
|
||||
optional RTCRtpTransceiverInit init);
|
||||
...
|
||||
};
|
||||
|
||||
dictionary RTCRtpTransceiverInit {
|
||||
RTCRtpTransceiverDirection direction = "sendrecv";
|
||||
sequence<MediaStream> streams;
|
||||
sequence<RTCRtpEncodingParameters> sendEncodings;
|
||||
};
|
||||
|
||||
addTransceiver
|
||||
2. If the dictionary argument is present, and it has a sendEncodings member,
|
||||
let sendEncodings be that list of RTCRtpEncodingParameters objects, or an
|
||||
empty list otherwise.
|
||||
7. Create an RTCRtpSender with track, streams and sendEncodings and let
|
||||
sender be the result.
|
||||
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<void> setParameters(optional RTCRtpParameters parameters);
|
||||
RTCRtpParameters getParameters();
|
||||
};
|
||||
|
||||
dictionary RTCRtpParameters {
|
||||
DOMString transactionId;
|
||||
sequence<RTCRtpEncodingParameters> encodings;
|
||||
sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
|
||||
RTCRtcpParameters rtcp;
|
||||
sequence<RTCRtpCodecParameters> codecs;
|
||||
RTCDegradationPreference degradationPreference;
|
||||
};
|
||||
|
||||
getParameters
|
||||
- transactionId is set to a new unique identifier, used to match this
|
||||
getParameters call to a setParameters call that may occur later.
|
||||
*/
|
||||
|
||||
/*
|
||||
5.2. getParameters
|
||||
- transactionId is set to a new unique identifier, used to match this
|
||||
getParameters call to a setParameters call that may occur later.
|
||||
*/
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param1 = sender.getParameters();
|
||||
const param2 = sender.getParameters();
|
||||
|
||||
validateSenderRtpParameters(param1);
|
||||
validateSenderRtpParameters(param2);
|
||||
|
||||
assert_not_equals(param1.transactionId, param2.transactionId);
|
||||
|
||||
}, `sender.getParameters() should return different transaction IDs for each call`);
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
7. If parameters.encodings.length is different from N, or if any parameter
|
||||
in the parameters argument, marked as a Read-only parameter, has a value
|
||||
that is different from the corresponding parameter value returned from
|
||||
sender.getParameters(), abort these steps and return a promise rejected
|
||||
with a newly created InvalidModificationError. Note that this also applies
|
||||
to transactionId.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
const { transactionId } = param;
|
||||
param.transactionId = `${transactionId}-modified`;
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `sender.setParameters() with transaction ID different from last getParameters() should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
param.transactionId = undefined;
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `sender.setParameters() with transaction ID unset should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param = sender.getParameters();
|
||||
validateSenderRtpParameters(param);
|
||||
|
||||
return sender.setParameters(param)
|
||||
.then(() =>
|
||||
promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param)));
|
||||
|
||||
}, `setParameters() twice with the same parameters should reject with InvalidModificationError`);
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
const param1 = sender.getParameters();
|
||||
const param2 = sender.getParameters();
|
||||
|
||||
validateSenderRtpParameters(param1);
|
||||
validateSenderRtpParameters(param2);
|
||||
|
||||
assert_not_equals(param1.transactionId, param2.transactionId);
|
||||
|
||||
return promise_rejects(t, 'InvalidModificationError',
|
||||
sender.setParameters(param1));
|
||||
|
||||
}, `setParameters() with parameters older than last getParameters() should reject with InvalidModificationError`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,34 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpReceiver.getCapabilities</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpCapabilities-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpCapabilities-helper.js:
|
||||
// validateRtpCapabilities
|
||||
|
||||
/*
|
||||
5.3. RTCRtpReceiver Interface
|
||||
interface RTCRtpReceiver {
|
||||
...
|
||||
static RTCRtpCapabilities getCapabilities(DOMString kind);
|
||||
};
|
||||
*/
|
||||
test(() => {
|
||||
const capabilities = RTCRtpReceiver.getCapabilities('audio');
|
||||
validateRtpCapabilities(capabilities);
|
||||
}, `RTCRtpSender.getCapabilities('audio') should return RTCRtpCapabilities dictionary`);
|
||||
|
||||
test(() => {
|
||||
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
||||
validateRtpCapabilities(capabilities);
|
||||
}, `RTCRtpSender.getCapabilities('video') should return RTCRtpCapabilities dictionary`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,86 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpReceiver.prototype.getContributingSources</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper function is called from RTCPeerConnection-helper.js
|
||||
// getTrackFromUserMedia
|
||||
// exchangeIceCandidates
|
||||
// doSignalingHandshake
|
||||
|
||||
/*
|
||||
5.3. RTCRtpReceiver Interface
|
||||
interface RTCRtpReceiver {
|
||||
...
|
||||
sequence<RTCRtpContributingSource> getContributingSources();
|
||||
};
|
||||
|
||||
interface RTCRtpContributingSource {
|
||||
readonly attribute DOMHighResTimeStamp timestamp;
|
||||
readonly attribute unsigned long source;
|
||||
readonly attribute byte? audioLevel;
|
||||
};
|
||||
|
||||
audioLevel
|
||||
The audio level contained in the last RTP packet played from this source.
|
||||
audioLevel will be the level value defined in [RFC6465] if the RFC 6465
|
||||
header extension is present, and otherwise null. RFC 6465 defines the
|
||||
level as a integral value from 0 to 127 representing the audio level in
|
||||
negative decibels relative to the loudest signal that the system could
|
||||
possibly encode. Thus, 0 represents the loudest signal the system could
|
||||
possibly encode, and 127 represents silence.
|
||||
*/
|
||||
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
const ontrackPromise = new Promise(resolve => {
|
||||
pc2.addEventListener('track', trackEvent => {
|
||||
const { receiver } = trackEvent;
|
||||
assert_true(receiver instanceof RTCRtpReceiver,
|
||||
'Expect trackEvent.receiver to be instance of RTCRtpReceiver');
|
||||
|
||||
resolve(receiver);
|
||||
});
|
||||
});
|
||||
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
pc1.addTrack(track, mediaStream);
|
||||
exchangeIceCandidates(pc1, pc2);
|
||||
return doSignalingHandshake(pc1, pc2);
|
||||
})
|
||||
.then(() => ontrackPromise)
|
||||
.then(receiver => {
|
||||
const contributingSources = receiver.getContributingSources();
|
||||
assert_greater_than(contributingSources.length, 0,
|
||||
'Expect CSRCs to be available after RTP connection is established');
|
||||
|
||||
for(const csrc of contributingSources) {
|
||||
assert_true(csrc instanceof RTCRtpContributingSource,
|
||||
'Expect contributingSources elements to be instance of RTCRtpContributingSource');
|
||||
|
||||
assert_equals(typeof csrc.timestamp, 'number',
|
||||
'Expect csrc.timestamp attribute to be DOMHighResTimeStamp');
|
||||
|
||||
assert_true(Number.isInteger(csrc.source) && csrc.source > 0,
|
||||
'Expect CSRC identifier to be unsigned long');
|
||||
|
||||
if(csrc.audioLevel !== null) {
|
||||
assert_true(Number.isInteger(csrc.audioLevel) &&
|
||||
csrc.audioLevel >= 0 && csrc.audioLevel <= 127,
|
||||
'Expect csrc.audioLevel to be either null or byte value from 0-127.');
|
||||
}
|
||||
}
|
||||
});
|
||||
}, `getContributingSources() should return list of CSRC after connection is established`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,47 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpReceiver.prototype.getParameters</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpParameters-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpParameters-helper.js:
|
||||
// validateReceiverRtpParameters
|
||||
|
||||
/*
|
||||
Validates the RTCRtpParameters returned from RTCRtpReceiver.prototype.getParameters
|
||||
|
||||
5.3. RTCRtpReceiver Interface
|
||||
getParameters
|
||||
When getParameters is called, the RTCRtpParameters dictionary is constructed
|
||||
as follows:
|
||||
|
||||
- encodings is populated based on SSRCs and RIDs present in the current remote
|
||||
description, including SSRCs used for RTX and FEC, if signaled. Every member
|
||||
of the RTCRtpEncodingParameters dictionaries other than the SSRC and RID fields
|
||||
is left undefined.
|
||||
|
||||
- The headerExtensions sequence is populated based on the header extensions that
|
||||
the receiver is currently prepared to receive.
|
||||
|
||||
- The codecs sequence is populated based on the codecs that the receiver is currently
|
||||
prepared to receive.
|
||||
|
||||
- rtcp.reducedSize is set to true if the receiver is currently prepared to receive
|
||||
reduced-size RTCP packets, and false otherwise. rtcp.cname is left undefined.
|
||||
|
||||
- transactionId and degradationPreference are left undefined.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { receiver } = pc.addTransceiver('audio');
|
||||
const param = pc.getParameters();
|
||||
validateReceiverRtpParameters(param);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,54 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpReceiver.prototype.getStats</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCStats-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
// https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
|
||||
|
||||
// The following helper function is called from RTCStats-helper.js
|
||||
// validateStatsReport
|
||||
// assert_stats_report_has_stats
|
||||
|
||||
/*
|
||||
5.3. RTCRtpReceiver Interface
|
||||
interface RTCRtpReceiver {
|
||||
Promise<RTCStatsReport> getStats();
|
||||
...
|
||||
};
|
||||
|
||||
getStats
|
||||
1. Let selector be the RTCRtpReceiver object on which the method was invoked.
|
||||
2. Let p be a new promise, and run the following steps in parallel:
|
||||
1. Gather the stats indicated by selector according to the stats selection
|
||||
algorithm.
|
||||
2. Resolve p with the resulting RTCStatsReport object, containing the
|
||||
gathered stats.
|
||||
3. Return p.
|
||||
|
||||
8.5. The stats selection algorithm
|
||||
4. If selector is an RTCRtpReceiver, gather stats for and add the following objects
|
||||
to result:
|
||||
- All RTCInboundRTPStreamStats objects corresponding to selector.
|
||||
- All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
|
||||
added.
|
||||
*/
|
||||
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { receiver } = pc.addTransceiver('audio');
|
||||
|
||||
return receiver.getStats()
|
||||
.then(statsReport => {
|
||||
validateStatsReport(statsReport);
|
||||
assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
|
||||
});
|
||||
}, 'receiver.getStats() should return stats report containing inbound-rtp stats');
|
||||
|
||||
</script>
|
|
@ -0,0 +1,76 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpReceiver.prototype.getSynchronizationSources</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
5.3. RTCRtpReceiver Interface
|
||||
interface RTCRtpReceiver {
|
||||
...
|
||||
sequence<RTCRtpSynchronizationSource> getSynchronizationSources();
|
||||
};
|
||||
|
||||
interface RTCRtpSynchronizationSource {
|
||||
readonly attribute DOMHighResTimeStamp timestamp;
|
||||
readonly attribute unsigned long source;
|
||||
readonly attribute byte audioLevel;
|
||||
readonly attribute boolean? voiceActivityFlag;
|
||||
};
|
||||
*/
|
||||
|
||||
promise_test(() => {
|
||||
const pc1 = new RTCPeerConnection();
|
||||
const pc2 = new RTCPeerConnection();
|
||||
|
||||
const ontrackPromise = new Promise(resolve => {
|
||||
pc2.addEventListener('track', trackEvent => {
|
||||
const { receiver } = trackEvent;
|
||||
assert_true(receiver instanceof RTCRtpReceiver,
|
||||
'Expect trackEvent.receiver to be instance of RTCRtpReceiver');
|
||||
|
||||
resolve(receiver);
|
||||
});
|
||||
});
|
||||
|
||||
return getTrackFromUserMedia('audio')
|
||||
.then(([track, mediaStream]) => {
|
||||
pc1.addTrack(track, mediaStream);
|
||||
exchangeIceCandidates(pc1, pc2);
|
||||
return doSignalingHandshake(pc1, pc2);
|
||||
})
|
||||
.then(() => ontrackPromise)
|
||||
.then(receiver => {
|
||||
const syncSources = receiver.getSynchronizationSources();
|
||||
assert_greater_than(syncSources.length, 0,
|
||||
'Expect SSRCs to be available after RTP connection is established');
|
||||
|
||||
for(const ssrc of syncSources) {
|
||||
assert_true(ssrc instanceof RTCRtpSynchronizationSource,
|
||||
'Expect synchronizationSources elements to be instance of RTCSynchronizationSource');
|
||||
|
||||
assert_equals(typeof ssrc.timestamp, 'number',
|
||||
'Expect ssrc.timestamp attribute to be DOMHighResTimeStamp');
|
||||
|
||||
assert_true(Number.isInteger(ssrc.source) && ssrc.source > 0,
|
||||
'Expect SSRC identifier to be unsigned long');
|
||||
|
||||
assert_true(Number.isInteger(ssrc.audioLevel) &&
|
||||
ssrc.audioLevel >= 0 && ssrc.audioLevel <= 127,
|
||||
'Expect ssrc.audioLevel to be byte value from 0-127.');
|
||||
|
||||
if(ssrc.voiceActivityFlag !== null) {
|
||||
assert_equals(typeof ssrc.voiceActivityFlag, 'boolean',
|
||||
'Expect ssrc.voiceActivity to be either null or boolean');
|
||||
}
|
||||
}
|
||||
});
|
||||
}, `getContributingSources() should return list of CSRC after connection is established`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,40 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpSender.getCapabilities</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCRtpCapabilities-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCRtpCapabilities-helper.js:
|
||||
// validateRtpCapabilities
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
...
|
||||
static RTCRtpCapabilities getCapabilities(DOMString kind);
|
||||
};
|
||||
|
||||
getCapabilities
|
||||
The getCapabilities() method returns the most optimist view on the capabilities
|
||||
of the system for sending media of the given kind. It does not reserve any
|
||||
resources, ports, or other state but is meant to provide a way to discover
|
||||
the types of capabilities of the browser including which codecs may be supported.
|
||||
*/
|
||||
test(() => {
|
||||
const capabilities = RTCRtpSender.getCapabilities('audio');
|
||||
validateRtpCapabilities(capabilities);
|
||||
}, `RTCRtpSender.getCapabilities('audio') should return RTCRtpCapabilities dictionary`);
|
||||
|
||||
test(() => {
|
||||
const capabilities = RTCRtpSender.getCapabilities('video');
|
||||
validateRtpCapabilities(capabilities);
|
||||
}, `RTCRtpSender.getCapabilities('video') should return RTCRtpCapabilities dictionary`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,54 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpSender.prototype.getStats</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="dictionary-helper.js"></script>
|
||||
<script src="RTCStats-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
// https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
|
||||
|
||||
// The following helper function is called from RTCStats-helper.js
|
||||
// validateStatsReport
|
||||
// assert_stats_report_has_stats
|
||||
|
||||
/*
|
||||
5.2. RTCRtpSender Interface
|
||||
interface RTCRtpSender {
|
||||
Promise<RTCStatsReport> getStats();
|
||||
...
|
||||
};
|
||||
|
||||
getStats
|
||||
1. Let selector be the RTCRtpSender object on which the method was invoked.
|
||||
2. Let p be a new promise, and run the following steps in parallel:
|
||||
1. Gather the stats indicated by selector according to the stats selection
|
||||
algorithm.
|
||||
2. Resolve p with the resulting RTCStatsReport object, containing the
|
||||
gathered stats.
|
||||
3. Return p.
|
||||
|
||||
8.5. The stats selection algorithm
|
||||
3. If selector is an RTCRtpSender, gather stats for and add the following objects
|
||||
to result:
|
||||
- All RTCOutboundRTPStreamStats objects corresponding to selector.
|
||||
- All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
|
||||
objects added.
|
||||
*/
|
||||
|
||||
promise_test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const { sender } = pc.addTransceiver('audio');
|
||||
|
||||
return sender.getStats()
|
||||
.then(statsReport => {
|
||||
validateStatsReport(statsReport);
|
||||
assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
|
||||
});
|
||||
}, 'sender.getStats() should return stats report containing outbound-rtp stats');
|
||||
|
||||
</script>
|
|
@ -219,29 +219,6 @@
|
|||
});
|
||||
}, 'Calling replaceTrack on sender with similar track and and set to session description should resolve with sender.track set to new track');
|
||||
|
||||
/*
|
||||
5.2. replaceTrack
|
||||
Not 8. If transceiver is not yet associated with a media description
|
||||
[JSEP] (section 3.4.1.), then set sender's track attribute to
|
||||
withTrack, and return a promise resolved with undefined.
|
||||
10. Run the following steps in parallel:
|
||||
3. Queue a task that runs the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
*/
|
||||
test_never_resolve(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const track = generateMediaStreamTrack('audio');
|
||||
const { transceiver: { sender } } = pc.addTransceiver('audio');
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer => pc.setLocalDescription(offer))
|
||||
.then(() => {
|
||||
const promise = sender.replaceTrack(track);
|
||||
pc.close();
|
||||
return promise;
|
||||
});
|
||||
}, 'replaceTrack should never resolve if connection is closed in parallel');
|
||||
|
||||
/*
|
||||
TODO
|
||||
5.2. replaceTrack
|
||||
|
@ -266,5 +243,7 @@
|
|||
negotiating. Otherwise, have the sender switch seamlessly to
|
||||
transmitting withTrack instead of the sender's existing track,
|
||||
without negotiating.
|
||||
3. Queue a task that runs the following steps:
|
||||
1. If connection's [[isClosed]] slot is true, abort these steps.
|
||||
*/
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpSender.prototype.setParameters</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
5.2. setParameters
|
||||
6. If transceiver.stopped is true, abort these steps and return a promise
|
||||
rejected with a newly created InvalidStateError.
|
||||
*/
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { sender } = transceiver;
|
||||
|
||||
const param = sender.getParameters();
|
||||
transceiver.stop();
|
||||
|
||||
return promise_rejects(t, 'InvalidStateError',
|
||||
sender.setParameters(param));
|
||||
|
||||
}, `setParameters() when transceiver is stopped should reject with InvalidStateError`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,136 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpTransceiver.prototype.setCodecPreferences</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
5.4. RTCRtpTransceiver Interface
|
||||
interface RTCRtpTransceiver {
|
||||
...
|
||||
void setCodecPreferences(sequence<RTCRtpCodecCapability> codecs);
|
||||
};
|
||||
|
||||
setCodecPreferences
|
||||
- Setting codecs to an empty sequence resets codec preferences to any
|
||||
default value.
|
||||
|
||||
- The codecs sequence passed into setCodecPreferences can only contain
|
||||
codecs that are returned by RTCRtpSender.getCapabilities(kind) or
|
||||
RTCRtpReceiver.getCapabilities(kind), where kind is the kind of the
|
||||
RTCRtpTransceiver on which the method is called. Additionally, the
|
||||
RTCRtpCodecParameters dictionary members cannot be modified. If
|
||||
codecs does not fulfill these requirements, the user agent MUST throw
|
||||
an InvalidAccessError.
|
||||
*/
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const capabilities = RTCRtpSender.getCapabilities('audio');
|
||||
transceiver.setCodecPreferences(capabilities.codecs);
|
||||
|
||||
}, `setCodecPreferences() on audio transceiver with codecs returned from RTCRtpSender.getCapabilities('audio') should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('video');
|
||||
const capabilities = RTCRtpReceiver.getCapabilities('video');
|
||||
transceiver.setCodecPreferences(capabilities.codecs);
|
||||
|
||||
}, `setCodecPreferences() on video transceiver with codecs returned from RTCRtpReceiver.getCapabilities('video') should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const capabilities1 = RTCRtpSender.getCapabilities('audio');
|
||||
const capabilities2 = RTCRtpReceiver.getCapabilities('audio');
|
||||
transceiver.setCodecPreferences([...capabilities1.codecs, ... capabilities2.codecs]);
|
||||
|
||||
}, `setCodecPreferences() with both sender receiver codecs combined should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
transceiver.setCodecPreferences([]);
|
||||
|
||||
}, `setCodecPreferences([]) should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const capabilities = RTCRtpSender.getCapabilities('audio');
|
||||
const { codecs } = capabilities;
|
||||
|
||||
if(codecs.length >= 2) {
|
||||
const tmp = codecs[0];
|
||||
codecs[0] = codecs[1];
|
||||
codecs[1] = tmp;
|
||||
}
|
||||
|
||||
transceiver.setCodecPreferences(codecs);
|
||||
|
||||
}, `setCodecPreferences() with reordered codecs should succeed`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const capabilities = RTCRtpSender.getCapabilities('video');
|
||||
assert_throws(() => transceiver.setCodecPreferences(capabilities.codecs));
|
||||
|
||||
}, `setCodecPreferences() on audio transceiver with codecs returned from getCapabilities('video') should throw InvalidAccessError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const codecs = [{
|
||||
mimeType: 'audio/piepiper',
|
||||
clockRate: 2000,
|
||||
channels: 2,
|
||||
sdpFmtpLine: 'a=fmtp:98 0-15'
|
||||
}];
|
||||
|
||||
assert_throws(() => transceiver.setCodecPreferences(codecs));
|
||||
|
||||
}, `setCodecPreferences() with user defined codec should throw InvalidAccessError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const capabilities = RTCRtpSender.getCapabilities('audio');
|
||||
const codecs = [
|
||||
...capabilities.codecs,
|
||||
{
|
||||
mimeType: 'audio/piepiper',
|
||||
clockRate: 2000,
|
||||
channels: 2,
|
||||
sdpFmtpLine: 'a=fmtp:98 0-15'
|
||||
}];
|
||||
|
||||
assert_throws(() => transceiver.setCodecPreferences(codecs));
|
||||
|
||||
}, `setCodecPreferences() with user defined codec together with codecs returned from getCapabilities() should throw InvalidAccessError`);
|
||||
|
||||
test(() => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const capabilities = RTCRtpSender.getCapabilities('audio');
|
||||
|
||||
const { codecs } = capabilities;
|
||||
assert_greater_than(codecs.length, 0,
|
||||
'Expect at least one codec available');
|
||||
|
||||
const [ codec ] = codecs;
|
||||
const { channels=2 } = codec;
|
||||
codec.channels = channels+1;
|
||||
|
||||
assert_throws(() => transceiver.setCodecPreferences(codecs));
|
||||
|
||||
}, `setCodecPreferences() with modified codecs returned from getCapabilities() should throw InvalidAccessError`);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,94 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCRtpTransceiver.prototype.setDirection</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://rawgit.com/w3c/webrtc-pc/cc8d80f455b86c8041d63bceb8b457f45c72aa89/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateAnswer
|
||||
|
||||
/*
|
||||
5.4. RTCRtpTransceiver Interface
|
||||
interface RTCRtpTransceiver {
|
||||
readonly attribute RTCRtpTransceiverDirection direction;
|
||||
readonly attribute RTCRtpTransceiverDirection? currentDirection;
|
||||
void setDirection(RTCRtpTransceiverDirection direction);
|
||||
...
|
||||
};
|
||||
*/
|
||||
|
||||
/*
|
||||
5.4. setDirection
|
||||
4. Set transceiver's [[Direction]] slot to newDirection.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
assert_equals(transceiver.direction, 'sendrecv');
|
||||
assert_equals(transceiver.currentDirection, null);
|
||||
|
||||
transceiver.setDirection('recvonly');
|
||||
assert_equals(transceiver.direction, 'recvonly');
|
||||
assert_equals(transceiver.currentDirection, null,
|
||||
'Expect transceiver.currentDirection to not change');
|
||||
|
||||
}, 'setDirection should change transceiver.direction');
|
||||
|
||||
/*
|
||||
5.4. setDirection
|
||||
3. If newDirection is equal to transceiver's [[Direction]] slot, abort
|
||||
these steps.
|
||||
*/
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', { direction: 'sendonly' });
|
||||
assert_equals(transceiver.direction, 'sendonly');
|
||||
transceiver.setDirection('sendonly');
|
||||
assert_equals(transceiver.direction, 'sendonly');
|
||||
|
||||
}, 'setDirection with same direction should have no effect');
|
||||
|
||||
promise_test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio', { direction: 'recvonly' });
|
||||
assert_equals(transceiver.direction, 'recvonly');
|
||||
assert_equals(transceiver.currentDirection, null);
|
||||
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer => pc.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
assert_equals(transceiver.currentDirection, 'recvonly');
|
||||
transceiver.setDirection('sendrecv');
|
||||
assert_equals(transceiver.direction, 'sendrecv');
|
||||
assert_equals(transceiver.currentDirection, 'recvonly');
|
||||
});
|
||||
}, 'setDirection should change transceiver.direction independent of transceiver.currentDirection');
|
||||
|
||||
/*
|
||||
TODO
|
||||
Calls to setDirection() do not take effect immediately. Instead, future calls
|
||||
to createOffer and createAnswer mark the corresponding media description as
|
||||
sendrecv, sendonly, recvonly or inactive as defined in [JSEP] (section 5.2.2.
|
||||
and section 5.3.2.).
|
||||
|
||||
Tested in RTCPeerConnection-onnegotiationneeded.html
|
||||
5.4. setDirection
|
||||
6. Update the negotiation-needed flag for connection.
|
||||
|
||||
Coverage Report
|
||||
Tested 6
|
||||
Not Tested 1
|
||||
Untestable 0
|
||||
Total 7
|
||||
*/
|
||||
|
||||
</script>
|
|
@ -8,7 +8,7 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170515/webrtc.html
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateOffer()
|
||||
|
@ -27,18 +27,16 @@
|
|||
readonly attribute unsigned long maxMessageSize;
|
||||
};
|
||||
|
||||
4.3.1. Operation
|
||||
When the RTCPeerConnection() constructor is invoked
|
||||
8. Let connection have an [[sctpTransport]] internal slot,
|
||||
initialized to null.
|
||||
4.4.1.1. Constructor
|
||||
8. Let connection have an [[sctpTransport]] internal slot, initialized to null.
|
||||
|
||||
To set an RTCSessionDescription description
|
||||
6. If description is of type "answer" or "pranswer", then run the
|
||||
following steps:
|
||||
1. If description initiates the establishment of a new SCTP
|
||||
association, as defined in [SCTP-SDP], Sections 10.3 and 10.4,
|
||||
set the value of connection's [[sctpTransport]] internal slot
|
||||
to a newly created RTCSctpTransport.
|
||||
4.3.1.6. Set the RTCSessionSessionDescription
|
||||
2.2.6. If description is of type "answer" or "pranswer", then run the
|
||||
following steps:
|
||||
1. If description initiates the establishment of a new SCTP association,
|
||||
as defined in [SCTP-SDP], Sections 10.3 and 10.4, set the value of
|
||||
connection's [[SctpTransport]] internal slot to a newly created
|
||||
RTCSctpTransport.
|
||||
*/
|
||||
|
||||
promise_test(t => {
|
||||
|
@ -69,7 +67,7 @@
|
|||
const pc = new RTCPeerConnection();
|
||||
assert_equals(pc.sctp, null);
|
||||
|
||||
return generateOffer({ data: true })
|
||||
return generateOffer({ pc, data: true })
|
||||
.then(offer => pc.setRemoteDescription(offer))
|
||||
.then(() => pc.createAnswer())
|
||||
.then(answer => pc.setLocalDescription(answer))
|
||||
|
|
857
tests/wpt/web-platform-tests/webrtc/RTCStats-helper.js
Normal file
857
tests/wpt/web-platform-tests/webrtc/RTCStats-helper.js
Normal file
|
@ -0,0 +1,857 @@
|
|||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
// https://w3c.github.io/webrtc-stats/archives/20170614/webrtc-stats.html
|
||||
|
||||
|
||||
// This file depends on dictionary-helper.js which should
|
||||
// be loaded from the main HTML file.
|
||||
|
||||
// To improve readability, the WebIDL definitions of the Stats
|
||||
// dictionaries are modified to annotate with required fields when
|
||||
// they are required by section 8.6 of webrtc-pc. ID fields are
|
||||
// also annotated with the stats type that they are linked to.
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
6.1. RTCStatsType enum
|
||||
enum RTCStatsType {
|
||||
"codec",
|
||||
"inbound-rtp",
|
||||
"outbound-rtp",
|
||||
"remote-inbound-rtp",
|
||||
"remote-outbound-rtp",
|
||||
"csrc",
|
||||
"peer-connection",
|
||||
"data-channel",
|
||||
"stream",
|
||||
"track",
|
||||
"transport",
|
||||
"candidate-pair",
|
||||
"local-candidate",
|
||||
"remote-candidate",
|
||||
"certificate"
|
||||
};
|
||||
*/
|
||||
const statsValidatorTable = {
|
||||
'codec': validateCodecStats,
|
||||
'inbound-rtp': validateInboundRtpStreamStats,
|
||||
'outbound-rtp': validateOutboundRtpStreamStats,
|
||||
'remote-inbound-rtp': validateRemoteInboundRtpStreamStats,
|
||||
'remote-outbound-rtp': validateRemoteOutboundRtpStreamStats,
|
||||
'csrc': validateContributingSourceStats,
|
||||
'peer-connection': validatePeerConnectionStats,
|
||||
'data-channel': validateDataChannelStats,
|
||||
'stream': validateMediaStreamStats,
|
||||
'track': validateMediaStreamTrackStats,
|
||||
'transport': validateTransportStats,
|
||||
'candidate-pair': validateIceCandidatePairStats,
|
||||
'local-candidate': validateIceCandidateStats,
|
||||
'remote-candidate': validateIceCandidateStats,
|
||||
'certificate': validateCertificateStats
|
||||
};
|
||||
|
||||
// Validate that the stats objects in a stats report
|
||||
// follows the respective definitions.
|
||||
// Stats objects with unknown type are ignored and
|
||||
// only basic validation is done.
|
||||
function validateStatsReport(statsReport) {
|
||||
for(const [id, stats] of statsReport.entries()) {
|
||||
assert_equals(stats.id, id,
|
||||
'expect stats.id to be the same as the key in statsReport');
|
||||
|
||||
const validator = statsValidatorTable[stats.type];
|
||||
if(validator) {
|
||||
validator(statsReport, stats);
|
||||
} else {
|
||||
validateRtcStats(statsReport, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that the stats report have stats objects of
|
||||
// given types
|
||||
function assert_stats_report_has_stats(statsReport, statsTypes) {
|
||||
const hasTypes = new Set([...statsReport.values()]
|
||||
.map(stats => stats.type));
|
||||
|
||||
for(const type of statsTypes) {
|
||||
assert_true(hasTypes.has(type),
|
||||
`Expect statsReport to contain stats object of type ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get stats object of type that is expected to be
|
||||
// found in the statsReport
|
||||
function getRequiredStats(statsReport, type) {
|
||||
for(const stats of statsReport.values()) {
|
||||
if(stats.type === type) {
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
assert_unreached(`required stats of type ${type} is not found in stats report`);
|
||||
}
|
||||
|
||||
// Get stats object by the stats ID.
|
||||
// This is used to retreive other stats objects
|
||||
// linked to a stats object
|
||||
function getStatsById(statsReport, statsId) {
|
||||
assert_true(statsReport.has(statsId),
|
||||
`Expect stats report to have stats object with id ${statsId}`);
|
||||
|
||||
return statsReport.get(statsId);
|
||||
}
|
||||
|
||||
// Validate an ID field in a stats object by making sure
|
||||
// that the linked stats object is found in the stats report
|
||||
// and have the type field value same as expected type
|
||||
// It doesn't validate the other fields of the linked stats
|
||||
// as validateStatsReport already does all validations
|
||||
function validateIdField(statsReport, stats, field, type) {
|
||||
assert_string_field(stats, field);
|
||||
const linkedStats = getStatsById(statsReport, stats[field]);
|
||||
assert_equals(linkedStats.type, type,
|
||||
`Expect linked stats object to have type ${type}`);
|
||||
}
|
||||
|
||||
function validateOptionalIdField(statsReport, stats, field, type) {
|
||||
if(stats[field] !== undefined) {
|
||||
validateIdField(statsReport, stats, field, type);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-pc]
|
||||
8.4. RTCStats Dictionary
|
||||
dictionary RTCStats {
|
||||
required DOMHighResTimeStamp timestamp;
|
||||
required RTCStatsType type;
|
||||
required DOMString id;
|
||||
};
|
||||
*/
|
||||
function validateRtcStats(statsReport, stats) {
|
||||
assert_number_field(stats, 'timeStamp');
|
||||
assert_string_field(stats, 'type');
|
||||
assert_string_field(stats, 'id');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.1. RTCRTPStreamStats dictionary
|
||||
dictionary RTCRTPStreamStats : RTCStats {
|
||||
required unsigned long ssrc;
|
||||
required DOMString mediaType;
|
||||
|
||||
[RTCMediaStreamTrackStats]
|
||||
required DOMString trackId;
|
||||
|
||||
[RTCTransportStats]
|
||||
required DOMString transportId;
|
||||
|
||||
[RTCCodecStats]
|
||||
required DOMString codecId;
|
||||
|
||||
unsigned long firCount;
|
||||
unsigned long pliCount;
|
||||
required unsigned long nackCount;
|
||||
unsigned long sliCount;
|
||||
unsigned long long qpSum;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCRTPStreamStats, with attributes ssrc, associateStatsId, isRemote, mediaType,
|
||||
mediaTrackId, transportId, codecId, nackCount
|
||||
*/
|
||||
function validateRtpStreamStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_unsigned_int_field(stats, 'ssrc');
|
||||
assert_string_field(stats, 'mediaType');
|
||||
|
||||
validateIdField(statsReport, stats, 'trackId', 'track');
|
||||
validateIdField(statsReport, stats, 'transportId', 'transport');
|
||||
validateIdField(statsReport, stats, 'codecId', 'codec');
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'firCount');
|
||||
assert_optional_unsigned_int_field(stats, 'pliCount');
|
||||
assert_unsigned_int_field(stats, 'nackCount');
|
||||
assert_optional_unsigned_int_field(stats, 'sliCount');
|
||||
assert_optional_unsigned_int_field(stats, 'qpSum');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.2. RTCCodecStats dictionary
|
||||
dictionary RTCCodecStats : RTCStats {
|
||||
required unsigned long payloadType;
|
||||
required RTCCodecType codecType;
|
||||
|
||||
[RTCTransportStats]
|
||||
DOMString transportId;
|
||||
|
||||
DOMString mimeType;
|
||||
required unsigned long clockRate;
|
||||
required unsigned long channels;
|
||||
DOMString sdpFmtpLine;
|
||||
DOMString implementation;
|
||||
};
|
||||
|
||||
enum RTCCodecType {
|
||||
"encode",
|
||||
"decode",
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCCodecStats, with attributes payloadType, codec, clockRate, channels, parameters
|
||||
*/
|
||||
|
||||
function validateCodecStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_unsigned_int_field(stats, 'payloadType');
|
||||
assert_enum_field(stats, 'codecType', ['encode', 'decode']);
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'transportId', 'transport');
|
||||
|
||||
assert_optional_string_field(stats, 'mimeType');
|
||||
assert_unsigned_int_field(stats, 'clockRate');
|
||||
assert_unsigned_int_field(stats, 'channels');
|
||||
|
||||
assert_optional_string_field(stats, 'sdpFmtpLine');
|
||||
assert_optional_string_field(stats, 'implementation');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.3. RTCReceivedRTPStreamStats dictionary
|
||||
dictionary RTCReceivedRTPStreamStats : RTCRTPStreamStats {
|
||||
unsigned long packetsReceived;
|
||||
unsigned long long bytesReceived;
|
||||
unsigned long packetsLost;
|
||||
double jitter;
|
||||
double fractionLost;
|
||||
unsigned long packetsDiscarded;
|
||||
unsigned long packetsRepaired;
|
||||
unsigned long burstPacketsLost;
|
||||
unsigned long burstPacketsDiscarded;
|
||||
unsigned long burstLossCount;
|
||||
unsigned long burstDiscardCount;
|
||||
double burstLossRate;
|
||||
double burstDiscardRate;
|
||||
double gapLossRate;
|
||||
double gapDiscardRate;
|
||||
};
|
||||
*/
|
||||
function validateReceivedRtpStreamStats(statsReport, stats) {
|
||||
validateRtpStreamStats(statsReport, stats);
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'packetsReceived');
|
||||
assert_optional_unsigned_int_field(stats, 'bytesReceived');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsLost');
|
||||
|
||||
assert_optional_number_field(stats, 'jitter');
|
||||
assert_optional_number_field(stats, 'fractionLost');
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'packetsDiscarded');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsRepaired');
|
||||
assert_optional_unsigned_int_field(stats, 'burstPacketsLost');
|
||||
assert_optional_unsigned_int_field(stats, 'burstPacketsDiscarded');
|
||||
assert_optional_unsigned_int_field(stats, 'burstLossCount');
|
||||
assert_optional_unsigned_int_field(stats, 'burstDiscardCount');
|
||||
|
||||
assert_optional_number_field(stats, 'burstLossRate');
|
||||
assert_optional_number_field(stats, 'burstDiscardRate');
|
||||
assert_optional_number_field(stats, 'gapLossRate');
|
||||
assert_optional_number_field(stats, 'gapDiscardRate');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.4. RTCInboundRTPStreamStats dictionary
|
||||
dictionary RTCInboundRTPStreamStats : RTCReceivedRTPStreamStats {
|
||||
required unsigned long packetsReceived;
|
||||
required unsigned long long bytesReceived;
|
||||
required unsigned long packetsLost;
|
||||
required double jitter;
|
||||
required unsigned long packetsDiscarded;
|
||||
|
||||
[RTCRemoteOutboundRTPStreamStats]
|
||||
DOMString remoteId;
|
||||
|
||||
unsigned long framesDecoded;
|
||||
DOMHighResTimeStamp lastPacketReceivedTimestamp;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCInboundRTPStreamStats, with all required attributes from RTCRTPStreamStats,
|
||||
and also attributes packetsReceived, bytesReceived, packetsLost, jitter,
|
||||
packetsDiscarded
|
||||
*/
|
||||
function validateInboundRtpStreamStats(statsReport, stats) {
|
||||
validateReceivedRtpStreamStats(statsReport, stats);
|
||||
|
||||
assert_unsigned_int_field(stats, 'packetsReceived');
|
||||
assert_unsigned_int_field(stats, 'bytesReceived');
|
||||
assert_unsigned_int_field(stats, 'packetsLost');
|
||||
assert_number_field(stats, 'jitter');
|
||||
assert_unsigned_int_field(stats, 'packetsDiscarded');
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'remoteId', 'remote-outbound-rtp');
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'framesDecoded');
|
||||
assert_optional_number_field(stats, 'lastPacketReceivedTimeStamp');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.5. RTCRemoteInboundRTPStreamStats dictionary
|
||||
dictionary RTCRemoteInboundRTPStreamStats : RTCReceivedRTPStreamStats {
|
||||
[RTCOutboundRTPStreamStats]
|
||||
DOMString localId;
|
||||
|
||||
double roundTripTime;
|
||||
};
|
||||
*/
|
||||
|
||||
function validateRemoteInboundRtpStreamStats(statsReport, stats) {
|
||||
validateReceivedRtpStreamStats(statsReport, stats);
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'localId', 'outbound-rtp');
|
||||
assert_optional_number_field(stats, 'roundTripTime');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.6. RTCSentRTPStreamStats dictionary
|
||||
dictionary RTCSentRTPStreamStats : RTCRTPStreamStats {
|
||||
unsigned long packetsSent;
|
||||
unsigned long packetsDiscardedOnSend;
|
||||
unsigned long long bytesSent;
|
||||
unsigned long long bytesDiscardedOnSend;
|
||||
};
|
||||
*/
|
||||
function validateSentRtpStreamStats(statsReport, stats) {
|
||||
validateRtpStreamStats(statsReport, stats);
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'packetsSent');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsDiscardedOnSend');
|
||||
assert_optional_unsigned_int_field(stats, 'bytesSent');
|
||||
assert_optional_unsigned_int_field(stats, 'bytesDiscardedOnSend');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.7. RTCOutboundRTPStreamStats dictionary
|
||||
dictionary RTCOutboundRTPStreamStats : RTCSentRTPStreamStats {
|
||||
required unsigned long packetsSent;
|
||||
required unsigned long long bytesSent;
|
||||
|
||||
[RTCRemoteInboundRTPStreamStats]
|
||||
DOMString remoteId;
|
||||
|
||||
DOMHighResTimeStamp lastPacketSentTimestamp;
|
||||
double targetBitrate;
|
||||
unsigned long framesEncoded;
|
||||
double totalEncodeTime;
|
||||
double averageRTCPInterval;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCOutboundRTPStreamStats, with all required attributes from RTCRTPStreamStats,
|
||||
and also attributes packetsSent, bytesSent, roundTripTime
|
||||
*/
|
||||
function validateOutboundRtpStreamStats(statsReport, stats) {
|
||||
validateOptionalIdField(statsReport, stats, 'remoteId', 'remote-inbound-rtp');
|
||||
|
||||
assert_unsigned_int_field(stats, 'packetsSent');
|
||||
assert_unsigned_int_field(stats, 'bytesSent');
|
||||
|
||||
assert_optional_number_field(stats, 'lastPacketSentTimestamp');
|
||||
assert_optional_number_field(stats, 'targetBitrate');
|
||||
assert_optional_unsigned_int_field(stats, 'framesEncoded');
|
||||
assert_optional_number_field(stats, 'totalEncodeTime');
|
||||
assert_optional_number_field(stats, 'averageRTCPInterval');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.8. RTCRemoteOutboundRTPStreamStats dictionary
|
||||
dictionary RTCRemoteOutboundRTPStreamStats : RTCSentRTPStreamStats {
|
||||
[RTCInboundRTPStreamStats]
|
||||
DOMString localId;
|
||||
|
||||
DOMHighResTimeStamp remoteTimestamp;
|
||||
};
|
||||
*/
|
||||
function validateRemoteOutboundRtpStreamStats(statsReport, stats) {
|
||||
validateSentRtpStreamStats(statsReport, stats);
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'localId', 'inbound-rtp');
|
||||
assert_optional_number_field(stats, 'remoteTimeStamp');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.9. RTCRTPContributingSourceStats
|
||||
dictionary RTCRTPContributingSourceStats : RTCStats {
|
||||
unsigned long contributorSsrc;
|
||||
|
||||
[RTCInboundRTPStreamStats]
|
||||
DOMString inboundRtpStreamId;
|
||||
|
||||
unsigned long packetsContributedTo;
|
||||
double audioLevel;
|
||||
};
|
||||
*/
|
||||
function validateContributingSourceStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'contributorSsrc');
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'inboundRtpStreamId', 'inbound-rtp');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsContributedTo');
|
||||
assert_optional_number_field(stats, 'audioLevel');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.10. RTCPeerConnectionStats dictionary
|
||||
dictionary RTCPeerConnectionStats : RTCStats {
|
||||
required unsigned long dataChannelsOpened;
|
||||
required unsigned long dataChannelsClosed;
|
||||
unsigned long dataChannelsRequested;
|
||||
unsigned long dataChannelsAccepted;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCPeerConnectionStats, with attributes dataChannelsOpened, dataChannelsClosed
|
||||
*/
|
||||
function validatePeerConnectionStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_unsigned_int_field(stats, 'dataChannelsOpened');
|
||||
assert_unsigned_int_field(stats, 'dataChannelsClosed');
|
||||
assert_optional_unsigned_int_field(stats, 'dataChannelsRequested');
|
||||
assert_optional_unsigned_int_field(stats, 'dataChannelsAccepted');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.11. RTCMediaStreamStats dictionary
|
||||
dictionary RTCMediaStreamStats : RTCStats {
|
||||
required DOMString streamIdentifier;
|
||||
|
||||
[RTCMediaStreamTrackStats]
|
||||
required sequence<DOMString> trackIds;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCMediaStreamStats, with attributes streamIdentifer, trackIds
|
||||
*/
|
||||
function validateMediaStreamStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_string_field(stats, 'streamIdentifier');
|
||||
assert_array_field(stats, 'trackIds');
|
||||
|
||||
for(const trackId of stats.trackIds) {
|
||||
assert_equals(typeof trackId, 'string',
|
||||
'Expect trackId elements to be string');
|
||||
|
||||
assert_true(statsReport.has(trackId),
|
||||
`Expect stats report to have stats object with id ${trackId}`);
|
||||
|
||||
const trackStats = statsReport.get(trackId);
|
||||
assert_equals(trackStats.type, 'track',
|
||||
`Expect track stats object to have type 'track'`);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.12. RTCMediaStreamTrackStats dictionary
|
||||
dictionary RTCMediaStreamTrackStats : RTCStats {
|
||||
required DOMString trackIdentifier;
|
||||
required boolean remoteSource;
|
||||
required boolean ended;
|
||||
required boolean detached;
|
||||
DOMString kind;
|
||||
DOMHighResTimeStamp estimatedPlayoutTimestamp;
|
||||
required unsigned long frameWidth;
|
||||
required unsigned long frameHeight;
|
||||
required double framesPerSecond;
|
||||
unsigned long framesCaptured;
|
||||
required unsigned long framesSent;
|
||||
required unsigned long framesReceived;
|
||||
required unsigned long framesDecoded;
|
||||
required unsigned long framesDropped;
|
||||
required unsigned long framesCorrupted;
|
||||
unsigned long partialFramesLost;
|
||||
unsigned long fullFramesLost;
|
||||
required double audioLevel;
|
||||
double totalAudioEnergy;
|
||||
boolean voiceActivityFlag;
|
||||
double echoReturnLoss;
|
||||
double echoReturnLossEnhancement;
|
||||
unsigned long long totalSamplesSent;
|
||||
unsigned long long totalSamplesReceived;
|
||||
double totalSamplesDuration;
|
||||
unsigned long long concealedSamples;
|
||||
unsigned long long concealmentEvents;
|
||||
double jitterBufferDelay;
|
||||
RTCPriorityType priority;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
4.9.1. RTCPriorityType Enum
|
||||
enum RTCPriorityType {
|
||||
"very-low",
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
};
|
||||
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCMediaStreamTrackStats, with attributes trackIdentifier, remoteSource, ended,
|
||||
detached, ssrcIds, frameWidth, frameHeight, framesPerSecond, framesSent,
|
||||
framesReceived, framesDecoded, framesDropped, framesCorrupted, audioLevel
|
||||
*/
|
||||
|
||||
function validateMediaStreamTrackStats(stats, stat) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_string_field(stat, 'trackIdentifier');
|
||||
assert_boolean_field(stat, 'remoteSource');
|
||||
assert_boolean_field(stat, 'ended');
|
||||
assert_boolean_field(stat, 'detached');
|
||||
|
||||
assert_optional_string_field(stat, 'kind');
|
||||
assert_optional_number_field(stat, 'estimatedPlayoutTimestamp');
|
||||
|
||||
assert_unsigned_int_field(stat, 'frameWidth');
|
||||
assert_unsigned_int_field(stat, 'frameHeight');
|
||||
assert_number_field(stat, 'framesPerSecond');
|
||||
|
||||
assert_optional_unsigned_int_field(stat, 'framesCaptured');
|
||||
assert_unsigned_int_field(stat, 'frameSent');
|
||||
assert_unsigned_int_field(stat, 'frameReceived');
|
||||
assert_unsigned_int_field(stat, 'frameDecoded');
|
||||
assert_unsigned_int_field(stat, 'frameDropped');
|
||||
assert_unsigned_int_field(stat, 'frameCorrupted');
|
||||
|
||||
assert_optional_unsigned_int_field(stat, 'partialFramesLost');
|
||||
assert_optional_unsigned_int_field(stat, 'fullFramesLost');
|
||||
|
||||
assert_number_field(stat, 'audioLevel');
|
||||
assert_optional_number_field(stat, 'totalAudioEnergy');
|
||||
assert_optional_boolean_field(stat, 'voiceActivityFlag');
|
||||
assert_optional_number_field(stat, 'echoReturnLoss');
|
||||
assert_optional_number_field(stat, 'echoReturnLossEnhancement');
|
||||
|
||||
assert_optional_unsigned_int_field(stat, 'totalSamplesSent');
|
||||
assert_optional_unsigned_int_field(stat, 'totalSamplesReceived');
|
||||
assert_optional_number_field(stat, 'totalSamplesDuration');
|
||||
assert_optional_unsigned_int_field(stat, 'concealedSamples');
|
||||
assert_optional_unsigned_int_field(stat, 'concealmentEvents');
|
||||
assert_optional_number_field(stat, 'jitterBufferDelay');
|
||||
|
||||
assert_optional_enum_field(stats, 'priority',
|
||||
['very-low', 'low', 'medium', 'high']);
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.13. RTCDataChannelStats dictionary
|
||||
dictionary RTCDataChannelStats : RTCStats {
|
||||
required DOMString label;
|
||||
required DOMString protocol;
|
||||
required long datachannelid;
|
||||
|
||||
[RTCTransportStats]
|
||||
DOMString transportId;
|
||||
|
||||
required RTCDataChannelState state;
|
||||
required unsigned long messagesSent;
|
||||
required unsigned long long bytesSent;
|
||||
required unsigned long messagesReceived;
|
||||
required unsigned long long bytesReceived;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
6.2. RTCDataChannel
|
||||
enum RTCDataChannelState {
|
||||
"connecting",
|
||||
"open",
|
||||
"closing",
|
||||
"closed"
|
||||
};
|
||||
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCDataChannelStats, with attributes label, protocol, datachannelId, state,
|
||||
messagesSent, bytesSent, messagesReceived, bytesReceived
|
||||
*/
|
||||
|
||||
function validateDataChannelStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_string_field(stats, 'label');
|
||||
assert_string_field(stats, 'protocol');
|
||||
assert_int_field(stats, 'datachannelid');
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'transportId', 'transport');
|
||||
|
||||
assert_enum_field(stats, 'state',
|
||||
['connecting', 'open', 'closing', 'closed']);
|
||||
|
||||
assert_unsigned_int_field(stats, 'messageSent');
|
||||
|
||||
assert_unsigned_int_field(stats, 'messageSent');
|
||||
assert_unsigned_int_field(stats, 'bytesSent');
|
||||
assert_unsigned_int_field(stats, 'messagesReceived');
|
||||
assert_unsigned_int_field(stats, 'bytesReceived');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.14. RTCTransportStats dictionary
|
||||
dictionary RTCTransportStats : RTCStats {
|
||||
unsigned long packetsSent;
|
||||
unsigned long packetsReceived;
|
||||
required unsigned long long bytesSent;
|
||||
required unsigned long long bytesReceived;
|
||||
|
||||
[RTCTransportStats]
|
||||
required DOMString rtcpTransportStatsId;
|
||||
|
||||
RTCIceRole iceRole;
|
||||
RTCDtlsTransportState dtlsState;
|
||||
|
||||
[RTCIceCandidatePairStats]
|
||||
required DOMString selectedCandidatePairId;
|
||||
|
||||
[RTCCertificateStats]
|
||||
required DOMString localCertificateId;
|
||||
|
||||
[RTCCertificateStats]
|
||||
required DOMString remoteCertificateId;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
5.5. RTCDtlsTransportState Enum
|
||||
enum RTCDtlsTransportState {
|
||||
"new",
|
||||
"connecting",
|
||||
"connected",
|
||||
"closed",
|
||||
"failed"
|
||||
};
|
||||
|
||||
5.6. RTCIceRole Enum
|
||||
enum RTCIceRole {
|
||||
"controlling",
|
||||
"controlled"
|
||||
};
|
||||
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCTransportStats, with attributes bytesSent, bytesReceived, rtcpTransportStatsId,
|
||||
activeConnection, selectedCandidatePairId, localCertificateId, remoteCertificateId
|
||||
*/
|
||||
|
||||
function validateTransportStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'packetsSent');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsReceived');
|
||||
assert_unsigned_int_field(stats, 'bytesSent');
|
||||
assert_unsigned_int_field(stats, 'bytesReceived');
|
||||
|
||||
validateIdField(statsReport, stats, 'rtcpTransportStatsId', 'transport');
|
||||
|
||||
assert_optional_enum_field(stats, 'iceRole',
|
||||
['controlling', 'controlled']);
|
||||
|
||||
assert_optional_enum_field(stats, 'dtlsState',
|
||||
['new', 'connecting', 'connected', 'closed', 'failed']);
|
||||
|
||||
validateIdField(statsReport, stats, 'selectedCandidatePairId', 'candidate-pair');
|
||||
validateIdField(stateReport, stats, 'localCertificateId', 'certificate');
|
||||
validateIdField(stateReport, stats, 'remoteCertificateId', 'certificate');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.15. RTCIceCandidateStats dictionary
|
||||
dictionary RTCIceCandidateStats : RTCStats {
|
||||
[RTCTransportStats]
|
||||
DOMString transportId;
|
||||
|
||||
boolean isRemote;
|
||||
required DOMString ip;
|
||||
required long port;
|
||||
required DOMString protocol;
|
||||
required RTCIceCandidateType candidateType;
|
||||
required long priority;
|
||||
required DOMString url;
|
||||
DOMString relayProtocol;
|
||||
boolean deleted = false;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
4.8.1.3. RTCIceCandidateType Enum
|
||||
enum RTCIceCandidateType {
|
||||
"host",
|
||||
"srflx",
|
||||
"prflx",
|
||||
"relay"
|
||||
};
|
||||
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCIceCandidateStats, with attributes ip, port, protocol, candidateType, priority,
|
||||
url
|
||||
*/
|
||||
|
||||
function validateIceCandidateStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
validateOptionalIdField(statsReport, stats, 'transportId', 'transport');
|
||||
assert_optional_boolean_field(stats, 'isRemote');
|
||||
|
||||
assert_string_field(stats, 'ip');
|
||||
assert_int_field(stats, 'port');
|
||||
assert_string_field(stats, 'protocol');
|
||||
|
||||
assert_enum_field(stats, 'candidateType',
|
||||
['host', 'srflx', 'prflx', 'relay']);
|
||||
|
||||
assert_int_field(stats, 'priority');
|
||||
assert_string_field(stats, 'url');
|
||||
assert_optional_string_field(stats, 'relayProtocol');
|
||||
assert_optional_boolean_field(stats, 'deleted');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.16. RTCIceCandidatePairStats dictionary
|
||||
dictionary RTCIceCandidatePairStats : RTCStats {
|
||||
[RTCTransportStats]
|
||||
required DOMString transportId;
|
||||
|
||||
[RTCIceCandidateStats]
|
||||
required DOMString localCandidateId;
|
||||
|
||||
[RTCIceCandidateStats]
|
||||
required DOMString remoteCandidateId;
|
||||
|
||||
required RTCStatsIceCandidatePairState state;
|
||||
required unsigned long long priority;
|
||||
required boolean nominated;
|
||||
unsigned long packetsSent;
|
||||
unsigned long packetsReceived;
|
||||
required unsigned long long bytesSent;
|
||||
required unsigned long long bytesReceived;
|
||||
DOMHighResTimeStamp lastPacketSentTimestamp;
|
||||
DOMHighResTimeStamp lastPacketReceivedTimestamp;
|
||||
DOMHighResTimeStamp firstRequestTimestamp;
|
||||
DOMHighResTimeStamp lastRequestTimestamp;
|
||||
DOMHighResTimeStamp lastResponseTimestamp;
|
||||
required double totalRoundTripTime;
|
||||
required double currentRoundTripTime;
|
||||
double availableOutgoingBitrate;
|
||||
double availableIncomingBitrate;
|
||||
unsigned long circuitBreakerTriggerCount;
|
||||
unsigned long long requestsReceived;
|
||||
unsigned long long requestsSent;
|
||||
unsigned long long responsesReceived;
|
||||
unsigned long long responsesSent;
|
||||
unsigned long long retransmissionsReceived;
|
||||
unsigned long long retransmissionsSent;
|
||||
unsigned long long consentRequestsSent;
|
||||
DOMHighResTimeStamp consentExpiredTimestamp;
|
||||
};
|
||||
|
||||
enum RTCStatsIceCandidatePairState {
|
||||
"frozen",
|
||||
"waiting",
|
||||
"in-progress",
|
||||
"failed",
|
||||
"succeeded"
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCIceCandidatePairStats, with attributes transportId, localCandidateId,
|
||||
remoteCandidateId, state, priority, nominated, writable, readable, bytesSent,
|
||||
bytesReceived, totalRtt, currentRtt
|
||||
*/
|
||||
function validateIceCandidatePairStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
validateIdField(statsReport, stats, 'transportId', 'transport');
|
||||
validateIdField(statsReport, stats, 'localCandidateId', 'local-candidate');
|
||||
validateIdField(statsReport, stats, 'remoteCandidateId', 'remote-candidate');
|
||||
|
||||
assert_enum_field(stats, 'state',
|
||||
['frozen', 'waiting', 'in-progress', 'failed', 'succeeded']);
|
||||
|
||||
assert_unsigned_int_field(stats, 'priority');
|
||||
assert_boolean_field(stats, 'nominated');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsSent');
|
||||
assert_optional_unsigned_int_field(stats, 'packetsReceived');
|
||||
assert_unsigned_int_field(stats, 'bytesSent');
|
||||
assert_unsigned_int_field(stats, 'byteReceived');
|
||||
|
||||
assert_optional_number_field(stats, 'lastPacketSentTimestamp');
|
||||
assert_optional_number_field(stats, 'lastPacketReceivedTimestamp');
|
||||
assert_optional_number_field(stats, 'firstRequestTimestamp');
|
||||
assert_optional_number_field(stats, 'lastRequestTimestamp');
|
||||
assert_optional_number_field(stats, 'lastResponseTimestamp');
|
||||
|
||||
assert_number_field(stats, 'totalRoundTripTime');
|
||||
assert_number_field(stats, 'currentRoundTripTime');
|
||||
|
||||
assert_optional_number_field(stats, 'availableOutgoingBitrate');
|
||||
assert_optional_number_field(stats, 'availableIncomingBitrate');
|
||||
|
||||
assert_optional_unsigned_int_field(stats, 'circuitBreakerTriggerCount');
|
||||
assert_optional_unsigned_int_field(stats, 'requestsReceived');
|
||||
assert_optional_unsigned_int_field(stats, 'requestsSent');
|
||||
assert_optional_unsigned_int_field(stats, 'responsesReceived');
|
||||
assert_optional_unsigned_int_field(stats, 'responsesSent');
|
||||
assert_optional_unsigned_int_field(stats, 'retransmissionsReceived');
|
||||
assert_optional_unsigned_int_field(stats, 'retransmissionsSent');
|
||||
assert_optional_unsigned_int_field(stats, 'consentRequestsSent');
|
||||
assert_optional_number_field(stats, 'consentExpiredTimestamp');
|
||||
}
|
||||
|
||||
/*
|
||||
[webrtc-stats]
|
||||
7.17. RTCCertificateStats dictionary
|
||||
dictionary RTCCertificateStats : RTCStats {
|
||||
required DOMString fingerprint;
|
||||
required DOMString fingerprintAlgorithm;
|
||||
required DOMString base64Certificate;
|
||||
required DOMString issuerCertificateId;
|
||||
};
|
||||
|
||||
[webrtc-pc]
|
||||
8.6. Mandatory To Implement Stats
|
||||
- RTCCertificateStats, with attributes fingerprint, fingerprintAlgorithm,
|
||||
base64Certificate, issuerCertificateId
|
||||
*/
|
||||
|
||||
function validateCertificateStats(statsReport, stats) {
|
||||
validateRtcStats(statsReport, stats);
|
||||
|
||||
assert_string_field(stats, 'fingerprint');
|
||||
assert_string_field(stats, 'fingerprintAlgorithm');
|
||||
assert_string_field(stats, 'base64Certificate');
|
||||
assert_string_field(stats, 'issuerCertificateId');
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>RTCTrackEvent constructor</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
// Test is based on the following editor draft:
|
||||
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
/*
|
||||
5.7. RTCTrackEvent
|
||||
[Constructor(DOMString type, RTCTrackEventInit eventInitDict)]
|
||||
interface RTCTrackEvent : Event {
|
||||
readonly attribute RTCRtpReceiver receiver;
|
||||
readonly attribute MediaStreamTrack track;
|
||||
[SameObject]
|
||||
readonly attribute FrozenArray<MediaStream> streams;
|
||||
readonly attribute RTCRtpTransceiver transceiver;
|
||||
};
|
||||
|
||||
dictionary RTCTrackEventInit : EventInit {
|
||||
required RTCRtpReceiver receiver;
|
||||
required MediaStreamTrack track;
|
||||
sequence<MediaStream> streams = [];
|
||||
required RTCRtpTransceiver transceiver;
|
||||
};
|
||||
*/
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { receiver } = transceiver;
|
||||
const { track } = receiver;
|
||||
|
||||
const trackEvent = new RTCTrackEvent('track', {
|
||||
receiver, track, transceiver
|
||||
});
|
||||
|
||||
assert_equals(trackEvent.receiver, receiver);
|
||||
assert_equals(trackEvent.track, track);
|
||||
assert_array_equals(trackEvent.streams, []);
|
||||
assert_equals(trackEvent.transceiver, transceiver);
|
||||
|
||||
assert_equals(trackEvent.type, 'track');
|
||||
assert_false(trackEvent.bubbles);
|
||||
assert_false(trackEvent.cancelable);
|
||||
|
||||
}, `new RTCTrackEvent() with valid receiver, track, transceiver should succeed`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { receiver } = transceiver;
|
||||
const { track } = receiver;
|
||||
|
||||
const stream = new MediaStream([track]);
|
||||
|
||||
const trackEvent = new RTCTrackEvent('track', {
|
||||
receiver, track, transceiver,
|
||||
streams: [stream]
|
||||
});
|
||||
|
||||
assert_equals(trackEvent.receiver, receiver);
|
||||
assert_equals(trackEvent.track, track);
|
||||
assert_array_equals(trackEvent.streams, [stream]);
|
||||
assert_equals(trackEvent.transceiver, transceiver);
|
||||
|
||||
}, `new RTCTrackEvent() with valid receiver, track, streams, transceiver should succeed`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { receiver } = transceiver;
|
||||
const { track } = receiver;
|
||||
|
||||
const stream1 = new MediaStream([track]);
|
||||
const stream2 = new MediaStream([track]);
|
||||
|
||||
const trackEvent = new RTCTrackEvent('track', {
|
||||
receiver, track, transceiver,
|
||||
streams: [stream1, stream2]
|
||||
});
|
||||
|
||||
assert_equals(trackEvent.receiver, receiver);
|
||||
assert_equals(trackEvent.track, track);
|
||||
assert_array_equals(trackEvent.streams, [stream1, stream2]);
|
||||
assert_equals(trackEvent.transceiver, transceiver);
|
||||
|
||||
}, `new RTCTrackEvent() with valid receiver, track, multiple streams, transceiver should succeed`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const receiver = pc.addTransceiver('audio').receiver;
|
||||
const track = pc.addTransceiver('audio').receiver.track;
|
||||
|
||||
const stream = new MediaStream();
|
||||
|
||||
const trackEvent = new RTCTrackEvent('track', {
|
||||
receiver, track, transceiver,
|
||||
streams: [stream]
|
||||
});
|
||||
|
||||
assert_equals(trackEvent.receiver, receiver);
|
||||
assert_equals(trackEvent.track, track);
|
||||
assert_array_equals(trackEvent.streams, [stream]);
|
||||
assert_equals(trackEvent.transceiver, transceiver);
|
||||
|
||||
}, `new RTCTrackEvent() with unrelated receiver, track, streams, transceiver should succeed`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { receiver } = transceiver;
|
||||
const { track } = receiver;
|
||||
|
||||
assert_throws(new TypeError(), () =>
|
||||
new RTCTrackEvent('track', {
|
||||
receiver, track
|
||||
}));
|
||||
|
||||
}, `new RTCTrackEvent() with no transceiver should throw TypeError`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { receiver } = transceiver;
|
||||
|
||||
assert_throws(new TypeError(), () =>
|
||||
new RTCTrackEvent('track', {
|
||||
receiver, transceiver
|
||||
}));
|
||||
|
||||
}, `new RTCTrackEvent() with no track should throw TypeError`);
|
||||
|
||||
test(t => {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { receiver } = transceiver;
|
||||
const { track } = receiver;
|
||||
|
||||
assert_throws(new TypeError(), () =>
|
||||
new RTCTrackEvent('track', {
|
||||
track, transceiver
|
||||
}));
|
||||
|
||||
}, `new RTCTrackEvent() with no receiver should throw TypeError`);
|
||||
|
||||
/*
|
||||
Coverage Report
|
||||
Interface tests are counted as 1 trivial test
|
||||
|
||||
Tested 1
|
||||
Total 1
|
||||
*/
|
||||
</script>
|
122
tests/wpt/web-platform-tests/webrtc/coverage/RTCDTMFSender.txt
Normal file
122
tests/wpt/web-platform-tests/webrtc/coverage/RTCDTMFSender.txt
Normal file
|
@ -0,0 +1,122 @@
|
|||
Coverage is based on the following editor draft:
|
||||
https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
7. insertDTMF
|
||||
|
||||
[Trivial]
|
||||
- The tones parameter is treated as a series of characters.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
- The characters 0 through 9, A through D, #, and * generate the associated
|
||||
DTMF tones.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
- The characters a to d MUST be normalized to uppercase on entry and are equivalent
|
||||
to A to D.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
- As noted in [RTCWEB-AUDIO] Section 3, support for the characters 0 through 9,
|
||||
A through D, #, and * are required.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
- The character ',' MUST be supported, and indicates a delay of 2 seconds before
|
||||
processing the next character in the tones parameter.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
- All other characters (and only those other characters) MUST
|
||||
be considered unrecognized.
|
||||
|
||||
[Trivial]
|
||||
- The duration parameter indicates the duration in ms to use for each character passed
|
||||
in the tones parameters.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
- The duration cannot be more than 6000 ms or less than 40 ms.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
- The default duration is 100 ms for each tone.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
- The interToneGap parameter indicates the gap between tones in ms. The user agent
|
||||
clamps it to at least 30 ms. The default value is 70 ms.
|
||||
|
||||
[Untestable]
|
||||
- The browser MAY increase the duration and interToneGap times to cause the times
|
||||
that DTMF start and stop to align with the boundaries of RTP packets but it MUST
|
||||
not increase either of them by more than the duration of a single RTP audio packet.
|
||||
|
||||
[Trivial]
|
||||
When the insertDTMF() method is invoked, the user agent MUST run the following steps:
|
||||
|
||||
[Trivial]
|
||||
1. let sender be the RTCRtpSender used to send DTMF.
|
||||
|
||||
[Trivial]
|
||||
2. Let transceiver be the RTCRtpTransceiver object associated with sender.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
3. If transceiver.stopped is true, throw an InvalidStateError.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
4. If transceiver.currentDirection is recvonly or inactive, throw an
|
||||
InvalidStateError.
|
||||
|
||||
[Trivial]
|
||||
5. Let tones be the method's first argument.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
6. If tones contains any unrecognized characters, throw an InvalidCharacterError.
|
||||
|
||||
[RTCDTMFSender-insertDTMF]
|
||||
7. Set the object's toneBuffer attribute to tones.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
8. If the value of the duration parameter is less than 40, set it to 40.
|
||||
|
||||
[RTCDTMFSender-ontonechange-long]
|
||||
If, on the other hand, the value is greater than 6000, set it to 6000.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
9. If the value of the interToneGap parameter is less than 30, set it to 30.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
10. If toneBuffer is an empty string, abort these steps.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
11. If a Playout task is scheduled to be run; abort these steps;
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
otherwise queue a task that runs the following steps (Playout task):
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
1. If transceiver.stopped is true, abort these steps.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
2. If transceiver.currentDirection is recvonly or inactive, abort these steps.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
3. If toneBuffer is an empty string, fire an event named tonechange with an
|
||||
empty string at the RTCDTMFSender object and abort these steps.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
4. Remove the first character from toneBuffer and let that character be tone.
|
||||
|
||||
[Untestable]
|
||||
5. Start playout of tone for duration ms on the associated RTP media stream,
|
||||
using the appropriate codec.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
6. Queue a task to be executed in duration + interToneGap ms from now that
|
||||
runs the steps labelled Playout task.
|
||||
|
||||
[RTCDTMFSender-ontonechange]
|
||||
7. Fire an event named tonechange with a string consisting of tone at the
|
||||
RTCDTMFSender object.
|
||||
|
||||
Coverage Report
|
||||
|
||||
Tested 31
|
||||
Not Tested 0
|
||||
Untestable 1
|
||||
|
||||
Total 32
|
220
tests/wpt/web-platform-tests/webrtc/coverage/identity.txt
Normal file
220
tests/wpt/web-platform-tests/webrtc/coverage/identity.txt
Normal file
|
@ -0,0 +1,220 @@
|
|||
Coverage is based on the following editor draft:
|
||||
https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
9.3 Requesting Identity Assertions
|
||||
|
||||
[Trivial]
|
||||
The identity assertion request process is triggered by a call to createOffer,
|
||||
createAnswer, or getIdentityAssertion. When these calls are invoked and an
|
||||
identity provider has been set, the following steps are executed:
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
1. The RTCPeerConnection instantiates an IdP as described in Identity Provider
|
||||
Selection and Registering an IdP Proxy. If the IdP cannot be loaded, instantiated,
|
||||
or the IdP proxy is not registered, this process fails.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
2. The RTCPeerConnection invokes the generateAssertion method on the
|
||||
RTCIdentityProvider methods registered by the IdP.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
The RTCPeerConnection generates the contents parameter to this method as
|
||||
described in [RTCWEB-SECURITY-ARCH]. The value of contents includes the
|
||||
fingerprint of the certificate that was selected or generated during the
|
||||
construction of the RTCPeerConnection. The origin parameter contains the
|
||||
origin of the script that calls the RTCPeerConnection method that triggers
|
||||
this behavior. The usernameHint value is the same value that is provided
|
||||
to setIdentityProvider, if any such value was provided.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
3. The IdP proxy returns a Promise to the RTCPeerConnection. The IdP proxy is
|
||||
expected to generate the identity assertion asynchronously.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
If the user has been authenticated by the IdP, and the IdP is able to generate
|
||||
an identity assertion, the IdP resolves the promise with an identity assertion
|
||||
in the form of an RTCIdentityAssertionResult .
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
This step depends entirely on the IdP. The methods by which an IdP authenticates
|
||||
users or generates assertions is not specified, though they could involve
|
||||
interacting with the IdP server or other servers.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
4. If the IdP proxy produces an error or returns a promise that does not resolve
|
||||
to a valid RTCIdentityValidationResult (see 9.5 IdP Error Handling), then
|
||||
identity validation fails.
|
||||
|
||||
[Untestable]
|
||||
5. The RTCPeerConnection MAY store the identity assertion for use with future
|
||||
offers or answers. If a fresh identity assertion is needed for any reason,
|
||||
applications can create a new RTCPeerConnection.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
6. If the identity request was triggered by a createOffer() or createAnswer(),
|
||||
then the assertion is converted to a JSON string, base64-encoded and inserted
|
||||
into an a=identity attribute in the session description.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
If assertion generation fails, then the promise for the corresponding function call
|
||||
is rejected with a newly created OperationError.
|
||||
|
||||
9.3.1 User Login Procedure
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
An IdP MAY reject an attempt to generate an identity assertion if it is unable to
|
||||
verify that a user is authenticated. This might be due to the IdP not having the
|
||||
necessary authentication information available to it (such as cookies).
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
Rejecting the promise returned by generateAssertion will cause the error to propagate
|
||||
to the application. Login errors are indicated by rejecting the promise with an RTCError
|
||||
with errorDetail set to "idp-need-login".
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
The URL to login at will be passed to the application in the idpLoginUrl attribute of
|
||||
the RTCPeerConnection.
|
||||
|
||||
[Out of Scope]
|
||||
An application can load the login URL in an IFRAME or popup window; the resulting page
|
||||
then SHOULD provide the user with an opportunity to enter any information necessary to
|
||||
complete the authorization process.
|
||||
|
||||
[Out of Scope]
|
||||
Once the authorization process is complete, the page loaded in the IFRAME or popup sends
|
||||
a message using postMessage [webmessaging] to the page that loaded it (through the
|
||||
window.opener attribute for popups, or through window.parent for pages loaded in an IFRAME).
|
||||
The message MUST consist of the DOMString "LOGINDONE". This message informs the application
|
||||
that another attempt at generating an identity assertion is likely to be successful.
|
||||
|
||||
9.4. Verifying Identity Assertions
|
||||
The identity assertion request process involves the following asynchronous steps:
|
||||
|
||||
[TODO]
|
||||
1. The RTCPeerConnection awaits any prior identity validation. Only one identity
|
||||
validation can run at a time for an RTCPeerConnection. This can happen because
|
||||
the resolution of setRemoteDescription is not blocked by identity validation
|
||||
unless there is a target peer identity.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
2. The RTCPeerConnection loads the identity assertion from the session description
|
||||
and decodes the base64 value, then parses the resulting JSON. The idp parameter
|
||||
of the resulting dictionary contains a domain and an optional protocol value
|
||||
that identifies the IdP, as described in [RTCWEB-SECURITY-ARCH].
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
3. The RTCPeerConnection instantiates the identified IdP as described in 9.1.1
|
||||
Identity Provider Selection and 9.2 Registering an IdP Proxy. If the IdP
|
||||
cannot be loaded, instantiated or the IdP proxy is not registered, this
|
||||
process fails.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
4. The RTCPeerConnection invokes the validateAssertion method registered by the IdP.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
The assertion parameter is taken from the decoded identity assertion. The origin
|
||||
parameter contains the origin of the script that calls the RTCPeerConnection
|
||||
method that triggers this behavior.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
5. The IdP proxy returns a promise and performs the validation process asynchronously.
|
||||
|
||||
[Out of Scope]
|
||||
The IdP proxy verifies the identity assertion using whatever means necessary.
|
||||
Depending on the authentication protocol this could involve interacting with the
|
||||
IdP server.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
6. If the IdP proxy produces an error or returns a promise that does not resolve
|
||||
to a valid RTCIdentityValidationResult (see 9.5 IdP Error Handling), then
|
||||
identity validation fails.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
7. Once the assertion is successfully verified, the IdP proxy resolves the promise
|
||||
with an RTCIdentityValidationResult containing the validated identity and the
|
||||
original contents that are the payload of the assertion.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
8. The RTCPeerConnection decodes the contents and validates that it contains a
|
||||
fingerprint value for every a=fingerprint attribute in the session description.
|
||||
This ensures that the certificate used by the remote peer for communications
|
||||
is covered by the identity assertion.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
9. The RTCPeerConnection validates that the domain portion of the identity matches
|
||||
the domain of the IdP as described in [RTCWEB-SECURITY-ARCH]. If this check fails
|
||||
then the identity validation fails.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
10. The RTCPeerConnection resolves the peerIdentity attribute with a new instance
|
||||
of RTCIdentityAssertion that includes the IdP domain and peer identity.
|
||||
|
||||
[Out of Scope]
|
||||
11. The user agent MAY display identity information to a user in its UI. Any user
|
||||
identity information that is displayed in this fashion MUST use a mechanism that
|
||||
cannot be spoofed by content.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
If identity validation fails, the peerIdentity promise is rejected with a newly
|
||||
created OperationError.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
If identity validation fails and there is a target peer identity for the
|
||||
RTCPeerConnection, the promise returned by setRemoteDescription MUST be rejected
|
||||
with the same DOMException.
|
||||
|
||||
9.5. IdP Error Handling
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
- A RTCPeerConnection might be configured with an identity provider, but loading of
|
||||
the IdP URI fails. Any procedure that attempts to invoke such an identity provider
|
||||
and cannot load the URI fails with an RTCError with errorDetail set to
|
||||
"idp-load-failure" and the httpRequestStatusCode attribute of the error set to the
|
||||
HTTP status code of the response.
|
||||
|
||||
[Untestable]
|
||||
- If the IdP loads fails due to the TLS certificate used for the HTTPS connection not
|
||||
being trusted, it fails with an RTCError with errorDetail set to "idp-tls-failure".
|
||||
This typically happens when the IdP uses certificate pinning and an intermediary
|
||||
such as an enterprise firewall has intercepted the TLS connection.
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
- If the script loaded from the identity provider is not valid JavaScript or does not
|
||||
implement the correct interfaces, it causes an IdP failure with an RTCError with
|
||||
errorDetail set to "idp-bad-script-failure".
|
||||
|
||||
[TODO]
|
||||
- An apparently valid identity provider might fail in several ways.
|
||||
|
||||
If the IdP token has expired, then the IdP MUST fail with an RTCError with
|
||||
errorDetail set to "idp-token-expired".
|
||||
|
||||
If the IdP token is not valid, then the IdP MUST fail with an RTCError with
|
||||
errorDetail set to "idp-token-invalid".
|
||||
|
||||
[Untestable]
|
||||
- The user agent SHOULD limit the time that it allows for an IdP to 15 seconds.
|
||||
This includes both the loading of the IdP proxy and the identity assertion
|
||||
generation or validation. Failure to do so potentially causes the corresponding
|
||||
operation to take an indefinite amount of time. This timer can be cancelled when
|
||||
the IdP proxy produces a response. Expiration of this timer cases an IdP failure
|
||||
with an RTCError with errorDetail set to "idp-timeout".
|
||||
|
||||
[RTCPeerConnection-getIdentityAssertion]
|
||||
- If the identity provider requires the user to login, the operation will fail
|
||||
RTCError with errorDetail set to "idp-need-login" and the idpLoginUrl attribute
|
||||
of the error set to the URL that can be used to login.
|
||||
|
||||
[RTCPeerConnection-peerIdentity]
|
||||
- Even when the IdP proxy produces a positive result, the procedure that uses this
|
||||
information might still fail. Additional validation of a RTCIdentityValidationResult
|
||||
value is still necessary. The procedure for validation of identity assertions
|
||||
describes additional steps that are required to successfully validate the output
|
||||
of the IdP proxy.
|
||||
|
||||
|
||||
Coverage Report
|
||||
|
||||
Tested 29
|
||||
Not Tested 2
|
||||
Untestable 4
|
||||
|
||||
Total 35
|
|
@ -0,0 +1,240 @@
|
|||
Coverage Report is based on the following editor draft:
|
||||
https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
|
||||
|
||||
4.3.1.6 Set the RTCSessionSessionDescription
|
||||
|
||||
[Trivial]
|
||||
1. Let p be a new promise.
|
||||
|
||||
[Trivial]
|
||||
2. In parallel, start the process to apply description as described in [JSEP]
|
||||
(section 5.5. and section 5.6.).
|
||||
|
||||
[Trivial]
|
||||
1. If the process to apply description fails for any reason, then user agent
|
||||
MUST queue a task that runs the following steps:
|
||||
|
||||
[Untestable]
|
||||
1. If connection's [[IsClosed]] slot is true, then abort these steps.
|
||||
|
||||
[Untestable]
|
||||
2. If elements of the SDP were modified, then reject p with a newly created
|
||||
InvalidModificationError and abort these steps.
|
||||
|
||||
[RTCPeerConnection-setLocalDescription-answer]
|
||||
[RTCPeerConnection-setRemoteDescription-offer]
|
||||
[RTCPeerConnection-setRemoteDescription-answer]
|
||||
3. If the description's type is invalid for the current signaling state of
|
||||
connection as described in [JSEP] (section 5.5. and section 5.6.), then
|
||||
reject p with a newly created InvalidStateError and abort these steps.
|
||||
|
||||
[RTCPeerConnection-setRemoteDescription-offer]
|
||||
4. If the content of description is not valid SDP syntax, then reject p
|
||||
with an RTCError (with errorDetail set to "sdp-syntax-error" and the
|
||||
sdpLineNumber attribute set to the line number in the SDP where the
|
||||
syntax error was detected) and abort these steps.
|
||||
|
||||
[Untestable]
|
||||
5. If the content of description is invalid, then reject p with a newly
|
||||
created InvalidAccessError and abort these steps.
|
||||
|
||||
[Untestable]
|
||||
6. For all other errors, for example if description cannot be applied at
|
||||
the media layer, reject p with a newly created OperationError.
|
||||
|
||||
[Trivial]
|
||||
2. If description is applied successfully, the user agent MUST queue a task
|
||||
that runs the following steps:
|
||||
|
||||
[Untestable]
|
||||
1. If connection's [[isClosed]] slot is true, then abort these steps.
|
||||
|
||||
[RTCPeerConnection-setLocalDescription]
|
||||
2. If description is set as a local description, then run one of the
|
||||
following steps:
|
||||
|
||||
[RTCPeerConnection-setLocalDescription-offer]
|
||||
- If description is of type "offer", set connection.pendingLocalDescription
|
||||
to description and signaling state to have-local-offer.
|
||||
|
||||
[RTCPeerConnection-setLocalDescription-answer]
|
||||
- If description is of type "answer", then this completes an offer answer
|
||||
negotiation.
|
||||
|
||||
Set connection's currentLocalDescription to description and
|
||||
currentRemoteDescription to the value of pendingRemoteDescription.
|
||||
|
||||
Set both pendingRemoteDescription and pendingLocalDescription to null.
|
||||
Finally set connection's signaling state to stable
|
||||
|
||||
[RTCPeerConnection-setLocalDescription-rollback]
|
||||
- If description is of type "rollback", then this is a rollback. Set
|
||||
connection.pendingLocalDescription to null and signaling state to stable.
|
||||
|
||||
[RTCPeerConnection-setLocalDescription-pranswer]
|
||||
- If description is of type "pranswer", then set
|
||||
connection.pendingLocalDescription to description and signaling state to
|
||||
have-local-pranswer.
|
||||
|
||||
[RTCPeerConnection-setRemoteDescription]
|
||||
3. Otherwise, if description is set as a remote description, then run one of the
|
||||
following steps:
|
||||
|
||||
[RTCPeerConnection-setRemoteDescription-offer]
|
||||
- If description is of type "offer", set connection.pendingRemoteDescription
|
||||
attribute to description and signaling state to have-remote-offer.
|
||||
|
||||
[RTCPeerConnection-setRemoteDescription-answer]
|
||||
- If description is of type "answer", then this completes an offer answer
|
||||
negotiation.
|
||||
|
||||
Set connection's currentRemoteDescription to description and
|
||||
currentLocalDescription to the value of pendingLocalDescription.
|
||||
|
||||
Set both pendingRemoteDescription and pendingLocalDescription to null.
|
||||
|
||||
Finally setconnection's signaling state to stable
|
||||
|
||||
[RTCPeerConnection-setRemoteDescription-rollback]
|
||||
- If description is of type "rollback", then this is a rollback.
|
||||
Set connection.pendingRemoteDescription to null and signaling state to stable.
|
||||
|
||||
[RTCPeerConnection-setRemoteDescription-rollback]
|
||||
- If description is of type "pranswer", then set
|
||||
connection.pendingRemoteDescription to description and signaling state
|
||||
to have-remote-pranswer.
|
||||
|
||||
[RTCPeerConnection-setLocalDescription]
|
||||
[RTCPeerConnection-setRemoteDescription]
|
||||
4. If connection's signaling state changed above, fire a simple event named
|
||||
signalingstatechange at connection.
|
||||
|
||||
[TODO]
|
||||
5. If description is of type "answer", and it initiates the closure of an existing
|
||||
SCTP association, as defined in [SCTP-SDP], Sections 10.3 and 10.4, set the value
|
||||
of connection's [[sctpTransport]] internal slot to null.
|
||||
|
||||
[RTCSctpTransport]
|
||||
6. If description is of type "answer" or "pranswer", then run the following steps:
|
||||
|
||||
[RTCSctpTransport]
|
||||
1. If description initiates the establishment of a new SCTP association,
|
||||
as defined in [SCTP-SDP], Sections 10.3 and 10.4, set the value of connection's
|
||||
[[sctpTransport]] internal slot to a newly created RTCSctpTransport.
|
||||
|
||||
[TODO]
|
||||
2. If description negotiates the DTLS role of the SCTP transport, and there is an
|
||||
RTCDataChannel with a null id, then generate an ID according to
|
||||
[RTCWEB-DATA-PROTOCOL].
|
||||
|
||||
[Untestable]
|
||||
If no available ID could be generated, then run the following steps:
|
||||
|
||||
[Untestable]
|
||||
1. Let channel be the RTCDataChannel object for which an ID could not be
|
||||
generated.
|
||||
|
||||
[Untestable]
|
||||
2. Set channel's readyState attribute to closed.
|
||||
|
||||
[Untestable]
|
||||
3. Fire an event named error with a ResourceInUse exception at channel.
|
||||
|
||||
[Untestable]
|
||||
4. Fire a simple event named close at channel.
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
7. If description is set as a local description, then run the following steps for
|
||||
each media description in description that is not yet associated with an
|
||||
RTCRtpTransceiver object:
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
1. Let transceiver be the RTCRtpTransceiver used to create the media
|
||||
description.
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
2. Set transceiver's mid value to the mid of the corresponding media
|
||||
description.
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
8. If description is set as a remote description, then run the following steps
|
||||
for each media description in description:
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
1. As described by [JSEP] (section 5.9.), attempt to find an existing
|
||||
RTCRtpTransceiver object, transceiver, to represent the media description.
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
2. If no suitable transceiver is found (transceiver is unset), run the following
|
||||
steps:
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
1. Create an RTCRtpSender, sender, from the media description.
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
2. Create an RTCRtpReceiver, receiver, from the media description.
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
3. Create an RTCRtpTransceiver with sender, receiver and direction, and let
|
||||
transceiver be the result.
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
3. Set transceiver's mid value to the mid of the corresponding media description.
|
||||
If the media description has no MID, and transceiver's mid is unset, generate
|
||||
a random value as described in [JSEP] (section 5.9.).
|
||||
|
||||
[RTCPeerConnection-ontrack]
|
||||
4. If the direction of the media description is sendrecv or sendonly, and
|
||||
transceiver.receiver.track has not yet been fired in a track event, process
|
||||
the remote track for the media description, given transceiver.
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
5. If the media description is rejected, and transceiver is not already stopped,
|
||||
stop the RTCRtpTransceiver transceiver.
|
||||
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
9. If description is of type "rollback", then run the following steps:
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
1. If the mid value of an RTCRtpTransceiver was set to a non-null value by
|
||||
the RTCSessionDescription that is being rolled back, set the mid value
|
||||
of that transceiver to null, as described by [JSEP] (section 4.1.8.2.).
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
2. If an RTCRtpTransceiver was created by applying the RTCSessionDescription
|
||||
that is being rolled back, and a track has not been attached to it via
|
||||
addTrack, remove that transceiver from connection's set of transceivers,
|
||||
as described by [JSEP] (section 4.1.8.2.).
|
||||
|
||||
[TODO RTCPeerConnection-setDescription-transceiver]
|
||||
3. Restore the value of connection's [[SctpTransport]] internal slot to its
|
||||
value at the last stable signaling state.
|
||||
|
||||
[RTCPeerConnection-onnegotiationneeded]
|
||||
10. If connection's signaling state is now stable, update the negotiation-needed
|
||||
flag. If connection's [[NegotiationNeeded]] slot was true both before and after
|
||||
this update, queue a task that runs the following steps:
|
||||
|
||||
[Untestable]
|
||||
1. If connection's [[IsClosed]] slot is true, abort these steps.
|
||||
|
||||
[RTCPeerConnection-onnegotiationneeded]
|
||||
2. If connection's [[NegotiationNeeded]] slot is false, abort these steps.
|
||||
|
||||
[RTCPeerConnection-onnegotiationneeded]
|
||||
3. Fire a simple event named negotiationneeded at connection.
|
||||
|
||||
[Trivial]
|
||||
11. Resolve p with undefined.
|
||||
|
||||
[Trivial]
|
||||
3. Return p.
|
||||
|
||||
|
||||
Coverage Report
|
||||
|
||||
Tested 35
|
||||
Not Tested 15
|
||||
Untestable 8
|
||||
Total 58
|
101
tests/wpt/web-platform-tests/webrtc/dictionary-helper.js
Normal file
101
tests/wpt/web-platform-tests/webrtc/dictionary-helper.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
'use strict';
|
||||
|
||||
// Helper assertion functions to validate dictionary fields
|
||||
// on dictionary objects returned from APIs
|
||||
|
||||
function assert_unsigned_int_field(object, field) {
|
||||
const num = object[field];
|
||||
assert_true(Number.isInteger(num) && (num >= 0),
|
||||
`Expect dictionary.${field} to be unsigned integer`);
|
||||
}
|
||||
|
||||
function assert_int_field(object, field) {
|
||||
const num = object[field];
|
||||
assert_true(Number.isInteger(num),
|
||||
`Expect dictionary.${field} to be integer`);
|
||||
}
|
||||
|
||||
function assert_string_field(object, field) {
|
||||
const str = object[field];
|
||||
assert_equals(typeof str, 'string',
|
||||
`Expect dictionary.${field} to be string`);
|
||||
}
|
||||
|
||||
function assert_number_field(object, field) {
|
||||
const num = object[field];
|
||||
assert_equals(typeof num, 'number',
|
||||
`Expect dictionary.${field} to be number`);
|
||||
}
|
||||
|
||||
function assert_boolean_field(object, field) {
|
||||
const bool = object[field];
|
||||
assert_equals(typeof bool, 'boolean',
|
||||
`Expect dictionary.${field} to be boolean`);
|
||||
}
|
||||
|
||||
function assert_array_field(object, field) {
|
||||
assert_true(Array.isArray(object[field]),
|
||||
`Expect dictionary.${field} to be array`);
|
||||
}
|
||||
|
||||
function assert_dict_field(object, field) {
|
||||
assert_equals(typeof object[field], 'object',
|
||||
`Expect dictionary.${field} to be plain object`);
|
||||
|
||||
assert_not_equals(object[field], null,
|
||||
`Expect dictionary.${field} to not be null`);
|
||||
}
|
||||
|
||||
function assert_enum_field(object, field, validValues) {
|
||||
assert_string_field(object, field);
|
||||
assert_true(validValues.includes(object[field]),
|
||||
`Expect dictionary.${field} to have one of the valid enum values: ${validValues}`);
|
||||
}
|
||||
|
||||
function assert_optional_unsigned_int_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_unsigned_int_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_int_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_int_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_string_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_string_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_number_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_number_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_boolean_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_boolean_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_array_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_array_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_dict_field(object, field) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_dict_field(object, field);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_optional_enum_field(object, field, validValues) {
|
||||
if(object[field] !== undefined) {
|
||||
assert_enum_field(object, field, validValues);
|
||||
}
|
||||
}
|
37
tests/wpt/web-platform-tests/webrtc/historical.html
Normal file
37
tests/wpt/web-platform-tests/webrtc/historical.html
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!doctype html>
|
||||
<title>Historical WebRTC features</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
test(function() {
|
||||
assert_false("reliable" in RTCDataChannel.prototype);
|
||||
}, "RTCDataChannel member reliable should not exist");
|
||||
|
||||
[
|
||||
"addStream",
|
||||
"createDTMFSender",
|
||||
"getLocalStreams",
|
||||
"getRemoteStreams",
|
||||
"getStreamById",
|
||||
"onaddstream",
|
||||
"onremovestream",
|
||||
"removeStream",
|
||||
"updateIce",
|
||||
].forEach(function(name) {
|
||||
test(function() {
|
||||
assert_false(name in RTCPeerConnection.prototype);
|
||||
}, "RTCPeerConnection member " + name + " should not exist");
|
||||
});
|
||||
|
||||
[
|
||||
"mozRTCIceCandidate",
|
||||
"mozRTCPeerConnection",
|
||||
"mozRTCSessionDescription",
|
||||
"webkitRTCPeerConnection",
|
||||
].forEach(function(name) {
|
||||
test(function() {
|
||||
assert_false(name in window);
|
||||
}, name + " interface should not exist");
|
||||
});
|
||||
</script>
|
70
tests/wpt/web-platform-tests/webrtc/identity-helper.js
Normal file
70
tests/wpt/web-platform-tests/webrtc/identity-helper.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
In web-platform-test, the following domains are required to be set up locally:
|
||||
127.0.0.1 web-platform.test
|
||||
127.0.0.1 www.web-platform.test
|
||||
127.0.0.1 www1.web-platform.test
|
||||
127.0.0.1 www2.web-platform.test
|
||||
127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
|
||||
127.0.0.1 xn--lve-6lad.web-platform.test
|
||||
0.0.0.0 nonexistent-origin.web-platform.test
|
||||
*/
|
||||
|
||||
/*
|
||||
dictionary RTCIdentityProviderDetails {
|
||||
required DOMString domain;
|
||||
DOMString protocol = "default";
|
||||
};
|
||||
*/
|
||||
|
||||
// Parse a base64 JSON encoded string returned from getIdentityAssertion().
|
||||
// This is also the string that is set in the a=identity line.
|
||||
// Returns a { idp, assertion } where idp is of type RTCIdentityProviderDetails
|
||||
// and assertion is the deserialized JSON that was returned by the
|
||||
// IdP proxy's generateAssertion() function.
|
||||
function parseAssertionResult(assertionResultStr) {
|
||||
const assertionResult = JSON.parse(atob(assertionResultStr));
|
||||
|
||||
const { idp } = assertionResult;
|
||||
const assertion = JSON.parse(assertionResult.assertion);
|
||||
|
||||
return { idp, assertion };
|
||||
}
|
||||
|
||||
// Return two distinct IdP domains that are different from current domain
|
||||
function getIdpDomains() {
|
||||
if(window.location.hostname === 'www1.web-platform.test') {
|
||||
return ['www.web-platform.test', 'www2.web-platform.test'];
|
||||
} else if(window.location.hostname === 'www2.web-platform.test') {
|
||||
return ['www.web-platform.test', 'www1.web-platform.test'];
|
||||
} else {
|
||||
return ['www1.web-platform.test', 'www2.web-platform.test'];
|
||||
}
|
||||
}
|
||||
|
||||
function assert_rtcerror_rejection(errorDetail, promise, desc) {
|
||||
return promise.then(
|
||||
res => {
|
||||
assert_unreached(`Expect promise to be rejected with RTCError, but instead got ${res}`);
|
||||
}, err => {
|
||||
assert_true(err instanceof RTCError,
|
||||
'Expect error object to be instance of RTCError');
|
||||
|
||||
assert_equals(err.errorDetail, errorDetail,
|
||||
`Expect RTCError object have errorDetail set to ${errorDetail}`);
|
||||
|
||||
return err;
|
||||
});
|
||||
}
|
||||
|
||||
// construct a host string consist of domain and optionally port
|
||||
// If the default HTTP/HTTPS port is used, window.location.port returns
|
||||
// empty string.
|
||||
function hostString(domain, port) {
|
||||
if(port === '') {
|
||||
return domain;
|
||||
} else {
|
||||
return `${domain}:${port}`;
|
||||
}
|
||||
}
|
|
@ -1,57 +1,180 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebRTC IDL Tests</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=/resources/WebIDLParser.js></script>
|
||||
<script src=/resources/idlharness.js></script>
|
||||
<script>
|
||||
'use strict';
|
||||
<meta charset=utf-8>
|
||||
<title>WebRTC IDL Tests</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/WebIDLParser.js"></script>
|
||||
<script src="/resources/idlharness.js"></script>
|
||||
<script src="./RTCPeerConnection-helper.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
function generateCertificate() {
|
||||
if (!RTCPeerConnection.generateCertificate)
|
||||
return null;
|
||||
return RTCPeerConnection.generateCertificate({
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: 'SHA-256'
|
||||
})
|
||||
.catch(() => {}); // ignore error
|
||||
}
|
||||
// The following helper functions are called from RTCPeerConnection-helper.js:
|
||||
// generateAnswer()
|
||||
// generateMediaStreamTrack()
|
||||
|
||||
function doIdlTest([idlText, certificate]) {
|
||||
const idlArray = new IdlArray();
|
||||
// Put the global IDL test objects under a parent object.
|
||||
// This allows easier search for the test cases when
|
||||
// viewing the web page
|
||||
const idlTestObjects = {};
|
||||
|
||||
idlArray.add_untested_idls('interface EventHandler {};');
|
||||
idlArray.add_untested_idls('interface MediaStreamTrack {};');
|
||||
idlArray.add_idls(idlText);
|
||||
// Helper function to create RTCTrackEvent object
|
||||
function initTrackEvent() {
|
||||
const pc = new RTCPeerConnection();
|
||||
const transceiver = pc.addTransceiver('audio');
|
||||
const { sender, receiver } = transceiver;
|
||||
const { track } = receiver;
|
||||
return new RTCTrackEvent('track', {
|
||||
receiver, track, transceiver
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Add object for all IDL interfaces
|
||||
idlArray.add_objects({
|
||||
'RTCPeerConnection': ['new RTCPeerConnection'],
|
||||
'RTCSessionDescription': ['new RTCSessionDescription({ type: "offer" })'],
|
||||
'RTCIceCandidate': ['new RTCIceCandidate'],
|
||||
'RTCPeerConnectionIceEvent': ['new RTCPeerConnectionIceEvent("ice")'],
|
||||
'RTCPeerConnectionIceErrorEvent': ['new RTCPeerConnectionIceErrorEvent("ice-error", { errorCode: 701 });'],
|
||||
// List of async test driver functions
|
||||
const asyncInitTasks = [
|
||||
asyncInitCertificate,
|
||||
asyncInitTransports,
|
||||
asyncInitMediaStreamTrack,
|
||||
];
|
||||
|
||||
// Asynchronously generate an RTCCertificate
|
||||
function asyncInitCertificate() {
|
||||
return RTCPeerConnection.generateCertificate({
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
modulusLength: 2048,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: 'SHA-256'
|
||||
}).then(cert => {
|
||||
idlTestObjects.certificate = cert;
|
||||
});
|
||||
}
|
||||
|
||||
// Asynchronously generate instances of
|
||||
// RTCSctpTransport, RTCDtlsTransport,
|
||||
// and RTCIceTransport
|
||||
function asyncInitTransports() {
|
||||
const pc = new RTCPeerConnection();
|
||||
pc.createDataChannel('test');
|
||||
|
||||
// setting answer description initializes pc.sctp
|
||||
return pc.createOffer()
|
||||
.then(offer =>
|
||||
pc.setLocalDescription(offer)
|
||||
.then(() => generateAnswer(offer)))
|
||||
.then(answer => pc.setRemoteDescription(answer))
|
||||
.then(() => {
|
||||
const sctpTransport = pc.sctp;
|
||||
assert_true(sctpTransport instanceof RTCSctpTransport,
|
||||
'Expect pc.sctp to be instance of RTCSctpTransport');
|
||||
idlTestObjects.sctpTransport = sctpTransport;
|
||||
|
||||
const dtlsTransport = sctpTransport.transport;
|
||||
assert_true(dtlsTransport instanceof RTCDtlsTransport,
|
||||
'Expect sctpTransport.transport to be instance of RTCDtlsTransport');
|
||||
idlTestObjects.dtlsTransport = dtlsTransport;
|
||||
|
||||
const iceTransport = dtlsTransport.transport;
|
||||
idlTestObjects.iceTransport = iceTransport;
|
||||
});
|
||||
}
|
||||
|
||||
// Asynchoronously generate MediaStreamTrack from getUserMedia
|
||||
function asyncInitMediaStreamTrack() {
|
||||
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
.then(mediaStream => {
|
||||
const tracks = mediaStream.getTracks();
|
||||
assert_greater_than(tracks.length, 0,
|
||||
'Expect media stream to have at least one track');
|
||||
|
||||
idlTestObjects.mediaStreamTrack = tracks[0];
|
||||
});
|
||||
}
|
||||
|
||||
// Run all async test drivers, report and swallow any error
|
||||
// thrown/rejected. Proper test for correct initialization
|
||||
// of the objects are done in their respective test files.
|
||||
function asyncInit() {
|
||||
return Promise.all(asyncInitTasks.map(
|
||||
task => {
|
||||
const t = async_test(`Test driver for ${task.name}`);
|
||||
let promise;
|
||||
t.step(() => {
|
||||
promise = task().then(
|
||||
t.step_func_done(),
|
||||
t.step_func(err =>
|
||||
assert_unreached(`Failed to run ${task.name}: ${err}`)));
|
||||
});
|
||||
return promise;
|
||||
}));
|
||||
}
|
||||
|
||||
if (certificate) {
|
||||
window.certificate = certificate;
|
||||
idlArray.add_objects({'RTCCertificate': ['certificate']});
|
||||
}
|
||||
// Main function to do the IDL test, using fetched IDL text
|
||||
function doIdlTest(idlText) {
|
||||
const idlArray = new IdlArray();
|
||||
|
||||
idlArray.test();
|
||||
}
|
||||
idlArray.add_untested_idls('interface EventHandler {};');
|
||||
idlArray.add_idls('interface MediaStreamTrack : EventTarget {};');
|
||||
idlArray.add_idls(idlText);
|
||||
|
||||
promise_test(() => {
|
||||
return Promise.all([fetch('/interfaces/webrtc-pc.idl').then(response => response.text()),
|
||||
generateCertificate()])
|
||||
.then(doIdlTest);
|
||||
}, 'Test driver');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
idlArray.add_objects({
|
||||
'RTCPeerConnection': [`new RTCPeerConnection()`],
|
||||
|
||||
'RTCSessionDescription': [`new RTCSessionDescription({ type: 'offer' })`],
|
||||
|
||||
'RTCIceCandidate': [`new RTCIceCandidate({ sdpMid: 1 })`],
|
||||
|
||||
'RTCDataChannel': [`new RTCPeerConnection().createDataChannel('')`],
|
||||
|
||||
'RTCRtpTransceiver': [`new RTCPeerConnection().addTransceiver('audio')`],
|
||||
|
||||
'RTCRtpSender': [`new RTCPeerConnection().addTransceiver('audio').sender`],
|
||||
|
||||
'RTCRtpReceiver': [`new RTCPeerConnection().addTransceiver('audio').receiver`],
|
||||
|
||||
'RTCPeerConnectionIceEvent': [`new RTCPeerConnectionIceEvent('ice')`],
|
||||
|
||||
'RTCPeerConnectionIceErrorEvent':
|
||||
[`new RTCPeerConnectionIceErrorEvent('ice-error', { errorCode: 701 });`],
|
||||
|
||||
'RTCTrackEvent': [`initTrackEvent()`],
|
||||
|
||||
'RTCErrorEvent': [`new RTCErrorEvent('error')`],
|
||||
|
||||
'RTCDataChannelEvent': [`new RTCDataChannelEvent('channel',
|
||||
{ channel: new RTCPeerConnection().createDataChannel('') })`],
|
||||
|
||||
// Async initialized objects below
|
||||
|
||||
'RTCCertificate': ['idlTestObjects.certificate'],
|
||||
|
||||
'RTCSctpTransport': ['idlTestObjects.sctpTransport'],
|
||||
|
||||
'RTCDtlsTransport': ['idlTestObjects.dtlsTransport'],
|
||||
|
||||
'RTCIceTransport': ['idlTestObjects.iceTransport'],
|
||||
|
||||
// Test on both MediaStreamTrack from getUserMedia and transceiver
|
||||
'MediaStreamTrack': [
|
||||
`idlTestObjects.mediaStreamTrack`,
|
||||
`generateMediaStreamTrack('audio')`]
|
||||
});
|
||||
|
||||
idlArray.test();
|
||||
}
|
||||
|
||||
promise_test(t => {
|
||||
return asyncInit()
|
||||
.then(() => fetch('/interfaces/webrtc-pc.idl'))
|
||||
.then(response => response.text())
|
||||
.then(doIdlTest);
|
||||
}, 'Main test driver');
|
||||
|
||||
/*
|
||||
TODO
|
||||
RTCRtpContributingSource
|
||||
RTCRtpSynchronizationSource
|
||||
RTCDTMFSender
|
||||
RTCDTMFToneChangeEvent
|
||||
RTCIdentityProviderRegistrar
|
||||
RTCIdentityAssertion
|
||||
*/
|
||||
</script>
|
||||
|
|
|
@ -24,12 +24,6 @@ property to true in Firefox.
|
|||
<!-- These files are in place when executing on W3C. -->
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/common/vendor-prefix.js"
|
||||
data-prefixed-objects=
|
||||
'[{"ancestors":["navigator"], "name":"getUserMedia"}]'
|
||||
data-prefixed-prototypes=
|
||||
'[{"ancestors":["HTMLMediaElement"],"name":"srcObject"}]'>
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var test = async_test('Can set up a basic WebRTC call.', {timeout: 5000});
|
||||
|
||||
|
@ -108,15 +102,13 @@ property to true in Firefox.
|
|||
|
||||
// Returns a suitable error callback.
|
||||
function failed(function_name) {
|
||||
return test.step_func(function() {
|
||||
assert_unreached('WebRTC called error callback for ' + function_name);
|
||||
});
|
||||
return test.unreached_func('WebRTC called error callback for ' + function_name);
|
||||
}
|
||||
|
||||
// This function starts the test.
|
||||
test.step(function() {
|
||||
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
.then(getUserMediaOkCallback, failed('getUserMedia'));
|
||||
.then(test.step_func(getUserMediaOkCallback), failed('getUserMedia'));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue