Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
const SPECULATIONS_ROOT =
URL_ROOT.replace("http:", "https:") + "resources/speculative-loads/";
const SPECULATIONS_PAGE = SPECULATIONS_ROOT + "index.html";
add_setup(async function () {
await enableApplicationPanel();
await SpecialPowers.pushPrefEnv({
set: [
["devtools.application.speculativeLoads", true],
["dom.speculation_rules.enabled", true],
],
});
});
function getRows(doc) {
return Array.from(doc.querySelectorAll(".js-speculations-table tbody tr"));
}
async function initiatePrefetches(browser, urls) {
await SpecialPowers.spawn(browser, [urls], targets => {
const rule = content.document.createElement("script");
rule.type = "speculationrules";
rule.textContent = JSON.stringify({
prefetch: [{ source: "list", eagerness: "immediate", urls: targets }],
});
content.document.head.appendChild(rule);
});
}
add_task(async function test_prefetch_lifecycle_and_navigation() {
const { panel, tab, commands } =
await openNewTabAndApplicationPanel(SPECULATIONS_PAGE);
const doc = panel.panelWin.document;
await selectPage(panel, "speculative-loads");
ok(doc.querySelector(".js-speculations-empty"), "The empty state is shown");
const panes = doc.querySelectorAll(".speculative-loads-pane");
is(
panel.panelWin.getComputedStyle(panes[0]).borderBottomStyle,
"solid",
"The page status retains its separator"
);
is(
panel.panelWin.getComputedStyle(panes[1]).borderBottomStyle,
"none",
"The speculations list has no extra bottom border"
);
const key = "application-speculative-loads";
const pendingURL = SPECULATIONS_ROOT + "target.sjs?hold=" + key;
const failureURL = SPECULATIONS_ROOT + "target.sjs?failure=" + key;
const releaseURL = SPECULATIONS_ROOT + "target.sjs?release=" + key;
registerCleanupFunction(async () => {
await fetch(releaseURL);
});
await initiatePrefetches(tab.linkedBrowser, [pendingURL, failureURL]);
await waitFor(() => getRows(doc).length === 2);
await waitFor(() =>
getRows(doc).some(row => row.dataset.status === "failure")
);
const rows = getRows(doc);
const pendingRow = rows.find(row => row.cells[0].textContent === pendingURL);
const failedRow = rows.find(row => row.cells[0].textContent === failureURL);
is(
pendingRow.dataset.status,
"pending",
"An incomplete response remains pending"
);
is(failedRow.dataset.status, "failure", "An HTTP error is a failure");
is(pendingRow.cells[1].textContent, "Prefetch", "The action is displayed");
is(doc.querySelectorAll(".js-speculations-table th").length, 3);
await SpecialPowers.spawn(tab.linkedBrowser, [releaseURL], async url => {
await content.fetch(url);
});
await waitFor(() => pendingRow.dataset.status === "success");
is(getRows(doc).length, 2, "A regular fetch is not listed as a speculation");
ok(!doc.querySelector(".js-speculations-empty"), "The empty state is hidden");
await navigateTo(SPECULATIONS_PAGE);
await waitFor(() => doc.querySelector(".js-speculations-empty"));
is(
getRows(doc).length,
0,
"Reload clears the previous document's speculations"
);
const nextURL = SPECULATIONS_ROOT + "target.sjs?success=after-reload";
await initiatePrefetches(tab.linkedBrowser, [nextURL]);
await waitFor(() => getRows(doc)[0]?.dataset.status === "success");
is(
getRows(doc)[0].cells[0].textContent,
nextURL,
"Capture continues after reload"
);
await navigateTo("https://example.org/");
await waitFor(() => doc.querySelector(".js-speculations-empty"));
is(getRows(doc).length, 0, "A process-changing navigation clears old rows");
await commands.client.waitForRequestsToSettle();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_existing_network_resources() {
const tab = await addTab(SPECULATIONS_PAGE);
const toolbox = await gDevTools.showToolboxForTab(tab, {
toolId: "netmonitor",
});
const { resourceCommand } = toolbox.commands;
const url = SPECULATIONS_ROOT + "target.sjs?success=existing";
let complete = false;
const onAvailable = () => {};
const onUpdated = updates => {
complete ||= updates.some(
({ resource }) => resource.url === url && resource.responseEndAvailable
);
};
await resourceCommand.watchResources([resourceCommand.TYPES.NETWORK_EVENT], {
onAvailable,
onUpdated,
});
await initiatePrefetches(tab.linkedBrowser, [url]);
await waitFor(() => complete);
const panel = await toolbox.selectTool("application");
await selectPage(panel, "speculative-loads");
const doc = panel.panelWin.document;
await waitFor(() => getRows(doc)[0]?.dataset.status === "success");
is(getRows(doc).length, 1, "A previously captured request is replayed once");
is(getRows(doc)[0].cells[0].textContent, url);
resourceCommand.unwatchResources([resourceCommand.TYPES.NETWORK_EVENT], {
onAvailable,
onUpdated,
});
await toolbox.commands.client.waitForRequestsToSettle();
await BrowserTestUtils.removeTab(tab);
});
add_task(async function test_feature_disabled() {
await SpecialPowers.pushPrefEnv({
set: [["devtools.application.speculativeLoads", false]],
});
const { panel, tab } = await openNewTabAndApplicationPanel(SPECULATIONS_PAGE);
ok(
!panel.panelWin.document.querySelector(".js-sidebar-speculative-loads"),
"The sidebar item is hidden when the feature is disabled"
);
ok(
!panel.panelWin.Application._watchedResources.includes("network-event"),
"The disabled feature does not add network observation"
);
await BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});