diff options
Diffstat (limited to 'testing/web-platform/tests/long-animation-frame')
36 files changed, 1353 insertions, 0 deletions
diff --git a/testing/web-platform/tests/long-animation-frame/META.yml b/testing/web-platform/tests/long-animation-frame/META.yml new file mode 100644 index 0000000000..769c325aee --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/META.yml @@ -0,0 +1,2 @@ +suggested_reviewers: + - noamr diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-basic.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-basic.html new file mode 100644 index 0000000000..c6d3f8e32a --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-basic.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: basic</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: basic</h1> +<div id="log"></div> +<script> + +promise_test(async t => { + await expect_long_frame(() => busy_wait(), t); +}, 'A long busy wait is a long animation frame'); + +promise_test(async t => { + await expect_long_frame(() => requestAnimationFrame(busy_wait), t); +}, 'A long busy wait in a requestAnimationFrame is a long animation frame'); + +promise_test(async t => { + const segment_duration = very_long_frame_duration / 2; + const entry = await expect_long_frame(async () => { + busy_wait(segment_duration); + await new Promise(resolve => requestAnimationFrame(() => { + busy_wait(segment_duration) + resolve(); + })); + }, t); + + assert_not_equals(entry, "timeout"); + assert_greater_than_equal(entry.renderStart - entry.startTime, segment_duration); +}, 'A long busy wait split between a task and a requestAnimationFrame is a long animation frame'); + +promise_test(async t => { + const segment_duration = very_long_frame_duration / 3; + const entry = await expect_long_frame(async () => { + const element = document.createElement("div"); + document.body.appendChild(element); + t.add_cleanup(() => element.remove()); + busy_wait(segment_duration); + requestAnimationFrame(() => { + busy_wait(segment_duration); + }); + + new ResizeObserver(() => { + busy_wait(segment_duration); + }).observe(element); + }, t); + + assert_not_equals(entry, "timeout"); + assert_greater_than_equal(entry.renderStart - entry.startTime, segment_duration); + assert_greater_than_equal(entry.styleAndLayoutStart - entry.renderStart, segment_duration); +}, 'ResizeObservers should create a long-frame and affect layoutStartTime'); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-blocking-duration.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-blocking-duration.html new file mode 100644 index 0000000000..e56a98649e --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-blocking-duration.html @@ -0,0 +1,65 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: basic</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: blocking duration</h1> +<div id="log"></div> +<script> + +function loaf_blocking_duration_test(run, label) { + const OVERHEAD_EPSILON = 5; + const BLOCKING_THRESHOLD = 50; + promise_test(async t => { + let found = false; + for (let i = 0; i < 10 && !found; ++i) { + const longtask_promise = new Promise(resolve => new PerformanceObserver( + (entries, observer) => { + resolve(entries.getEntries()); + observer.disconnect(); + }).observe({entryTypes: ["longtask"]})); + const [longtask_entries, loaf_entry] = await Promise.all( + [longtask_promise, expect_long_frame(run, t)]); + const overlapping = longtask_entries.filter(longtask => + (longtask.startTime >= loaf_entry.startTime && + longtask.startTime < (loaf_entry.startTime + loaf_entry.duration) && + (!loaf_entry.renderStart || + (longtask.startTime < loaf_entry.renderStart - OVERHEAD_EPSILON)))); + + const longest_index = overlapping.reduce( + (max, cur, i) => cur > overlapping[max] ? i : max, 0); + let expected_blocking_duration = 0; + overlapping.forEach(({duration}, i) => { + if (i === longest_index && loaf_entry.renderStart) + duration += loaf_entry.startTime + loaf_entry.duration - + loaf_entry.renderStart; + expected_blocking_duration += Math.max(0, duration - BLOCKING_THRESHOLD); + }); + + if (!overlapping.length && loaf_entry.renderStart) { + expected_blocking_duration = + Math.max(0, + loaf_entry.startTime + loaf_entry.duration - loaf_entry.renderStart - + BLOCKING_THRESHOLD); + } + + if (Math.abs(loaf_entry.blockingDuration - expected_blocking_duration) < + OVERHEAD_EPSILON) { + found = true; + } + } + assert_true(found); + }, `LoAF blockingDuration should be equivalent to long tasks: ${label}`); +} + +loaf_blocking_duration_test(t => t.step_timeout(busy_wait), "Non-rendering"); +loaf_blocking_duration_test(t => t.step_timeout(() => { + busy_wait(); + requestAnimationFrame(busy_wait); +}), "Rendering"); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-buffered.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-buffered.html new file mode 100644 index 0000000000..1a07036b15 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-buffered.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: basic</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: buffered</h1> +<div id="log"></div> +<script> +promise_test(async t => { + busy_wait(very_long_frame_duration); + await new Promise(resolve => t.step_timeout(resolve, 0)); + const result = await new Promise(resolve => { + new PerformanceObserver(t.step_func(entries => { + for (const e of entries.getEntries()) { + if (e.duration >= very_long_frame_duration) + resolve("entry-found"); + } + })).observe({type: 'long-animation-frame', buffered: true}); + t.step_timeout(() => resolve("timeout"), waiting_for_long_frame_timeout); + }); + assert_equals(result, "entry-found"); +}, 'PerformanceObserver with buffered flag can see previous long-animation-frame entries.'); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-event-blocking-duration.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-event-blocking-duration.html new file mode 100644 index 0000000000..ed31244a1d --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-event-blocking-duration.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: blocking duration with events</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: blocking duration with events</h1> +<div id="log"></div> +<output id="output"></output> +<script> +promise_test(async t => { + const [entry, script] = await expect_long_frame_with_script(async () => { + const button = document.createElement("button"); + button.innerText = "click"; + button.addEventListener("click", () => busy_wait()); + document.body.append(button); + t.add_cleanup(() => button.remove()); + await test_driver.click(button); + }, (script) => script.invoker === "BUTTON.onclick", t); + assert_greater_than(entry.duration, 50); + assert_greater_than_equal(entry.blockingDuration, 300); +}, "LoAF generated by events should generate correct blockingDuration"); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-event-listener.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-event-listener.html new file mode 100644 index 0000000000..f866a1dfd8 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-event-listener.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: basic</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: event handlers</h1> +<div id="log"></div> +<script> + +test_self_event_listener(t => { + const img = document.createElement("img"); + img.src = "/images/green.png"; + img.addEventListener("load", () => { + busy_wait(); + }); + img.id = "image"; + document.body.appendChild(img); + t.add_cleanup(() => img.remove()); +}, "IMG#image.onload"); + +test_self_event_listener(t => { + const img = document.createElement("img"); + img.src = "/images/green.png"; + img.addEventListener("load", () => { + busy_wait(); + }); + document.body.appendChild(img); + t.add_cleanup(() => img.remove()); +}, "IMG[src=/images/green.png].onload"); + +test_self_event_listener(t => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", "/common/dummy.xml"); + xhr.addEventListener("load", () => { + busy_wait(); + }); + xhr.send(); +}, "XMLHttpRequest.onload"); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-first-ui-event.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-first-ui-event.html new file mode 100644 index 0000000000..7e32010189 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-first-ui-event.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: first UI Event</title> +<meta name="timeout" content="long"> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: First UI Event</h1> +<div id="log"></div> +<script> + +promise_test(async t => { + const button = document.createElement("button"); + button.innerText = "Click"; + document.body.appendChild(button); + t.add_cleanup(() => button.remove()); + const eventPromise = new Promise(resolve => button.addEventListener("click", event => { + busy_wait(); + resolve(event); + })); + const entryPromise = expect_long_frame_with_script(() => { + test_driver.click(button); + }, s => s.invoker === "BUTTON.onclick", t); + await new Promise(resolve => t.step_timeout(resolve, 0)); + const event = await eventPromise; + const [entry] = await entryPromise; + assert_equals(entry.firstUIEventTimestamp, event.timeStamp); +}, "LoAF should expose firstUIEventTimestamp for click events"); + +promise_test(async t => { + const button = document.createElement("button"); + button.innerText = "Hover"; + document.body.appendChild(button); + t.add_cleanup(() => button.remove()); + let expectedTimestamp = null; + const entryPromise = expect_long_frame_with_script(async () => { + const eventPromise = new Promise(resolve => button.addEventListener("pointermove", event => { + busy_wait(); + expectedTimestamp = event.timeStamp; + resolve(); + })); + + const actions = new test_driver.Actions() + .pointerMove(0, 0, {origin: button}) + .pointerDown() + .pointerUp(); + await actions.send(); + await eventPromise; + }, (script, entry) => + script.invoker === "BUTTON.onpointermove" && + entry.firstUIEventTimestamp === expectedTimestamp, t); +}, "LoAF should expose firstUIEventTimestamp for pointermove events"); + +promise_test(async t => { + const button = document.createElement("button"); + button.innerText = "Click"; + document.body.appendChild(button); + t.add_cleanup(() => button.remove()); + let firstUIEventTimestamp = null; + const eventPromise = new Promise(resolve => button.addEventListener("click", event => { + if (firstUIEventTimestamp) + resolve(event); + else { + firstUIEventTimestamp = event.timeStamp; + busy_wait(); + } + })); + const entryPromise = expect_long_frame_with_script(() => { + test_driver.click(button); + test_driver.click(button); + }, s => s.invoker === "BUTTON.onclick", t); + const [event, [entry]] = await Promise.all([eventPromise, entryPromise]); + assert_equals(entry.firstUIEventTimestamp, firstUIEventTimestamp); +}, "firstUIEventTimestamp doesn't have to come from a long script"); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-idle.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-idle.html new file mode 100644 index 0000000000..bc9f910bb1 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-idle.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: requestIdleCallback</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: requestIdleCallback</h1> +<div id="log"></div> +<script> +setup(() => + assert_implements(window.requestIdleCallback, + 'requestIdleCallback is not supported.')); + +/* +promise_test(async t => { + await expect_no_long_frame(() => requestIdleCallback(busy_wait), t); +}, 'A long busy wait in an idle callback is not a long animation frame'); +*/ + +promise_test(async t => { + const segment_duration = very_long_frame_duration / 2; + requestIdleCallback(() => { + busy_wait(segment_duration); + requestAnimationFrame(() => { + busy_wait(segment_duration); + }); + }); + await expect_long_frame(() => {}, t); +}, 'A long busy wait split between an idle callback and a ' + + 'requestAnimationFrame is a long animation frame'); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-crossorigin.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-crossorigin.html new file mode 100644 index 0000000000..16ecfd7017 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-crossorigin.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: iframes (cross-origin)</title> +<meta name="timeout" content="long"> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<div id="log"></div> +<script> +const host_info = get_host_info(); + +for (const type of ["REMOTE_ORIGIN", "HTTP_NOTSAMESITE_ORIGIN"]) { + promise_test(async t => { + const [executor] = await prepare_exec_iframe(t, host_info[type]); + await expect_no_long_frame(() => executor.execute_script((duration) => { + const deadline = performance.now() + duration; + while (performance.now() < deadline) {} + }, [very_long_frame_duration]), t); + }, `A long busy wait in a ${type} iframe is not a long animation frame`); +} + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-same-origin.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-same-origin.html new file mode 100644 index 0000000000..65dc89f29a --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-same-origin.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: iframes (same-origin)</title> +<meta name="timeout" content="long"> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<div id="log"></div> +<script> +const host_info = get_host_info(); +const {ORIGIN, REMOTE_ORIGIN, HTTP_NOTSAMESITE_ORIGIN} = host_info; + +promise_test(async t => { + const [executor] = await prepare_exec_iframe(t, ORIGIN); + await expect_no_long_frame(() => executor.execute_script((duration) => { + const deadline = performance.now() + duration; + while (performance.now() < deadline) {} + }, [very_long_frame_duration]), t); +}, 'A long busy wait without render in a same-origin iframe is not a long animation frame'); + +promise_test(async t => { + const [executor] = await prepare_exec_iframe(t, ORIGIN); + await expect_long_frame(() => executor.execute_script(async (duration) => { + await new Promise(resolve => window.requestAnimationFrame(resolve)); + const deadline = performance.now() + duration; + while (performance.now() < deadline) {} + }, [very_long_frame_duration]), t); +}, 'A long busy wait in a same-origin requestAnimationFrame is a long animation frame'); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-self.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-self.html new file mode 100644 index 0000000000..7511ff1072 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-iframe-self.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: iframes (self)</title> +<meta name="timeout" content="long"> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<div id="log"></div> +<script> +const host_info = get_host_info(); + +for (const origin of ["ORIGIN", "REMOTE_ORIGIN", "HTTP_NOTSAMESITE_ORIGIN"]) { + promise_test(async t => { + const [executor] = await prepare_exec_iframe(t, host_info[origin]); + const entry = await executor.execute_script(async (duration) => { + const entryPromise = new Promise(resolve => new PerformanceObserver(list => { + resolve(list.getEntries(0)); + }).observe({entryTypes: ["long-animation-frame"]})); + const deadline = performance.now() + duration; + while (performance.now() < deadline) {} + return entryPromise; + }, [very_long_frame_duration]); + }, `frames receive own long animation frames (${origin})`); +} + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-pause-duration.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-pause-duration.html new file mode 100644 index 0000000000..a4181239d4 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-pause-duration.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: pause</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: pause</h1> +<div id="log"></div> +<script> + +promise_test(async t => { + const pause_duration = very_long_frame_duration / 2; + [entry, script] = await expect_long_frame_with_script(() => t.step_timeout(() => { + busy_wait(pause_duration); + const sync_xhr = new XMLHttpRequest(); + sync_xhr.open("GET", `/xhr/resources/delay.py?ms=${pause_duration}`, /*async=*/false); + sync_xhr.send(); + }, 0), script => ( + script.invoker === "TimerHandler:setTimeout" && + script.duration >= very_long_frame_duration), t); + assert_true("pauseDuration" in script); + assert_greater_than(script.pauseDuration, pause_duration); +}, "Synchronous XHR should be counted as pauseDuration"); + +// TODO: Test for alert/confirm, requires WPT infra changes. +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-popup.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-popup.html new file mode 100644 index 0000000000..3f9758953e --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-popup.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: popups</title> +<meta name="timeout" content="long"> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<div id="log"></div> +<script> +const host_info = get_host_info(); +const { ORIGIN } = host_info; + +promise_test(async t => { + const [executor] = await prepare_exec_popup(t, ORIGIN); + await expect_no_long_frame(() => executor.execute_script((duration) => { + const deadline = performance.now() + duration; + while (performance.now() < deadline) {} + }, [very_long_frame_duration]), t); +}, 'A long busy wait in a same-origin popup is a not long animation frame'); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-promise.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-promise.html new file mode 100644 index 0000000000..5ead569c8a --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-promise.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: promise resolvers</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: promise resolvers</h1> +<div id="log"></div> +<script type="module"> + +const {REMOTE_ORIGIN} = get_host_info(); + +test_promise_script(async t => { + await fetch("/common/dummy.xml"); + busy_wait(very_long_frame_duration); +}, "resolve", "Window.fetch.then"); + +test_promise_script(async t => { + const response = await fetch("/common/dummy.xml"); + await response.text(); + busy_wait(very_long_frame_duration); +}, "resolve", "Response.text.then"); + +test_promise_script(async t => { + const response = await fetch("/common/dummy.xml"); + await response.arrayBuffer(); + busy_wait(very_long_frame_duration); +}, "resolve", "Response.arrayBuffer.then"); + +test_promise_script(async t => { + const response = await fetch("/fetch/api/resources/data.json"); + await response.json(); + busy_wait(very_long_frame_duration); +}, "resolve", "Response.json.then"); + +test_promise_script(async t => { + const response = await import("/loading/resources/dummy.js"); + busy_wait(very_long_frame_duration); +}, "resolve", "import.then"); + +test_promise_script(async t => { + fetch(new URL("/common/dummy.xml", REMOTE_ORIGIN).href, {mode: "cors"}) + .catch(() => { + busy_wait(very_long_frame_duration); + }) +}, "reject", "Window.fetch.catch" ); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-block.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-block.html new file mode 100644 index 0000000000..759b31f9a1 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-block.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: script blocks</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/busy.js?from-parser"></script> +<body> +<h1>Long Animation Frame: script blocks</h1> +<div id="log"></div> +<script> +promise_test(async t => { + await new Promise(resolve => { + new PerformanceObserver((list, observer) => { + if (list.getEntries().find(loaf => loaf.scripts.some(script => + script.invoker === new URL("resources/busy.js?from-parser", location.href).href))) { + observer.disconnect(); + resolve(); + } + }).observe({type: "long-animation-frame", buffered: true}); + }) +}, "Parser-inserted classic script should generate a long script"); + +test_self_script_block(t => { + const script = document.createElement("script"); + script.type = "module"; + script.innerHTML = `(${busy_wait.toString()})()`; + document.body.appendChild(script); +}, location.href, "module-script"); + +test_self_script_block(t => { + const script = document.createElement("script"); + script.src = "resources/busy.js"; + document.body.appendChild(script); +}, new URL("resources/busy.js", location.href).href, "classic-script"); + +test_self_script_block(t => { + const uid = token(); + const script = document.createElement("script"); + script.src = `resources/busy.js?token=${uid}`; + script.type = "module"; + document.body.appendChild(script); +}, new URL("resources/busy.js", location.href).href, "module-script"); + +test_self_script_block(t => { + const uid = token(); + const script = document.createElement("script"); + script.type = "module"; + script.innerHTML = `import("./resources/busy.js?import=${uid}");`; + document.body.appendChild(script); +}, new URL("resources/busy.js?import", location.href).href, "module-script"); + +const busy_wait_str = ` (function() { + const deadline = performance.now() + 365; + while (performance.now() < deadline) {} + })() +`; + +const data_url = `data:text/javascript;charset=utf-8,${encodeURIComponent(busy_wait_str)}`; + +test_self_script_block(t => { + const script = document.createElement("script"); + script.src = data_url; + document.body.appendChild(script); +}, "data:", "classic-script"); + +const blob_url = URL.createObjectURL(new Blob([busy_wait_str], {type: "text/javascript"})); + +test_self_script_block(t => { + const script = document.createElement("script"); + script.src = blob_url; + document.body.appendChild(script); +}, blob_url, "classic-script"); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-nested-callback.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-nested-callback.html new file mode 100644 index 0000000000..8d1304fc80 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-nested-callback.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: nested scripts</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: nested scripts</h1> +<div id="log"></div> +<div id="dummy"></div> +<script> +promise_test(async t => { + const [entry, script] = await expect_long_frame_with_script(t => { + const script_element = document.createElement("script"); + script_element.async = true; + script_element.src = "resources/loaf-after-callback.js"; + t.add_cleanup(() => script_element.remove()); + document.body.appendChild(script_element); + }, script => + script.invoker === new URL("resources/loaf-after-callback.js", location.href).href, + t); + assert_greater_than_equal(script.duration, very_long_frame_duration); +}, "a callback inside a script block should not mask LoAFs that come afterwards") + +promise_test(async t => { + const [entry, script] = await expect_long_frame_with_script(t => { + const script_element = document.createElement("script"); + script_element.async = true; + script_element.src = "resources/loaf-in-microtask-after-callback.js"; + t.add_cleanup(() => script_element.remove()); + document.body.appendChild(script_element); + }, script => + script.invoker === new URL("resources/loaf-in-microtask-after-callback.js", location.href).href, + t); + assert_greater_than_equal(script.duration, very_long_frame_duration); +}, "a callback inside a script block should not mask LoAFs in a microtask") +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-window-attribution.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-window-attribution.html new file mode 100644 index 0000000000..0b8f45bed0 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-script-window-attribution.html @@ -0,0 +1,61 @@ + +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: window attribution</title> +<meta name="timeout" content="long"> +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<div id="log"></div> +<script> + +const host_info = get_host_info(); +const {ORIGIN, REMOTE_ORIGIN, HTTP_NOTSAMESITE_ORIGIN} = host_info; + +promise_test (async t => { + const [entry, script] = await expect_long_frame_with_script(() => { + requestAnimationFrame(() => busy_wait()); + }, () => true, t); + assert_equals(script.windowAttribution, "self"); + assert_equals(script.window, window); +}, 'Scripts in this window should be self-attributed'); + +promise_test (async t => { + let found = false; + for (let i = 0; i < 10 && !found; ++i) { + const [executor, iframe] = await prepare_exec_iframe(t, ORIGIN); + const [entry, script] = await expect_long_frame_with_script(() => + executor.execute_script(async (duration) => { + await new Promise(resolve => window.requestAnimationFrame(resolve)); + const deadline = performance.now() + duration; + while (performance.now() < deadline) {} + }, [very_long_frame_duration]), () => true, t); + + if (script.windowAttribution === "descendant" && script.window === iframe.contentWindow) { + found = true; + } + } + + assert_true(found); +}, 'Scripts in subframes should be descendant-attributed'); + +promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.src = "/resource-timing/resources/green.html"; + document.body.append(iframe); + t.add_cleanup(() => iframe.remove()); + const [entry, script] = await expect_long_frame_with_script( + () => requestAnimationFrame(busy_wait), () => true, t); + const in_iframe = iframe.contentWindow.performance.getEntriesByType("long-animation-frame").some( + e => e.scripts.some(script_in_iframe => script_in_iframe.invoker === script.invoker) + ); + + assert_false(in_iframe, "IFrame should not receive the LoAF entry"); +}, 'A long busy wait in a requestAnimationFrame should not be observable by same-origin iframes'); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-source-location-redirect.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-source-location-redirect.html new file mode 100644 index 0000000000..c0bb96b1ec --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-source-location-redirect.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: source location should not expose cross-origin redirects</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: source location with cross-origin redirects</h1> +<div id="log"></div> +<script> + +const {REMOTE_ORIGIN} = get_host_info(); + +function corsify(url, cors) { + if (!cors) + return url.href; + url.searchParams.set("pipe", "header(Access-Control-Allow-Origin, *)"); + return url.href; +} + +const targetURL = (path, cors) => + corsify(new URL(path, REMOTE_ORIGIN), cors); + +function crossOriginRedirectURL(path, cors) { + const url = new URL(`/common/redirect.py`, REMOTE_ORIGIN); + url.searchParams.set("location", targetURL(path, cors)); + return corsify(url, cors); +} + +function test_source_location_with_redirect({path, type, name}) { + for (let scriptType of ["classic-opaque", "classic-cors", "module"]) { + const cors = scriptType !== "classic-opaque"; + promise_test(async t => { + const VERY_LONG_FRAME_DURATION = 360; + const preRedirectURL = crossOriginRedirectURL(path, cors); + const postRedirectURL = targetURL(path, cors); + let [entry, script] = await expect_long_frame_with_script(() => { + const script = document.createElement("script"); + script.src = preRedirectURL; + if (scriptType === "module") + script.type = "module"; + else if (cors) + script.crossOrigin = "anonymous"; + document.body.appendChild(script); + t.add_cleanup(() => script.remove()); + }, script => script.duration >= VERY_LONG_FRAME_DURATION - 5, t); + + const result = + script.sourceURL.startsWith(postRedirectURL) ? "post-redirect" : + script.sourceURL.startsWith(preRedirectURL) ? "pre-redirect" : + script.sourceURL === "" ? + "empty" : "other"; + + assert_not_equals(result, "other", `Unexpected source location ${script.sourceURL}`); + if (!cors) + assert_equals(script.executionStart, script.startTime, "Opaque scripts should hide execution start time"); + + if (cors) { + assert_not_equals(result, "empty", "CORS-ok scripts should expose sourceLocation"); + } else { + assert_not_equals(result, "post-redirect", "No-CORS classic scripts should not expose post-redirect URL"); + assert_equals(script.sourceCharPosition, type === "script-block" ? 0 : -1, "No-CORS classic scripts should not expose character index"); + } + }, `Test ${type} with ${scriptType}`); + } +} + +test_source_location_with_redirect({ + path: "/long-animation-frame/tentative/resources/busy.js", + type: "script-block" +}); + +test_source_location_with_redirect({ + path: "/long-animation-frame/tentative/resources/raf-generates-loaf.js", + name: "FrameRequestCallback", + type: "user-callback" +}); +test_source_location_with_redirect({ + path: "/long-animation-frame/tentative/resources/event-generates-loaf.js", + type: "event-listener", + name: "XMLHttpRequest.onload" +}); + +test_source_location_with_redirect({ + path: "/long-animation-frame/tentative/resources/promise-generates-loaf.js", + type: "resolve-promise", + name: "Window.fetch.then" +}); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-source-location.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-source-location.html new file mode 100644 index 0000000000..51e8565644 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-source-location.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: source location extraction</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: source location extraction</h1> +<div id="log"></div> +<script> + +promise_test(async t => { + const [entry, script] = await expect_long_frame_with_script(() => { + requestAnimationFrame(function non_bound_function() { + busy_wait(); + }); + }, script => script.invoker === "FrameRequestCallback", t); + assert_equals(script.sourceURL, location.href); + assert_equals(script.sourceFunctionName, "non_bound_function"); + assert_greater_than(script.sourceCharPosition, 0); +}, "Source location should be extracted from non-bound functions"); + +promise_test(async t => { + const [entry, script] = await expect_long_frame_with_script(() => { + const object = {}; + requestAnimationFrame((function my_bound_function() { + busy_wait(); + }).bind(object)); + }, script => script.invoker === "FrameRequestCallback", t); + assert_equals(script.sourceURL, location.href); + assert_equals(script.sourceFunctionName, "my_bound_function"); + assert_greater_than(script.sourceCharPosition, 0); +}, "Source location should be extracted from bound functions"); + +promise_test(async t => { + const [entry, script] = await expect_long_frame_with_script(() => { + t.step_timeout(function my_timeout() { + busy_wait(); + }); + }, script => script.invoker === "TimerHandler:setTimeout" && script.sourceURL, t ); + assert_true(script.sourceURL.includes("testharness.js")); +}, "Source location should be extracted for setTimeout"); + +promise_test(async t => { + const scriptLocation = new URL("resources/promise-generates-loaf.js", location.href); + const [entry, script] = await expect_long_frame_with_script(() => { + const scriptElement = document.createElement("script"); + scriptElement.src = scriptLocation; + document.body.appendChild(scriptElement); + }, script => script.invoker === "Window.fetch.then", t); + assert_true(script.sourceURL.includes("promise-generates-loaf.js")); +}, "Source location should be extracted for promises"); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-stream-source-location.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-stream-source-location.html new file mode 100644 index 0000000000..0fd30859d7 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-stream-source-location.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: source location extraction for streams</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: source location extraction for streams</h1> +<div id="log"></div> +<script> + +promise_test(async t => { + const scriptLocation = new URL("resources/stream-promise-generates-loaf.js", location.href); + const [entry, script] = await expect_long_frame_with_script(() => { + const scriptElement = document.createElement("script"); + scriptElement.src = scriptLocation; + document.body.appendChild(scriptElement); + }, script => script.invoker === "StreamPromise.resolve.then", t); + + assert_true(script.sourceURL.includes("stream-promise-generates-loaf.js")); +}, "Source location should be extracted for stream promises"); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-stream.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-stream.html new file mode 100644 index 0000000000..e35bc2f9aa --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-stream.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: stream promise resolvers</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: stream promise resolvers</h1> +<div id="log"></div> +<script> + +test_promise_script(async t => { + const response = await fetch("/common/dummy.xml"); + await response.body.getReader().read(); + busy_wait(very_long_frame_duration); +}, "resolve", "ReadableStreamDefaultReader.read.then"); + +test_promise_script(async t => { + const response = await fetch("/common/dummy.xml"); + await response.body.getReader({ mode: "byob" }).read(new Int32Array(1000)); + busy_wait(very_long_frame_duration); +}, "resolve", "ReadableStreamBYOBReader.read.then"); + +test_promise_script(async t => { + const response = await fetch("/common/dummy.xml"); + const {readable, writable} = new TransformStream({ + start() {}, + transform() { + busy_wait(very_long_frame_duration); + } + }); + response.body.pipeTo(writable); + await readable.getReader().read(); +}, "resolve", "StreamPromise.resolve"); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-supportedEntryTypes.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-supportedEntryTypes.html new file mode 100644 index 0000000000..efa01481fa --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-supportedEntryTypes.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: supportedEntryTypes</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<h1>Long Animation Frame: supportedEntryTypes</h1> +<div id="log"></div> +<script> + +setup(() => + assert_implements(window.PerformanceLongAnimationFrameTiming, + 'Long animation frames are not supported.')); + +test(() => { + assert_true(PerformanceObserver.supportedEntryTypes.includes("long-animation-frame")); +}, 'supportedEntryTypes should include long-animation-frame'); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-timeline.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-timeline.html new file mode 100644 index 0000000000..c434a26ef8 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-timeline.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: basic</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: basic</h1> +<div id="log"></div> +<script> +promise_test(async t => { + busy_wait(very_long_frame_duration); + const is_loaf = entry => entry.duration >= very_long_frame_duration && + entry.entryType == "long-animation-frame"; + + await new Promise(resolve => t.step_timeout(resolve, 10)); + const entry_from_all = [...performance.getEntries()].find(is_loaf); + const entry_by_type = [...performance.getEntriesByType("long-animation-frame")].find(is_loaf); + const entry_by_name = [...performance.getEntriesByName("long-animation-frame")].find(is_loaf); + assert_true(!!entry_from_all, "LoAF Entry found"); + assert_equals(entry_from_all, entry_by_type); + assert_equals(entry_from_all, entry_by_name); +}, 'LoAF entries are available in the performnace timeline'); +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-toJSON.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-toJSON.html new file mode 100644 index 0000000000..5b249e6972 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-toJSON.html @@ -0,0 +1,46 @@ +<!doctype html> +<html> +<head> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<script> + promise_test(async t => { + window.onload = () => { + // Trigger a long task. + const begin = window.performance.now(); + while (window.performance.now() < begin + 60); + }; + + assert_implements(window.PerformanceLongAnimationFrameTiming, 'Lon are not supported.'); + const entry = await new Promise(resolve => new PerformanceObserver( + t.step_func(entryList => { + const entries = entryList.getEntries(); + assert_greater_than_equal(entries.length, 1); + resolve(entries[0]); + })).observe({entryTypes: ["long-animation-frame"]})); + + assert_equals(typeof(entry.toJSON), 'function'); + const entryJSON = entry.toJSON(); + assert_equals(typeof(entryJSON), 'object'); + // Check attributes inheritted from PerformanceEntry. + const performanceEntryKeys = [ + 'name', + 'entryType', + 'startTime', + 'duration', + 'renderStart', + 'styleAndLayoutStart', + 'blockingTime', + 'firstUIEventTimestamp' + ]; + for (const key of performanceEntryKeys) { + assert_equals(entryJSON[key], entry[key], + `entry.toJSON().${key} should match entry.${key}`); + } + + }, 'Test toJSON() in PerformanceLongAnimationFrameTiming'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-ui-event-render-start.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-ui-event-render-start.html new file mode 100644 index 0000000000..01d50d47e9 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-ui-event-render-start.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: UI events and renderStart</title> +<meta name="timeout" content="long"> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: UI events and renderStart</h1> +<div id="log"></div> +<script> + +promise_test(async t => { + const BUSY_DURATION_1 = 150; + const BUSY_DURATION_2 = 250; + const eventPromise = new Promise(resolve => document.body.addEventListener("pointermove", () => { + busy_wait(BUSY_DURATION_1); + requestAnimationFrame(() => busy_wait(BUSY_DURATION_2)); + resolve(); + }, {passive: true})); + const actions = new test_driver.Actions(); + await actions.pointerMove(10, 10, {origin: document.body}) + .pointerDown() + .pointerMove(3, 3) + .pointerUp() + .send(); + + const scriptPredicate = s => s.invoker === "BODY.onpointermove"; + + const loaf = await new Promise(resolve => + new PerformanceObserver(entries => { + const entry = entries.getEntries().find( + e => e.scripts.some(scriptPredicate)); + resolve(entry); + }).observe({type: "long-animation-frame", buffered: true})); + + const script = loaf.scripts.find(scriptPredicate) + assert_greater_than_equal(loaf.renderStart, script.startTime + script.duration); + assert_greater_than_equal(loaf.blockingDuration, BUSY_DURATION_1 + BUSY_DURATION_2 - 50); +}, "UI events should always be before renderStart but still affect blockingDuration") +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-user-callback.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-user-callback.html new file mode 100644 index 0000000000..3d868af87f --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-user-callback.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Long Animation Frame Timing: basic</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<h1>Long Animation Frame: user callbacks</h1> +<div id="log"></div> +<script> + +test_self_user_callback(t => + t.step_timeout(() => busy_wait()), "TimerHandler:setTimeout"); + +test_self_user_callback(() => { + const interval = setInterval(() => { + busy_wait(); + clearInterval(interval); + }, 10); +}, "TimerHandler:setInterval"); +test_self_user_callback(() => + requestAnimationFrame(() => busy_wait()), "FrameRequestCallback"); + +test_self_user_callback(t => { + const element = document.createElement("div"); + document.body.appendChild(element); + t.add_cleanup(() => element.remove()); + new ResizeObserver((entries, observer) => { + busy_wait(very_long_frame_duration); + observer.disconnect(); + }).observe(element); +}, "ResizeObserverCallback"); + +test_self_user_callback(t => { + const element = document.createElement("div"); + element.innerText = "123"; + t.add_cleanup(() => element.remove()); + new IntersectionObserver((entries, observer) => { + busy_wait(very_long_frame_duration); + observer.disconnect(); + }).observe(element); + document.body.appendChild(element); +}, "IntersectionObserverCallback"); + +test_self_user_callback(t => + scheduler.postTask(() => busy_wait()), "SchedulerPostTaskCallback"); + + test_self_user_callback(t => { + new PerformanceObserver(() => busy_wait()).observe( + {type: "navigation", buffered: true}); +}, "PerformanceObserverCallback"); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-visibility.html b/testing/web-platform/tests/long-animation-frame/tentative/loaf-visibility.html new file mode 100644 index 0000000000..97038e3073 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-visibility.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<meta name="timeout" content="long"> +<title>Long Animation Frame Timing: iframes</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 src="/common/utils.js"></script> +<script src="/page-visibility/resources/window_state_context.js"></script> +<script src="resources/utils.js"></script> +<body> + <div id="log"></div> +<script> + +promise_test(async t => { + const {minimize, restore} = window_state_context(t); + await minimize(); + expect_no_long_frame(busy_wait, t); + await restore(); + expect_long_frame(busy_wait, t); +}, 'Invisible windows do not report long animation frames'); + +</script> +</body> diff --git a/testing/web-platform/tests/long-animation-frame/tentative/loaf-window-only.worker.js b/testing/web-platform/tests/long-animation-frame/tentative/loaf-window-only.worker.js new file mode 100644 index 0000000000..c1f0439c4b --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/loaf-window-only.worker.js @@ -0,0 +1,11 @@ +importScripts("/resources/testharness.js"); + +test(() => { + assert_false(PerformanceObserver.supportedEntryTypes.includes("long-animation-frame")); +}, 'PerformanceObserver should not include "long-animation-frame" in workers'); + +test(() => { + assert_false("PerformanceLongAnimationFrameTiming" in self); +}, 'PerformanceLongAnimationFrameTiming should not be exposed in workers'); + +done(); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/busy.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/busy.js new file mode 100644 index 0000000000..9d761b6de5 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/busy.js @@ -0,0 +1,4 @@ +(() => { + const deadline = performance.now() + 360; + while (performance.now() < deadline) {} +})(); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/event-generates-loaf.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/event-generates-loaf.js new file mode 100644 index 0000000000..fe7c3eca2b --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/event-generates-loaf.js @@ -0,0 +1,10 @@ +(() => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', '/common/dummy.xml'); + xhr.addEventListener('load', () => { + const deadline = performance.now() + 360; + while (performance.now() < deadline) { + } + }); + xhr.send(); +})(); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/loaf-after-callback.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/loaf-after-callback.js new file mode 100644 index 0000000000..d9ac74ccf4 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/loaf-after-callback.js @@ -0,0 +1,7 @@ +(function() { + busy_wait(60); + new URLSearchParams([["a", "hello"]]).forEach((value, key) => { + document.querySelector("#dummy").innerText += value; + }); + busy_wait(); +})(); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/loaf-in-microtask-after-callback.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/loaf-in-microtask-after-callback.js new file mode 100644 index 0000000000..c73b604f6d --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/loaf-in-microtask-after-callback.js @@ -0,0 +1,7 @@ +(function() { + busy_wait(60); + Promise.resolve().then(busy_wait); + new URLSearchParams([["a", "hello"]]).forEach((value, key) => { + document.querySelector("#dummy").innerText += value; + }); +})(); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/promise-generates-loaf.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/promise-generates-loaf.js new file mode 100644 index 0000000000..53369f7226 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/promise-generates-loaf.js @@ -0,0 +1,4 @@ +fetch("/common/dummy.xml").then(() => { + const deadline = performance.now() + 360; + while (performance.now() < deadline) {} +}); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/raf-generates-loaf.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/raf-generates-loaf.js new file mode 100644 index 0000000000..b7544af4e5 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/raf-generates-loaf.js @@ -0,0 +1,5 @@ +requestAnimationFrame(() => { + const deadline = performance.now() + 360; + while (performance.now() < deadline) { + } +}); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/stream-promise-generates-loaf.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/stream-promise-generates-loaf.js new file mode 100644 index 0000000000..35e7920bb2 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/stream-promise-generates-loaf.js @@ -0,0 +1,12 @@ +(async() => { + const response = await fetch("/common/dummy.xml"); + const {readable, writable} = new TransformStream({ + start() {}, + transform() { + const deadline = performance.now() + 360; + while (performance.now() < deadline) {} + } + }); + response.body.pipeTo(writable); + await readable.getReader().read(); +})(); diff --git a/testing/web-platform/tests/long-animation-frame/tentative/resources/utils.js b/testing/web-platform/tests/long-animation-frame/tentative/resources/utils.js new file mode 100644 index 0000000000..aa537d39a7 --- /dev/null +++ b/testing/web-platform/tests/long-animation-frame/tentative/resources/utils.js @@ -0,0 +1,131 @@ +const windowLoaded = new Promise(resolve => window.addEventListener('load', resolve)); +setup(() => + assert_implements(window.PerformanceLongAnimationFrameTiming, + 'Long animation frames are not supported.')); + +const very_long_frame_duration = 360; +const no_long_frame_timeout = very_long_frame_duration * 2; +const waiting_for_long_frame_timeout = very_long_frame_duration * 10; + +function loaf_promise(t) { + return new Promise(resolve => { + const observer = new PerformanceObserver(entries => { + const entry = entries.getEntries()[0]; + // TODO: understand why we need this 5ms epsilon. + if (entry.duration > very_long_frame_duration - 5) { + observer.disconnect(); + resolve(entry); + } + }); + + t.add_cleanup(() => observer.disconnect()); + + observer.observe({entryTypes: ['long-animation-frame']}); + }); +} + +function busy_wait(ms_delay = very_long_frame_duration) { + const deadline = performance.now() + ms_delay; + while (performance.now() < deadline) {} +} + +async function expect_long_frame(cb, t) { + await windowLoaded; + await new Promise(resolve => t.step_timeout(resolve, 0)); + const timeout = new Promise((resolve, reject) => + t.step_timeout(() => resolve("timeout"), waiting_for_long_frame_timeout)); + const receivedLongFrame = loaf_promise(t); + await cb(t); + const entry = await Promise.race([ + receivedLongFrame, + timeout + ]); + return entry; +} + +async function expect_long_frame_with_script(cb, predicate, t) { + for (let i = 0; i < 10; ++i) { + const entry = await expect_long_frame(cb, t); + if (entry === "timeout" || !entry.scripts.length) + continue; + for (const script of entry.scripts) { + if (predicate(script, entry)) + return [entry, script]; + } + } + + return []; +} + +async function expect_no_long_frame(cb, t) { + await windowLoaded; + for (let i = 0; i < 5; ++i) { + const receivedLongFrame = loaf_promise(t); + await cb(); + const result = await Promise.race([receivedLongFrame, + new Promise(resolve => t.step_timeout(() => resolve("timeout"), + no_long_frame_timeout))]); + if (result === "timeout") + return false; + } + + throw new Error("Consistently creates long frame"); +} + +async function prepare_exec_iframe(t, origin) { + const iframe = document.createElement("iframe"); + t.add_cleanup(() => iframe.remove()); + const url = new URL("/common/dispatcher/remote-executor.html", origin); + const uuid = token(); + url.searchParams.set("uuid", uuid); + iframe.src = url.href; + document.body.appendChild(iframe); + await new Promise(resolve => iframe.addEventListener("load", resolve)); + return [new RemoteContext(uuid), iframe]; +} + + +async function prepare_exec_popup(t, origin) { + const url = new URL("/common/dispatcher/remote-executor.html", origin); + const uuid = token(); + url.searchParams.set("uuid", uuid); + const popup = window.open(url); + t.add_cleanup(() => popup.close()); + return [new RemoteContext(uuid), popup]; +} +function test_loaf_script(cb, invoker, invokerType, label) { + promise_test(async t => { + let [entry, script] = []; + [entry, script] = await expect_long_frame_with_script(cb, + script => ( + script.invokerType === invokerType && + script.invoker.startsWith(invoker) && + script.duration >= very_long_frame_duration), t); + + assert_true(!!entry, "Entry detected"); + assert_greater_than_equal(script.duration, very_long_frame_duration); + assert_greater_than_equal(entry.duration, script.duration); + assert_greater_than_equal(script.executionStart, script.startTime); + assert_greater_than_equal(script.startTime, entry.startTime) + assert_equals(script.window, window); + assert_equals(script.forcedStyleAndLayoutDuration, 0); + assert_equals(script.windowAttribution, "self"); +}, `LoAF script: ${invoker} ${invokerType},${label ? ` ${label}` : ''}`); + +} + +function test_self_user_callback(cb, invoker, label) { + test_loaf_script(cb, invoker, "user-callback", label); +} + +function test_self_event_listener(cb, invoker) { + test_loaf_script(cb, invoker, "event-listener"); +} + +function test_promise_script(cb, resolve_or_reject, invoker, label) { + test_loaf_script(cb, invoker, `${resolve_or_reject}-promise`, label); +} + +function test_self_script_block(cb, invoker, type) { + test_loaf_script(cb, invoker, type); +} |