mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #25544 - servo-wpt-sync:wpt_update_17-01-2020, r=servo-wpt-sync
Sync WPT with upstream (17-01-2020) Automated downstream sync of changes from upstream as of 17-01-2020. [no-wpt-sync] r? @servo-wpt-sync
This commit is contained in:
commit
2a594821ba
112 changed files with 2867 additions and 303 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,2 @@
|
|||
[background-size-near-zero-gradient.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[background-size-near-zero-png.html]
|
||||
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
|||
[background-size-near-zero-svg.html]
|
||||
expected: FAIL
|
|
@ -1,2 +1,2 @@
|
|||
[no-transition-from-ua-to-blocking-stylesheet.html]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[CaretPosition-001.html]
|
||||
[Element at (400, 100)]
|
||||
expected: FAIL
|
||||
|
|
@ -18,9 +18,6 @@
|
|||
[test some point of the element: bottom right corner]
|
||||
expected: FAIL
|
||||
|
||||
[test some point of the element: top left corner]
|
||||
expected: FAIL
|
||||
|
||||
[test the top of layer]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[elementsFromPoint-invalid-cases.html]
|
||||
[The root element is the last element returned for otherwise empty queries within the viewport]
|
||||
expected: FAIL
|
||||
|
|
@ -318,15 +318,18 @@
|
|||
[<iframe>: combined response Content-Type: text/html;x=" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: text/html;charset=gbk text/plain text/html]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/html;" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: combined response Content-Type: text/html;" \\" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/plain */*;charset=gbk]
|
||||
[<iframe>: separate response Content-Type: text/html */*;charset=gbk]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/html */*]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/plain */*]
|
||||
expected: FAIL
|
||||
|
||||
[<iframe>: separate response Content-Type: text/html;x=" text/plain]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -53,3 +53,6 @@
|
|||
[combined text/javascript ]
|
||||
expected: FAIL
|
||||
|
||||
[separate text/javascript x/x]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[embedded-credentials.tentative.sub.html]
|
||||
type: testharness
|
||||
expected: CRASH
|
||||
expected: TIMEOUT
|
||||
[Embedded credentials are treated as network errors.]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -13,3 +13,9 @@
|
|||
[Embedded credentials matching the top-level are treated as network errors for cross-origin URLs.]
|
||||
expected: TIMEOUT
|
||||
|
||||
[Embedded credentials matching the top-level are not treated as network errors for same-origin URLs.]
|
||||
expected: TIMEOUT
|
||||
|
||||
[Embedded credentials matching the top-level are not treated as network errors for relative URLs.]
|
||||
expected: TIMEOUT
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[traverse_the_history_4.html]
|
||||
[traverse_the_history_2.html]
|
||||
[Multiple history traversals, last would be aborted]
|
||||
expected: FAIL
|
||||
|
|
@ -2436,9 +2436,6 @@
|
|||
[HTMLElement interface: document.createElement("noscript") must inherit property "translate" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLMediaElement interface: document.createElement("video") must inherit property "srcObject" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLFrameElement interface: document.createElement("frame") must inherit property "contentWindow" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -2889,9 +2886,6 @@
|
|||
[HTMLInputElement interface: createInput("color") must inherit property "useMap" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLMediaElement interface: document.createElement("audio") must inherit property "srcObject" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("month") must inherit property "validationMessage" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -3861,9 +3855,6 @@
|
|||
[HTMLInputElement interface: createInput("color") must inherit property "align" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLMediaElement interface: new Audio() must inherit property "srcObject" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLMenuElement interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
[skip-document-with-fragment.html]
|
||||
expected: TIMEOUT
|
||||
[Autofocus elements in iframed documents with URL fragments should be skipped.]
|
||||
expected: FAIL
|
||||
|
||||
[Autofocus elements in top-level browsing context's documents with URI fragments should be skipped.]
|
||||
expected: TIMEOUT
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[iframe_sandbox_popups_escaping-3.html]
|
||||
type: testharness
|
||||
expected: TIMEOUT
|
||||
[Check that popups from a sandboxed iframe escape the sandbox if\n allow-popups-to-escape-sandbox is used]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[iframe-inheritance-data.html]
|
||||
[iframes with data url uses no referrer]
|
||||
expected: FAIL
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[017.html]
|
||||
expected: TIMEOUT
|
||||
[origin of the script that invoked the method, about:blank]
|
||||
expected: TIMEOUT
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
[ar_hittest_subscription_refSpaces.https.html]
|
||||
expected: ERROR
|
|
@ -0,0 +1,2 @@
|
|||
[Worker-constructor.html]
|
||||
expected: ERROR
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline', img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline'"]
|
||||
},
|
||||
"content_security": {
|
||||
"policies": ["img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline'"],
|
||||
"policies": ["img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies_report_only": ["img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": "script-src 'self' 'unsafe-inline'"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"content_security": ["script-src 'self' 'unsafe-inline'"]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": [["script-src 'self' 'unsafe-inline'"]]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline'", "img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline'; img-src 'none'"]
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"content-security-policy": [{ "policy": "script-src 'self' 'unsafe-inline'" }]
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'unsafe-inline'"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"content-security-policy": [{
|
||||
"policy": "script-src 'self' 'nonce-test'"
|
||||
}]
|
||||
"content_security": {
|
||||
"policies": ["script-src 'self' 'nonce-test'"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"content-security-policy": [{ "policy": "img-src 'none'" }]
|
||||
}
|
|
@ -86,7 +86,7 @@ function define_tests() {
|
|||
// - illegal name for hash algorithm (NotSupportedError)
|
||||
var badHash = hashName.substring(0, 3) + hashName.substring(4);
|
||||
subsetTest(promise_test, function(test) {
|
||||
var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash};
|
||||
var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash, info: algorithm.info};
|
||||
return subtle.deriveKey(badAlgorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages)
|
||||
.then(function(key) {
|
||||
assert_unreached("bad hash name should have thrown an NotSupportedError");
|
||||
|
@ -143,9 +143,9 @@ function define_tests() {
|
|||
subsetTest(promise_test, function(test) {
|
||||
return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], null)
|
||||
.then(function(derivation) {
|
||||
assert_unreached("null length should have thrown an TypeError");
|
||||
assert_unreached("null length should have thrown an OperationError");
|
||||
}, function(err) {
|
||||
assert_equals(err.name, "TypeError", "deriveBits with null length correctly threw OperationError: " + err.message);
|
||||
assert_equals(err.name, "OperationError", "deriveBits with null length correctly threw OperationError: " + err.message);
|
||||
});
|
||||
}, testName + " with null length");
|
||||
|
||||
|
@ -162,7 +162,7 @@ function define_tests() {
|
|||
// - illegal name for hash algorithm (NotSupportedError)
|
||||
var badHash = hashName.substring(0, 3) + hashName.substring(4);
|
||||
subsetTest(promise_test, function(test) {
|
||||
var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash};
|
||||
var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash, info: algorithm.info};
|
||||
return subtle.deriveBits(badAlgorithm, baseKeys[derivedKeySize], 256)
|
||||
.then(function(derivation) {
|
||||
assert_unreached("bad hash name should have thrown an NotSupportedError");
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<title>Color background with near-zero background-size</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-size">
|
||||
<link rel="match" href="reference/background-size-near-zero-ref.html">
|
||||
<div style="background-color: green;
|
||||
width: 100px; height: 100px; background-size: 0.2px 0.2px">
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<title>Gradient background with near-zero background-size</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-size">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-image">
|
||||
<link rel="match" href="reference/background-size-near-zero-ref.html">
|
||||
<div style="background-image: linear-gradient(green, green);
|
||||
width: 100px; height: 100px; background-size: 0.2px 0.2px">
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<title>PNG background with near-zero background-size</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-size">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-image">
|
||||
<link rel="match" href="reference/background-size-near-zero-ref.html">
|
||||
<div style="background-image: url(support/50x50-green.png);
|
||||
width: 100px; height: 100px; background-size: 0.2px 0.2px">
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<title>SVG background with near-zero background-size</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-size">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#background-image">
|
||||
<link rel="match" href="reference/background-size-near-zero-ref.html">
|
||||
<div style="background-image: url(support/50x50-green.svg);
|
||||
width: 100px; height: 100px; background-size: 0.2px 0.2px">
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE html>
|
||||
<div style="width: 100px; height: 100px; background: green"></div>
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
|
||||
<rect fill="green" width="50" height="50"/>
|
||||
</svg>
|
After Width: | Height: | Size: 116 B |
|
@ -97,7 +97,7 @@
|
|||
'<line-names>': ['[a] auto [b] auto [c]', '[a] 50px [b] 50px [c]'],
|
||||
'<track-size>.auto': ['auto', '50px'],
|
||||
'<track-size>.<track-breadth>.<length>': ['100px', '100px'],
|
||||
'<track-size>.<track-breadth>.<percentage>': ['100%', '50px'],
|
||||
'<track-size>.<track-breadth>.<percentage>': ['100%', '150px'],
|
||||
'<track-size>.<track-breadth>.<flex>': ['1fr', '50px'],
|
||||
'<track-size>.<track-breadth>.min-content': ['min-content', '50px'],
|
||||
'<track-size>.<track-breadth>.max-content': ['max-content', '50px'],
|
||||
|
@ -111,13 +111,13 @@
|
|||
'reset': ['none', 'none'],
|
||||
},
|
||||
'grid-template': {
|
||||
initial: '150px / 50px 50px 50px',
|
||||
'none': ['', '150px / 50px 50px 50px'],
|
||||
initial: 'none',
|
||||
'none': ['', 'none'],
|
||||
'<grid-template-rows> / <grid-template-columns>': ['100px 100px / 200px 200px', '100px 100px / 200px 200px'],
|
||||
'<line-names>': ['[a] auto [b] auto [c] / [d] auto [e] auto [f]', '[a] auto [b] auto [c] / [d] auto [e] auto [f]'],
|
||||
'<string>+': ['"a b" "a b"', '"a b" "a b"'],
|
||||
'<string><track-size>+': ['100px / "a b" 50px', '100px / "a b" 50px'],
|
||||
'reset': ['', '150px / 50px 50px 50px'],
|
||||
'reset': ['', 'none'],
|
||||
},
|
||||
'grid-auto-columns': {
|
||||
initial: 'auto',
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html class=reftest-wait>
|
||||
<link rel="help" href="https://drafts.css-houdini.org/css-layout-api/#layout-children">
|
||||
<link rel="match" href="../green-square-ref.html">
|
||||
<meta name="assert" content="This test checks that text children are correctly blockified when no other children exist." />
|
||||
|
||||
<style>
|
||||
/* We have a wrapper in this test to ensure that any text that is positioned
|
||||
* slightly outside the "test" box doesn't affect the rendering.
|
||||
* This wrapper has a 10px inline padding which does the trick. */
|
||||
.wrapper {
|
||||
background: green;
|
||||
padding: 0 10px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.test {
|
||||
--child-expected: ["default"];
|
||||
|
||||
background: red;
|
||||
color: green;
|
||||
width: 80px;
|
||||
--child: default;
|
||||
}
|
||||
|
||||
@supports (display: layout(test)) {
|
||||
.test {
|
||||
background: green;
|
||||
display: layout(test);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script src="/common/reftest-wait.js"></script>
|
||||
<script src="/common/worklet-reftest.js"></script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="test">
|
||||
Text text text
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
importWorkletAndTerminateTestAfterAsyncPaint(CSS.layoutWorklet, {url: 'support/layout-child-worklet.js'});
|
||||
</script>
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Pseudo-Elements Test: Computed size of ::marker</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-content/#content-property">
|
||||
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
|
||||
<meta name="assert" content="This test checks that 'content' resolves correctly in ::marker." />
|
||||
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
|
||||
<style>
|
||||
.no-list > li {
|
||||
display: block;
|
||||
}
|
||||
.normal::marker {
|
||||
content: normal;
|
||||
}
|
||||
.string::marker {
|
||||
content: "string";
|
||||
}
|
||||
.image::marker {
|
||||
content: url("about:invalid");
|
||||
}
|
||||
.none::marker {
|
||||
content: none;
|
||||
}
|
||||
</style>
|
||||
<div id="log"></div>
|
||||
<ol class="list">
|
||||
<li class="default">item</li>
|
||||
<li class="normal">item</li>
|
||||
<li class="string">item</li>
|
||||
<li class="image">item</li>
|
||||
<li class="none">item</li>
|
||||
</ol>
|
||||
<ol class="no-list">
|
||||
<li class="default">item</li>
|
||||
<li class="normal">item</li>
|
||||
<li class="string">item</li>
|
||||
<li class="image">item</li>
|
||||
<li class="none">item</li>
|
||||
</ol>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
const expectations = {
|
||||
default: 'normal',
|
||||
normal: 'normal',
|
||||
string: '"string"',
|
||||
image: 'url("about:invalid")',
|
||||
none: 'none',
|
||||
};
|
||||
for (const target of document.querySelectorAll('.list > li')) {
|
||||
const {content} = getComputedStyle(target, '::marker');
|
||||
test(() => {
|
||||
debugger;
|
||||
assert_equals(content, expectations[target.className]);
|
||||
}, `Computed 'content' for list-item ::marker, variant ${target.className}`);
|
||||
}
|
||||
for (const target of document.querySelectorAll('.no-list > li')) {
|
||||
const {content} = getComputedStyle(target, '::marker');
|
||||
test(() => {
|
||||
assert_equals(content, expectations[target.className]);
|
||||
}, `Computed 'content' for non-list-item ::marker, variant ${target.className}`);
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Reference: ::marker pseudo elements styled with 'content' property</title>
|
||||
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
|
||||
<style>
|
||||
li {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<ol>
|
||||
<li>inside symbol</li>
|
||||
<li>inside decimal</li>
|
||||
<li>inside string</li>
|
||||
<li>inside image</li>
|
||||
</ol>
|
||||
<ol>
|
||||
<li>outside symbol</li>
|
||||
<li>outside decimal</li>
|
||||
<li>outside string</li>
|
||||
<li>outside image</li>
|
||||
</ol>
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS Test: ::marker pseudo elements styled with 'content' property</title>
|
||||
<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
|
||||
<link rel="match" href="marker-content-019-ref.html">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
|
||||
<link rel="help" href="https://drafts.csswg.org/css-content/#content-property">
|
||||
<meta name="assert" content="Checks that the 'content: none' hides the ::marker.">
|
||||
<style>
|
||||
::marker {
|
||||
content: none
|
||||
}
|
||||
.inside {
|
||||
list-style-position: inside;
|
||||
}
|
||||
.symbol {
|
||||
list-style-type: disc;
|
||||
}
|
||||
.decimal {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
.string {
|
||||
list-style-type: "string";
|
||||
}
|
||||
.image {
|
||||
list-style-image: url("/images/green-100x50.png");
|
||||
}
|
||||
</style>
|
||||
<ol class="inside">
|
||||
<li class="symbol">inside symbol</li>
|
||||
<li class="decimal">inside decimal</li>
|
||||
<li class="string">inside string</li>
|
||||
<li class="image">inside image</li>
|
||||
</ol>
|
||||
<ol class="outside">
|
||||
<li class="symbol">outside symbol</li>
|
||||
<li class="decimal">outside decimal</li>
|
||||
<li class="string">outside string</li>
|
||||
<li class="image">outside image</li>
|
||||
</ol>
|
|
@ -467,6 +467,19 @@ function attachShadowDiv(host) {
|
|||
return shadowDiv;
|
||||
}
|
||||
|
||||
test(() => {
|
||||
const sheet = new CSSStyleSheet();
|
||||
assert_equals(sheet.cssRules.length, 0);
|
||||
|
||||
sheet.replaceSync(redStyleTexts[0])
|
||||
assert_equals(sheet.cssRules.length, 1);
|
||||
assert_equals(redStyleTexts[0], sheet.cssRules[0].cssText);
|
||||
|
||||
sheet.replaceSync(redStyleTexts[1]);
|
||||
assert_equals(sheet.cssRules.length, 1);
|
||||
assert_equals(redStyleTexts[1], sheet.cssRules[0].cssText);
|
||||
}, 'CSSStyleSheet.replaceSync replaces stylesheet text synchronously');
|
||||
|
||||
test(() => {
|
||||
// Attach a div inside a shadow root with the class ".red".
|
||||
const span = document.createElement("span");
|
||||
|
@ -485,7 +498,7 @@ test(() => {
|
|||
sheet.insertRule(redStyleTexts[1]);
|
||||
assert_equals(sheet.cssRules.length, 2);
|
||||
assert_equals(sheet.cssRules[0].cssText, redStyleTexts[1]);
|
||||
}, 'CSSStyleSheet.replaceSync replaces stylesheet text synchronously');
|
||||
}, 'CSSStyleSheet.replaceSync correctly updates the style of its adopters synchronously');
|
||||
|
||||
const import_text = '@import url("support/constructable-import.css");';
|
||||
|
||||
|
@ -497,6 +510,27 @@ test(() => {
|
|||
assert_throws("NotAllowedError", () => { (new CSSStyleSheet).insertRule(import_text) });
|
||||
}, 'Inserting an @import rule through insertRule on a constructed stylesheet throws an exception');
|
||||
|
||||
async_test(t => {
|
||||
const importUrl = "support/constructable-import.css";
|
||||
const sheet = new CSSStyleSheet();
|
||||
|
||||
assert_throws("NotAllowedError", () => { sheet.replaceSync(`@import url("${importUrl}");`) });
|
||||
|
||||
const timeAfterReplaceSync = performance.now();
|
||||
let link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = importUrl;
|
||||
|
||||
link.addEventListener("error", t.unreached_func("Load shouldn't fail"));
|
||||
link.addEventListener("load", t.step_func_done(event => {
|
||||
let entries = window.performance.getEntriesByType('resource').filter(entry => entry.name.includes(importUrl));
|
||||
assert_equals(entries.length, 1, "There should be only one entry for the import URL");
|
||||
assert_greater_than_equal(entries[0].startTime, timeAfterReplaceSync, "The entry's start time should be after replaceSync threw");
|
||||
link.remove();
|
||||
}));
|
||||
document.body.appendChild(link);
|
||||
}, "CSSStyleSheet.replaceSync should not trigger any loads from @import rules")
|
||||
|
||||
promise_test(() => {
|
||||
const span = document.createElement("span");
|
||||
thirdSection.appendChild(span);
|
||||
|
|
|
@ -2,5 +2,5 @@ Template-Python==0.1.post1
|
|||
html5lib==1.0.1
|
||||
lxml==4.1.1
|
||||
mercurial==4.5
|
||||
six==1.12.0
|
||||
six==1.14.0
|
||||
webencodings==0.5.1
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>This test validates some Timing-Allow-Origin header usage in multiple redirects.</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/element-timing-helpers.js"></script>
|
||||
<script src="/common/get-host-info.sub.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
async_test(t => {
|
||||
assert_precondition(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
|
||||
let destUrl = get_host_info().HTTP_REMOTE_ORIGIN
|
||||
+ '/element-timing/resources/multiple-redirects.py?';
|
||||
destUrl += 'redirect_count=2';
|
||||
// The final resource has '*' in TAO header, so will not affect the result.
|
||||
destUrl += '&final_resource=/element-timing/resources/circle-tao.svg';
|
||||
destUrl += '&origin1=' + get_host_info().UNAUTHENTICATED_ORIGIN;
|
||||
destUrl += '&origin2=' + get_host_info().HTTP_REMOTE_ORIGIN;
|
||||
const taoCombinations = [
|
||||
{tao1: location.origin, tao2: location.origin, passes: false},
|
||||
{tao1: location.origin, tao2: get_host_info().HTTP_REMOTE_ORIGIN, passes: false},
|
||||
{tao1: location.origin, tao2: 'null', passes: true},
|
||||
{tao1: location.origin, tao2: '*', passes: true},
|
||||
{tao1: location.origin, tao2: location.origin, passes: false},
|
||||
{tao1: 'null', tao2: '*', passes: false},
|
||||
{tao1: '*', tao2: 'null', passes: true},
|
||||
];
|
||||
function getURL(item) {
|
||||
return destUrl + '&tao1=' + item.tao1 + '&tao2=' + item.tao2;
|
||||
}
|
||||
taoCombinations.forEach((item, index) => {
|
||||
const image = document.createElement('img');
|
||||
image.src = getURL(item);
|
||||
image.setAttribute('elementtiming', item.passes.toString());
|
||||
image.setAttribute('id', index);
|
||||
document.body.appendChild(image);
|
||||
});
|
||||
let observed = new Array(taoCombinations.length).fill(false);
|
||||
let observedCount = 0;
|
||||
const beforeRender = performance.now();
|
||||
new PerformanceObserver(t.step_func(entries => {
|
||||
entries.getEntries().forEach(e => {
|
||||
const index = parseInt(e.id);
|
||||
assert_false(observed[index]);
|
||||
const item = taoCombinations[index];
|
||||
const url = getURL(item);
|
||||
checkElement(e, url, item.passes.toString(), e.id, item.passes ? beforeRender : 0,
|
||||
document.getElementById(e.id));
|
||||
observed[index] = true;
|
||||
observedCount++;
|
||||
if (observedCount === taoCombinations.length)
|
||||
t.done();
|
||||
});
|
||||
})).observe({entryTypes: ['element']});
|
||||
}, 'Cross-origin images with passing/failing TAO should/shouldn\'t have its renderTime set.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -18,7 +18,7 @@ async_test(t => {
|
|||
+ '/resource-timing/resources/multi_redirect.py?';
|
||||
destUrl += 'page_origin=' + get_host_info().HTTP_ORIGIN;
|
||||
destUrl += '&cross_origin=' + get_host_info().HTTP_REMOTE_ORIGIN;
|
||||
destUrl += '&final_resource=' + '/element-timing/resources/circle.svg';
|
||||
destUrl += '&final_resource=' + '/element-timing/resources/circle-tao.svg';
|
||||
destUrl += '&timing_allow=1';
|
||||
destUrl += '&tao_steps=';
|
||||
for (let taoSteps=0; taoSteps < 4; taoSteps++) {
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
||||
<circle cx="50%" cy="50%" r="80" style="fill:blue;" />
|
||||
</svg>
|
After Width: | Height: | Size: 275 B |
|
@ -6,9 +6,11 @@ function checkElementInternal(entry, expectedUrl, expectedIdentifier, expectedID
|
|||
assert_equals(entry.identifier, expectedIdentifier, 'identifier does not match');
|
||||
if (beforeRender != 0) {
|
||||
// In this case, renderTime is not 0.
|
||||
assert_greater_than(entry.renderTime, 0, 'renderTime should be nonzero');
|
||||
assert_equals(entry.startTime, entry.renderTime, 'startTime should equal renderTime');
|
||||
} else {
|
||||
// In this case, renderTime is 0, so compare to loadTime.
|
||||
assert_equals(entry.renderTime, 0, 'renderTime should be zero');
|
||||
assert_equals(entry.startTime, entry.loadTime, 'startTime should equal loadTime');
|
||||
}
|
||||
assert_equals(entry.duration, 0, 'duration should be 0');
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
def main(request, response):
|
||||
"""Handler that causes multiple redirections.
|
||||
|
||||
Mandatory parameters:
|
||||
redirect_count - A number which is at least 1 (number of redirects).
|
||||
final_resource - The location of the last redirect.
|
||||
|
||||
For each number i between 1 and |redirect_count| we have the following optional parameters:
|
||||
tao{{i}} - The Timing-Allow-Origin header of the ith response. Default is no header.
|
||||
origin{{i}} - The origin of the ith redirect (i+1 response). Default is location.origin.
|
||||
Note that the origin of the initial request cannot be controlled here
|
||||
and the Timing-Allow-Origin header of the final response cannot be controlled here.
|
||||
|
||||
Example: redirect_count=2&final_resource=miau.png&tao1=*
|
||||
|
||||
Note: |step| is used internally to track the current redirect number.
|
||||
"""
|
||||
step = 1
|
||||
if "step" in request.GET:
|
||||
try:
|
||||
step = int(request.GET.first("step"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
redirect_count = int(request.GET.first("redirect_count"))
|
||||
final_resource = request.GET.first("final_resource")
|
||||
|
||||
tao_value = None
|
||||
tao = "tao" + str(step)
|
||||
if tao in request.GET:
|
||||
tao_value = request.GET.first(tao)
|
||||
|
||||
redirect_url = ""
|
||||
origin = "origin" + str(step)
|
||||
if origin in request.GET:
|
||||
redirect_url = request.GET.first(origin)
|
||||
|
||||
if step == redirect_count:
|
||||
redirect_url += final_resource
|
||||
else:
|
||||
redirect_url += "/element-timing/resources/multiple-redirects.py?"
|
||||
redirect_url += "redirect_count=" + str(redirect_count)
|
||||
redirect_url += "&final_resource=" + final_resource
|
||||
for i in range(1, redirect_count + 1):
|
||||
tao = "tao" + str(i)
|
||||
if tao in request.GET:
|
||||
redirect_url += "&" + tao + "=" + request.GET.first(tao)
|
||||
origin = "origin" + str(i)
|
||||
if origin in request.GET:
|
||||
redirect_url += "&" + origin + "=" + request.GET.first(origin)
|
||||
redirect_url += "&step=" + str(step + 1)
|
||||
|
||||
if tao_value:
|
||||
response.headers.set("timing-allow-origin", tao_value)
|
||||
|
||||
response.status = 302
|
||||
response.headers.set("Location", redirect_url)
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>This test validates element timing information for same-origin redirect chain.</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/element-timing-helpers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
async_test(t => {
|
||||
assert_precondition(window.PerformanceElementTiming, "PerformanceElementTiming is not implemented");
|
||||
// First redirect
|
||||
let destUrl = '/common/redirect.py?location='
|
||||
// Second redirect
|
||||
destUrl += '/common/redirect.py?location='
|
||||
// Image without TAO headers.
|
||||
destUrl += '/element-timing/resources/square20.png';
|
||||
let beforeRender;
|
||||
new PerformanceObserver(t.step_func_done(entries => {
|
||||
assert_equals(entries.getEntries().length, 1, 'There should be one entry');
|
||||
const entry = entries.getEntries()[0];
|
||||
checkElement(entry, location.origin + destUrl, 'et', 'id', beforeRender,
|
||||
document.getElementById('id'));
|
||||
})).observe({entryTypes: ['element']});
|
||||
const image = document.createElement('img');
|
||||
image.src = destUrl;
|
||||
image.setAttribute('elementtiming', 'et');
|
||||
image.setAttribute('id', 'id')
|
||||
document.body.appendChild(image);
|
||||
beforeRender = performance.now();
|
||||
}, 'Same-origin image redirect without TAO should have its renderTime set.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -38,7 +38,7 @@ const waitForLoad = new Promise(resolve => { addEventListener('load', resolve);
|
|||
|
||||
idl_test(
|
||||
['html'],
|
||||
['SVG', 'cssom', 'touch-events', 'uievents', 'dom', 'xhr', 'FileAPI'],
|
||||
['SVG', 'cssom', 'touch-events', 'uievents', 'dom', 'xhr', 'FileAPI', 'mediacapture-streams'],
|
||||
async idlArray => {
|
||||
self.documentWithHandlers = new Document();
|
||||
const handler = function(e) {};
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>Inputs remain focusable upon changing type</title>
|
||||
<link rel="help" href="https://wicg.github.io/auxclick">
|
||||
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=981248">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-actions.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<h1>Can still focus on inputs that change types</h1>
|
||||
<input type="text" value="123" onfocus="javascript:event.target.type='number'"
|
||||
onblur="javascript:event.target.type='text'">
|
||||
<script>
|
||||
promise_test(() => {
|
||||
// Click the input to attempt to focus on it
|
||||
const target = document.querySelector("input");
|
||||
const actions = new test_driver.Actions();
|
||||
return actions.pointerMove(0, 0, {origin: target})
|
||||
.pointerDown({button: actions.ButtonType.LEFT})
|
||||
.pointerUp({button: actions.ButtonType.LEFT})
|
||||
.send()
|
||||
.then(() => assert_equals(document.activeElement, target,
|
||||
"The element was correctly focused"));
|
||||
}, "Can change an input's type during focus handler without breaking focus");
|
||||
</script>
|
|
@ -23,6 +23,10 @@
|
|||
tag: "input",
|
||||
types: ["datetime-local"],
|
||||
testData: [
|
||||
// Note that several of these datetime strings use a ' ' separator, which
|
||||
// is questionably spec-compliant. See:
|
||||
// - crbug.com/521871
|
||||
// - https://github.com/whatwg/html/issues/2276
|
||||
{conditions: {required: false, value: ""}, expected: false, name: "[target] The required attribute is not set"},
|
||||
{conditions: {required: true, value: "2000-12-10T12:00:00"}, expected: false, name: "[target] Valid local date and time string(2000-12-10T12:00:00)"},
|
||||
{conditions: {required: true, value: "2000-12-10 12:00"}, expected: false, name: "[target] Valid local date and time string(2000-12-10 12:00)"},
|
||||
|
|
93
tests/wpt/web-platform-tests/html/syntax/parsing/zero.html
Normal file
93
tests/wpt/web-platform-tests/html/syntax/parsing/zero.html
Normal file
|
@ -0,0 +1,93 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title></title>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<body>
|
||||
<div></div>
|
||||
<script>
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<!-\u0000>";
|
||||
assert_equals(div.firstChild.data, "-\uFFFD");
|
||||
}, "U+0000 should get replaced with U+FFFD after markup declaration hyphen");
|
||||
|
||||
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "&\u0000auml;";
|
||||
assert_equals(div.firstChild.data, "ä");
|
||||
}, "U+0000 should vanish after ampersand");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "&a\u0000uml;";
|
||||
assert_equals(div.firstChild.data, "ä");
|
||||
}, "U+0000 should vanish after ampersand and one letter of entity prefix");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "&au\u0000ml;";
|
||||
assert_equals(div.firstChild.data, "ä");
|
||||
}, "U+0000 should vanish after ampersand and two letters of entity prefix");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "&aum\u0000l;";
|
||||
assert_equals(div.firstChild.data, "ä");
|
||||
}, "U+0000 should vanish after ampersand and three letters of entity prefix");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "ä\u0000;";
|
||||
assert_equals(div.firstChild.data, "\u00E4;");
|
||||
}, "U+0000 should vanish after semicolonless entity");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "¬in\u0000;";
|
||||
assert_equals(div.firstChild.data, "\u00ACin;");
|
||||
}, "U+0000 should vanish before required semicolon");
|
||||
|
||||
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<span title='&\u0000auml;'>";
|
||||
assert_equals(div.firstChild.title, "&\uFFFDauml;");
|
||||
}, "U+0000 should get replaced with U+FFFD after ampersand");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<span title='&a\u0000uml;'>";
|
||||
assert_equals(div.firstChild.title, "&a\uFFFDuml;");
|
||||
}, "U+0000 should get replaced with U+FFFD after ampersand and one letter of entity prefix");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<span title='&au\u0000ml;'>";
|
||||
assert_equals(div.firstChild.title, "&au\uFFFDml;");
|
||||
}, "U+0000 should get replaced with U+FFFD after ampersand and two letters of entity prefix");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<span title='&aum\u0000l;'>";
|
||||
assert_equals(div.firstChild.title, "&aum\uFFFDl;");
|
||||
}, "U+0000 should get replaced with U+FFFD after ampersand and three letters of entity prefix");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<span title='ä\u0000;'>";
|
||||
assert_equals(div.firstChild.title, "\u00E4\uFFFD;");
|
||||
}, "U+0000 should get replaced with U+FFFD after semicolonless entity");
|
||||
|
||||
test(function() {
|
||||
var div = document.getElementsByTagName("div")[0];
|
||||
div.innerHTML = "<span title='¬in\u0000;'>";
|
||||
assert_equals(div.firstChild.title, "¬in\uFFFD;");
|
||||
}, "U+0000 should get replaced with U+FFFD before required semicolon");
|
||||
|
||||
|
||||
|
||||
</script>
|
|
@ -1904,7 +1904,6 @@ interface mixin GlobalEventHandlers {
|
|||
attribute EventHandler onmouseout;
|
||||
attribute EventHandler onmouseover;
|
||||
attribute EventHandler onmouseup;
|
||||
attribute EventHandler onwheel;
|
||||
attribute EventHandler onpause;
|
||||
attribute EventHandler onplay;
|
||||
attribute EventHandler onplaying;
|
||||
|
@ -1929,6 +1928,7 @@ interface mixin GlobalEventHandlers {
|
|||
attribute EventHandler onwebkitanimationiteration;
|
||||
attribute EventHandler onwebkitanimationstart;
|
||||
attribute EventHandler onwebkittransitionend;
|
||||
attribute EventHandler onwheel;
|
||||
};
|
||||
|
||||
interface mixin WindowEventHandlers {
|
||||
|
|
|
@ -21,6 +21,7 @@ interface mixin AriaAttributes {
|
|||
attribute FrozenArray<Element>? ariaControlsElements;
|
||||
attribute DOMString? ariaCurrent;
|
||||
attribute FrozenArray<Element>? ariaDescribedByElements;
|
||||
attribute DOMString? ariaDescription;
|
||||
attribute Element? ariaDetailsElement;
|
||||
attribute DOMString? ariaDisabled;
|
||||
attribute Element? ariaErrorMessageElement;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>This test validates LargestContenfulPaint information for cross-origin redirect chain images.</title>
|
||||
<title>This test validates LargestContentfulPaint information for cross-origin redirect chain images.</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/largest-contentful-paint-helpers.js"></script>
|
||||
|
@ -11,14 +11,12 @@
|
|||
<body>
|
||||
<script>
|
||||
async_test(t => {
|
||||
if (!window.LargestContentfulPaint) {
|
||||
assert_unreached("LargestContentfulPaint is not implemented");
|
||||
}
|
||||
assert_precondition(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented");
|
||||
let destUrl = get_host_info().HTTP_REMOTE_ORIGIN
|
||||
+ '/resource-timing/resources/multi_redirect.py?';
|
||||
destUrl += 'page_origin=' + get_host_info().HTTP_ORIGIN;
|
||||
destUrl += '&cross_origin=' + get_host_info().HTTP_REMOTE_ORIGIN;
|
||||
destUrl += '&final_resource=' + '/element-timing/resources/circle.svg';
|
||||
destUrl += '&final_resource=' + '/element-timing/resources/circle-tao.svg';
|
||||
destUrl += '&timing_allow=1';
|
||||
destUrl += '&tao_steps=';
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>This test validates LargestContentfulPaint for same-origin redirect chain.</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/largest-contentful-paint-helpers.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
async_test(t => {
|
||||
assert_precondition(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented");
|
||||
// First redirect
|
||||
let destUrl = '/common/redirect.py?location='
|
||||
// Second redirect
|
||||
destUrl += '/common/redirect.py?location='
|
||||
// Image without TAO headers.
|
||||
destUrl += '/element-timing/resources/square20.png';
|
||||
let beforeLoad;
|
||||
new PerformanceObserver(t.step_func_done(entries => {
|
||||
assert_equals(entries.getEntries().length, 1, 'There should be one entry');
|
||||
const entry = entries.getEntries()[0];
|
||||
checkImage(entry, location.origin + destUrl, 'id', 20*20, beforeLoad);
|
||||
})).observe({entryTypes: ['largest-contentful-paint']});
|
||||
const image = document.createElement('img');
|
||||
image.src = destUrl;
|
||||
image.setAttribute('id', 'id')
|
||||
document.body.appendChild(image);
|
||||
beforeLoad = performance.now();
|
||||
}, 'Same-origin image redirect without TAO should have its renderTime set.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
const recorderOnError = test.unreached_func('Recording error.');
|
||||
|
||||
const gotStream = test.step_func(function(stream) {
|
||||
for (track in stream.getTracks())
|
||||
for (track of stream.getTracks())
|
||||
track.enabled = false;
|
||||
|
||||
recorder = new MediaRecorder(stream);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>MediaRecorder MIMEType</title>
|
||||
<link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimetype">
|
||||
<title>MediaRecorder mimeType</title>
|
||||
<link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimeType">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
</head>
|
||||
|
@ -47,39 +47,39 @@ function createAudioVideoStream(t) {
|
|||
test(t => {
|
||||
const recorder = new MediaRecorder(createAudioStream(t));
|
||||
assert_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has no default MIMEtype");
|
||||
}, "MediaRecorder sets no default MIMEType in the constructor for audio");
|
||||
"MediaRecorder has no default mimeType");
|
||||
}, "MediaRecorder sets no default mimeType in the constructor for audio");
|
||||
|
||||
test(t => {
|
||||
const recorder = new MediaRecorder(createVideoStream(t));
|
||||
assert_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has no default MIMEtype");
|
||||
}, "MediaRecorder sets no default MIMEType in the constructor for video");
|
||||
"MediaRecorder has no default mimeType");
|
||||
}, "MediaRecorder sets no default mimeType in the constructor for video");
|
||||
|
||||
test(t => {
|
||||
const stream = createAudioVideoStream(t);
|
||||
const recorder = new MediaRecorder(stream);
|
||||
assert_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has no default MIMEtype");
|
||||
}, "MediaRecorder sets no default MIMEType in the constructor for audio/video");
|
||||
"MediaRecorder has no default mimeType");
|
||||
}, "MediaRecorder sets no default mimeType in the constructor for audio/video");
|
||||
|
||||
test(t => {
|
||||
assert_throws("NotSupportedError",
|
||||
() => new MediaRecorder(new MediaStream(), {mimeType: "audio/banana"}));
|
||||
}, "MediaRecorder invalid audio MIMEType throws");
|
||||
}, "MediaRecorder invalid audio mimeType throws");
|
||||
|
||||
test(t => {
|
||||
assert_false(MediaRecorder.isTypeSupported("audio/banana"));
|
||||
}, "MediaRecorder invalid audio MIMEType is unsupported");
|
||||
}, "MediaRecorder invalid audio mimeType is unsupported");
|
||||
|
||||
test(t => {
|
||||
assert_throws("NotSupportedError",
|
||||
() => new MediaRecorder(new MediaStream(), {mimeType: "video/pineapple"}));
|
||||
}, "MediaRecorder invalid video MIMEType throws");
|
||||
}, "MediaRecorder invalid video mimeType throws");
|
||||
|
||||
test(t => {
|
||||
assert_false(MediaRecorder.isTypeSupported("video/pineapple"));
|
||||
}, "MediaRecorder invalid video MIMEType is unsupported");
|
||||
}, "MediaRecorder invalid video mimeType is unsupported");
|
||||
|
||||
// New MIME types could be added to this list as needed.
|
||||
for (const mimeType of [
|
||||
|
@ -104,66 +104,138 @@ for (const mimeType of [
|
|||
if (MediaRecorder.isTypeSupported(mimeType)) {
|
||||
test(t => {
|
||||
const recorder = new MediaRecorder(new MediaStream(), {mimeType});
|
||||
assert_equals(recorder.mimeType, mimeType, "Supported MIMEType is set");
|
||||
}, `Supported MIMEType ${mimeType} is set immediately after constructing`);
|
||||
assert_equals(recorder.mimeType, mimeType, "Supported mimeType is set");
|
||||
}, `Supported mimeType ${mimeType} is set immediately after constructing`);
|
||||
} else {
|
||||
test(t => {
|
||||
assert_throws("NotSupportedError",
|
||||
() => new MediaRecorder(new MediaStream(), {mimeType}));
|
||||
}, `Unsupported MIMEType ${mimeType} throws`);
|
||||
}, `Unsupported mimeType ${mimeType} throws`);
|
||||
}
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioStream(t));
|
||||
recorder.start();
|
||||
assert_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has no MIMEtype after start() for audio");
|
||||
|
||||
await new Promise(r => recorder.onstart = r);
|
||||
assert_not_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has a MIMEtype after 'start' for audio");
|
||||
assert_regexp_match(recorder.mimeType, /^audio\//,
|
||||
"MIMEtype has an expected media type");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
|
||||
"MIMEtype has a container subtype");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+; codecs=[^,]+$/,
|
||||
"MIMEtype has one codec");
|
||||
}, "MediaRecorder sets a MIMEType after 'start' for audio");
|
||||
assert_not_equals(recorder.mimeType, "");
|
||||
}, "MediaRecorder sets a nonempty mimeType on 'onstart' for audio");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createVideoStream(t));
|
||||
recorder.start();
|
||||
assert_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has no MIMEtype after start() for video");
|
||||
|
||||
await new Promise(r => recorder.onstart = r);
|
||||
assert_not_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has a MIMEtype after 'start' for video");
|
||||
assert_regexp_match(recorder.mimeType, /^video\//,
|
||||
"MIMEtype has an expected media type");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
|
||||
"MIMEtype has a container subtype");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+; codecs=[^,]+$/,
|
||||
"MIMEtype has one codec");
|
||||
}, "MediaRecorder sets a MIMEType after 'start' for video");
|
||||
assert_not_equals(recorder.mimeType, "");
|
||||
}, "MediaRecorder sets a nonempty mimeType on 'onstart' for video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioVideoStream(t));
|
||||
recorder.start();
|
||||
assert_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has no MIMEtype after start() for audio/video");
|
||||
|
||||
await new Promise(r => recorder.onstart = r);
|
||||
assert_not_equals(recorder.mimeType, "",
|
||||
"MediaRecorder has a MIMEtype after 'start' for audio/video");
|
||||
assert_regexp_match(recorder.mimeType, /^video\//,
|
||||
"MIMEtype has an expected media type");
|
||||
assert_not_equals(recorder.mimeType, "");
|
||||
}, "MediaRecorder sets a nonempty mimeType on 'onstart' for audio/video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioStream(t));
|
||||
recorder.start();
|
||||
assert_equals(recorder.mimeType, "");
|
||||
}, "MediaRecorder mimeType is not set before 'onstart' for audio");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createVideoStream(t));
|
||||
recorder.start();
|
||||
assert_equals(recorder.mimeType, "");
|
||||
}, "MediaRecorder mimeType is not set before 'onstart' for video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioVideoStream(t));
|
||||
recorder.start();
|
||||
assert_equals(recorder.mimeType, "");
|
||||
}, "MediaRecorder mimeType is not set before 'onstart' for audio/video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioStream(t));
|
||||
const onstartPromise = new Promise(resolve => {
|
||||
recorder.onstart = () => {
|
||||
recorder.onstart = () => t.step_func(() => {
|
||||
assert_not_reached("MediaRecorder doesn't fire 'onstart' twice");
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
recorder.start();
|
||||
await onstartPromise;
|
||||
await new Promise(r => t.step_timeout(r, 1000));
|
||||
}, "MediaRecorder doesn't fire 'onstart' multiple times for audio");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createVideoStream(t));
|
||||
const onstartPromise = new Promise(resolve => {
|
||||
recorder.onstart = () => {
|
||||
recorder.onstart = () => t.step_func(() => {
|
||||
assert_not_reached("MediaRecorder doesn't fire 'onstart' twice");
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
recorder.start();
|
||||
await onstartPromise;
|
||||
await new Promise(r => t.step_timeout(r, 1000));
|
||||
}, "MediaRecorder doesn't fire 'onstart' multiple times for video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioVideoStream(t));
|
||||
const onstartPromise = new Promise(resolve => {
|
||||
recorder.onstart = () => {
|
||||
recorder.onstart = () => t.step_func(() => {
|
||||
assert_not_reached("MediaRecorder doesn't fire 'onstart' twice");
|
||||
});
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
recorder.start();
|
||||
await onstartPromise;
|
||||
await new Promise(r => t.step_timeout(r, 1000));
|
||||
}, "MediaRecorder doesn't fire 'onstart' multiple times for audio/video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioStream(t));
|
||||
recorder.start();
|
||||
await new Promise(r => recorder.onstart = r);
|
||||
assert_regexp_match(recorder.mimeType, /^audio\//,
|
||||
"mimeType has an expected media type");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
|
||||
"MIMEtype has a container subtype");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+; codecs=[^,]+,[^,]+$/,
|
||||
"MIMEtype has two codecs");
|
||||
}, "MediaRecorder sets a MIMEType after 'start' for audio/video");
|
||||
"mimeType has a container subtype");
|
||||
assert_regexp_match(
|
||||
recorder.mimeType, /^[a-z]+\/[a-z]+;[ ]*codecs=[^,]+$/,
|
||||
"mimeType has one codec a");
|
||||
}, "MediaRecorder formats mimeType well after 'start' for audio");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createVideoStream(t));
|
||||
recorder.start();
|
||||
await new Promise(r => recorder.onstart = r);
|
||||
assert_regexp_match(recorder.mimeType, /^video\//,
|
||||
"mimeType has an expected media type");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
|
||||
"mimeType has a container subtype");
|
||||
assert_regexp_match(
|
||||
recorder.mimeType, /^[a-z]+\/[a-z]+;[ ]*codecs=[^,]+$/,
|
||||
"mimeType has one codec a");
|
||||
}, "MediaRecorder formats mimeType well after 'start' for video");
|
||||
|
||||
promise_test(async t => {
|
||||
const recorder = new MediaRecorder(createAudioVideoStream(t));
|
||||
recorder.start();
|
||||
await new Promise(r => recorder.onstart = r);
|
||||
assert_regexp_match(recorder.mimeType, /^video\//,
|
||||
"mimeType has an expected media type");
|
||||
assert_regexp_match(recorder.mimeType, /^[a-z]+\/[a-z]+/,
|
||||
"mimeType has a container subtype");
|
||||
assert_regexp_match(
|
||||
recorder.mimeType, /^[a-z]+\/[a-z]+;[ ]*codecs=[^,]+,[^,]+$/,
|
||||
"mimeType has two codecs");
|
||||
}, "MediaRecorder formats mimeType well after 'start' for audio/video");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>MediaRecorder peer connection</title>
|
||||
<link rel="help"
|
||||
href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimeType">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="utils/peerconnection.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
|
||||
[{name: "audio", kind: {audio: true, video: false}},
|
||||
{name: "video", kind: {audio: false, video: true}},
|
||||
{name: "audio/video", kind: {audio: true, video: true}}].forEach(args => {
|
||||
promise_test(async t => {
|
||||
const [localPc, remotePc, stream] = await startConnection(
|
||||
t, args.kind.audio, args.kind.video);
|
||||
const recorder = new MediaRecorder(stream);
|
||||
// Set an arbitrary timeslice interval so the ondataavailable event
|
||||
// handler gets invoked repeatedly. Without it, the test would
|
||||
// deadlock as it's currently written.
|
||||
recorder.start(100);
|
||||
let combinedSize = 0;
|
||||
const dataPromise = new Promise(r => recorder.ondataavailable = e => {
|
||||
// Wait for an arbitrary amount of data to appear before we resolve.
|
||||
combinedSize += e.data.size;
|
||||
if (combinedSize > 4711) r();
|
||||
});
|
||||
await dataPromise;
|
||||
recorder.stop();
|
||||
}, "PeerConnection MediaRecorder records " + args.name +
|
||||
" from PeerConnection without sinks");
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,73 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<meta name="timeout" content="long">
|
||||
|
||||
<head>
|
||||
<title>MediaRecorder peer connection</title>
|
||||
<link rel="help"
|
||||
href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimeType">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="utils/peerconnection.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<video id="remote" autoplay width="240" />
|
||||
<script>
|
||||
|
||||
[{ name: "video", kind: { video: true, audio: false }, mimeType: "" },
|
||||
{ name: "audio", kind: { video: false, audio: true }, mimeType: "" },
|
||||
{ name: "audio/video", kind: { video: true, audio: true }, mimeType: "" },
|
||||
{ name: "audio", kind: { video: false, audio: true }, mimeType: "video/webm;codecs=vp8" },
|
||||
{ name: "video", kind: { video: true, audio: false }, mimeType: "video/webm;codecs=vp8" },
|
||||
{ name: "audio/video", kind: { video: true, audio: true }, mimeType: "video/webm;codecs=vp8" },
|
||||
{ name: "audio", kind: { video: false, audio: true }, mimeType: "video/webm;codecs=vp9" },
|
||||
{ name: "video", kind: { video: true, audio: false }, mimeType: "video/webm;codecs=vp9" },
|
||||
{ name: "audio/video", kind: { video: true, audio: true }, mimeType: "video/webm;codecs=vp9" }]
|
||||
.forEach(args => {
|
||||
const formatString = JSON.stringify(args.kind) +
|
||||
" with format " + (args.mimeType ? args.mimeType : "[passthrough]") + ".";
|
||||
promise_test(async t => {
|
||||
const [localPc, remotePc, stream] = await startConnection(
|
||||
t, args.kind.audio, args.kind.video);
|
||||
const recorder = new MediaRecorder(stream, { mimeType: args.mimeType });
|
||||
let combinedSize = 0;
|
||||
const dataPromise = new Promise(r => {
|
||||
recorder.onstart = () => {
|
||||
recorder.ondataavailable = e => {
|
||||
// Wait for an arbitrary amount of data to appear before we resolve.
|
||||
combinedSize += e.data.size;
|
||||
if (combinedSize > 4711) r();
|
||||
}
|
||||
}
|
||||
});
|
||||
recorder.start(100);
|
||||
await dataPromise;
|
||||
recorder.stop();
|
||||
}, "PeerConnection MediaRecorder receives data after onstart, " +
|
||||
formatString);
|
||||
|
||||
promise_test(async t => {
|
||||
const [localPc, remotePc, stream] = await startConnection(
|
||||
t, args.kind.audio, args.kind.video);
|
||||
const recorder = new MediaRecorder(stream, { mimeType: args.mimeType });
|
||||
const stopPromise = new Promise(r => recorder.onstop = r);
|
||||
const dataPromise = new Promise(r => recorder.ondataavailable = r);
|
||||
recorder.start();
|
||||
await waitForReceivedFrames(
|
||||
t, remotePc, args.kind.audio, args.kind.video, 10);
|
||||
for (transceiver of remotePc.getTransceivers())
|
||||
transceiver.receiver.track.stop();
|
||||
// As the tracks ended, we'd like to see data from the recorder.
|
||||
// For details:
|
||||
// https://www.w3.org/TR/mediastream-recording/#mediarecorder-methods.
|
||||
await dataPromise;
|
||||
await stopPromise;
|
||||
}, "PeerConnection MediaRecorder gets ondata on stopping recorded " +
|
||||
"tracks " + formatString);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -24,6 +24,20 @@
|
|||
return arr;
|
||||
}
|
||||
|
||||
// This function is used to check that elements of |actual| is a sub
|
||||
// sequence in the |expected| sequence.
|
||||
function assertSequenceIn(actual, expected) {
|
||||
let i = 0;
|
||||
for (event of actual) {
|
||||
const j = expected.slice(i).indexOf(event);
|
||||
assert_greater_than_equal(
|
||||
j, 0, "Sequence element " + event + " is not included in " +
|
||||
expected.slice(i));
|
||||
i = j;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
let video = createVideoStream();
|
||||
let recorder = new MediaRecorder(video);
|
||||
|
@ -40,8 +54,10 @@
|
|||
assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++");
|
||||
assert_equals(recorder.state, "inactive", "MediaRecorder is inactive after stop event");
|
||||
|
||||
assert_array_equals(events, ["start", "dataavailable", "stop"],
|
||||
"Should have gotten expected events");
|
||||
// As the test is written, it's not guaranteed that
|
||||
// onstart/ondataavailable is invoked, but it's fine if they are.
|
||||
// The stop element is guaranteed to be in events when we get here.
|
||||
assertSequenceIn(events, ["start", "dataavailable", "stop"]);
|
||||
}, "MediaRecorder will stop recording and fire a stop event when all tracks are ended");
|
||||
|
||||
promise_test(async t => {
|
||||
|
@ -59,8 +75,10 @@
|
|||
assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++");
|
||||
assert_equals(recorder.state, "inactive", "MediaRecorder is inactive after stop event");
|
||||
|
||||
assert_array_equals(events, ["start", "dataavailable", "stop"],
|
||||
"Should have gotten expected events");
|
||||
// As the test is written, it's not guaranteed that
|
||||
// onstart/ondataavailable is invoked, but it's fine if they are.
|
||||
// The stop element is guaranteed to be in events when we get here.
|
||||
assertSequenceIn(events, ["start", "dataavailable", "stop"]);
|
||||
}, "MediaRecorder will stop recording and fire a stop event when stop() is called");
|
||||
|
||||
promise_test(async t => {
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>MediaRecorder peer connection</title>
|
||||
<link rel="help"
|
||||
href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#dom-mediarecorder-mimeType">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../utils/peerconnection.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<video id="remote" autoplay width="240" />
|
||||
<script>
|
||||
|
||||
[{kind: "video", audio: false, codecPreference: "VP8", codecRegex: /.*vp8.*/},
|
||||
{kind: "audio/video", audio: true, codecPreference: "VP8", codecRegex: /.*vp8.*/},
|
||||
{kind: "video", audio: false, codecPreference: "VP9", codecRegex: /.*vp9.*/},
|
||||
{kind: "audio/video", audio: true, codecPreference: "VP9", codecRegex: /.*vp9.*/}]
|
||||
.forEach(args => {
|
||||
promise_test(async t => {
|
||||
const [localPc, remotePc, stream] = await startConnection(
|
||||
t, args.audio, /*video=*/true, args.codecPreference);
|
||||
const recorder = new MediaRecorder(stream); // Passthrough.
|
||||
const onstartPromise = new Promise(resolve => {
|
||||
recorder.onstart = t.step_func(() => {
|
||||
assert_regexp_match(
|
||||
recorder.mimeType, args.codecRegex,
|
||||
"mimeType is matching " + args.codecPreference +
|
||||
" in case of passthrough.");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
recorder.start();
|
||||
await(onstartPromise);
|
||||
}, "PeerConnection passthrough MediaRecorder receives " +
|
||||
args.codecPreference + " after onstart with a " + args.kind +
|
||||
" stream.");
|
||||
});
|
||||
|
||||
promise_test(async t => {
|
||||
const [localPc, remotePc, stream, transceivers] = await startConnection(
|
||||
t, /*audio=*/false, /*video=*/true, /*videoCodecPreference=*/"VP8");
|
||||
const recorder = new MediaRecorder(stream); // Possibly passthrough.
|
||||
recorder.start();
|
||||
await waitForReceivedFrames(t, remotePc, false, true, 10);
|
||||
|
||||
// Switch codec to VP9; we expect onerror to not be invoked.
|
||||
recorder.onerror = t.step_func(() => assert_unreached(
|
||||
"MediaRecorder should be prepared to handle codec switches"));
|
||||
setTransceiverCodecPreference(transceivers.video, "VP9");
|
||||
exchangeOfferAnswer(localPc, remotePc);
|
||||
await waitForReceivedCodec(t, remotePc, "VP9");
|
||||
}, "PeerConnection passthrough MediaRecorder should be prepared to handle " +
|
||||
"the codec switching from VP8 to VP9");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* @fileoverview Utility functions for tests utilizing PeerConnections
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exchanges offers and answers between two peer connections.
|
||||
*
|
||||
* pc1's offer is set as local description in pc1 and
|
||||
* remote description in pc2. After that, pc2's answer
|
||||
* is set as it's local description and remote description in pc1.
|
||||
*
|
||||
* @param {!RTCPeerConnection} pc1 The first peer connection.
|
||||
* @param {!RTCPeerConnection} pc2 The second peer connection.
|
||||
*/
|
||||
async function exchangeOfferAnswer(pc1, pc2) {
|
||||
await pc1.setLocalDescription(await pc1.createOffer());
|
||||
await pc2.setRemoteDescription(pc1.localDescription);
|
||||
await pc2.setLocalDescription(await pc2.createAnswer());
|
||||
await pc1.setRemoteDescription(pc2.localDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified codec preference if it's included in the transceiver's
|
||||
* list of supported codecs.
|
||||
* @param {!RTCRtpTransceiver} transceiver The RTP transceiver.
|
||||
* @param {string} codecPreference The codec preference.
|
||||
*/
|
||||
function setTransceiverCodecPreference(transceiver, codecPreference) {
|
||||
for (let codec of RTCRtpSender.getCapabilities('video').codecs) {
|
||||
if (codec.mimeType.includes(codecPreference)) {
|
||||
transceiver.setCodecPreferences([codec]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a connection between two peer connections, using a audio and/or video
|
||||
* stream.
|
||||
* @param {*} t Test instance.
|
||||
* @param {boolean} useAudio True if audio should be used.
|
||||
* @param {boolean} useVideo True if video should be used.
|
||||
* @param {string} [videoCodecPreference] String containing the codec preference.
|
||||
* @returns an array with the two connected peer connections, the remote stream,
|
||||
* and the list of transceivers.
|
||||
*/
|
||||
async function startConnection(t, useAudio, useVideo, videoCodecPreference) {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: useAudio, video: useVideo
|
||||
});
|
||||
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
||||
const pc1 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc1.close());
|
||||
const pc2 = new RTCPeerConnection();
|
||||
t.add_cleanup(() => pc2.close());
|
||||
let transceivers = {};
|
||||
stream.getTracks().forEach(track => {
|
||||
const transceiver = pc1.addTransceiver(track);
|
||||
transceivers[track.kind] = transceiver;
|
||||
if (videoCodecPreference && track.kind == 'video') {
|
||||
setTransceiverCodecPreference(transceiver, videoCodecPreference);
|
||||
}
|
||||
});
|
||||
function doExchange(localPc, remotePc) {
|
||||
localPc.addEventListener('icecandidate', event => {
|
||||
const { candidate } = event;
|
||||
if (candidate && remotePc.signalingState !== 'closed') {
|
||||
remotePc.addIceCandidate(candidate);
|
||||
}
|
||||
});
|
||||
}
|
||||
doExchange(pc1, pc2);
|
||||
doExchange(pc2, pc1);
|
||||
exchangeOfferAnswer(pc1, pc2);
|
||||
const remoteStream = await new Promise(resolve => {
|
||||
let tracks = [];
|
||||
pc2.ontrack = e => {
|
||||
tracks.push(e.track)
|
||||
if (tracks.length < useAudio + useVideo) return;
|
||||
const stream = new MediaStream(tracks);
|
||||
// The srcObject sink is needed for the tests to get exercised in Chrome.
|
||||
const remoteVideo = document.getElementById('remote');
|
||||
if (remoteVideo) {
|
||||
remoteVideo.srcObject = stream;
|
||||
}
|
||||
resolve(stream)
|
||||
}
|
||||
});
|
||||
return [pc1, pc2, remoteStream, transceivers]
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a peer connection, return after at least numFramesOrPackets
|
||||
* frames (video) or packets (audio) have been received.
|
||||
* @param {*} t Test instance.
|
||||
* @param {!RTCPeerConnection} pc The peer connection.
|
||||
* @param {boolean} lookForAudio True if audio packets should be waited for.
|
||||
* @param {boolean} lookForVideo True if video packets should be waited for.
|
||||
* @param {int} numFramesOrPackets Number of frames (video) and packets (audio)
|
||||
* to wait for.
|
||||
*/
|
||||
async function waitForReceivedFrames(
|
||||
t, pc, lookForAudio, lookForVideo, numFramesOrPackets) {
|
||||
let initialAudioPackets = 0;
|
||||
let initialVideoFrames = 0;
|
||||
while (lookForAudio || lookForVideo) {
|
||||
const report = await pc.getStats();
|
||||
report.forEach(stats => {
|
||||
if (stats.type && stats.type == 'inbound-rtp') {
|
||||
if (lookForAudio && stats.kind == 'audio') {
|
||||
if (!initialAudioPackets) {
|
||||
initialAudioPackets = stats.packetsReceived
|
||||
} else if (stats.packetsReceived > initialAudioPackets +
|
||||
numFramesOrPackets) {
|
||||
lookForAudio = false;
|
||||
}
|
||||
}
|
||||
if (lookForVideo && stats.kind == 'video') {
|
||||
if (!initialVideoFrames) {
|
||||
initialVideoFrames = stats.framesDecoded;
|
||||
} else if (stats.framesDecoded > initialVideoFrames +
|
||||
numFramesOrPackets) {
|
||||
lookForVideo = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Promise(r => { t.step_timeout(r, 100); });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a peer connection, return after one of its inbound RTP connections
|
||||
* includes use of the specified codec.
|
||||
* @param {*} t Test instance.
|
||||
* @param {!RTCPeerConnection} pc The peer connection.
|
||||
* @param {string} codecToLookFor The waited-for codec.
|
||||
*/
|
||||
async function waitForReceivedCodec(t, pc, codecToLookFor) {
|
||||
let currentCodecId;
|
||||
for (;;) {
|
||||
const report = await pc.getStats();
|
||||
report.forEach(stats => {
|
||||
if (stats.id) {
|
||||
if (stats.type == 'inbound-rtp' && stats.kind == 'video') {
|
||||
currentCodecId = stats.codecId;
|
||||
} else if (currentCodecId && stats.id == currentCodecId &&
|
||||
stats.mimeType.toLowerCase().includes(
|
||||
codecToLookFor.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Promise(r => { t.step_timeout(r, 100); });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Commas in "content_security/policy" cause parse errors and thus no CSP</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: true });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-comma-in-policy
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Of two "content_security" items only the second counts</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: true, img: false });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-double-content-security
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Of two "content_security/policies" items only the second counts</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: true, img: false });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-double-policies
|
|
@ -0,0 +1,73 @@
|
|||
window.waitForOneSecurityPolicyViolationEvent = expectedBlockedURI => {
|
||||
return new Promise(resolve => {
|
||||
let eventCount = 0;
|
||||
let blockedURI = null;
|
||||
|
||||
document.addEventListener("securitypolicyviolation", e => {
|
||||
++eventCount;
|
||||
blockedURI = e.blockedURI;
|
||||
|
||||
// We want to test that only one event is fired, but we want to do so
|
||||
// without waiting indefinitely. By waiting for one tick, we at least
|
||||
// ensure that there's no bug that leads to two securitypolicyviolation
|
||||
// events being fired at the same time, as a result of the one violation.
|
||||
step_timeout(() => {
|
||||
assert_equals(eventCount, 1);
|
||||
resolve(blockedURI);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.waitForImgFail = imgSrc => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = document.createElement("img");
|
||||
img.onload = () => reject(new Error("Must not load the image"));
|
||||
img.onerror = () => resolve();
|
||||
|
||||
img.src = imgSrc;
|
||||
document.body.append(img);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
window.waitForImgSuccess = imgSrc => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = document.createElement("img");
|
||||
img.onload = () => resolve();
|
||||
img.onerror = () => reject(new Error("Must load the image"));
|
||||
|
||||
img.src = imgSrc;
|
||||
document.body.append(img);
|
||||
});
|
||||
};
|
||||
|
||||
// Both params are optional; if they are not given as booleans then we will not test that aspect.
|
||||
window.runCSPTest = ({ unsafeEval, img }) => {
|
||||
if (unsafeEval === true) {
|
||||
test(() => {
|
||||
eval("window.evalAllowed = true;");
|
||||
assert_equals(window.evalAllowed, true);
|
||||
}, "eval must be allowed");
|
||||
} else if (unsafeEval === false) {
|
||||
test(() => {
|
||||
try {
|
||||
eval("window.evalAllowed = true;");
|
||||
} catch (e) { }
|
||||
|
||||
assert_equals(window.evalAllowed, undefined);
|
||||
}, "eval must be disallowed");
|
||||
}
|
||||
|
||||
if (img === true) {
|
||||
promise_test(
|
||||
() => waitForImgSuccess("/common/security-features/subresource/image.py"),
|
||||
"img loading must be allowed"
|
||||
);
|
||||
} else if (img === false) {
|
||||
promise_test(
|
||||
() => waitForImgFail("/common/security-features/subresource/image.py"),
|
||||
"img loading must be disallowed"
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Non-array "content_security/policies" member must be ignored</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: true });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-non-array
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Non-object "content_security" member must be ignored</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: true });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-non-object
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Non-string "content_security/policies" array member must be ignored</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: true });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-non-string
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>CSP via origin policy must trigger a securitypolicyviolation event even when the CSP is report-only</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
promise_test(() => {
|
||||
const imgURL = (new URL("/common/security-features/subresource/image.py", document.location)).href;
|
||||
|
||||
return Promise.all([
|
||||
waitForOneSecurityPolicyViolationEvent(imgURL).then(blockedURI => {
|
||||
assert_equals(blockedURI, imgURL);
|
||||
}),
|
||||
waitForImgSuccess(imgURL)
|
||||
]);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-noimg-report-only
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>CSP via origin policy must trigger a securitypolicyviolation event</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
promise_test(() => {
|
||||
const imgURL = (new URL("/common/security-features/subresource/image.py", document.location)).href;
|
||||
|
||||
return Promise.all([
|
||||
waitForOneSecurityPolicyViolationEvent(imgURL).then(blockedURI => {
|
||||
assert_equals(blockedURI, imgURL);
|
||||
}),
|
||||
waitForImgFail(imgURL)
|
||||
]);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-noimg
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>"content_security/policy" can contain multiple array items to enforce multiple CSPs</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: false, img: false });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-valid-with-semicolon
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>"content_security/policy" array items can contain semicolons to enforce multiple CSP directives</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: false, img: false });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-valid-with-semicolon
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<meta charset="utf-8">
|
||||
<title>Valid "content_security" member disallows eval</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
runCSPTest({ unsafeEval: false });
|
||||
</script>
|
|
@ -0,0 +1 @@
|
|||
Sec-Origin-Policy: policy=policy-content-security-valid
|
|
@ -1,30 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<script src='/resources/testharness.js'></script>
|
||||
<script src='/resources/testharnessreport.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id=frame></iframe>
|
||||
<script>
|
||||
async_test(t => {
|
||||
let violations = [];
|
||||
window.addEventListener("message", (e) => {
|
||||
violations.push(e);
|
||||
t.step_timeout(() => {
|
||||
assert_equals(violations.length, 1);
|
||||
t.done();
|
||||
});
|
||||
});
|
||||
|
||||
let forbidden_image = "<img src=https://127.0.0.1:1234/bla.jpg>";
|
||||
let event_bouncer = "<script>document.addEventListener(" +
|
||||
"'securitypolicyviolation'," +
|
||||
"(e) => window.parent.postMessage(e.blockedURI, '*'));</sc" +
|
||||
"ript>";
|
||||
document.getElementById("frame").src =
|
||||
"data:text/html;charset=utf-8," + event_bouncer + forbidden_image;
|
||||
}, "Origin-Policy-based CSP violation should trigger 1 violation event");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1 +0,0 @@
|
|||
Sec-Origin-Policy: policy=policy-noimg
|
|
@ -1,6 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="/resources/testdriver.js"></script>
|
||||
<script src="/resources/testdriver-vendor.js"></script>
|
||||
<script src="resources/open-blank-host.js"></script>
|
||||
<body>
|
||||
<script>
|
||||
|
@ -14,59 +16,89 @@
|
|||
|
||||
promise_test(async t => {
|
||||
let portal = await createPortal(document, new URL("resources/focus-page-with-button.html", location.href));
|
||||
portal.onmessage = t.step_func(e => {
|
||||
assert_unreached("button inside portal should not be focused");
|
||||
});
|
||||
portal.postMessage("focus", "*");
|
||||
return new Promise(r => t.step_timeout(r, 500));
|
||||
try {
|
||||
portal.onmessage = t.step_func(e => {
|
||||
assert_unreached("button inside portal should not be focused");
|
||||
});
|
||||
portal.postMessage("focus", "*");
|
||||
await new Promise(r => t.step_timeout(r, 500));
|
||||
} finally {
|
||||
document.body.removeChild(portal);
|
||||
}
|
||||
}, "test that an element inside a portal cannot steal focus");
|
||||
|
||||
promise_test(async t => {
|
||||
let portal = await createPortal(document, new URL("resources/focus-page-with-x-origin-iframe.sub.html", location.href));
|
||||
portal.onmessage = t.step_func(e => {
|
||||
assert_unreached("button inside portal should not be focused");
|
||||
});
|
||||
portal.postMessage("focus", "*");
|
||||
return new Promise(r => t.step_timeout(r, 500));
|
||||
try {
|
||||
portal.onmessage = t.step_func(e => {
|
||||
assert_unreached("button inside portal should not be focused");
|
||||
});
|
||||
portal.postMessage("focus", "*");
|
||||
await new Promise(r => t.step_timeout(r, 500));
|
||||
} finally {
|
||||
document.body.removeChild(portal);
|
||||
}
|
||||
}, "test that an element inside a portal's x-origin subframe cannot steal focus");
|
||||
|
||||
promise_test(async t => {
|
||||
let win = await openBlankPortalHost();
|
||||
let doc = win.document;
|
||||
try {
|
||||
let portal = await createPortal(doc, new URL("resources/simple-portal-adopts-predecessor.html", location.href));
|
||||
let button = doc.createElement("button");
|
||||
doc.body.appendChild(button);
|
||||
|
||||
let portal = await createPortal(doc, new URL("resources/simple-portal-adopts-predecessor.html", location.href));
|
||||
let button = doc.createElement("button");
|
||||
doc.body.appendChild(button);
|
||||
await portal.activate();
|
||||
doc.body.removeChild(portal);
|
||||
|
||||
await portal.activate();
|
||||
doc.body.removeChild(portal);
|
||||
|
||||
button.onfocus = t.step_func(() => {
|
||||
assert_unreached("button inside adopted portal should not be focused");
|
||||
});
|
||||
button.focus();
|
||||
return new Promise(r => t.step_timeout(r, 500));
|
||||
button.onfocus = t.step_func(() => {
|
||||
assert_unreached("button inside adopted portal should not be focused");
|
||||
});
|
||||
button.focus();
|
||||
await new Promise(r => t.step_timeout(r, 500));
|
||||
} finally {
|
||||
win.close();
|
||||
}
|
||||
}, "test that an element inside an adopted portal cannot steal focus");
|
||||
|
||||
promise_test(async t => {
|
||||
let win = await openBlankPortalHost();
|
||||
let doc = win.document;
|
||||
try {
|
||||
let portal = await createPortal(doc, new URL("resources/simple-portal-adopts-predecessor.html", location.href));
|
||||
let iframe = doc.createElement("iframe");
|
||||
iframe.src = new URL("resources/focus-page-with-button.html",
|
||||
"http://{{hosts[alt][www]}}:{{ports[http][0]}}/portals/");
|
||||
doc.body.appendChild(iframe);
|
||||
await new Promise(r => iframe.onload = r);
|
||||
|
||||
let portal = await createPortal(doc, new URL("resources/simple-portal-adopts-predecessor.html", location.href));
|
||||
let iframe = doc.createElement("iframe");
|
||||
iframe.src = new URL("resources/focus-page-with-button.html",
|
||||
"http://{{hosts[alt][www]}}:{{ports[http][0]}}/portals/");
|
||||
doc.body.appendChild(iframe);
|
||||
await new Promise(r => iframe.onload = r);
|
||||
await portal.activate();
|
||||
doc.body.removeChild(portal);
|
||||
|
||||
await portal.activate();
|
||||
doc.body.removeChild(portal);
|
||||
|
||||
iframe.contentWindow.postMessage("focus", "*");
|
||||
window.onmessage = t.step_func(() => {
|
||||
assert_unreached("button inside x-origin iframe inside a portal should not be focused");
|
||||
});
|
||||
await new Promise(r => t.step_timeout(r, 500));
|
||||
iframe.contentWindow.postMessage("focus", "*");
|
||||
window.onmessage = t.step_func(() => {
|
||||
assert_unreached("button inside x-origin iframe inside a portal should not be focused");
|
||||
});
|
||||
await new Promise(r => t.step_timeout(r, 500));
|
||||
} finally {
|
||||
win.close();
|
||||
}
|
||||
}, "test that a x-origin iframe inside an adopted portal cannot steal focus");
|
||||
|
||||
const TAB = "\ue004"; // https://w3c.github.io/webdriver/#keyboard-actions
|
||||
promise_test(async t => {
|
||||
let portal = await createPortal(document, "resources/focus-page-with-button.html");
|
||||
try {
|
||||
portal.tabIndex = 0;
|
||||
await test_driver.send_keys(document.body, TAB);
|
||||
portal.onmessage = t.step_func(e => {
|
||||
assert_unreached("button inside portal should not be focused");
|
||||
});
|
||||
await new Promise(r => t.step_timeout(r, 500));
|
||||
} finally {
|
||||
document.body.removeChild(portal);
|
||||
}
|
||||
}, "test that we cannot tab into a portal's contents");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<meta name="timeout" content="long">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<body>
|
||||
<script>
|
||||
// TODO(jbroman): Ideally these would also check that the portal element gets a
|
||||
// completion event.
|
||||
|
||||
async_test(t => {
|
||||
var portal = document.createElement('portal');
|
||||
portal.src = "/portals/xfo/resources/xfo-deny.asis";
|
||||
portal.onmessage = t.unreached_func("should not have received a message");
|
||||
document.body.appendChild(portal);
|
||||
t.add_cleanup(() => portal.remove());
|
||||
t.step_timeout(() => t.done(), 2000);
|
||||
}, "`XFO: DENY` blocks same-origin portals.");
|
||||
|
||||
async_test(t => {
|
||||
var portal = document.createElement('portal');
|
||||
portal.src = "http://{{domains[www]}}:{{ports[http][0]}}/portals/xfo/resources/xfo-deny.asis";
|
||||
portal.onmessage = t.unreached_func("should not have received a message");
|
||||
document.body.appendChild(portal);
|
||||
t.add_cleanup(() => portal.remove());
|
||||
t.step_timeout(() => t.done(), 2000);
|
||||
}, "`XFO: DENY` blocks cross-origin portals.");
|
||||
</script>
|
||||
</body>
|
|
@ -0,0 +1,8 @@
|
|||
HTTP/1.1 200 OK
|
||||
Content-Type: text/html
|
||||
X-Frame-Options: DENY
|
||||
|
||||
<!DOCTYPE html>
|
||||
<script>
|
||||
window.portalHost.postMessage('loaded');
|
||||
</script>
|
|
@ -24,7 +24,7 @@
|
|||
`data:text/html,<script src = "${BASE}/common/security-features/resources/common.sub.js"></` + `script>
|
||||
<script src = "${BASE}/referrer-policy/generic/referrer-policy-test-case.js"></` + `script>
|
||||
<script>
|
||||
var urlPath = "/referrer-policy/generic/subresource/xhr.py";
|
||||
var urlPath = "/common/security-features/subresource/xhr.py";
|
||||
var url = "${BASE}" + urlPath;
|
||||
requestViaXhr(url).then((msg) => {
|
||||
parent.postMessage({referrer: msg.referrer}, "*")})
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
'use strict';
|
||||
|
||||
// Math helper - used mainly in hit test implementation done by webxr-test.js
|
||||
class XRMathHelper {
|
||||
static toString(p) {
|
||||
return "[" + p.x + "," + p.y + "," + p.z + "," + p.w + "]";
|
||||
}
|
||||
|
||||
static transform_by_matrix(matrix, point) {
|
||||
return {
|
||||
x : matrix[0] * point.x + matrix[4] * point.y + matrix[8] * point.z + matrix[12] * point.w,
|
||||
y : matrix[1] * point.x + matrix[5] * point.y + matrix[9] * point.z + matrix[13] * point.w,
|
||||
z : matrix[2] * point.x + matrix[6] * point.y + matrix[10] * point.z + matrix[14] * point.w,
|
||||
w : matrix[3] * point.x + matrix[7] * point.y + matrix[11] * point.z + matrix[15] * point.w,
|
||||
};
|
||||
}
|
||||
|
||||
static neg(p) {
|
||||
return {x : -p.x, y : -p.y, z : -p.z, w : p.w};
|
||||
}
|
||||
|
||||
static sub(lhs, rhs) {
|
||||
// .w is treated here like an entity type, 1 signifies points, 0 signifies vectors.
|
||||
// point - point, point - vector, vector - vector are ok, vector - point is not.
|
||||
if (lhs.w != rhs.w && lhs.w == 0.0) {
|
||||
console.warn("vector - point not allowed: " + toString(lhs) + "-" + toString(rhs));
|
||||
}
|
||||
|
||||
return {x : lhs.x - rhs.x, y : lhs.y - rhs.y, z : lhs.z - rhs.z, w : lhs.w - rhs.w};
|
||||
}
|
||||
|
||||
static add(lhs, rhs) {
|
||||
if (lhs.w == rhs.w && lhs.w == 1.0) {
|
||||
console.warn("point + point not allowed", p1, p2);
|
||||
}
|
||||
|
||||
return {x : lhs.x + rhs.x, y : lhs.y + rhs.y, z : lhs.z + rhs.z, w : lhs.w + rhs.w};
|
||||
}
|
||||
|
||||
static cross(lhs, rhs) {
|
||||
if (lhs.w != 0.0 || rhs.w != 0.0) {
|
||||
console.warn("cross product not allowed: " + toString(lhs) + "x" + toString(rhs));
|
||||
}
|
||||
|
||||
return {
|
||||
x : lhs.y * rhs.z - lhs.z * rhs.y,
|
||||
y : lhs.z * rhs.x - lhs.x * rhs.z,
|
||||
z : lhs.x * rhs.y - lhs.y * rhs.x,
|
||||
w : 0
|
||||
};
|
||||
}
|
||||
|
||||
static dot(lhs, rhs) {
|
||||
if (lhs.w != 0 || rhs.w != 0) {
|
||||
console.warn("dot product not allowed: " + toString(lhs) + "x" + toString(rhs));
|
||||
}
|
||||
|
||||
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
|
||||
}
|
||||
|
||||
static mul(scalar, vector) {
|
||||
if (vector.w != 0) {
|
||||
console.warn("scalar * vector not allowed", scalar, vector);
|
||||
}
|
||||
|
||||
return {x : vector.x * scalar, y : vector.y * scalar, z : vector.z * scalar, w : vector.w};
|
||||
}
|
||||
|
||||
static length(vector) {
|
||||
return Math.sqrt(XRMathHelper.dot(vector, vector));
|
||||
}
|
||||
|
||||
static normalize(vector) {
|
||||
const l = XRMathHelper.length(vector);
|
||||
return XRMathHelper.mul(1.0/l, vector);
|
||||
}
|
||||
|
||||
// All |face|'s points and |point| must be co-planar.
|
||||
static pointInFace(point, face) {
|
||||
const normalize = XRMathHelper.normalize;
|
||||
const sub = XRMathHelper.sub;
|
||||
const length = XRMathHelper.length;
|
||||
const cross = XRMathHelper.cross;
|
||||
|
||||
let onTheRight = null;
|
||||
let previous_point = face[face.length - 1];
|
||||
|
||||
// |point| is in |face| if it's on the same side of all the edges.
|
||||
for (let i = 0; i < face.length; ++i) {
|
||||
const current_point = face[i];
|
||||
|
||||
const edge_direction = normalize(sub(current_point, previous_point));
|
||||
const turn_direction = normalize(sub(point, current_point));
|
||||
|
||||
const sin_turn_angle = length(cross(edge_direction, turn_direction));
|
||||
|
||||
if (onTheRight == null) {
|
||||
onTheRight = sin_turn_angle >= 0;
|
||||
} else {
|
||||
if (onTheRight && sin_turn_angle < 0) return false;
|
||||
if (!onTheRight && sin_turn_angle > 0) return false;
|
||||
}
|
||||
|
||||
previous_point = current_point;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static det2x2(m00, m01, m10, m11) {
|
||||
return m00 * m11 - m01 * m10;
|
||||
}
|
||||
|
||||
static det3x3(
|
||||
m00, m01, m02,
|
||||
m10, m11, m12,
|
||||
m20, m21, m22
|
||||
){
|
||||
const det2x2 = XRMathHelper.det2x2;
|
||||
|
||||
return m00 * det2x2(m11, m12, m21, m22)
|
||||
- m01 * det2x2(m10, m12, m20, m22)
|
||||
+ m02 * det2x2(m10, m11, m20, m21);
|
||||
}
|
||||
|
||||
static det4x4(
|
||||
m00, m01, m02, m03,
|
||||
m10, m11, m12, m13,
|
||||
m20, m21, m22, m23,
|
||||
m30, m31, m32, m33
|
||||
) {
|
||||
const det3x3 = XRMathHelper.det3x3;
|
||||
|
||||
return m00 * det3x3(m11, m12, m13,
|
||||
m21, m22, m23,
|
||||
m31, m32, m33)
|
||||
- m01 * det3x3(m10, m12, m13,
|
||||
m20, m22, m23,
|
||||
m30, m32, m33)
|
||||
+ m02 * det3x3(m10, m11, m13,
|
||||
m20, m21, m23,
|
||||
m30, m31, m33)
|
||||
- m03 * det3x3(m10, m11, m12,
|
||||
m20, m21, m22,
|
||||
m30, m31, m32);
|
||||
}
|
||||
|
||||
static inv2(m) {
|
||||
// mij - i-th column, j-th row
|
||||
const m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3];
|
||||
const m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7];
|
||||
const m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11];
|
||||
const m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15];
|
||||
|
||||
const det = det4x4(
|
||||
m00, m01, m02, m03,
|
||||
m10, m11, m12, m13,
|
||||
m20, m21, m22, m23,
|
||||
m30, m31, m32, m33
|
||||
);
|
||||
}
|
||||
|
||||
static transpose(m) {
|
||||
const result = Array(16);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 4; j++) {
|
||||
result[i * 4 + j] = m[j * 4 + i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Inverts the matrix, ported from transformation_matrix.cc.
|
||||
static inverse(m) {
|
||||
const det3x3 = XRMathHelper.det3x3;
|
||||
|
||||
// mij - i-th column, j-th row
|
||||
const m00 = m[0], m01 = m[1], m02 = m[2], m03 = m[3];
|
||||
const m10 = m[4], m11 = m[5], m12 = m[6], m13 = m[7];
|
||||
const m20 = m[8], m21 = m[9], m22 = m[10], m23 = m[11];
|
||||
const m30 = m[12], m31 = m[13], m32 = m[14], m33 = m[15];
|
||||
|
||||
const det = XRMathHelper.det4x4(
|
||||
m00, m01, m02, m03,
|
||||
m10, m11, m12, m13,
|
||||
m20, m21, m22, m23,
|
||||
m30, m31, m32, m33
|
||||
);
|
||||
|
||||
if (Math.abs(det) < 0.0001) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const invDet = 1.0 / det;
|
||||
// Calculate `comatrix * 1/det`:
|
||||
const result2 = [
|
||||
// First column (m0r):
|
||||
invDet * det3x3(m11, m12, m13, m21, m22, m23, m32, m32, m33),
|
||||
invDet * det3x3(m10, m12, m13, m20, m22, m23, m30, m32, m33),
|
||||
invDet * det3x3(m10, m11, m13, m20, m21, m23, m30, m31, m33),
|
||||
invDet * det3x3(m10, m11, m12, m20, m21, m22, m30, m31, m32),
|
||||
// Second column (m1r):
|
||||
invDet * det3x3(m01, m02, m03, m21, m22, m23, m32, m32, m33),
|
||||
invDet * det3x3(m00, m02, m03, m20, m22, m23, m30, m32, m33),
|
||||
invDet * det3x3(m00, m01, m03, m20, m21, m23, m30, m31, m33),
|
||||
invDet * det3x3(m00, m01, m02, m20, m21, m22, m30, m31, m32),
|
||||
// Third column (m2r):
|
||||
invDet * det3x3(m01, m02, m03, m11, m12, m13, m31, m32, m33),
|
||||
invDet * det3x3(m00, m02, m03, m10, m12, m13, m30, m32, m33),
|
||||
invDet * det3x3(m00, m01, m03, m10, m11, m13, m30, m31, m33),
|
||||
invDet * det3x3(m00, m01, m02, m10, m11, m12, m30, m31, m32),
|
||||
// Fourth column (m3r):
|
||||
invDet * det3x3(m01, m02, m03, m11, m12, m13, m21, m22, m23),
|
||||
invDet * det3x3(m00, m02, m03, m10, m12, m13, m20, m22, m23),
|
||||
invDet * det3x3(m00, m01, m03, m10, m11, m13, m20, m21, m23),
|
||||
invDet * det3x3(m00, m01, m02, m10, m11, m12, m20, m21, m22),
|
||||
];
|
||||
|
||||
// Actual inverse is `1/det * transposed(comatrix)`:
|
||||
return XRMathHelper.transpose(result2);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue