| 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, options = {}) { |
| 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({type: 'long-animation-frame', ...options}); |
| }); |
| } |
| |
| 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, opt = {}) { |
| 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, opt = {}) { |
| for (let i = 0; i < 10; ++i) { |
| const entry = await expect_long_frame(cb, t, opt); |
| 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); |
| } |
| |
| function loaf_with_inline_script(type) { |
| return new Promise(resolve => new PerformanceObserver(entries => { |
| for (const e of entries.getEntries()) { |
| if (e.duration < very_long_frame_duration - 5) |
| return; |
| const script = e.scripts.find(s => s.invokerType === type && |
| new URL(s.sourceURL, location.href).pathname === new URL(location.href).pathname); |
| if (script) |
| resolve(script); |
| } |
| }).observe({ type: "long-animation-frame", buffered: true })); |
| } |