Update web-platform-tests to revision 8a2ceb5f18911302b7a5c1cd2791f4ab50ad4326

This commit is contained in:
Josh Matthews 2017-10-12 09:25:50 -04:00
parent 462c272380
commit 1f531f66ea
5377 changed files with 174916 additions and 84369 deletions

View file

@ -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),

View file

@ -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>

View file

@ -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}`);
}

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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];
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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,

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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');

View file

@ -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];
});
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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);
}

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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');
}

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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))

View 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');
}

View file

@ -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>

View 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

View 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

View file

@ -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

View 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);
}
}

View 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>

View 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}`;
}
}

View file

@ -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>

View file

@ -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>