/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* global require, module */ const { logTest } = require("./utils/profiling"); const EAGERNESS_LEVELS = ["immediate", "eager", "moderate", "conservative"]; const SOURCES = ["document", "list"]; // Drive the click so each eagerness level gets a fair chance to fire and // complete its prefetch before navigation. `dwellMs` is the settle/hold // window, sized to clear the target page's server stall. async function navigateWithPrefetch(commands, eagerness, selector, dwellMs) { switch (eagerness) { case "immediate": // Prefetch starts at parse time; just let it finish, then click. await commands.wait.byTime(dwellMs); await commands.mouse.singleClick.bySelector(selector); break; case "eager": case "moderate": // Both fire on hover (eager sooner); sustain the hover, then click. await commands.mouse.moveTo.bySelector(selector); await commands.wait.byTime(dwellMs); await commands.mouse.singleClick.bySelector(selector); break; case "conservative": // Fires on pointerdown: press, hold so the prefetch can complete, then // release on the link to navigate. await commands.mouse.clickAndHold.bySelector(selector); await commands.wait.byTime(dwellMs); await commands.mouse.clickAndHold.releaseAtSelector(selector); break; default: throw new Error( `speculation-rules-prefetch: unknown eagerness "${eagerness}"` ); } } module.exports = logTest( "speculation rules prefetch", async function (context, commands) { const serverUrl = context.options.browsertime.server_url; if (!serverUrl) { throw new Error( "speculation-rules-prefetch: missing --browsertime.server_url; " + "is support_class=speculation_rules.py wired in?" ); } const buttonId = context.options.browsertime.button_id || "btn-a"; const eagerness = context.options.browsertime.eagerness || "moderate"; if (!EAGERNESS_LEVELS.includes(eagerness)) { throw new Error( `speculation-rules-prefetch: unsupported eagerness "${eagerness}"; ` + `expected one of ${EAGERNESS_LEVELS.join(", ")}` ); } const source = context.options.browsertime.source || "document"; if (!SOURCES.includes(source)) { throw new Error( `speculation-rules-prefetch: unsupported source "${source}"; ` + `expected one of ${SOURCES.join(", ")}` ); } // Covers each level's trigger latency plus the 500 ms backend stall. const dwellMs = Number(context.options.browsertime.dwell_ms ?? 1000); context.log.info( `speculation-rules-prefetch: button=${buttonId}, source=${source}, ` + `eagerness=${eagerness}, dwell_ms=${dwellMs}` ); await commands.navigate( `${serverUrl}/landing.html?eagerness=${eagerness}&source=${source}` ); await commands.wait.byTime(250); await commands.measure.start(); await navigateWithPrefetch(commands, eagerness, `#${buttonId}`, dwellMs); await commands.wait.byTime(2500); await commands.measure.stop(); const navInfo = await commands.js.run(` const nav = performance.getEntriesByType('navigation')[0]; return nav ? { deliveryType: nav.deliveryType || "", type: nav.type, duration: nav.duration, responseStart: nav.responseStart, domContentLoadedEventEnd: nav.domContentLoadedEventEnd, loadEventEnd: nav.loadEventEnd, href: location.href, } : null; `); context.log.info( `speculation-rules-prefetch: navigation entry = ${JSON.stringify(navInfo)}` ); if (!navInfo) { throw new Error( "speculation-rules-prefetch: no navigation entry found on target page" ); } await commands.measure.addObject({ custom_data: { delivery_type: navInfo.deliveryType, navigation_duration: navInfo.duration, response_start: navInfo.responseStart, }, }); return true; } );