Update web-platform-tests to revision b7a8b84debb42268ea95a45bdad8f727d1facdf7

This commit is contained in:
WPT Sync Bot 2019-03-21 21:40:20 -04:00
parent ba929208e4
commit 953dbda9a6
215 changed files with 6409 additions and 1644 deletions

File diff suppressed because it is too large Load diff

View file

@ -62,9 +62,6 @@
[Matching font-style: 'italic' should prefer 'italic' over 'oblique 20deg'] [Matching font-style: 'italic' should prefer 'italic' over 'oblique 20deg']
expected: FAIL expected: FAIL
[Matching font-style: 'italic' should prefer 'oblique 30deg 60deg' over 'oblique 40deg 50deg']
expected: FAIL
[Matching font-style: 'italic' should prefer 'oblique 40deg 50deg' over 'oblique 5deg 10deg'] [Matching font-style: 'italic' should prefer 'oblique 40deg 50deg' over 'oblique 5deg 10deg']
expected: FAIL expected: FAIL
@ -149,9 +146,6 @@
[Matching font-style: 'oblique 20deg' should prefer 'oblique 30deg 60deg' over 'oblique 40deg 50deg'] [Matching font-style: 'oblique 20deg' should prefer 'oblique 30deg 60deg' over 'oblique 40deg 50deg']
expected: FAIL expected: FAIL
[Matching font-style: 'oblique 20deg' should prefer 'oblique 40deg 50deg' over 'oblique 10deg']
expected: FAIL
[Matching font-style: 'oblique 20deg' should prefer 'oblique 0deg' over 'oblique -50deg -20deg'] [Matching font-style: 'oblique 20deg' should prefer 'oblique 0deg' over 'oblique -50deg -20deg']
expected: FAIL expected: FAIL
@ -224,9 +218,6 @@
[Matching font-weight: '500' should prefer '450 460' over '400'] [Matching font-weight: '500' should prefer '450 460' over '400']
expected: FAIL expected: FAIL
[Matching font-weight: '501' should prefer '501' over '502 510']
expected: FAIL
[Matching font-style: 'oblique -20deg' should prefer 'oblique -20deg' over 'oblique -60deg -40deg'] [Matching font-style: 'oblique -20deg' should prefer 'oblique -20deg' over 'oblique -60deg -40deg']
expected: FAIL expected: FAIL
@ -326,3 +317,12 @@
[Matching font-style: 'oblique -21deg' should prefer 'oblique -10deg' over 'italic'] [Matching font-style: 'oblique -21deg' should prefer 'oblique -10deg' over 'italic']
expected: FAIL expected: FAIL
[Matching font-weight: '399' should prefer '340 360' over '200 300']
expected: FAIL
[Matching font-style: 'oblique 0deg' should prefer 'oblique 5deg' over 'oblique 15deg 20deg']
expected: FAIL
[Matching font-weight: '501' should prefer '500' over '450 460']
expected: FAIL

View file

@ -38,3 +38,6 @@
[Test @font-face matching for weight 500] [Test @font-face matching for weight 500]
expected: FAIL expected: FAIL
[Test @font-face matching for weight 400]
expected: FAIL

View file

@ -74,18 +74,3 @@
[opacity end] [opacity end]
expected: FAIL expected: FAIL
[height end]
expected: FAIL
[border-top-width end]
expected: FAIL
[border-left-width end]
expected: FAIL
[border-bottom-width end]
expected: FAIL
[border-right-width end]
expected: FAIL

View file

@ -1,4 +0,0 @@
[navigation-unload-same-origin-fragment.html]
[Tests that a fragment navigation in the unload handler will not block the initial navigation]
expected: FAIL

View file

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

View file

@ -1,5 +1,5 @@
[name-attribute.window.html] [name-attribute.window.html]
expected: CRASH expected: TIMEOUT
[cross-origin <frame name=>] [cross-origin <frame name=>]
expected: TIMEOUT expected: TIMEOUT

View file

@ -1,10 +0,0 @@
[non-active-document.html]
[DOMParser]
expected: FAIL
[createHTMLDocument]
expected: FAIL
[<template>]
expected: FAIL

View file

@ -0,0 +1,4 @@
[DOMContentLoaded-defer.html]
[The end: DOMContentLoaded and defer scripts]
expected: FAIL

View file

@ -0,0 +1,11 @@
[Worker-custom-event.any.worker.html]
[Worker-custom-event.any.sharedworker.html]
[Worker-custom-event]
expected: FAIL
[Worker-custom-event.any.serviceworker.html]
[Worker-custom-event]
expected: FAIL

View file

@ -0,0 +1,11 @@
[Worker-replace-event-handler.any.sharedworker.html]
[Worker-replace-event-handler]
expected: FAIL
[Worker-replace-event-handler.any.worker.html]
[Worker-replace-event-handler.any.serviceworker.html]
[Worker-replace-event-handler]
expected: FAIL

View file

@ -0,0 +1,14 @@
[WorkerNavigator-hardware-concurrency.any.sharedworker.html]
[WorkerNavigator-hardware-concurrency]
expected: FAIL
[WorkerNavigator-hardware-concurrency.any.worker.html]
[Test worker navigator hardware concurrency.]
expected: FAIL
[WorkerNavigator-hardware-concurrency.any.serviceworker.html]
[WorkerNavigator-hardware-concurrency]
expected: FAIL

View file

@ -1,4 +1,5 @@
[005.html] [005.html]
expected: ERROR
[dedicated worker in shared worker in dedicated worker] [dedicated worker in shared worker in dedicated worker]
expected: FAIL expected: FAIL

View file

@ -1,5 +1,6 @@
[003.html] [003.html]
type: testharness type: testharness
expected: ERROR
[shared] [shared]
expected: FAIL expected: FAIL

View file

@ -0,0 +1,9 @@
[send-data-formdata.any.html]
[XMLHttpRequest.send(formdata)]
expected: FAIL
[send-data-formdata.any.worker.html]
[XMLHttpRequest.send(formdata)]
expected: FAIL

View file

@ -154,7 +154,7 @@ jobs:
- template: tools/ci/azure/install_certs.yml - template: tools/ci/azure/install_certs.yml
- template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_hosts.yml
- template: tools/ci/azure/update_manifest.yml - template: tools/ci/azure/update_manifest.yml
- script: python ./wpt run --yes --no-manifest-update --install-fonts --manifest MANIFEST.json --metadata infrastructure/metadata/ --log-tbpl $(Build.ArtifactStagingDirectory)/edge.tbpl.log --log-tbpl-level info edge_webdriver infrastructure/ - script: python ./wpt run --yes --no-manifest-update --install-fonts --manifest MANIFEST.json --metadata infrastructure/metadata/ --webdriver-arg=--verbose --log-tbpl $(Build.ArtifactStagingDirectory)/edge.tbpl.log --log-tbpl-level info edge_webdriver infrastructure/
displayName: 'Run tests (Edge)' displayName: 'Run tests (Edge)'
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: 'Publish results' displayName: 'Publish results'
@ -183,7 +183,7 @@ jobs:
- template: tools/ci/azure/install_certs.yml - template: tools/ci/azure/install_certs.yml
- template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_hosts.yml
- template: tools/ci/azure/update_manifest.yml - template: tools/ci/azure/update_manifest.yml
- script: python ./wpt run --no-manifest-update --no-fail-on-unexpected --install-fonts --test-types reftest testharness --this-chunk $(System.JobPositionInPhase) --total-chunks $(System.TotalJobsInPhase) --chunk-type hash --log-tbpl - --log-tbpl-level info --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json edge_webdriver - script: python ./wpt run --no-manifest-update --no-fail-on-unexpected --install-fonts --webdriver-arg=--verbose --test-types reftest testharness --this-chunk $(System.JobPositionInPhase) --total-chunks $(System.TotalJobsInPhase) --chunk-type hash --log-tbpl - --log-tbpl-level info --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt edge_webdriver
displayName: 'Run tests' displayName: 'Run tests'
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: 'Publish results' displayName: 'Publish results'
@ -217,7 +217,7 @@ jobs:
- template: tools/ci/azure/install_safari.yml - template: tools/ci/azure/install_safari.yml
- template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_hosts.yml
- template: tools/ci/azure/update_manifest.yml - template: tools/ci/azure/update_manifest.yml
- script: no_proxy='*' ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --channel preview safari - script: no_proxy='*' ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --channel preview safari
displayName: 'Run tests' displayName: 'Run tests'
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: 'Publish results' displayName: 'Publish results'

View file

@ -1,7 +1,11 @@
version: 1 version: 1
reporting: checks-v1
policy: policy:
pullRequests: public pullRequests: public
tasks: tasks:
$let:
event_str: {$json: {$eval: event}}
in:
$flattenDeep: $flattenDeep:
- $if: tasks_for == "github-push" - $if: tasks_for == "github-push"
then: then:
@ -61,7 +65,7 @@ tasks:
owner: ${event.pusher.email} owner: ${event.pusher.email}
source: ${event.repository.url} source: ${event.repository.url}
payload: payload:
image: harjgam/web-platform-tests:0.29 image: harjgam/web-platform-tests:0.30
maxRunTime: 7200 maxRunTime: 7200
artifacts: artifacts:
public/results: public/results:
@ -72,13 +76,19 @@ tasks:
- --login - --login
- -c - -c
- set -ex; - set -ex;
echo "wpt-${browser.name}-${browser.channel}-${chunk[0]}-${chunk[1]}";
export TASK_EVENT='${event_str}';
~/start.sh ~/start.sh
${event.repository.url} ${event.repository.url}
${event.ref} ${event.ref}
${event.after} ${event.after};
${browser.name}
${browser.channel};
cd ~/web-platform-tests; cd ~/web-platform-tests;
./tools/ci/run_tc.py
--oom-killer
--hosts
--browser=${browser.name}
--channel=${browser.channel}
--xvfb
./tools/ci/taskcluster-run.py ./tools/ci/taskcluster-run.py
${browser.name} ${browser.name}
-- --
@ -99,6 +109,7 @@ tasks:
$map: [{name: firefox, channel: nightly}, {name: chrome, channel: dev}] $map: [{name: firefox, channel: nightly}, {name: chrome, channel: dev}]
each(browser): each(browser):
$map: $map:
# This is the main place to define new stability checks
- name: wpt-${browser.name}-${browser.channel}-stability - name: wpt-${browser.name}-${browser.channel}-stability
checkout: FETCH_HEAD checkout: FETCH_HEAD
diff_range: HEAD^ diff_range: HEAD^
@ -144,7 +155,7 @@ tasks:
owner: ${event.pull_request.user.login}@users.noreply.github.com owner: ${event.pull_request.user.login}@users.noreply.github.com
source: ${event.repository.url} source: ${event.repository.url}
payload: payload:
image: harjgam/web-platform-tests:0.29 image: harjgam/web-platform-tests:0.30
maxRunTime: 7200 maxRunTime: 7200
artifacts: artifacts:
public/results: public/results:
@ -161,13 +172,20 @@ tasks:
- --login - --login
- -c - -c
- set -ex; - set -ex;
echo "${operation.name}";
export TASK_EVENT='${event_str}';
~/start.sh ~/start.sh
${event.repository.clone_url} ${event.repository.clone_url}
refs/pull/${event.number}/merge refs/pull/${event.number}/merge
${operation.checkout} FETCH_HEAD;
${browser.name} cd web-platform-tests;
${browser.channel}; ./tools/ci/run_tc.py
cd ~/web-platform-tests; --checkout=${operation.checkout}
--oom-killer
--browser=${browser.name}
--channel=${browser.channel}
--xvfb
stability
./tools/ci/taskcluster-run.py ./tools/ci/taskcluster-run.py
--commit-range ${operation.diff_range} --commit-range ${operation.diff_range}
${browser.name} ${browser.name}
@ -175,13 +193,90 @@ tasks:
--channel=${browser.channel} --channel=${browser.channel}
${operation.extra_args}; ${operation.extra_args};
- $map: - $map:
# This is the main point to define new CI checks other than stability checks
- name: lint - name: lint
description: >- description: >-
Lint for wpt-specific requirements Lint for wpt-specific requirements
script: tools/ci/ci_lint.sh script: ./tools/ci/run_tc.py --no-hosts lint tools/ci/ci_lint.sh
conditions: conditions:
push push
pull-request pull-request
- name: update built tests
description: >-
Ensure test suites that require a build step are updated
script: ./tools/ci/run_tc.py --no-hosts update_built tools/ci/ci_built_diff.sh
conditions:
pull-request
- name: tools/ unittests (Python 2)
description: >-
Unit tests for tools running under Python 2.7, excluding wptrunner
script: >-
export TOXENV=py27;
export HYPOTHESIS_PROFILE=ci;
./tools/ci/run_tc.py \
tools_unittest \
tools/ci/ci_tools_unittest.sh
conditions:
push
pull-request
- name: tools/ unittests (Python 3)
description: >-
Unit tests for tools running under Python 3, excluding wptrunner
script: >-
export TOXENV=py36;
export HYPOTHESIS_PROFILE=ci;
sudo apt install -qqy python3-pip;
./tools/ci/run_tc.py \
tools_unittest \
tools/ci/ci_tools_unittest.sh
conditions:
push
pull-request
- name: tools/wpt/ tests
description: >-
Integration tests for wpt commands
script: >-
export TOXENV=py27;
sudo apt install -qqy libnss3-tools;
./tools/ci/run_tc.py \
--oom-killer \
--browser=firefox \
--browser=chrome \
--channel=experimental \
--xvfb \
wpt_integration \
tools/ci/ci_wpt.sh
conditions:
pull-request
- name: resources/ tests
description: >-
Tests for testharness.js and other files in resources/
script: >-
export TOXENV=py27;
./tools/ci/run_tc.py \
--browser=firefox \
--channel=experimental \
--xvfb \
resources_unittest \
tools/ci/ci_resources_unittest.sh
conditions:
pull-request
- name: infrastructure/ tests
description: >-
Smoketests for wptrunner
script: >-
sudo apt install -qqy libnss3-tools libappindicator1 fonts-liberation;
./tools/ci/run_tc.py \
--oom-killer \
--browser=firefox \
--browser=chrome \
--channel=experimental \
--no-hosts \
--xvfb \
wptrunner_infrastructure \
tools/ci/ci_wptrunner_infrastructure.sh
conditions:
pull-request
each(operation): each(operation):
# Note: jsone doesn't short-circuit evaluation so all parts of the conditional are evaluated # Note: jsone doesn't short-circuit evaluation so all parts of the conditional are evaluated
# Accessing properties using the [] notation allows them to evaluate as null in case they're undefined # Accessing properties using the [] notation allows them to evaluate as null in case they're undefined
@ -213,27 +308,22 @@ tasks:
owner: ${event.sender.login}@users.noreply.github.com owner: ${event.sender.login}@users.noreply.github.com
source: ${event.repository.url} source: ${event.repository.url}
payload: payload:
image: harjgam/web-platform-tests:0.29 image: harjgam/web-platform-tests:0.30
maxRunTime: 7200 maxRunTime: 7200
artifacts: artifacts:
public/results: public/results:
path: /home/test/artifacts path: /home/test/artifacts
type: directory type: directory
# Fetch the GitHub-provided merge commit (rather than the pull
# request branch) so that the tasks simulate the behavior of the
# submitted patch after it is merged. Using the merge commit also
# simplifies detection of modified files because the first parent
# of the merge commit can consistently be used to summarize the
# changes.
command: command:
- /bin/bash - /bin/bash
- --login - --login
- -c - -c
- set -ex; - set -ex;
echo "${operation.name}";
export TASK_EVENT='${event_str}';
~/start.sh ~/start.sh
${event.repository.clone_url} ${event.repository.clone_url}
${checkout_ref} ${checkout_ref}
FETCH_HEAD FETCH_HEAD;
none;
cd ~/web-platform-tests; cd ~/web-platform-tests;
${operation.script}; ${operation.script};

View file

@ -28,66 +28,11 @@ matrix:
secure: "EljDx50oNpDLs7rzwIv+z1PxIgB5KMnx1W0OQkpNvltR0rBW9g/aQaE+Z/c8M/sPqN1bkvKPybKzGKjb6j9Dw3/EJhah4SskH78r3yMAe2DU/ngxqqjjfXcCc2t5MKxzHAILTAxqScPj2z+lG1jeK1Z+K5hTbSP9lk+AvS0D16w=" secure: "EljDx50oNpDLs7rzwIv+z1PxIgB5KMnx1W0OQkpNvltR0rBW9g/aQaE+Z/c8M/sPqN1bkvKPybKzGKjb6j9Dw3/EJhah4SskH78r3yMAe2DU/ngxqqjjfXcCc2t5MKxzHAILTAxqScPj2z+lG1jeK1Z+K5hTbSP9lk+AvS0D16w="
file: $WPT_MANIFEST_FILE.gz file: $WPT_MANIFEST_FILE.gz
skip_cleanup: true skip_cleanup: true
- name: "update-built-tests.sh"
if: type = pull_request
os: linux
python: "2.7"
env: JOB=update_built SCRIPT=tools/ci/ci_built_diff.sh
- name: "build-css-testsuites.sh" - name: "build-css-testsuites.sh"
if: type = pull_request if: type = pull_request
os: linux os: linux
python: "2.7" python: "2.7"
env: JOB=build_css SCRIPT=css/build-css-testsuites.sh env: JOB=build_css SCRIPT=css/build-css-testsuites.sh
- name: "tools/ unittests (Python 2)"
if: type = pull_request
os: linux
python: "2.7"
env: JOB=tools_unittest TOXENV=py27 HYPOTHESIS_PROFILE=ci SCRIPT=tools/ci/ci_tools_unittest.sh
- name: "tools/ unittests (Python 3)"
if: type = pull_request
os: linux
python: "3.6"
env: JOB=tools_unittest TOXENV=py36 HYPOTHESIS_PROFILE=ci SCRIPT=tools/ci/ci_tools_unittest.sh
- name: "tools/wpt/ tests"
if: type = pull_request
os: linux
python: "2.7"
addons:
apt:
packages:
- libnss3-tools
env: JOB=wpt_integration TOXENV=py27 SCRIPT=tools/ci/ci_wpt.sh
addons:
apt:
packages:
- fonts-liberation
- libappindicator1
- libnss3-tools
- pulseaudio
- name: "resources/ tests"
if: type = pull_request
os: linux
python: "2.7"
env: JOB=resources_unittest TOXENV=py27 SCRIPT=tools/ci/ci_resources_unittest.sh
addons:
apt:
packages:
- fonts-liberation
- libappindicator1
- libnss3-tools
- pulseaudio
- name: "infrastructure/ tests"
if: type = pull_request
os: linux
python: "2.7"
env: JOB=wptrunner_infrastructure SCRIPT=tools/ci/ci_wptrunner_infrastructure.sh
addons:
apt:
packages:
- fonts-liberation
- libappindicator1
- libnss3-tools
- pulseaudio
exclude: exclude:
- env: # exclude empty env from the top-level above - env: # exclude empty env from the top-level above
allow_failures: allow_failures:

View file

@ -0,0 +1,40 @@
// META: script=support.js
function cursorRequestTest({ useIndex, useKeyCursor }) {
indexeddb_test(
(t, db) => {
const objStore = db.createObjectStore("my_objectstore");
objStore.add("data", 1);
objStore.createIndex("my_index", "");
},
(t, db) => {
const tx = db.transaction("my_objectstore");
let source = tx.objectStore("my_objectstore");
if (useIndex) source = source.index('my_index');
const req = useKeyCursor ? source.openKeyCursor() : source.openCursor();
let cursor;
req.onsuccess = t.step_func(() => {
cursor = req.result;
assert_equals(cursor.request, req, 'cursor.request');
assert_readonly(cursor, 'request');
});
req.transaction.oncomplete = t.step_func(() => {
setTimeout(t.step_func(() => {
assert_equals(cursor.request, req, 'cursor.request after transaction complete');
t.done();
}), 0);
});
req.transaction.onerror = t.unreached_func('Transaction error');
},
`cursor.request from ${useIndex ? 'IDBIndex' : 'IDBObjectStore'}.${useKeyCursor ? 'openKeyCursor' : 'openCursor'}`
);
}
for (const useIndex of [false, true]) {
for (const useKeyCursor of [false, true]) {
cursorRequestTest({ useIndex, useKeyCursor });
}
}

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<title>Databases on different origins use separate locking</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="support.js"></script>
<script src="support-promises.js"></script>
<script>
var host_info = get_host_info();
promise_test(async testCase => {
await deleteAllDatabases(testCase);
// Create an iframe to open and hold a database on a different origin.
var iframe = document.createElement('iframe');
var newLocation = window.location.href.replace('www', 'www2');
const keepalive_watcher = new EventWatcher(testCase, window, 'message');
iframe.src = host_info.HTTP_REMOTE_ORIGIN +
'/IndexedDB/resources/idbfactory-origin-isolation-iframe.html';
document.body.appendChild(iframe);
// Wait until the iframe starts its transaction.
var event = await keepalive_watcher.wait_for('message');
assert_equals("keep_alive_started", event.data);
// Create our own database with the same name, and perform a simple get.
const db = await createNamedDatabase(
testCase, 'db-isolation-test', database => {
database.createObjectStore('s');
});
const tx = db.transaction('s');
var request = tx.objectStore('s').get(0);
request.onsuccess = testCase.step_func_done();
request.onerror = testCase.unreached_func("There should be no errors.");
}, "Test to make sure that origins have separate locking schemes");
</script>
<div id="log"></div>

View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<title>This iframe keeps a transaction on a database alive indefinitely to test</title>
<script>
// Keeps the passed transaction alive indefinitely (by making requests
// against the named store). Returns a function that asserts that the
// transaction has not already completed and then ends the request loop so that
// the transaction may autocommit and complete.
function keep_alive(tx, store_name) {
let completed = false;
tx.addEventListener('complete', () => { completed = true; });
let keepSpinning = true;
function spin() {
if (!keepSpinning)
return;
tx.objectStore(store_name).get(0).onsuccess = spin;
}
spin();
return () => {
assert_false(completed, 'Transaction completed while kept alive');
keepSpinning = false;
};
}
async function run() {
const dbs_to_delete = await indexedDB.databases();
for (const db_info of dbs_to_delete) {
let request = indexedDB.deleteDatabase(db_info.name);
await new Promise((resolve, reject) => {
request.onsuccess = resolve;
request.onerror = reject;
});
}
var openRequest = indexedDB.open('db-isolation-test');
openRequest.onupgradeneeded = () => {
openRequest.result.createObjectStore('s');
};
openRequest.onsuccess = () => {
var tx = openRequest.result.transaction('s');
keep_alive(tx, 's');
window.parent.postMessage("keep_alive_started", "*");
};
}
run();
</script>

View file

@ -48,7 +48,7 @@ async function waitForAnimationFrameWithCondition(condition) {
do { do {
await new Promise(window.requestAnimationFrame); await new Promise(window.requestAnimationFrame);
} while (!condition()) } while (!condition())
}; }
async function waitForDocumentTimelineAdvance() { async function waitForDocumentTimelineAdvance() {
const timeAtStart = document.timeline.currentTime; const timeAtStart = document.timeline.currentTime;
@ -56,3 +56,10 @@ async function waitForDocumentTimelineAdvance() {
await new Promise(window.requestAnimationFrame); await new Promise(window.requestAnimationFrame);
} while (timeAtStart === document.timeline.currentTime) } while (timeAtStart === document.timeline.currentTime)
} }
// Wait until animation's effect has a non-null localTime.
async function waitForNotNullLocalTime(animation) {
await waitForAnimationFrameWithCondition(_ => {
return animation.effect.getComputedTiming().localTime !== null;
});
}

View file

@ -58,9 +58,7 @@ async function effect_with_fill_mode_forwards(t) {
'constant_time', 'constant_time',
effect_with_fill_forwards); effect_with_fill_forwards);
animation.play(); animation.play();
await waitForNotNullLocalTime(animation);
await waitForAsyncAnimationFrames(1);
await waitForNextFrame();
assert_equals(getComputedStyle(target).opacity, '0'); assert_equals(getComputedStyle(target).opacity, '0');
@ -76,9 +74,7 @@ async function effect_without_fill_mode_forwards(t) {
'constant_time', 'constant_time',
effect_without_fill_forwards); effect_without_fill_forwards);
animation.play(); animation.play();
await waitForNotNullLocalTime(animation);
await waitForAsyncAnimationFrames(1);
await waitForNextFrame();
assert_equals(getComputedStyle(target).opacity, '1'); assert_equals(getComputedStyle(target).opacity, '1');
@ -94,9 +90,7 @@ async function effect_without_fill_forwards_at_end(t) {
'constant_time', 'constant_time',
effect_without_fill_forwards_at_end); effect_without_fill_forwards_at_end);
animation.play(); animation.play();
await waitForNotNullLocalTime(animation);
await waitForAsyncAnimationFrames(1);
await waitForNextFrame();
assert_equals(getComputedStyle(target).opacity, '1'); assert_equals(getComputedStyle(target).opacity, '1');
@ -112,9 +106,7 @@ async function effect_with_fill_backwards(t) {
'constant_time', 'constant_time',
effect_with_fill_backwards); effect_with_fill_backwards);
animation.play(); animation.play();
await waitForNotNullLocalTime(animation);
await waitForAsyncAnimationFrames(1);
await waitForNextFrame();
assert_equals(getComputedStyle(target).opacity, '0.5'); assert_equals(getComputedStyle(target).opacity, '0.5');
@ -130,9 +122,7 @@ async function effect_without_fill_backwards(t) {
'constant_time', 'constant_time',
effect_without_fill_backwards); effect_without_fill_backwards);
animation.play(); animation.play();
waitForNotNullLocalTime(animation);
await waitForAsyncAnimationFrames(1);
await waitForNextFrame();
assert_equals(getComputedStyle(target).opacity, '1'); assert_equals(getComputedStyle(target).opacity, '1');
@ -148,9 +138,7 @@ async function effect_without_fill_backwards_at_start(t) {
'constant_time', 'constant_time',
effect_without_fill_backwards_at_start); effect_without_fill_backwards_at_start);
animation.play(); animation.play();
await waitForNotNullLocalTime(animation);
await waitForAsyncAnimationFrames(1);
await waitForNextFrame();
assert_equals(getComputedStyle(target).opacity, '0.5'); assert_equals(getComputedStyle(target).opacity, '0.5');

View file

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<meta charset=utf-8> <meta charset=utf-8>
<title>Clipboard IDL test</title> <title>Clipboard IDL test</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/"> <link rel='help' href='https://w3c.github.io/clipboard-apis/'>
<script src=/resources/testharness.js></script> <script src="/resources/testharness.js"></script>
<script src=/resources/testharnessreport.js></script> <script src="/resources/testharnessreport.js"></script>
<script src=/resources/WebIDLParser.js></script> <script src="/resources/WebIDLParser.js"></script>
<script src=/resources/idlharness.js></script> <script src="/resources/idlharness.js"></script>
<script> <script>
'use strict'; 'use strict';
@ -31,9 +31,9 @@ function fetchText(url) {
promise_test(() => { promise_test(() => {
return Promise.all( return Promise.all(
[ [
"/interfaces/clipboard-apis.idl", '/interfaces/clipboard-apis.idl',
"/interfaces/dom.idl", '/interfaces/dom.idl',
].map(fetchText)) ].map(fetchText))
.then(([idl, dom]) => doTest(idl, dom)); .then(([idl, dom]) => doTest(idl, dom));
}, "Test driver"); }, 'Test driver');
</script> </script>

View file

@ -9,55 +9,59 @@ test(() => {
assert_not_equals(navigator.clipboard, undefined); assert_not_equals(navigator.clipboard, undefined);
assert_true(navigator.clipboard instanceof Clipboard); assert_true(navigator.clipboard instanceof Clipboard);
assert_equals(navigator.clipboard, navigator.clipboard); assert_equals(navigator.clipboard, navigator.clipboard);
}, "navigator.clipboard exists"); }, 'navigator.clipboard exists');
promise_test(async () => { promise_test(async () => {
const blob = new Blob(["hello"], {type: 'text/plain'}); const blob = new Blob(['hello'], {type: 'text/plain'});
await navigator.clipboard.write([blob]); await navigator.clipboard.write({'text/plain': blob});
}, "navigator.clipboard.write([text/plain Blob]) succeeds"); }, 'navigator.clipboard.write({string : text/plain Blob}) succeeds');
promise_test(async t => { promise_test(async t => {
await promise_rejects(t, new TypeError(), await promise_rejects(t, new TypeError(),
navigator.clipboard.write()); navigator.clipboard.write());
}, "navigator.clipboard.write() fails (expect [Blob])"); }, 'navigator.clipboard.write() fails (expect {string : Blob})');
promise_test(async t => { promise_test(async t => {
await promise_rejects(t, new TypeError(), await promise_rejects(t, new TypeError(),
navigator.clipboard.write(null)); navigator.clipboard.write(null));
}, "navigator.clipboard.write(null) fails (expect [Blob])"); }, 'navigator.clipboard.write(null) fails (expect {string : Blob})');
promise_test(async t => { promise_test(async t => {
await promise_rejects(t, new TypeError(), await promise_rejects(t, new TypeError(),
navigator.clipboard.write("Bad string")); navigator.clipboard.write('Bad string'));
}, "navigator.clipboard.write(DOMString) fails (expect [Blob])"); }, 'navigator.clipboard.write(DOMString) fails (expect {string : Blob})');
promise_test(async t => {
const blob = new Blob(['hello'], {type: 'text/plain'});
await promise_rejects(t, 'NotAllowedError',
navigator.clipboard.write(blob));
}, 'navigator.clipboard.write(Blob) fails (expect {string : Blob})');
promise_test(async () => { promise_test(async () => {
await navigator.clipboard.writeText("New clipboard text"); await navigator.clipboard.writeText('New clipboard text');
}, "navigator.clipboard.writeText(DOMString) succeeds"); }, 'navigator.clipboard.writeText(DOMString) succeeds');
promise_test(async t => { promise_test(async t => {
await promise_rejects(t, new TypeError(), await promise_rejects(t, new TypeError(),
navigator.clipboard.writeText()); navigator.clipboard.writeText());
}, "navigator.clipboard.writeText() fails (expect DOMString)"); }, 'navigator.clipboard.writeText() fails (expect DOMString)');
promise_test(async () => { promise_test(async () => {
const fetched = await fetch( const fetched = await fetch(
'http://localhost:8001/clipboard-apis/resources/greenbox.png'); 'http://localhost:8001/clipboard-apis/resources/greenbox.png');
const image = await fetched.blob(); const image = await fetched.blob();
await navigator.clipboard.write([image]); await navigator.clipboard.write({'image/png' : image});
}, "navigator.clipboard.write([image/png Blob]) succeeds"); }, 'navigator.clipboard.write({string : image/png Blob}) succeeds');
promise_test(async () => { promise_test(async () => {
const result = await navigator.clipboard.read(); const result = await navigator.clipboard.read();
assert_true(result instanceof Array); assert_true(result instanceof Object);
assert_true(result[0] instanceof Blob); }, 'navigator.clipboard.read() succeeds');
assert_equals(typeof result, "object");
}, "navigator.clipboard.read() succeeds");
promise_test(async () => { promise_test(async () => {
const result = await navigator.clipboard.readText(); const result = await navigator.clipboard.readText();
assert_equals(typeof result, "string"); assert_equals(typeof result, 'string');
}, "navigator.clipboard.readText() succeeds"); }, 'navigator.clipboard.readText() succeeds');
</script> </script>

View file

@ -13,19 +13,20 @@ async function loadBlob(fileName) {
} }
promise_test(async t => { promise_test(async t => {
const blobText = new Blob(["test text"], {type: 'text/plain'}); const blobText = new Blob(['test text'], {type: 'text/plain'});
const blobImage = await loadBlob('resources/greenbox.png'); const blobImage = await loadBlob('resources/greenbox.png');
assert_equals(blobText.type, "text/plain"); assert_equals(blobText.type, 'text/plain');
assert_equals(blobImage.type, "image/png"); assert_equals(blobImage.type, 'image/png');
await navigator.clipboard.write([blobText, blobImage]); await navigator.clipboard.write(
{'text/plain' : blobText, 'image/png' : blobImage});
const output = await navigator.clipboard.read(); const output = await navigator.clipboard.read();
assert_equals(output.length, 2); assert_equals(Object.keys(output).length, 2);
assert_equals(output[0].type, "text/plain"); assert_equals(output['text/plain'].type, 'text/plain');
assert_equals(output[1].type, "image/png"); assert_equals(output['image/png'].type, 'image/png');
}, "Verify write and read clipboard (multiple blobs)"); }, 'Verify write and read clipboard (multiple blobs)');
</script> </script>
<p> <p>
Note: This is a manual test because it writes/reads to the shared system Note: This is a manual test because it writes/reads to the shared system

View file

@ -10,19 +10,19 @@ async function readWriteTest(textInput) {
promise_test(async t => { promise_test(async t => {
const blobInput = new Blob([textInput], {type: 'text/plain'}); const blobInput = new Blob([textInput], {type: 'text/plain'});
await navigator.clipboard.write([blobInput]); await navigator.clipboard.write({'text/plain': blobInput});
const blobsOutput = await navigator.clipboard.read(); const blobsOutput = await navigator.clipboard.read();
assert_equals(blobsOutput.length, 1); assert_equals(Object.keys(blobsOutput).length, 1);
const blobOutput = blobsOutput[0]; const blobOutput = blobsOutput['text/plain'];
assert_equals(blobOutput.type, "text/plain"); assert_equals(blobOutput.type, 'text/plain');
const textOutput = await (new Response(blobOutput)).text(); const textOutput = await (new Response(blobOutput)).text();
assert_equals(textOutput, textInput); assert_equals(textOutput, textInput);
}, "Verify write and read clipboard given text: " + textInput); }, 'Verify write and read clipboard given text: ' + textInput);
} }
readWriteTest("Clipboard write ([text/plain Blob]) -> read ([text/plain Blob]) test"); readWriteTest('Clipboard write ([text/plain Blob]) -> read ([text/plain Blob]) test');
readWriteTest("non-Latin1 text encoding test データ"); readWriteTest('non-Latin1 text encoding test データ');
</script> </script>
<p> <p>
Note: This is a manual test because it writes/reads to the shared system Note: This is a manual test because it writes/reads to the shared system

View file

@ -8,15 +8,15 @@ async function readWriteTest(textInput) {
promise_test(async t => { promise_test(async t => {
const blobInput = new Blob([textInput], {type: 'text/plain'}); const blobInput = new Blob([textInput], {type: 'text/plain'});
await navigator.clipboard.write([blobInput]); await navigator.clipboard.write({'text/plain': blobInput});
const textOutput = await navigator.clipboard.readText(); const textOutput = await navigator.clipboard.readText();
assert_equals(textOutput, textInput); assert_equals(textOutput, textInput);
}, "Verify write and read clipboard given text: " + textInput); }, 'Verify write and read clipboard given text: ' + textInput);
} }
readWriteTest("Clipboard write ([text/plain Blob]) -> read text test"); readWriteTest('Clipboard write ([text/plain Blob]) -> read text test');
readWriteTest("non-Latin1 text encoding test データ"); readWriteTest('non-Latin1 text encoding test データ');
</script> </script>
<p> <p>
Note: This is a manual test because it writes/reads to the shared system Note: This is a manual test because it writes/reads to the shared system

View file

@ -1,26 +0,0 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>
Async Clipboard write duplicate mime type test
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
promise_test(async t => {
const blobText = new Blob(["test text"], {type: 'text/plain'});
const blobText2 = new Blob(["test text"], {type: 'text/plain'});
assert_equals(blobText.type, "text/plain");
assert_equals(blobText2.type, "text/plain");
await promise_rejects(t, 'NotAllowedError',
navigator.clipboard.write([blobText, blobText2]));
}, "Verify write and read clipboard (multiple blobs)");
</script>
<p>
Note: This is a manual test because it writes/reads to the shared system
clipboard and thus cannot be run async with other tests that might interact
with the clipboard.
</p>

View file

@ -9,11 +9,11 @@
<p> <p>
<p>The bottom image should display the same image as the top image.</p> <p>The bottom image should display the same image as the top image.</p>
<p>Original Image:</p> <p>Original Image:</p>
<image id='image-to-copy' width='20' height='20' <image id="image-to-copy" width="20" height="20"
src="resources/greenbox.png"></image> src="resources/greenbox.png"></image>
<p>Image after copy/paste:</p> <p>Image after copy/paste:</p>
<image id='image-on-clipboard'></image> <image id="image-on-clipboard"></image>
<canvas id='canvas' width='20' height='20'></canvas> <canvas id="canvas" width="20" height="20"></canvas>
</p> </p>
<script> <script>
@ -39,12 +39,12 @@ async function loadBlob(fileName) {
promise_test(async t => { promise_test(async t => {
const blobInput = await loadBlob('resources/greenbox.png'); const blobInput = await loadBlob('resources/greenbox.png');
assert_equals(blobInput.type, "image/png"); assert_equals(blobInput.type, 'image/png');
await navigator.clipboard.write([blobInput]); await navigator.clipboard.write({'image/png' : blobInput});
const blobsOutput = await navigator.clipboard.read(); const blobsOutput = await navigator.clipboard.read();
assert_equals(blobsOutput.length, 1); assert_equals(Object.keys(blobsOutput).length, 1);
const blobOutput = blobsOutput[0]; const blobOutput = blobsOutput['image/png'];
assert_equals(blobOutput.type, "image/png"); assert_equals(blobOutput.type, 'image/png');
document.getElementById('image-on-clipboard').src = document.getElementById('image-on-clipboard').src =
window.URL.createObjectURL(blobOutput); window.URL.createObjectURL(blobOutput);
@ -53,7 +53,7 @@ promise_test(async t => {
const comparableOutput = await getBitmapString(blobOutput); const comparableOutput = await getBitmapString(blobOutput);
assert_equals(comparableOutput, comparableInput); assert_equals(comparableOutput, comparableInput);
}, "Verify write and read clipboard ([image/png Blob])"); }, 'Verify write and read clipboard ([image/png Blob])');
</script> </script>
<p> <p>
Note: This is a manual test because it writes/reads to the shared system Note: This is a manual test because it writes/reads to the shared system

View file

@ -8,17 +8,17 @@ async function readWriteTest(textInput) {
promise_test(async t => { promise_test(async t => {
await navigator.clipboard.writeText(textInput); await navigator.clipboard.writeText(textInput);
const blobsOutput = await navigator.clipboard.read(); const blobsOutput = await navigator.clipboard.read();
assert_equals(blobsOutput.length, 1); assert_equals(Object.keys(blobsOutput).length, 1);
const blobOutput = blobsOutput[0]; const blobOutput = blobsOutput['text/plain'];
assert_equals(blobOutput.type, "text/plain"); assert_equals(blobOutput.type, 'text/plain');
const textOutput = await (new Response(blobOutput)).text(); const textOutput = await (new Response(blobOutput)).text();
assert_equals(textOutput, textInput); assert_equals(textOutput, textInput);
}, "Verify write and read clipboard given text: " + textInput); }, 'Verify write and read clipboard given text: ' + textInput);
} }
readWriteTest("Clipboard write text -> read ([text/plain Blob]) test"); readWriteTest('Clipboard write text -> read ([text/plain Blob]) test');
readWriteTest("non-Latin1 text encoding test データ"); readWriteTest('non-Latin1 text encoding test データ');
</script> </script>
<p> <p>
Note: This is a manual test because it writes/reads to the shared system Note: This is a manual test because it writes/reads to the shared system

View file

@ -10,11 +10,11 @@ async function readWriteTest(textInput) {
const textOutput = await navigator.clipboard.readText(); const textOutput = await navigator.clipboard.readText();
assert_equals(textOutput, textInput); assert_equals(textOutput, textInput);
}, "Verify write and read clipboard given text: " + textInput); }, 'Verify write and read clipboard given text: ' + textInput);
} }
readWriteTest("Clipboard write text -> read text test"); readWriteTest('Clipboard write text -> read text test');
readWriteTest("non-Latin1 text encoding test データ"); readWriteTest('non-Latin1 text encoding test データ');
</script> </script>
<p> <p>
Note: This is a manual test because it writes/reads to the shared system Note: This is a manual test because it writes/reads to the shared system

View file

@ -11,9 +11,9 @@ async_test(t => {
document.oncopy = t.step_func_done(event => { document.oncopy = t.step_func_done(event => {
// Nothing can be asserted about the event target until // Nothing can be asserted about the event target until
// https://github.com/w3c/clipboard-apis/issues/70 is resolved. // https://github.com/w3c/clipboard-apis/issues/70 is resolved.
// assert_equals(event.target, document.body, "event.target"); // assert_equals(event.target, document.body, 'event.target');
assert_true(event.isTrusted, "event.isTrusted"); assert_true(event.isTrusted, 'event.isTrusted');
assert_true(event.composed, "event.composed"); assert_true(event.composed, 'event.composed');
}); });
}); });
</script> </script>

View file

@ -11,9 +11,9 @@ async_test(t => {
document.oncut = t.step_func_done(event => { document.oncut = t.step_func_done(event => {
// Nothing can be asserted about the event target until // Nothing can be asserted about the event target until
// https://github.com/w3c/clipboard-apis/issues/70 is resolved. // https://github.com/w3c/clipboard-apis/issues/70 is resolved.
// assert_equals(event.target, document.body, "event.target"); // assert_equals(event.target, document.body, 'event.target');
assert_true(event.isTrusted, "event.isTrusted"); assert_true(event.isTrusted, 'event.isTrusted');
assert_true(event.composed, "event.composed"); assert_true(event.composed, 'event.composed');
}); });
}); });
</script> </script>

View file

@ -9,13 +9,13 @@
<script> <script>
setup({explicit_timeout: true}); setup({explicit_timeout: true});
async_test(t => { async_test(t => {
getSelection().selectAllChildren(document.querySelector("p")); getSelection().selectAllChildren(document.querySelector('p'));
document.onpaste = t.step_func_done(event => { document.onpaste = t.step_func_done(event => {
// Nothing can be asserted about the event target until // Nothing can be asserted about the event target until
// https://github.com/w3c/clipboard-apis/issues/70 is resolved. // https://github.com/w3c/clipboard-apis/issues/70 is resolved.
// assert_equals(event.target, document.body, "event.target"); // assert_equals(event.target, document.body, 'event.target');
assert_true(event.isTrusted, "event.isTrusted"); assert_true(event.isTrusted, 'event.isTrusted');
assert_true(event.composed, "event.composed"); assert_true(event.composed, 'event.composed');
}); });
}); });
</script> </script>

View file

@ -1,21 +0,0 @@
<!DOCTYPE html>
<link rel="author" title="Google" href="https://www.google.com/" />
<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes" />
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/1865" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<body onload="checkLayout('.flexbox')">
<div class="flexbox" style="display: flex; width: min-content;" data-expected-width="0">
<div style="overflow: auto;">
<div style="width: 100px; height: 100px;"></div>
</div>
</div>
<div class="flexbox" style="display: flex; width: min-content;" data-expected-width="10">
<div style="overflow: auto; border: 5px solid;">
<div style="width: 100px; height: 100px;"></div>
</div>
</div>

View file

@ -0,0 +1,153 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Grid Layout Test: Grid container baseline</title>
<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-baselines">
<meta name="assert" content="Grid container baseline should match the element with 'align-self: baseline' in the first row, even if it's an orthogonal element.">
<style>
.wrapper {
position: relative;
margin: 10px;
}
.grid {
display: inline-grid;
grid-auto-flow: column;
background: grey;
}
.i1 {
width: 150px;
height: 100px;
background: magenta;
}
.i2 {
align-self: baseline;
width: 75px;
height: 50px;
background: cyan;
}
.i3 {
width: 100px;
height: 75px;
background: yellow;
}
</style>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/check-layout-th.js"></script>
<body onload="checkLayout('.wrapper')">
<div class="wrapper" style="writing-mode: horizontal-tb;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-y="40"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" data-offset-y="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-y="40"></div>
</div>
<div class="wrapper" style="writing-mode: horizontal-tb;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-y="40"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" style="writing-mode: vertical-lr;" data-offset-y="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-y="40"></div>
</div>
<div class="wrapper" style="writing-mode: horizontal-tb;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-y="40"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" style="writing-mode: vertical-rl;" data-offset-y="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-y="40"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-lr;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" data-offset-x="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-lr;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" style="writing-mode: horizontal-tb;" data-offset-x="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-lr; text-orientation: sideways;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" data-offset-x="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-lr; text-orientation: sideways;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" style="writing-mode: horizontal-tb;" data-offset-x="0"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-rl;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" data-offset-x="75"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-rl;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" style="writing-mode: horizontal-tb;" data-offset-x="75"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="65"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-rl; text-orientation: sideways;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" data-offset-x="75"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
</div>
<div class="wrapper" style="writing-mode: vertical-rl; text-orientation: sideways;">
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
<div class="grid">
<div class="i1"></div>
<div class="i2" style="writing-mode: horizontal-tb;" data-offset-x="75"></div>
<div class="i3"></div>
</div>
<div style="display: inline-block; width: 20px; height: 10px; background: black" data-offset-x="75"></div>
</div>
</body>

View file

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<title>CSS Position Absolute: Chrome chrash</title> <title>CSS Position Absolute: Chrome crash</title>
<link rel="author" href="mailto:atotic@google.com"> <link rel="author" href="mailto:atotic@google.com">
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<title>CSS Position Absolute: Chrome crash</title>
<link rel="author" href="mailto:atotic@chromium.org">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=935805">
<meta name="assert" content="Nested abs/fixed/flex do not crash">
<style>
#flex {
display: flex;
}
.abs {
position: absolute;
}
#fixed {
position: fixed;
}
</style>
<div class="abs">
<div id="flex">
<div class="abs">
<div id="fixed"></div>
</div>
</div>
</div>
<script>
test(() => {
}, 'test passes if it does not crash');
</script>

View file

@ -0,0 +1,25 @@
<!doctype html>
<html>
<head>
<title>Absolute inside inline container location should be correct</title>
<link rel="author" href="mailto:atotic@chromium.org">
<link rel="help" href="https://www.w3.org/TR/css-position-3/#def-cb">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<!-- There should be a green square below -->
<body style="margin:0">
<span id="container" style="position:relative;">
<div style="width:100px; height:100px; background:red;"></div>
<div id="target" style="position:absolute; left:0; top:0; width:100px; height:100px; background:green;"></div>
</span>
<script>
test(_ => {
let bounds = document.querySelector("#target").getBoundingClientRect();
let container_bounds = document.querySelector("#container").getBoundingClientRect();
assert_equals(bounds.x, container_bounds.x);
assert_equals(bounds.y, container_bounds.y);
}, "absolute inside inline container location should be correct.");
</script>
</body>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html class="reftest-wait">
<meta charset="utf-8">
<title>position:sticky should operate correctly</title>
<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
<div style="position: fixed;">There should be text visible below.</div>
<div style="height: 200px;width:600px;position: fixed;top: 0px;">
<div style="position: relative;top: 100px;">
<div style="height: 150px;width: 500px;position: absolute;backface-visibility: hidden;background: white;">
</div>
<div style="overflow: hidden;">
<a style="position: relative;">THIS SHOULD STAY VISIBLE<BR>IF YOU SCROLL DOWN</a>
</div>
</div>
</div>
<div style="height: 2200px;">
</div>
<script>
window.onload = function() {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.scrollTo(0,300);
document.documentElement.classList.remove("reftest-wait");
});
});
};
</script>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html class="reftest-wait">
<meta charset="utf-8">
<title>position:sticky should operate correctly</title>
<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
<meta name="assert" content="This test checks that the combination of position:sticky, overflow clip, and out-of-flow descendants are properly displayed when scrolled" />
<link rel="match" href="position-sticky-scroll-with-clip-and-abspos-ref.html">
<div style="position: fixed;">There should be text visible below.</div>
<div style="height: 200px;width:600px;position: sticky;top: 0px;">
<div style="position: relative;top: 100px;">
<div style="height: 150px;width: 500px;position: absolute;backface-visibility: hidden;background: white;">
</div>
<div style="overflow: hidden;">
<a style="position: relative;">THIS SHOULD STAY VISIBLE<BR>IF YOU SCROLL DOWN</a>
</div>
</div>
</div>
<div style="height: 2000px;">
</div>
<script>
window.onload = function() {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
window.scrollTo(0,300);
document.documentElement.classList.remove("reftest-wait");
});
});
};
</script>
</html>

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-flows">
<meta name="flags" content="" />
<style>
x-table {
display: table;
width: 300px;
height: 300px;
writing-mode: vertical-lr;
outline: 2px dashed blue;
}
x-caption {
display: table-caption;
height: 200px;
width: 200px;
writing-mode: horizontal-tb;
outline: 1px solid black;
}
</style>
<x-table>
<x-caption>caption</x-caption>
</x-table>
<script>
let body_height = document.querySelector("body").offsetHeight;
test(() => {
assert_greater_than(body_height, 0, "Vertical table with horizontal caption doesn't make <body> have crazy height.");
assert_less_than(body_height, 10000, "Vertical table with horizontal caption doesn't make <body> have crazy height.");
})
</script>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<div style="padding: 0.4px 0.6px">
<div style="width: 100px; height: 100px; border: 1px solid black"></div>
</div>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<link ref="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-property">
<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#backface-visibility-property">
<link rel="match" href="subpixel-perspective-backface-hidden-ref.html">
<meta name="assert" content="Subpixel-positioned contents should not renderred the same regardless of perspective and backface-visibility:hidden">
<div style="padding: 0.4px 0.6px">
<div style="perspective: 1000px; backface-visibility: hidden">
<div style="width: 100px; height: 100px;
backface-visibility: hidden;border: 1px solid black">
</div>
</div>
</div>

View file

@ -0,0 +1,4 @@
<!DOCTYPE html>
<div style="padding: 0.6px 0.4px">
<div style="width: 100px; height: 100px; border: 1px solid black"></div>
</div>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<link ref="help" href="https://drafts.csswg.org/css-transforms-2/#perspective-property">
<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#propdef-backface-visibility">
<link rel="match" href="subpixel-perspective-translate-z-0-ref.html">
<meta name="assert" content="Subpixel-positioned contents should be rendered the same regardless of perspective and translateZ(0)">
<div style="position: relative; top: 0.6px; left: 0.4px; width: 300px; height: 300px;
perspective: 1000px; overflow: hidden">
<div style="width: 100px; height: 100px;
transform: translateZ(0); border: 1px solid black"></div>
</div>

View file

@ -568,5 +568,11 @@ test(() => {
assert_equals(getComputedStyle(shadowDiv).color, "rgb(0, 0, 0)"); assert_equals(getComputedStyle(shadowDiv).color, "rgb(0, 0, 0)");
}, 'Adopting a shadow host will empty adoptedStyleSheets if adopting to a different document'); }, 'Adopting a shadow host will empty adoptedStyleSheets if adopting to a different document');
</script> test(() => {
const host = document.createElement("div");
const root = host.attachShadow({mode: "open"});
root.adoptedStyleSheets = [new CSSStyleSheet()];
document.body.offsetTop;
}, 'Forcing a style update after adding an adopted stylesheet on a disconnected shadow root should not crash.');
</script>

View file

@ -21,7 +21,7 @@ you must either [fix all lint errors](#fixing-lint-errors), or you must
You must fix any errors the lint tool reports, unless an error is for You must fix any errors the lint tool reports, unless an error is for
something essential to a certain test or that for some other something essential to a certain test or that for some other
exceptional reason shouldn't prevent the test from being merged; in exceptional reason shouldn't prevent the test from being merged; in
those cases you can [whitelist test files](#updating-the-whiteslist) those cases you can [whitelist test files](#updating-the-whitelist)
to suppress the errors. In all other cases, follow the instructions to suppress the errors. In all other cases, follow the instructions
below to fix all errors reported. below to fix all errors reported.

View file

@ -1,5 +1,6 @@
<!doctype html> <!doctype html>
<meta charset="utf8"> <meta charset="utf8">
<meta name="timeout" content="long">
<title>Events must dispatch on disabled elements</title> <title>Events must dispatch on disabled elements</title>
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>

View file

@ -5,9 +5,11 @@ if (typeof postMessage === 'function') {
onmessage = event => { onmessage = event => {
switch(event.data.type) { switch(event.data.type) {
case 'ready': case 'ready':
navigator.idle.query().then( new IdleDetector().start().then(() => {
() => postMessage({ enabled: true }), postMessage({ enabled: true });
error => postMessage ({ enabled: false })); }, error => {
postMessage ({ enabled: false });
});
break; break;
} }
}; };

View file

@ -1,9 +1,10 @@
<script> <script>
'use strict'; 'use strict';
navigator.idle.query().then(status => { new IdleDetector().start().then(() => {
window.parent.postMessage({ enabled: true }, '*'); window.parent.postMessage({ enabled: true }, '*');
}, error => { }, error => {
window.parent.postMessage({ enabled: false }, '*'); window.parent.postMessage({ enabled: false }, '*');
}); });
</script> </script>

View file

@ -1,18 +1,23 @@
def main(request, response): def main(request, response):
token = request.GET.first("token", None) token = request.GET.first("token", None)
is_query = request.GET.first("query", None) != None
with request.server.stash.lock:
value = request.server.stash.take(token) value = request.server.stash.take(token)
count = 0 count = 0
if value != None: if value != None:
count = int(value) count = int(value)
if request.GET.first("query", None) != None: if is_query:
headers = [("Count", count)]
content = ""
if count < 2: if count < 2:
request.server.stash.put(token, count) request.server.stash.put(token, count)
return 200, headers, content
else: else:
count = count + 1 count = count + 1
request.server.stash.put(token, count)
if is_query:
headers = [("Count", count)]
content = ""
return 200, headers, content
else:
content = "body { background: rgb(0, 128, 0); }" content = "body { background: rgb(0, 128, 0); }"
if count > 1: if count > 1:
content = "body { background: rgb(255, 0, 0); }" content = "body { background: rgb(255, 0, 0); }"
@ -20,5 +25,4 @@ def main(request, response):
headers = [("Content-Type", "text/css"), headers = [("Content-Type", "text/css"),
("Cache-Control", "private, max-age=0, stale-while-revalidate=60")] ("Cache-Control", "private, max-age=0, stale-while-revalidate=60")]
request.server.stash.put(token, count)
return 200, headers, content return 200, headers, content

View file

@ -3,18 +3,24 @@ import os.path
def main(request, response): def main(request, response):
token = request.GET.first("token", None) token = request.GET.first("token", None)
is_query = request.GET.first("query", None) != None
with request.server.stash.lock:
value = request.server.stash.take(token) value = request.server.stash.take(token)
count = 0 count = 0
if value != None: if value != None:
count = int(value) count = int(value)
if request.GET.first("query", None) != None: if is_query:
headers = [("Count", count)]
content = ""
if count < 2: if count < 2:
request.server.stash.put(token, count) request.server.stash.put(token, count)
return 200, headers, content
else: else:
count = count + 1 count = count + 1
request.server.stash.put(token, count)
if is_query:
headers = [("Count", count)]
content = ""
return 200, headers, content
else:
filename = "green-16x16.png" filename = "green-16x16.png"
if count > 1: if count > 1:
filename = "green-256x256.png" filename = "green-256x256.png"
@ -22,7 +28,6 @@ def main(request, response):
path = os.path.join(os.path.dirname(__file__), "../../images", filename) path = os.path.join(os.path.dirname(__file__), "../../images", filename)
body = open(path, "rb").read() body = open(path, "rb").read()
request.server.stash.put(token, count)
response.add_required_headers = False response.add_required_headers = False
response.writer.write_status(200) response.writer.write_status(200)
response.writer.write_header("content-length", len(body)) response.writer.write_header("content-length", len(body))

View file

@ -6,23 +6,27 @@ def id_token():
def main(request, response): def main(request, response):
token = request.GET.first("token", None) token = request.GET.first("token", None)
is_query = request.GET.first("query", None) != None
with request.server.stash.lock:
value = request.server.stash.take(token) value = request.server.stash.take(token)
count = 0 count = 0
if value != None: if value != None:
count = int(value) count = int(value)
if request.GET.first("query", None) != None: if is_query:
headers = [("Count", count)]
content = ""
if count < 2: if count < 2:
request.server.stash.put(token, count) request.server.stash.put(token, count)
return 200, headers, content
else: else:
count = count + 1 count = count + 1
request.server.stash.put(token, count)
if is_query:
headers = [("Count", count)]
content = ""
return 200, headers, content
else:
unique_id = id_token() unique_id = id_token()
headers = [("Content-Type", "text/javascript"), headers = [("Content-Type", "text/javascript"),
("Cache-Control", "private, max-age=0, stale-while-revalidate=60"), ("Cache-Control", "private, max-age=0, stale-while-revalidate=60"),
("Unique-Id", unique_id)] ("Unique-Id", unique_id)]
content = "report('{}')".format(unique_id) content = "report('{}')".format(unique_id)
request.server.stash.put(token, count)
return 200, headers, content return 200, headers, content

View file

@ -77,11 +77,8 @@ test1.step(function(){
test3.step(function(){ test3.step(function(){
var c3 = new VTTCue(0, 2, "text3"); var c3 = new VTTCue(0, 2, "text3");
t1.addCue(c3); t1.addCue(c3);
assert_equals(t1.activeCues.length, 1, "t1.activeCues.length after adding a cue in the same script"); assert_equals(t1.activeCues.length, 2, "t1.activeCues.length should be changed immediately");
test3.step_timeout(function(){
assert_equals(t1.activeCues.length, 2, "t1.activeCues.length after the event loop has spun");
test3.done(); test3.done();
}, 0);
}); });
test2.done(); test2.done();
}); });

View file

@ -25,7 +25,7 @@
} }
var cueCount = 0; var cueCount = 0;
function cueEntered() { function cueEntered(event) {
var currentCue = event.target; var currentCue = event.target;
// This cue is the currently active cue. // This cue is the currently active cue.

View file

@ -3,70 +3,76 @@
'use strict'; 'use strict';
promise_test(async t => { promise_test(async t => {
let promise = navigator.idle.query(); let status = new IdleDetector();
assert_equals(promise.constructor, Promise,
'query() returns a promise');
let status = await promise; let watcher = new EventWatcher(t, status, ["change"]);
assert_true(status instanceof IdleStatus,
'query() promise resolves to an IdleStatus'); await status.start();
await watcher.wait_for("change");
assert_true(['active', 'idle'].includes(status.state.user), assert_true(['active', 'idle'].includes(status.state.user),
'status has a valid user state'); 'status has a valid user state');
assert_true(['locked', 'unlocked'].includes(status.state.screen), assert_true(['locked', 'unlocked'].includes(status.state.screen),
'status has a valid screen state'); 'status has a valid screen state');
}, 'query() basics'); }, 'start() basics');
promise_test(async t => { promise_test(async t => {
let used = false; let used = false;
await navigator.idle.query({ new IdleDetector({
get threshold() { get threshold() {
used = true; used = true;
return 1; return 1;
} }
}); });
assert_true(used, 'query() options "threshold" member was used'); assert_true(used, 'constructor options "threshold" member was used');
}, 'query() uses threshold property'); }, 'constructor uses threshold property');
promise_test(async t => { promise_test(async t => {
return promise_rejects( try {
t, new IdleDetector({threshold: 0});
new TypeError, assert_unreached('Threshold of 0 should reject');
navigator.idle.query({threshold: 0}), } catch (error) {
'Threshold of 0 should reject'); assert_equals(error.name, 'TypeError');
}, 'query() throws with invalid threshold (0)'); }
}, 'constructor throws with invalid threshold (0)');
promise_test(async t => { promise_test(async t => {
return promise_rejects( try {
t, new IdleDetector({threshold: null});
new TypeError, assert_unreached('Threshold of null should reject');
navigator.idle.query({threshold: null}), } catch (error) {
'Threshold of null should reject'); assert_equals(error.name, 'TypeError');
}, 'query() throws with invalid threshold (null)'); }
}, 'constructor throws with invalid threshold (null)');
promise_test(async t => { promise_test(async t => {
return promise_rejects( try {
t, new IdleDetector({threshold: -1});
new TypeError, assert_unreached('Threshold of negative numbers should reject');
navigator.idle.query({threshold: -1}), } catch (error) {
'Threshold of negative numbers should reject'); assert_equals(error.name, 'TypeError');
}, 'query() throws with invalid threshold (-1)'); }
}, 'constructor throws with invalid threshold (-1)');
promise_test(async t => { promise_test(async t => {
return promise_rejects( try {
t, new IdleDetector({threshold: NaN});
new TypeError, assert_unreached('Threshold of NaN should reject');
navigator.idle.query({threshold: NaN}), } catch (error) {
'Threshold of NaN should reject'); assert_equals(error.name, 'TypeError');
}, 'query() throws with invalid threshold (NaN)'); }
}, 'constructor throws with invalid threshold (NaN)');
promise_test(async t => { promise_test(async t => {
return navigator.idle.query(); new IdleDetector();
}, 'query() uses a default value for the threshold when none is passed'); }, 'constructor uses a default value for the threshold when none is passed');
promise_test(async t => { promise_test(async t => {
return navigator.idle.query({threshold: undefined}); new IdleDetector({threshold: undefined});
}, 'query() uses a default value for the threshold'); }, 'constructor uses a default value for the threshold');

View file

@ -19,25 +19,25 @@ const cross_origin_worker_frame_src = base_src + sub +
relative_worker_frame_path; relative_worker_frame_path;
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_src, test_feature_availability('new IdleDetector().start()', t, same_origin_src,
expect_feature_available_default, 'idle-detection'); expect_feature_available_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame ' + }, 'Attribute allow="idle-detection" in top-level frame ' +
'allows same-origin relocation.'); 'allows same-origin relocation.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, same_origin_worker_frame_src,
expect_feature_available_default, 'idle-detection'); expect_feature_available_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame ' + }, 'Attribute allow="idle-detection" in top-level frame ' +
'allows workers in same-origin relocation.'); 'allows workers in same-origin relocation.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_src,
expect_feature_unavailable_default, 'idle-detection'); expect_feature_unavailable_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame ' + }, 'Attribute allow="idle-detection" in top-level frame ' +
'disallows cross-origin relocation.'); 'disallows cross-origin relocation.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_worker_frame_src,
expect_feature_unavailable_default, 'idle-detection'); expect_feature_unavailable_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame ' + }, 'Attribute allow="idle-detection" in top-level frame ' +
'disallows workers in cross-origin relocation.'); 'disallows workers in cross-origin relocation.');

View file

@ -15,25 +15,25 @@ const cross_origin_src = sub + same_origin_src;
const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src; const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src;
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_src, test_feature_availability('new IdleDetector().start()', t, same_origin_src,
expect_feature_available_default, 'idle-detection'); expect_feature_available_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame can be enabled ' + }, 'Attribute allow="idle-detection" in top-level frame can be enabled ' +
'in same-origin iframe using Feature policy "idle-detection".'); 'in same-origin iframe using Feature policy "idle-detection".');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, same_origin_worker_frame_src,
expect_feature_available_default, 'idle-detection'); expect_feature_available_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame can be enabled ' + }, 'Attribute allow="idle-detection" in top-level frame can be enabled ' +
'in a worker in same-origin iframe using Feature policy "idle-detection".'); 'in a worker in same-origin iframe using Feature policy "idle-detection".');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_src,
expect_feature_available_default, 'idle-detection'); expect_feature_available_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame can be enabled ' + }, 'Attribute allow="idle-detection" in top-level frame can be enabled ' +
'in cross-origin iframe using Feature policy "idle-detection".'); 'in cross-origin iframe using Feature policy "idle-detection".');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_worker_frame_src,
expect_feature_available_default, 'idle-detection'); expect_feature_available_default, 'idle-detection');
}, 'Attribute allow="idle-detection" in top-level frame can be enabled ' + }, 'Attribute allow="idle-detection" in top-level frame can be enabled ' +
'in a worker in cross-origin iframe using Feature policy "idle-detection".'); 'in a worker in cross-origin iframe using Feature policy "idle-detection".');

View file

@ -14,31 +14,32 @@ const same_origin_worker_frame_src =
const cross_origin_src = sub + same_origin_src; const cross_origin_src = sub + same_origin_src;
const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src; const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src;
promise_test( promise_test(async () => {
() => navigator.idle.query(), await new IdleDetector().start();
},
'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' + 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' +
'frame allows the top-level document.'); 'frame allows the top-level document.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_src, test_feature_availability('new IdleDetector().start()', t, same_origin_src,
expect_feature_available_default); expect_feature_available_default);
}, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' + }, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' +
'frame allows same-origin iframes.'); 'frame allows same-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, same_origin_worker_frame_src,
expect_feature_available_default); expect_feature_available_default);
}, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' + }, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' +
'frame allows workers in same-origin iframes.'); 'frame allows workers in same-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_src,
expect_feature_available_default); expect_feature_available_default);
}, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' + }, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' +
'frame allows cross-origin iframes.'); 'frame allows cross-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_worker_frame_src,
expect_feature_available_default); expect_feature_available_default);
}, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' + }, 'Feature-Policy {"idle-detection" : ["*"]} explicity set by top-level ' +
'frame allows workers in cross-origin iframes.'); 'frame allows workers in cross-origin iframes.');

View file

@ -11,19 +11,20 @@ const same_origin_src =
const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
same_origin_src; same_origin_src;
promise_test( promise_test(async () => {
() => navigator.idle.query(), await new IdleDetector().start()
},
'Default "idle-detection" feature policy ["self"] ' + 'Default "idle-detection" feature policy ["self"] ' +
'allows the top-level document.'); 'allows the top-level document.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_src, test_feature_availability('new IdleDetector().start()', t, same_origin_src,
expect_feature_available_default); expect_feature_available_default);
}, 'Default "idle-detection" feature policy ["self"] ' + }, 'Default "idle-detection" feature policy ["self"] ' +
'allows same-origin iframes.'); 'allows same-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_src,
expect_feature_unavailable_default); expect_feature_unavailable_default);
}, 'Default "idle-detection" feature policy ["self"] ' + }, 'Default "idle-detection" feature policy ["self"] ' +
'disallows cross-origin iframes.'); 'disallows cross-origin iframes.');

View file

@ -14,35 +14,37 @@ const same_origin_worker_frame_src =
const cross_origin_src = sub + same_origin_src; const cross_origin_src = sub + same_origin_src;
const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src; const cross_origin_worker_frame_src = sub + same_origin_worker_frame_src;
promise_test(() => { promise_test(async () => {
return navigator.idle.query().then(() => { try {
let idleDetector = new IdleDetector();
await idleDetector.start();
assert_unreached('expected promise to reject with SecurityError'); assert_unreached('expected promise to reject with SecurityError');
}, error => { } catch (error) {
assert_equals(error.name, 'SecurityError'); assert_equals(error.name, 'SecurityError');
}); }
}, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' + }, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' +
'disallows query in the top-level document.'); 'disallows query in the top-level document.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_src, test_feature_availability('new IdleDetector().start()', t, same_origin_src,
expect_feature_unavailable_default); expect_feature_unavailable_default);
}, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' + }, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' +
'disallows same-origin iframes.'); 'disallows same-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, same_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, same_origin_worker_frame_src,
expect_feature_unavailable_default); expect_feature_unavailable_default);
}, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' + }, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' +
'disallows workers in same-origin iframes.'); 'disallows workers in same-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_src,
expect_feature_unavailable_default); expect_feature_unavailable_default);
}, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' + }, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' +
'disallows cross-origin iframes.'); 'disallows cross-origin iframes.');
async_test(t => { async_test(t => {
test_feature_availability('idle.query()', t, cross_origin_worker_frame_src, test_feature_availability('new IdleDetector().start()', t, cross_origin_worker_frame_src,
expect_feature_unavailable_default); expect_feature_unavailable_default);
}, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' + }, 'Feature-Policy {"idle-detection" : []} explicitly set by top-level frame ' +
'disallows workers in cross-origin iframes.'); 'disallows workers in cross-origin iframes.');

View file

@ -1,28 +1,16 @@
[SecureContext]
interface mixin NavigatorIdle {
readonly attribute IdleManager idle;
};
Navigator includes NavigatorIdle;
WorkerNavigator includes NavigatorIdle;
[
SecureContext,
Exposed=(Window,Worker)
] interface IdleManager {
Promise<IdleStatus> query(optional IdleOptions options);
};
dictionary IdleOptions { dictionary IdleOptions {
unsigned long threshold; unsigned long threshold;
}; };
[ [
SecureContext, SecureContext,
Constructor(optional IdleOptions options),
Exposed=(Window,Worker) Exposed=(Window,Worker)
] interface IdleStatus : EventTarget { ] interface IdleDetector : EventTarget {
readonly attribute IdleState state; readonly attribute IdleState state;
attribute EventHandler onchange; attribute EventHandler onchange;
Promise<any> start();
void stop();
}; };
[ [

View file

@ -1,34 +1,36 @@
// META: script=/resources/WebIDLParser.js // META: script=/resources/WebIDLParser.js
// META: script=/resources/idlharness.js // META: script=/resources/idlharness.js
// https://github.com/inexorabletash/idle-detection // https://github.com/samuelgoto/idle-detection
'use strict'; 'use strict';
promise_test(async () => { promise_test(async (t) => {
const srcs = ['./idle-detection.idl', const srcs = ['./idle-detection.idl',
'/interfaces/dom.idl', '/interfaces/dom.idl',
'/interfaces/html.idl']; '/interfaces/html.idl'];
const [idle, dom, html] = await Promise.all( const [idle, dom, html] = await Promise.all(
srcs.map(i => fetch(i).then(r => r.text()))); srcs.map(i => fetch(i).then(r => r.text()))
);
const idl_array = new IdlArray(); const idl_array = new IdlArray();
idl_array.add_idls(idle); idl_array.add_idls(idle);
idl_array.add_dependency_idls(dom); idl_array.add_dependency_idls(dom);
idl_array.add_dependency_idls(html); idl_array.add_dependency_idls(html);
self.idle = await navigator.idle.query(); self.idle = new IdleDetector({threshold: 1});
let watcher = new EventWatcher(t, self.idle, ["change"]);
self.idle.start();
await watcher.wait_for("change");
idl_array.add_objects({ idl_array.add_objects({
IdleManager: ['navigator.idle'], IdleDetector: ['idle'],
IdleStatus: ['idle'],
IdleState: ['idle.state'] IdleState: ['idle.state']
}); });
if (self.Window) {
idl_array.add_objects({ Navigator: ['navigator'] });
} else {
idl_array.add_objects({ WorkerNavigator: ['navigator'] });
}
idl_array.test(); idl_array.test();
}, 'Test IDL implementation of Idle Detection API'); }, 'Test IDL implementation of Idle Detection API');

View file

@ -1,19 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<link rel="help" href="https://github.com/inexorabletash/idle-detection"> <link rel="help" href="https://github.com/samuelgoto/idle-detection">
<title>Tests the Idle Detection API</title> <title>Tests the Idle Detection API</title>
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script src="/gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script> <script src="/gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="/gen/mojo/public/mojom/base/string16.mojom.js"></script> <script src="/gen/mojo/public/mojom/base/string16.mojom.js"></script>
<script src="/gen/mojo/public/mojom/base/time.mojom.js"></script> <script src="/gen/mojo/public/mojom/base/time.mojom.js"></script>
<script src="/gen/third_party/blink/public/platform/modules/idle/idle_manager.mojom.js"></script> <script src="/gen/third_party/blink/public/mojom/idle/idle_manager.mojom.js"></script>
<script src="mock.js"></script> <script src="mock.js"></script>
<script> <script>
'use strict'; 'use strict';
promise_test(async t => { promise_test(async t => {
// Basic test that expects navigator.idle.query() to call internally // Basic test that expects start() to call internally
// addMonitor, which in turn will return an ACTIVE state. // addMonitor, which in turn return an ACTIVE state.
expect(addMonitor).andReturn((threshold, monitorPtr) => { expect(addMonitor).andReturn((threshold, monitorPtr) => {
return Promise.resolve({ return Promise.resolve({
state: { state: {
@ -23,36 +23,58 @@ promise_test(async t => {
}); });
}); });
let status = await navigator.idle.query({threshold: 10}); let detector = new IdleDetector({threshold: 10});
assert_equals(status.state.user, "active"); let watcher = new EventWatcher(t, detector, ["change"]);
assert_equals(status.state.screen, "locked");
await detector.start();
// Waits for the first event.
await watcher.wait_for("change");
assert_equals(detector.state.user, "active");
assert_equals(detector.state.screen, "locked");
detector.stop();
}, 'query()'); }, 'query()');
promise_test(async t => { promise_test(async t => {
// Verifies that an event is thrown when a change of state from IDLE to ACTIVE // Verifies that an event is thrown when a change of state from IDLE to ACTIVE
// is detected. // is detected.
expect(addMonitor).andReturn((threshold, monitorPtr) => { expect(addMonitor).andReturn((threshold, monitorPtr) => {
let first = Promise.resolve({
state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
t.step_timeout(() => { t.step_timeout(() => {
monitorPtr.update({ monitorPtr.update({
user: UserIdleState.IDLE, user: UserIdleState.IDLE,
screen: ScreenIdleState.UNLOCKED screen: ScreenIdleState.UNLOCKED
}); });
}, 0); }, 0);
return Promise.resolve({
state: { return first;
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
}); });
let monitor = await navigator.idle.query({threshold: 10}); let detector = new IdleDetector({threshold: 10});
await new EventWatcher(t, monitor, ["change"]).wait_for("change"); let watcher = new EventWatcher(t, detector, ["change"]);
assert_equals(monitor.state.user, "idle"); await detector.start();
assert_equals(monitor.state.screen, "unlocked");
// Wait for the initial state.
await watcher.wait_for("change");
// Wait for the first change in state.
await watcher.wait_for("change");
assert_equals(detector.state.user, "idle");
assert_equals(detector.state.screen, "unlocked");
detector.stop();
}, 'updates once'); }, 'updates once');
@ -60,6 +82,13 @@ promise_test(async t => {
// Simulates the user being active, going idle and then going back active // Simulates the user being active, going idle and then going back active
// again. // again.
expect(addMonitor).andReturn((threshold, monitorPtr) => { expect(addMonitor).andReturn((threshold, monitorPtr) => {
let first = Promise.resolve({
state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
// Updates the client once with the user idle. // Updates the client once with the user idle.
t.step_timeout(() => { t.step_timeout(() => {
monitorPtr.update({ monitorPtr.update({
@ -74,25 +103,27 @@ promise_test(async t => {
screen: ScreenIdleState.UNLOCKED screen: ScreenIdleState.UNLOCKED
}); });
}, 1); }, 1);
return Promise.resolve({ return first;
state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
}); });
let monitor = await navigator.idle.query({threshold: 10}); let detector = new IdleDetector({threshold: 10});
let watcher = new EventWatcher(t, monitor, ["change"]); let watcher = new EventWatcher(t, detector, ["change"]);
// waits for the first event. await detector.start();
// Waits for the initial state.
await watcher.wait_for("change"); await watcher.wait_for("change");
assert_equals(monitor.state.user, "idle");
// waits for the second event. // Waits for the first event.
await watcher.wait_for("change"); await watcher.wait_for("change");
assert_equals(monitor.state.user, "active"); assert_equals(detector.state.user, "idle");
// Waits for the second event.
await watcher.wait_for("change");
assert_equals(detector.state.user, "active");
detector.stop();
}, 'updates twice'); }, 'updates twice');
promise_test(async t => { promise_test(async t => {
@ -106,28 +137,140 @@ promise_test(async t => {
}); });
}); });
let monitor = await navigator.idle.query({threshold: 10}); let detector = new IdleDetector({threshold: 10});
assert_equals(monitor.state.screen, "locked"); let watcher = new EventWatcher(t, detector, ["change"]);
await detector.start();
// waits for the initial state.
await watcher.wait_for("change");
assert_equals(detector.state.screen, "locked");
detector.stop();
}, 'locked screen'); }, 'locked screen');
promise_test(async t => { promise_test(async t => {
// Simulates the service becoming unavailable.
expect(addMonitor).andReturn((threshold, monitorPtr) => { expect(addMonitor).andReturn((threshold, monitorPtr) => {
return new Promise((resolve, reject) => { return Promise.resolve({
// leave the renderer deliberately hanging by not resolve()-ing. state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.LOCKED
}
}); });
}); });
let error = new Promise((resolve, reject) => { let detector = new IdleDetector({threshold: 10});
navigator.idle.query({threshold: 10})
.then((e) => {reject("unexpected response :(")}) let event = new Promise((resolve, reject) => {
.catch((e) => {resolve(e.message)}); detector.onchange = resolve;
}); });
// simulates what happens when the service is unavailable. await detector.start();
close();
// Waits for the first event.
await event;
assert_equals(detector.state.user, "active");
assert_equals(detector.state.screen, "locked");
detector.stop();
}, 'IdleDetector.onchange');
promise_test(async t => {
expect(addMonitor).andReturn((threshold, monitorPtr) => {
return Promise.resolve({
state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
});
let detector = new IdleDetector({threshold: 10});
let watcher = new EventWatcher(t, detector, ["change"]);
// Calling start() multiple times should be safe.
await detector.start();
await detector.start();
await detector.start();
await detector.start();
// waits for the initial state.
await watcher.wait_for("change");
assert_equals(detector.state.user, "active");
assert_equals(detector.state.screen, "unlocked");
// Calling stop() multiple times should be safe.
detector.stop();
detector.stop();
detector.stop();
detector.stop();
}, 'Safe to call start() or stop() multiple times');
promise_test(async t => {
expect(addMonitor).andReturn((threshold, monitorPtr) => {
return Promise.resolve({
state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
});
let detector = new IdleDetector({threshold: 10});
// Calling stop() before start() is a no-op.
detector.stop();
let watcher = new EventWatcher(t, detector, ["change"]);
await detector.start();
// waits for the initial state.
await watcher.wait_for("change");
assert_equals(detector.state.user, "active");
assert_equals(detector.state.screen, "unlocked");
detector.stop();
}, 'Calling stop() after start() is a no-op');
promise_test(async t => {
expect(addMonitor).andReturn((threshold, monitorPtr) => {
return Promise.resolve({
state: {
user: UserIdleState.ACTIVE,
screen: ScreenIdleState.UNLOCKED
}
});
});
let detector = new IdleDetector({threshold: 10});
let watcher = new EventWatcher(t, detector, ["change"]);
await detector.start();
await watcher.wait_for("change");
detector.stop();
expect(addMonitor).andReturn((threshold, monitorPtr) => {
return Promise.resolve({
state: {
user: UserIdleState.IDLE,
screen: ScreenIdleState.LOCKED
}
});
});
// Restarting the monitor.
await detector.start();
await watcher.wait_for("change");
assert_equals(detector.state.user, "idle");
assert_equals(detector.state.screen, "locked");
detector.stop();
}, 'Calling start() after stop(): re-starting monitor.');
assert_equals(await error, "Idle detection not available");
}, "service unavailable");
</script> </script>

View file

@ -8,7 +8,9 @@ if (typeof postMessage === 'function') {
workerType = 'dedicated'; workerType = 'dedicated';
} }
promise_test(() => navigator.idle.query(), promise_test(async () => {
await new IdleDetector().start()
},
`Inherited header feature policy allows ${workerType} workers.`) `Inherited header feature policy allows ${workerType} workers.`)
done(); done();

View file

@ -9,9 +9,14 @@ if (typeof postMessage === 'function') {
workerType = 'dedicated'; workerType = 'dedicated';
} }
promise_test(() => navigator.idle.query().then( promise_test(async () => {
() => assert_unreached('expected promise to reject with SecurityError'), try {
error => assert_equals(error.name, 'SecurityError')), await new IdleDetector().start();
`Inherited ${header} disallows ${workerType} workers.`); assert_unreached('expected start() to throw with SecurityError');
} catch (error) {
assert_equals(error.name, 'SecurityError');
}
},
`Inherited ${header} disallows ${workerType} workers.`);
done(); done();

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/test-helper.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=B",
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=C": "../resources/log.js?pipe=sub&name=D"
}
}
</script>
<script>
promise_test(t => {
return promise_rejects(t, TypeError(),
import('../resources/log.js?pipe=sub&name=A'),
'Dynamic import should fail');
}, 'The URL after mapping violates CSP (but not the URL before mapping)');
promise_test(t => {
return import('https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=C')
.then(() => assert_array_equals(log, ["log:D"]));
}, 'The URL before mapping violates CSP (but not the URL after mapping)');
</script>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/test-helper.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=B",
"https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=C": "../resources/log.js?pipe=sub&name=D"
}
}
</script>
<script type="module">
import '../resources/log.js?pipe=sub&name=A';
</script>
<script type="module">
test(t => {
assert_array_equals(log, []);
}, 'The URL after mapping violates CSP (but not the URL before mapping)');
</script>
<script type="module">
import 'https://{{domains[www1]}}:{{ports[https][0]}}/import-maps/resources/log.js?pipe=sub&name=C';
</script>
<script type="module">
test(t => {
assert_array_equals(log, ["log:D"]);
}, 'The URL before mapping violates CSP (but not the URL after mapping)');
</script>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-wrong9e+pZbSYIkpB8BIE0Hs7yHajJDiX5mnT/wrong=' 'sha256-RAsyam34o4peVe9sCebtaZWRVhqAhudem+NlcnP2Kp8=';">
<!-- 'sha256-P5xqp9e+pZbSYIkpB8BIE0Hs7yHajJDiX5mnT/1PO1I=' -->
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
<!-- 'sha256-RAsyam34o4peVe9sCebtaZWRVhqAhudem+NlcnP2Kp8=' -->
<script>
const log = [];
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
},
'Importmap should not be accepted due to wrong hash');
</script>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-P5xqp9e+pZbSYIkpB8BIE0Hs7yHajJDiX5mnT/1PO1I=' 'sha256-Ciqph+wQDoB2suzqZVHOD0iw99WqaTUwZXRl7ATzBxc=';">
<!-- 'sha256-P5xqp9e+pZbSYIkpB8BIE0Hs7yHajJDiX5mnT/1PO1I=' -->
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
<!-- 'sha256-Ciqph+wQDoB2suzqZVHOD0iw99WqaTUwZXRl7ATzBxc=' -->
<script>
const log = [];
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:B"]))
},
'Importmap should be accepted due to hash');
</script>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc';">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
<script type="importmap" nonce="wrong">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
<script nonce="abc">
const log = [];
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:A"]))
},
'Importmap should be rejected due to nonce');
</script>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc';">
<script type="importmap" nonce="abc">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
<script nonce="abc">
const log = [];
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:B"]))
},
'Importmap should be accepted according to its correct nonce');
</script>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';">
<script type="importmap">
{
"imports": {
"../resources/log.js?pipe=sub&name=A": "../resources/log.js?pipe=sub&name=B"
}
}
</script>
<script>
const log = [];
promise_test(() => {
return import("../resources/log.js?pipe=sub&name=A")
.then(() => assert_array_equals(log, ["log:B"]))
},
'Importmap should be accepted due to unsafe-inline');
</script>

View file

@ -0,0 +1,9 @@
[actionsWithKeyPressed.html]
expected:
if product == "safari" or product == "firefox": ERROR
[TestDriver actions: actions with key pressed]
expected:
if product == "chrome": FAIL

View file

@ -0,0 +1,4 @@
[generate_test_report.html]
expected:
if product == "firefox": ERROR
if product == "safari": ERROR

View file

@ -0,0 +1,67 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>TestDriver actions: actions with key pressed</title>
<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>
<style>
div#test1, div#test2 {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background-color: blue;
}
div#test2 {
position: fixed;
top: 100px;
left: 0;
width: 100px;
height: 100px;
background-color: green;
}
</style>
<div id="test1">
</div>
<div id="test2">
</div>
<script>
let keys = [];
async_test(t => {
let test1 = document.getElementById("test1");
let test2 = document.getElementById("test2");
document.getElementById("test1").addEventListener("click",
e => {keys.push(e.getModifierState("Control"))});
document.getElementById("test2").addEventListener("click",
e => {keys.push(e.getModifierState("Control"))});
let actions = new test_driver.Actions()
.keyDown("\uE009")
.addTick()
.pointerMove(0, 0, {origin: test1})
.pointerDown()
.pointerUp()
.pointerMove(0, 0, {origin: test2})
.pointerDown()
.pointerUp()
.addTick()
.keyUp("\uE009")
.addTick()
.pointerMove(0, 0, {origin: test1})
.pointerDown()
.pointerUp();
actions.send()
.then(t.step_func_done(() => assert_array_equals(keys, [true, true, false])))
.catch(e => t.step_func(() => assert_unreached("Actions sequence failed " + e)));
});
</script>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>TestDriver generate_test_report method</title>
<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>
async_test(t => {
test_driver
.generate_test_report("Test message.")
.then(() => t.done())
.catch(t.unreached_func("generate_test_report failed"));
});
</script>

View file

@ -1,30 +0,0 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into reffy-reports
// (https://github.com/tidoust/reffy-reports)
// Source: Resize Observer 1 (https://wicg.github.io/ResizeObserver/)
[Constructor(ResizeObserverCallback callback),
Exposed=Window]
interface ResizeObserver {
void observe(Element target);
void unobserve(Element target);
void disconnect();
};
callback ResizeObserverCallback = void (sequence<ResizeObserverEntry> entries, ResizeObserver observer);
[Constructor(Element target)
]
interface ResizeObserverEntry {
readonly attribute Element target;
readonly attribute DOMRectReadOnly contentRect;
};
[Constructor(Element target)
]
interface ResizeObservation {
readonly attribute Element target;
readonly attribute float broadcastWidth;
readonly attribute float broadcastHeight;
boolean isActive();
};

View file

@ -25,7 +25,7 @@ interface NamedFlow : EventTarget {
sequence<Region> getRegionsByContent(Node node); sequence<Region> getRegionsByContent(Node node);
}; };
interface Region { interface mixin Region {
readonly attribute CSSOMString regionOverset; readonly attribute CSSOMString regionOverset;
sequence<Range>? getRegionFlowRanges(); sequence<Range>? getRegionFlowRanges();
}; };

View file

@ -37,7 +37,7 @@ partial interface CSSStyleRule {
[SameObject] readonly attribute StylePropertyMap styleMap; [SameObject] readonly attribute StylePropertyMap styleMap;
}; };
partial interface ElementCSSInlineStyle { partial interface mixin ElementCSSInlineStyle {
[SameObject] readonly attribute StylePropertyMap attributeStyleMap; [SameObject] readonly attribute StylePropertyMap attributeStyleMap;
}; };

View file

@ -59,9 +59,11 @@ partial dictionary MediaTrackSupportedConstraints {
boolean brightness = true; boolean brightness = true;
boolean contrast = true; boolean contrast = true;
boolean pan = true;
boolean saturation = true; boolean saturation = true;
boolean sharpness = true; boolean sharpness = true;
boolean focusDistance = true; boolean focusDistance = true;
boolean tilt = true;
boolean zoom = true; boolean zoom = true;
boolean torch = true; boolean torch = true;
}; };
@ -82,6 +84,8 @@ partial dictionary MediaTrackCapabilities {
MediaSettingsRange sharpness; MediaSettingsRange sharpness;
MediaSettingsRange focusDistance; MediaSettingsRange focusDistance;
MediaSettingsRange pan;
MediaSettingsRange tilt;
MediaSettingsRange zoom; MediaSettingsRange zoom;
boolean torch; boolean torch;
@ -104,6 +108,8 @@ partial dictionary MediaTrackConstraintSet {
ConstrainDouble sharpness; ConstrainDouble sharpness;
ConstrainDouble focusDistance; ConstrainDouble focusDistance;
ConstrainDouble pan;
ConstrainDouble tilt;
ConstrainDouble zoom; ConstrainDouble zoom;
ConstrainBoolean torch; ConstrainBoolean torch;
@ -126,6 +132,8 @@ partial dictionary MediaTrackSettings {
double sharpness; double sharpness;
double focusDistance; double focusDistance;
double pan;
double tilt;
double zoom; double zoom;
boolean torch; boolean torch;

View file

@ -0,0 +1,43 @@
// GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into reffy-reports
// (https://github.com/tidoust/reffy-reports)
// Source: Resize Observer (https://drafts.csswg.org/resize-observer/)
enum ResizeObserverBoxOptions {
"border-box", "content-box"
};
dictionary ResizeObserverOptions {
ResizeObserverBoxOptions box = "content-box";
};
[Exposed=(Window),
Constructor(ResizeObserverCallback callback)]
interface ResizeObserver {
void observe(Element target, optional ResizeObserverOptions options);
void unobserve(Element target);
void disconnect();
};
callback ResizeObserverCallback = void (sequence<ResizeObserverEntry> entries, ResizeObserver observer);
[Exposed=Window, Constructor(Element target)]
interface ResizeObserverEntry {
readonly attribute Element target;
readonly attribute DOMRectReadOnly contentRect;
readonly attribute ResizeObserverSize borderBoxSize;
readonly attribute ResizeObserverSize contentBoxSize;
};
interface ResizeObserverSize {
readonly attribute unrestricted double inlineSize;
readonly attribute unrestricted double blockSize;
};
[Constructor(Element target)
]
interface ResizeObservation {
readonly attribute Element target;
readonly attribute ResizeObserverBoxOptions observedBox;
readonly attribute ResizeObserverSize lastReportedSize;
};

View file

@ -10,15 +10,15 @@ dictionary PerformanceMarkOptions {
dictionary PerformanceMeasureOptions { dictionary PerformanceMeasureOptions {
any detail = null; any detail = null;
(DOMString or DOMHighResTimeStamp) startTime; (DOMString or DOMHighResTimeStamp) start;
DOMHighResTimeStamp duration; DOMHighResTimeStamp duration;
(DOMString or DOMHighResTimeStamp) endTime; (DOMString or DOMHighResTimeStamp) end;
}; };
partial interface Performance { partial interface Performance {
PerformanceMark mark(DOMString markName, optional PerformanceMarkOptions markOptions); PerformanceMark mark(DOMString markName, optional PerformanceMarkOptions markOptions);
void clearMarks(optional DOMString markName); void clearMarks(optional DOMString markName);
PerformanceMeasure measure(DOMString measureName, optional (DOMString or PerformanceMeasureOptions) startOrMeasureOptions, optional DOMString endMark); PerformanceMeasure measure(DOMString measureName, optional (DOMString or PerformanceMeasureOptions)? startOrMeasureOptions, optional DOMString endMark);
void clearMeasures(optional DOMString measureName); void clearMeasures(optional DOMString measureName);
}; };

View file

@ -1,7 +1,7 @@
// GENERATED CONTENT - DO NOT EDIT // GENERATED CONTENT - DO NOT EDIT
// Content was automatically extracted by Reffy into reffy-reports // Content was automatically extracted by Reffy into reffy-reports
// (https://github.com/tidoust/reffy-reports) // (https://github.com/tidoust/reffy-reports)
// Source: Web Authentication: An API for accessing Public Key Credentials - Level 1 (https://w3c.github.io/webauthn/) // Source: Web Authentication: An API for accessing Public Key Credentials - Level 2 (https://w3c.github.io/webauthn/)
[SecureContext, Exposed=Window] [SecureContext, Exposed=Window]
interface PublicKeyCredential : Credential { interface PublicKeyCredential : Credential {
@ -30,6 +30,7 @@ interface AuthenticatorResponse {
[SecureContext, Exposed=Window] [SecureContext, Exposed=Window]
interface AuthenticatorAttestationResponse : AuthenticatorResponse { interface AuthenticatorAttestationResponse : AuthenticatorResponse {
[SameObject] readonly attribute ArrayBuffer attestationObject; [SameObject] readonly attribute ArrayBuffer attestationObject;
sequence<AuthenticatorTransport> getTransports();
}; };
[SecureContext, Exposed=Window] [SecureContext, Exposed=Window]

View file

@ -20,7 +20,8 @@ enum RTCStatsType {
"candidate-pair", "candidate-pair",
"local-candidate", "local-candidate",
"remote-candidate", "remote-candidate",
"certificate" "certificate",
"stunserverconnection"
}; };
dictionary RTCRtpStreamStats : RTCStats { dictionary RTCRtpStreamStats : RTCStats {
@ -28,11 +29,6 @@ dictionary RTCRtpStreamStats : RTCStats {
DOMString kind; DOMString kind;
DOMString transportId; DOMString transportId;
DOMString codecId; DOMString codecId;
unsigned long firCount;
unsigned long pliCount;
unsigned long nackCount;
unsigned long sliCount;
unsigned long long qpSum;
}; };
dictionary RTCCodecStats : RTCStats { dictionary RTCCodecStats : RTCStats {
@ -72,13 +68,19 @@ dictionary RTCInboundRtpStreamStats : RTCReceivedRtpStreamStats {
DOMString receiverId; DOMString receiverId;
DOMString remoteId; DOMString remoteId;
unsigned long framesDecoded; unsigned long framesDecoded;
unsigned long long qpSum;
DOMHighResTimeStamp lastPacketReceivedTimestamp; DOMHighResTimeStamp lastPacketReceivedTimestamp;
double averageRtcpInterval; double averageRtcpInterval;
unsigned long fecPacketsReceived; unsigned long fecPacketsReceived;
unsigned long fecPacketsDiscarded;
unsigned long long bytesReceived; unsigned long long bytesReceived;
unsigned long packetsFailedDecryption; unsigned long packetsFailedDecryption;
unsigned long packetsDuplicated; unsigned long packetsDuplicated;
record<USVString, unsigned long> perDscpPacketsReceived; record<USVString, unsigned long> perDscpPacketsReceived;
unsigned long nackCount;
unsigned long firCount;
unsigned long pliCount;
unsigned long sliCount;
}; };
dictionary RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats { dictionary RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats {
@ -102,11 +104,16 @@ dictionary RTCOutboundRtpStreamStats : RTCSentRtpStreamStats {
DOMHighResTimeStamp lastPacketSentTimestamp; DOMHighResTimeStamp lastPacketSentTimestamp;
double targetBitrate; double targetBitrate;
unsigned long framesEncoded; unsigned long framesEncoded;
unsigned long long qpSum;
double totalEncodeTime; double totalEncodeTime;
double averageRtcpInterval; double averageRtcpInterval;
RTCQualityLimitationReason qualityLimitationReason; RTCQualityLimitationReason qualityLimitationReason;
record<DOMString, double> qualityLimitationDurations; record<DOMString, double> qualityLimitationDurations;
record<USVString, unsigned long> perDscpPacketsSent; record<USVString, unsigned long> perDscpPacketsSent;
unsigned long nackCount;
unsigned long firCount;
unsigned long pliCount;
unsigned long sliCount;
}; };
enum RTCQualityLimitationReason { enum RTCQualityLimitationReason {
@ -198,6 +205,7 @@ dictionary RTCAudioReceiverStats : RTCAudioHandlerStats {
unsigned long long jitterBufferEmittedCount; unsigned long long jitterBufferEmittedCount;
unsigned long long totalSamplesReceived; unsigned long long totalSamplesReceived;
unsigned long long concealedSamples; unsigned long long concealedSamples;
unsigned long long silentConcealedSamples;
unsigned long long concealmentEvents; unsigned long long concealmentEvents;
}; };
@ -298,6 +306,16 @@ dictionary RTCCertificateStats : RTCStats {
DOMString issuerCertificateId; DOMString issuerCertificateId;
}; };
dictionary RTCStunServerConnectionStats : RTCStats {
DOMString url;
long port;
DOMString protocol;
RTCNetworkType networkType;
unsigned long totalRequestsSent;
unsigned long totalResponsesReceived;
double totalRoundTripTime;
};
partial dictionary RTCIceCandidateStats { partial dictionary RTCIceCandidateStats {
boolean isRemote; boolean isRemote;
}; };
@ -312,3 +330,7 @@ partial dictionary RTCRtpStreamStats {
DOMString mediaType; DOMString mediaType;
double averageRTCPInterval; double averageRTCPInterval;
}; };
partial dictionary RTCInboundRtpStreamStats {
double fractionLost;
};

View file

@ -25,7 +25,6 @@ enum XREnvironmentBlendMode {
[SecureContext, Exposed=Window] interface XRSession : EventTarget { [SecureContext, Exposed=Window] interface XRSession : EventTarget {
// Attributes // Attributes
readonly attribute XRSessionMode mode; readonly attribute XRSessionMode mode;
readonly attribute XRPresentationContext? outputContext;
readonly attribute XREnvironmentBlendMode environmentBlendMode; readonly attribute XREnvironmentBlendMode environmentBlendMode;
readonly attribute XRRenderState renderState; readonly attribute XRRenderState renderState;
readonly attribute XRSpace viewerSpace; readonly attribute XRSpace viewerSpace;
@ -59,19 +58,20 @@ enum XRSessionMode {
dictionary XRSessionCreationOptions { dictionary XRSessionCreationOptions {
XRSessionMode mode = "inline"; XRSessionMode mode = "inline";
XRPresentationContext? outputContext = null;
}; };
dictionary XRRenderStateInit { dictionary XRRenderStateInit {
double depthNear; double depthNear;
double depthFar; double depthFar;
XRLayer? baseLayer; XRLayer? baseLayer;
XRPresentationContext? outputContext;
}; };
[SecureContext, Exposed=Window] interface XRRenderState { [SecureContext, Exposed=Window] interface XRRenderState {
readonly attribute double depthNear; readonly attribute double depthNear;
readonly attribute double depthFar; readonly attribute double depthFar;
readonly attribute XRLayer? baseLayer; readonly attribute XRLayer? baseLayer;
readonly attribute XRPresentationContext? outputContext;
}; };
callback XRFrameRequestCallback = void (DOMHighResTimeStamp time, XRFrame frame); callback XRFrameRequestCallback = void (DOMHighResTimeStamp time, XRFrame frame);

View file

@ -804,4 +804,5 @@ CSS-COLLIDING-REF-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/cont
# Signed Exchange files have hard-coded URLs in the certUrl field # Signed Exchange files have hard-coded URLs in the certUrl field
WEB-PLATFORM.TEST:signed-exchange/resources/*.sxg WEB-PLATFORM.TEST:signed-exchange/resources/*.sxg
WEB-PLATFORM.TEST:signed-exchange/appcache/resources/*.sxg
WEB-PLATFORM.TEST:signed-exchange/resources/generate-test-sxgs.sh WEB-PLATFORM.TEST:signed-exchange/resources/generate-test-sxgs.sh

View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<title>&lt;mo&gt; paint lspace rspace</title>
<meta charset="utf-8">
</head>
<body>
<h1>LTR case</h1>
<p>The test passes if the arrow has a leading space of 100px, which is as wide as the black block to the left,
and a trailing space of 200px, which is as wide as the black block to the right.</p>
<math>
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
<mspace width="100px"></mspace>
<mo lspace="0px" rspace="0px"></mo>
<mspace width="200px"></mspace>
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 150px, which is as wide as the black block to the left,
and a trailing space of 150px, which is as wide as the black block to the right.</p>
<math>
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
<mspace width="150px"></mspace>
<mo lspace="0px" rspace="0px"></mo>
<mspace width="150px"></mspace>
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 200px, which is as wide as the black block to the left,
and a trailing space of 100px, which is as wide as the black block to the right.</p>
<math>
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
<mspace width="200px"></mspace>
<mo lspace="0px" rspace="0px"></mo>
<mspace width="100px"></mspace>
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<h1>RTL case</h1>
<p>The test passes if the arrow has a leading space of 100px, which is as wide as the black block to the right,
and a trailing space of 200px, which is as wide as the black block to the left.</p>
<math dir="rtl">
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
<mspace width="100px"></mspace>
<mo lspace="0px" rspace="0px"></mo>
<mspace width="200px"></mspace>
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 150px, which is as wide as the black block to the right,
and a trailing space of 150px, which is as wide as the black block to the left.</p>
<math dir="rtl">
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
<mspace width="150px"></mspace>
<mo lspace="0px" rspace="0px"></mo>
<mspace width="150px"></mspace>
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 200px, which is as wide as the black block to the right,
and a trailing space of 100px, which is as wide as the black block to the left.</p>
<math dir="rtl">
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
<mspace width="200px"></mspace>
<mo lspace="0px" rspace="0px"></mo>
<mspace width="100px"></mspace>
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
</math>
</body>
</html>

View file

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>&lt;mo&gt; paint lspace rspace</title>
<link rel="help" href="https://mathml-refresh.github.io/mathml-core/#tokenmo">
<meta name="assert" content="Verifies values for lspace and rspace for element mo in LTR and RTL modes.">
<link rel="match" href="mo-paint-lspace-rspace-ref.html">
</head>
<body>
<h1>LTR case</h1>
<p>The test passes if the arrow has a leading space of 100px, which is as wide as the black block to the left,
and a trailing space of 200px, which is as wide as the black block to the right.</p>
<mth>
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
<mo lspace="100px" rspace="200px"></mo>
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 150px, which is as wide as the black block to the left,
and a trailing space of 150px, which is as wide as the black block to the right.</p>
<math>
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
<mo lspace="150px" rspace="150px"></mo>
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 200px, which is as wide as the black block to the left,
and a trailing space of 100px, which is as wide as the black block to the right.</p>
<math>
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
<mo lspace="200px" rspace="100px"></mo>
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<h1>RTL case</h1>
<p>The test passes if the arrow has a leading space of 100px, which is as wide as the black block to the right,
and a trailing space of 200px, which is as wide as the black block to the left.</p>
<math dir="rtl">
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
<mo lspace="100px" rspace="200px"></mo>
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 150px, which is as wide as the black block to the right,
and a trailing space of 150px, which is as wide as the black block to the left.</p>
<math dir="rtl">
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
<mo lspace="150px" rspace="150px"></mo>
<mspace width="150px" height="10px" depth="10px" style="background: black"></mspace>
</math>
<p>The test passes if the arrow has a leading space of 200px, which is as wide as the black block to the right,
and a trailing space of 100px, which is as wide as the black block to the left.</p>
<math dir="rtl">
<mspace width="200px" height="10px" depth="10px" style="background: black"></mspace>
<mo lspace="200px" rspace="100px"></mo>
<mspace width="100px" height="10px" depth="10px" style="background: black"></mspace>
</math>
</body>
</html>

View file

@ -0,0 +1,162 @@
<!DOCTYPE html>
<!-- Copyright © 2019 Igalia. -->
<html>
<head>
<title>Frame checking test for MSE playback in presence of a reappend.</title>
<meta name="timeout" content="long">
<meta name="charset" content="UTF-8">
<link rel="author" title="Alicia Boya García" href="mailto:aboya@igalia.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="mediasource-util.js"></script>
</head>
<body>
<div id="log"></div>
<canvas id="test-canvas"></canvas>
<script>
function waitForEventPromise(element, event) {
return new Promise(resolve => {
function handler(ev) {
element.removeEventListener(event, handler);
resolve(ev);
}
element.addEventListener(event, handler);
});
}
function appendBufferPromise(sourceBuffer, data) {
sourceBuffer.appendBuffer(data);
return waitForEventPromise(sourceBuffer, "update");
}
function waitForPlayerToReachTimePromise(mediaElement, time) {
return new Promise(resolve => {
function timeupdate() {
if (mediaElement.currentTime < time)
return;
mediaElement.removeEventListener("timeupdate", timeupdate);
resolve();
}
mediaElement.addEventListener("timeupdate", timeupdate);
});
}
function readPixel(imageData, x, y) {
return {
r: imageData.data[4 * (y * imageData.width + x)],
g: imageData.data[1 + 4 * (y * imageData.width + x)],
b: imageData.data[2 + 4 * (y * imageData.width + x)],
a: imageData.data[3 + 4 * (y * imageData.width + x)],
};
}
function isPixelLit(pixel) {
const threshold = 200; // out of 255
return pixel.r >= threshold && pixel.g >= threshold && pixel.b >= threshold;
}
// The test video has a few gray boxes. Each box interval (1 second) a new box is lit white and a different note
// is played. This test makes sure the right number of lit boxes and the right note are played at the right time.
const totalBoxes = 7;
const boxInterval = 1; // seconds
const videoWidth = 320;
const videoHeight = 240;
const boxesY = 210;
const boxSide = 20;
const boxMargin = 20;
const allBoxesWidth = totalBoxes * boxSide + (totalBoxes - 1) * boxMargin;
const boxesX = new Array(totalBoxes).fill(undefined)
.map((_, i) => (videoWidth - allBoxesWidth) / 2 + boxSide / 2 + i * (boxSide + boxMargin));
// Sound starts playing A4 (440 Hz) and goes one chromatic note up with every box lit.
// By comparing the player position to both the amount of boxes lit and the note played we can detect A/V
// synchronization issues automatically.
const noteFrequencies = new Array(1 + totalBoxes).fill(undefined)
.map((_, i) => 440 * Math.pow(Math.pow(2, 1 / 12), i));
// We also check the first second [0, 1) where no boxes are lit, therefore we start counting at -1 to do the check
// for zero lit boxes.
let boxesLitSoFar = -1;
mediasource_test(async function (test, mediaElement, mediaSource) {
const canvas = document.getElementById("test-canvas");
const canvasCtx = canvas.getContext("2d");
canvas.width = videoWidth;
canvas.height = videoHeight;
const videoData = await (await fetch("mp4/test-boxes-video.mp4")).arrayBuffer();
const audioData = (await (await fetch("mp4/test-boxes-audio.mp4")).arrayBuffer());
const videoSb = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.4d401f"');
const audioSb = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
mediaElement.addEventListener('error', test.unreached_func("Unexpected event 'error'"));
mediaElement.addEventListener('ended', onEnded);
mediaElement.addEventListener('timeupdate', onTimeUpdate);
await appendBufferPromise(videoSb, videoData);
await appendBufferPromise(audioSb, audioData);
mediaElement.play();
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
source = audioCtx.createMediaElementSource(mediaElement);
analyser = audioCtx.createAnalyser();
analyser.fftSize = 8192;
source.connect(analyser);
analyser.connect(audioCtx.destination);
const freqDomainArray = new Float32Array(analyser.frequencyBinCount);
function checkNoteBeingPlayed() {
const expectedNoteFrequency = noteFrequencies[boxesLitSoFar];
analyser.getFloatFrequencyData(freqDomainArray);
const maxBin = freqDomainArray.reduce((prev, curValue, i) =>
curValue > prev.value ? {index: i, value: curValue} : prev,
{index: -1, value: -Infinity});
const binFrequencyWidth = audioCtx.sampleRate / analyser.fftSize;
const binFreq = maxBin.index * binFrequencyWidth;
assert_true(Math.abs(expectedNoteFrequency - binFreq) <= binFrequencyWidth,
`The note being played matches the expected one (boxes lit: ${boxesLitSoFar}, ${expectedNoteFrequency.toFixed(1)} Hz)` +
`, found ~${binFreq.toFixed(1)} Hz`);
}
function countLitBoxesInCurrentVideoFrame() {
canvasCtx.drawImage(mediaElement, 0, 0);
const imageData = canvasCtx.getImageData(0, 0, videoWidth, videoHeight);
const lights = boxesX.map(boxX => isPixelLit(readPixel(imageData, boxX, boxesY)));
let litBoxes = 0;
for (let i = 0; i < lights.length; i++) {
if (lights[i])
litBoxes++;
}
for (let i = litBoxes; i < lights.length; i++) {
assert_false(lights[i], 'After the first non-lit box, all boxes must non-lit');
}
return litBoxes;
}
await waitForPlayerToReachTimePromise(mediaElement, 2.5);
await appendBufferPromise(audioSb, audioData);
mediaSource.endOfStream();
function onTimeUpdate() {
const graceTime = 0.5;
if (mediaElement.currentTime >= (1 + boxesLitSoFar) * boxInterval + graceTime && boxesLitSoFar < totalBoxes) {
assert_equals(countLitBoxesInCurrentVideoFrame(), boxesLitSoFar + 1, "Num of lit boxes:");
boxesLitSoFar++;
checkNoteBeingPlayed();
}
}
function onEnded() {
assert_equals(boxesLitSoFar, totalBoxes, "Boxes lit at video ended event");
test.done();
}
}, "Test the expected frames are played at the expected times, even in presence of reappends");
</script>
</body>
</html>

View file

@ -0,0 +1,146 @@
<!DOCTYPE html>
<!-- Copyright © 2019 Igalia. -->
<html>
<head>
<title>Frame checking test for simple MSE playback.</title>
<meta name="timeout" content="long">
<meta name="charset" content="UTF-8">
<link rel="author" title="Alicia Boya García" href="mailto:aboya@igalia.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="mediasource-util.js"></script>
</head>
<body>
<div id="log"></div>
<canvas id="test-canvas"></canvas>
<script>
function waitForEventPromise(element, event) {
return new Promise(resolve => {
function handler(ev) {
element.removeEventListener(event, handler);
resolve(ev);
}
element.addEventListener(event, handler);
});
}
function appendBufferPromise(sourceBuffer, data) {
sourceBuffer.appendBuffer(data);
return waitForEventPromise(sourceBuffer, "update");
}
function readPixel(imageData, x, y) {
return {
r: imageData.data[4 * (y * imageData.width + x)],
g: imageData.data[1 + 4 * (y * imageData.width + x)],
b: imageData.data[2 + 4 * (y * imageData.width + x)],
a: imageData.data[3 + 4 * (y * imageData.width + x)],
};
}
function isPixelLit(pixel) {
const threshold = 200; // out of 255
return pixel.r >= threshold && pixel.g >= threshold && pixel.b >= threshold;
}
// The test video has a few gray boxes. Each box interval (1 second) a new box is lit white and a different note
// is played. This test makes sure the right number of lit boxes and the right note are played at the right time.
const totalBoxes = 7;
const boxInterval = 1; // seconds
const videoWidth = 320;
const videoHeight = 240;
const boxesY = 210;
const boxSide = 20;
const boxMargin = 20;
const allBoxesWidth = totalBoxes * boxSide + (totalBoxes - 1) * boxMargin;
const boxesX = new Array(totalBoxes).fill(undefined)
.map((_, i) => (videoWidth - allBoxesWidth) / 2 + boxSide / 2 + i * (boxSide + boxMargin));
// Sound starts playing A4 (440 Hz) and goes one chromatic note up with every box lit.
// By comparing the player position to both the amount of boxes lit and the note played we can detect A/V
// synchronization issues automatically.
const noteFrequencies = new Array(1 + totalBoxes).fill(undefined)
.map((_, i) => 440 * Math.pow(Math.pow(2, 1 / 12), i));
// We also check the first second [0, 1) where no boxes are lit, therefore we start counting at -1 to do the check
// for zero lit boxes.
let boxesLitSoFar = -1;
mediasource_test(async function (test, mediaElement, mediaSource) {
const canvas = document.getElementById("test-canvas");
const canvasCtx = canvas.getContext("2d");
canvas.width = videoWidth;
canvas.height = videoHeight;
const videoData = await (await fetch("mp4/test-boxes-video.mp4")).arrayBuffer();
const audioData = (await (await fetch("mp4/test-boxes-audio.mp4")).arrayBuffer());
const videoSb = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.4d401f"');
const audioSb = mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
mediaElement.addEventListener('error', test.unreached_func("Unexpected event 'error'"));
mediaElement.addEventListener('ended', onEnded);
mediaElement.addEventListener('timeupdate', onTimeUpdate);
await appendBufferPromise(videoSb, videoData);
await appendBufferPromise(audioSb, audioData);
mediaSource.endOfStream();
mediaElement.play();
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const source = audioCtx.createMediaElementSource(mediaElement);
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 8192;
source.connect(analyser);
analyser.connect(audioCtx.destination);
const freqDomainArray = new Float32Array(analyser.frequencyBinCount);
function checkNoteBeingPlayed() {
const expectedNoteFrequency = noteFrequencies[boxesLitSoFar];
analyser.getFloatFrequencyData(freqDomainArray);
const maxBin = freqDomainArray.reduce((prev, curValue, i) =>
curValue > prev.value ? {index: i, value: curValue} : prev,
{index: -1, value: -Infinity});
const binFrequencyWidth = audioCtx.sampleRate / analyser.fftSize;
const binFreq = maxBin.index * binFrequencyWidth;
assert_true(Math.abs(expectedNoteFrequency - binFreq) <= binFrequencyWidth,
`The note being played matches the expected one (boxes lit: ${boxesLitSoFar}, ${expectedNoteFrequency.toFixed(1)} Hz)` +
`, found ~${binFreq.toFixed(1)} Hz`);
}
function countLitBoxesInCurrentVideoFrame() {
canvasCtx.drawImage(mediaElement, 0, 0);
const imageData = canvasCtx.getImageData(0, 0, videoWidth, videoHeight);
const lights = boxesX.map(boxX => isPixelLit(readPixel(imageData, boxX, boxesY)));
let litBoxes = 0;
for (let i = 0; i < lights.length; i++) {
if (lights[i])
litBoxes++;
}
for (let i = litBoxes; i < lights.length; i++) {
assert_false(lights[i], 'After the first non-lit box, all boxes must non-lit');
}
return litBoxes;
}
function onTimeUpdate() {
const graceTime = 0.5;
if (mediaElement.currentTime >= (1 + boxesLitSoFar) * boxInterval + graceTime && boxesLitSoFar < totalBoxes) {
assert_equals(countLitBoxesInCurrentVideoFrame(), boxesLitSoFar + 1, "Num of lit boxes:");
boxesLitSoFar++;
checkNoteBeingPlayed();
}
}
function onEnded() {
assert_equals(boxesLitSoFar, totalBoxes, "Boxes lit at video ended event");
test.done();
}
}, "Test the expected frames are played at the expected times");
</script>
</body>
</html>

View file

@ -24,220 +24,28 @@
let advanced_constraints_depth = [{ let advanced_constraints_depth = [{
videoKind: "depth", videoKind: "depth",
focalLengthX: 0.5,
focalLengthY: 0.5,
principalPointX: 0.1,
principalPointY: 0.1,
deprojectionDistortionCoefficients: true,
projectionDistortionCoefficients: true,
depthNear: 0.5,
depthFar: 1,
depthToVideoTransform: true
}]; }];
let advanced_constraints_color = [{ let advanced_constraints_color = [{
videoKind: "color", videoKind: "color",
focalLengthX: 0.5, }];
focalLengthY: 0.5,
principalPointX: 0.1,
principalPointY: 0.1,
deprojectionDistortionCoefficients: true,
projectionDistortionCoefficients: true
}];
/*
partial dictionary MediaTrackCapabilities {
// Apply to both depth stream track and color stream track:
DOMString videoKind;
(double or DoubleRange) focalLengthX;
(double or DoubleRange) focalLengthY;
(double or DoubleRange) principalPointX;
(double or DoubleRange) principalPointY;
boolean deprojectionDistortionCoefficients;
boolean projectionDistortionCoefficients;
// Apply to depth stream track:
(double or DoubleRange) depthNear;
(double or DoubleRange) depthFar;
boolean depthToVideoTransform;
};
dictionary DoubleRange {
double max;
double min;
};
*/
function validateMediaTrackCapabilities(capabilities, type) { function validateMediaTrackCapabilities(capabilities, type) {
assert_string_field(capabilities, 'videoKind'); assert_string_field(capabilities, 'videoKind');
assert_number_or_number_range_field(capabilities, 'focalLengthX');
assert_number_or_number_range_field(capabilities, 'focalLengthY');
assert_number_or_number_range_field(capabilities, 'principalPointX');
assert_number_or_number_range_field(capabilities, 'principalPointY');
assert_boolean_field(capabilities, 'deprojectionDistortionCoefficients');
assert_boolean_field(capabilities, 'projectionDistortionCoefficients');
if (type == "depth") {
assert_number_or_number_range_field(capabilities, 'depthNear');
assert_number_or_number_range_field(capabilities, 'depthFar');
assert_boolean_field(capabilities, 'depthToVideoTransform');
}
} }
/*
partial dictionary MediaTrackConstraintSet {
// Apply to both depth stream track and color stream track:
ConstrainDOMString videoKind;
ConstrainDouble focalLengthX;
ConstrainDouble focalLengthY;
ConstrainDouble principalPointX;
ConstrainDouble principalPointY;
ConstrainBoolean deprojectionDistortionCoefficients;
ConstrainBoolean projectionDistortionCoefficients;
// Apply to depth stream track:
ConstrainDouble depthNear;
ConstrainDouble depthFar;
ConstrainBoolean depthToVideoTransform;
};
typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString;
dictionary ConstrainDOMStringParameters {
(DOMString or sequence<DOMString>) exact;
(DOMString or sequence<DOMString>) ideal;
};
typedef (double or ConstrainDoubleRange) ConstrainDouble;
dictionary DoubleRange {
double max;
double min;
};
dictionary ConstrainDoubleRange : DoubleRange {
double exact;
double ideal;
};
typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean;
dictionary ConstrainBooleanParameters {
boolean exact;
boolean ideal;
};
*/
function validateMediaTrackConstraintSet(constraints, type) { function validateMediaTrackConstraintSet(constraints, type) {
assert_constrain_string_field(constraints, 'videoKind'); assert_constrain_string_field(constraints, 'videoKind');
assert_constrain_number_field(constraints, 'focalLengthX');
assert_constrain_number_field(constraints, 'focalLengthY');
assert_constrain_number_field(constraints, 'principalPointX');
assert_constrain_number_field(constraints, 'principalPointY');
assert_constrain_boolean_field(constraints, 'deprojectionDistortionCoefficients');
assert_constrain_boolean_field(constraints, 'projectionDistortionCoefficients');
if (type == "depth") {
assert_constrain_number_field(constraints, 'depthNear');
assert_constrain_number_field(constraints, 'depthFar');
assert_constrain_boolean_field(constraints, 'depthToVideoTransform');
}
}
/*
partial dictionary MediaTrackSettings {
// Apply to both depth stream track and color stream track:
DOMString videoKind;
double focalLengthX;
double focalLengthY;
double principalPointX;
double principalPointY;
DistortionCoefficients deprojectionDistortionCoefficients;
DistortionCoefficients projectionDistortionCoefficients;
// Apply to depth stream track:
double depthNear;
double depthFar;
Transformation depthToVideoTransform;
};
dictionary DistortionCoefficients {
double k1;
double k2;
double p1;
double p2;
double k3;
};
dictionary Transformation {
Float32Array transformationMatrix;
DOMString videoDeviceId;
};
enum VideoKindEnum {
"color",
"depth"
};
*/
function validateDistortionCoefficients(coefficients) {
assert_number_field(coefficients, 'k1');
assert_number_field(coefficients, 'k2');
assert_number_field(coefficients, 'p1');
assert_number_field(coefficients, 'p2');
assert_number_field(coefficients, 'k3');
}
function validateTransformation(depthToVideoTransform) {
assert_array_field(depthToVideoTransform, 'transformationMatrix');
assert_string_field(depthToVideoTransform, 'videoDeviceId');
} }
function validateMediaTrackSettings(settings, type) { function validateMediaTrackSettings(settings, type) {
assert_string_field(settings, 'videoKind'); assert_string_field(settings, 'videoKind');
assert_enum_field(settings, 'videoKind', ['color', 'depth']) assert_enum_field(settings, 'videoKind', ['color', 'depth'])
assert_number_field(settings, 'focalLengthX');
assert_number_field(settings, 'focalLengthY');
assert_number_field(settings, 'principalPointX');
assert_number_field(settings, 'principalPointY');
if (settings.deprojectionDistortionCoefficients) {
validateDistortionCoefficients(settings.deprojectionDistortionCoefficients);
}
if (settings.projectionDistortionCoefficients) {
validateDistortionCoefficients(settings.projectionDistortionCoefficients);
}
if (type == "depth") {
assert_number_field(settings, 'depthNear');
assert_number_field(settings, 'depthFar');
if (settings.depthToVideoTransform) {
validateTransformation(settings.depthToVideoTransform);
}
}
} }
/*
partial dictionary MediaTrackSupportedConstraints {
// Apply to both depth stream track and color stream track:
boolean videoKind = true;
boolean focalLengthX = false;
boolean focalLengthY = false;
boolean principalPointX = false;
boolean principalPointY = false;
boolean deprojectionDistortionCoefficients = false;
boolean projectionDistortionCoefficients = false;
// Apply to depth stream track:
boolean depthNear = false;
boolean depthFar = false;
boolean depthToVideoTransform = false;
};
*/
function validateMediaTrackSupportedConstraints(supports) { function validateMediaTrackSupportedConstraints(supports) {
assert_boolean_field(supports, 'videoKind', true); assert_boolean_field(supports, 'videoKind', true);
assert_boolean_field(supports, 'focalLengthX', false);
assert_boolean_field(supports, 'focalLengthY', false);
assert_boolean_field(supports, 'principalPointX', false);
assert_boolean_field(supports, 'principalPointY', false);
assert_boolean_field(supports, 'deprojectionDistortionCoefficients', false);
assert_boolean_field(supports, 'projectionDistortionCoefficients', false);
assert_boolean_field(supports, 'depthNear', false);
assert_boolean_field(supports, 'depthFar', false);
assert_boolean_field(supports, 'depthToVideoTransform', false);
} }
function runDictionaryTests(type, constraints) { function runDictionaryTests(type, constraints) {

Some files were not shown because too many files have changed in this diff Show more