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:
bors-servo 2020-01-17 17:04:46 -05:00 committed by GitHub
commit 2a594821ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
112 changed files with 2867 additions and 303 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
[background-size-near-zero-gradient.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[background-size-near-zero-png.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[background-size-near-zero-svg.html]
expected: FAIL

View file

@ -1,2 +1,2 @@
[no-transition-from-ua-to-blocking-stylesheet.html]
expected: TIMEOUT
expected: FAIL

View file

@ -1,4 +0,0 @@
[CaretPosition-001.html]
[Element at (400, 100)]
expected: FAIL

View file

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

View file

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

View file

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

View file

@ -53,3 +53,6 @@
[combined text/javascript ]
expected: FAIL
[separate text/javascript x/x]
expected: FAIL

View file

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

View file

@ -1,4 +1,4 @@
[traverse_the_history_4.html]
[traverse_the_history_2.html]
[Multiple history traversals, last would be aborted]
expected: FAIL

View file

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

View file

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

View file

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

View file

@ -1,4 +0,0 @@
[iframe-inheritance-data.html]
[iframes with data url uses no referrer]
expected: FAIL

View file

@ -1,5 +0,0 @@
[017.html]
expected: TIMEOUT
[origin of the script that invoked the method, about:blank]
expected: TIMEOUT

View file

@ -0,0 +1,2 @@
[ar_hittest_subscription_refSpaces.https.html]
expected: ERROR

View file

@ -0,0 +1,2 @@
[Worker-constructor.html]
expected: ERROR

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline', img-src 'none'"]
}
}

View file

@ -0,0 +1,8 @@
{
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline'"]
},
"content_security": {
"policies": ["img-src 'none'"]
}
}

View file

@ -0,0 +1,6 @@
{
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline'"],
"policies": ["img-src 'none'"]
}
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": ["img-src 'none'"]
}
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies_report_only": ["img-src 'none'"]
}
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": "script-src 'self' 'unsafe-inline'"
}
}

View file

@ -0,0 +1,3 @@
{
"content_security": ["script-src 'self' 'unsafe-inline'"]
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": [["script-src 'self' 'unsafe-inline'"]]
}
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline'"]
}
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline'", "img-src 'none'"]
}
}

View file

@ -0,0 +1,5 @@
{
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline'; img-src 'none'"]
}
}

View file

@ -1,3 +1,5 @@
{
"content-security-policy": [{ "policy": "script-src 'self' 'unsafe-inline'" }]
"content_security": {
"policies": ["script-src 'self' 'unsafe-inline'"]
}
}

View file

@ -1,5 +1,5 @@
{
"content-security-policy": [{
"policy": "script-src 'self' 'nonce-test'"
}]
"content_security": {
"policies": ["script-src 'self' 'nonce-test'"]
}
}

View file

@ -1,3 +0,0 @@
{
"content-security-policy": [{ "policy": "img-src 'none'" }]
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<div style="width: 100px; height: 100px; background: green"></div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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, "&auml;");
}, "U+0000 should vanish after ampersand");
test(function() {
var div = document.getElementsByTagName("div")[0];
div.innerHTML = "&a\u0000uml;";
assert_equals(div.firstChild.data, "&auml;");
}, "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, "&auml;");
}, "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, "&auml;");
}, "U+0000 should vanish after ampersand and three letters of entity prefix");
test(function() {
var div = document.getElementsByTagName("div")[0];
div.innerHTML = "&auml\u0000;";
assert_equals(div.firstChild.data, "\u00E4;");
}, "U+0000 should vanish after semicolonless entity");
test(function() {
var div = document.getElementsByTagName("div")[0];
div.innerHTML = "&notin\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='&auml\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='&notin\u0000;'>";
assert_equals(div.firstChild.title, "&notin\uFFFD;");
}, "U+0000 should get replaced with U+FFFD before required semicolon");
</script>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-comma-in-policy

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-double-content-security

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-double-policies

View file

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

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-non-array

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-non-object

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-non-string

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-noimg-report-only

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-noimg

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-valid-with-semicolon

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-valid-with-semicolon

View file

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

View file

@ -0,0 +1 @@
Sec-Origin-Policy: policy=policy-content-security-valid

View file

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

View file

@ -1 +0,0 @@
Sec-Origin-Policy: policy=policy-noimg

View file

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

View file

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

View file

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

View file

@ -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}, "*")})

View file

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