Looks good overall, but I didn't have time to fully read through test codes. I'll take another look later.
# performance.getSpeculations().navigations reports the renderer's activatedCan you add a blank line between line 3123 and 3124?
crbug.com/532860179 virtual/speculation-rules-renderer-side-heuristics/external/wpt/speculation-rules/speculation-measurement/performance-speculations-navigations.tentative.https.html [ Pass ]Can you add a blank line between 3132 and 3133 to make the block clear?
{ source: 'list', urls: moderateUrls, eagerness: 'moderate' },Can you remove this deprecated "source" param here and from other places in this file?
{source: 'list', urls: [barrierUrl], eagerness: 'immediate'},Can you remove this deprecated "source" param?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
# performance.getSpeculations().navigations reports the renderer's activatedCan you add a blank line between line 3123 and 3124?
Done
crbug.com/532860179 virtual/speculation-rules-renderer-side-heuristics/external/wpt/speculation-rules/speculation-measurement/performance-speculations-navigations.tentative.https.html [ Pass ]Can you add a blank line between 3132 and 3133 to make the block clear?
Done
{ source: 'list', urls: moderateUrls, eagerness: 'moderate' },Can you remove this deprecated "source" param here and from other places in this file?
Done
{source: 'list', urls: [barrierUrl], eagerness: 'immediate'},Can you remove this deprecated "source" param?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// prefetch. Therefore navigations must NOT list them.IIUC, "navigations" here refers to `performance.getSpeculations().navigations`. Since it's a common term, could we either write it out fully as `performance.getSpeculations().navigations` or wrap it in backticks like \`navigations\`?
This applies to other occurrences in this file.
// internal anchor at moderate eagerness — the Shopify-storefront shape.Can we remove this part?
{ urls: [url], eagerness: 'immediate' },These are not aligned.
async function waitForNavigation(`waitForNavigation` sounds like this waits until completion of real navigation, but actually this just waits for speculation. Can we rename this to `waitForSpeculationMatching` or something like that?
// We use `immediate` (not `eager`) because `eager` is only fired
// immediately when the kPreloadingEagerHoverHeuristics and
// kPreloadingEagerViewportHeuristics features are both disabled; with
// either enabled, `eager` candidates go to standby and never fire without
// user gesture, which would deadlock the barrier.I think this comment is too specific to Chromium's internal implementation. I wonder if we could simply say something like, "We use immediate to immediately trigger the barrier ruleset"?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// prefetch. Therefore navigations must NOT list them.IIUC, "navigations" here refers to `performance.getSpeculations().navigations`. Since it's a common term, could we either write it out fully as `performance.getSpeculations().navigations` or wrap it in backticks like \`navigations\`?
This applies to other occurrences in this file.
Done
// internal anchor at moderate eagerness — the Shopify-storefront shape.Can we remove this part?
Sorry, missed this! done!
{ urls: [url], eagerness: 'immediate' },Yoav Weiss (@Shopify)These are not aligned.
Done
`waitForNavigation` sounds like this waits until completion of real navigation, but actually this just waits for speculation. Can we rename this to `waitForSpeculationMatching` or something like that?
Done
// We use `immediate` (not `eager`) because `eager` is only fired
// immediately when the kPreloadingEagerHoverHeuristics and
// kPreloadingEagerViewportHeuristics features are both disabled; with
// either enabled, `eager` candidates go to standby and never fire without
// user gesture, which would deadlock the barrier.I think this comment is too specific to Chromium's internal implementation. I wonder if we could simply say something like, "We use immediate to immediately trigger the barrier ruleset"?
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
13 is the latest approved patch-set.
The change was submitted with unreviewed changes in the following files:
```
The name of the file: third_party/blink/web_tests/external/wpt/speculation-rules/speculation-measurement/performance-speculations-preredirect-urls.tentative.https.html
Insertions: 2, Deletions: 2.
@@ -83,7 +83,7 @@
// Wait for the browser to actually start the prefetch. The entry appears
// in navigations at that point; polling avoids fixed-duration waits.
- const preRedirectEntry = await waitForNavigation(t, redirectUrl);
+ const preRedirectEntry = await waitForSpeculationMatching(t, redirectUrl);
assert_equals(preRedirectEntry.type, 'prefetch');
assert_equals(preRedirectEntry.eagerness, 'immediate');
@@ -107,7 +107,7 @@
// Wait for the browser to actually create the prerender host. The entry
// appears in navigations at that point.
- const preRedirectEntry = await waitForNavigation(t, redirectUrl);
+ const preRedirectEntry = await waitForSpeculationMatching(t, redirectUrl);
assert_equals(preRedirectEntry.type, 'prerender');
const navigations = performance.getSpeculations().navigations;
```
```
The name of the file: third_party/blink/web_tests/external/wpt/speculation-rules/speculation-measurement/support/speculation-measurement-utils.js
Insertions: 14, Deletions: 17.
@@ -4,9 +4,9 @@
// determine whether a speculative navigation has (or has not) been started.
// Instead:
// - For positive assertions ("this URL must appear in navigations"), poll
-// performance.getSpeculations().navigations until the entry shows up,
-// using `waitForNavigation`. Polling is state-driven — the test resumes
-// as soon as the state flips.
+// `performance.getSpeculations().navigations` until the entry shows up,
+// using `waitForSpeculationMatching`. Polling is state-driven — the
+// test resumes as soon as the state flips.
// - For negative assertions ("this URL must NOT appear"), use a *barrier*:
// add a known-eager candidate alongside the candidate under test in the
// same rule set, wait for the barrier's entry to appear, and only then
@@ -40,12 +40,12 @@
.href;
}
-// Poll performance.getSpeculations().navigations until an entry matching
+// Poll `performance.getSpeculations().navigations` until an entry matching
// `predicate` appears. `predicate` may be a URL string (exact match) or a
// callback receiving each entry.
//
// Returns the matching entry, or throws on the safety-net timeout.
-async function waitForNavigation(
+async function waitForSpeculationMatching(
t, predicate, {timeoutMs = SPECULATION_POLL_SAFETY_MS} = {}) {
const match =
typeof predicate === 'string' ? (n => n.url === predicate) : predicate;
@@ -66,20 +66,17 @@
// Barrier-based synchronization for negative assertions.
//
-// The browser processes each SpeculationHost::UpdateSpeculationCandidates
-// message atomically: all candidates in one rule set are consumed together.
-// So if we insert a rule set containing one URL under test PLUS an
-// `immediate`-eagerness "barrier" URL, and we then wait until the barrier
-// appears in navigations, we know the browser has had time to enact
-// anything else it was going to enact from that same rule set. If the URL
+// The browser processes each rule set atomically: all of its candidates are
+// consumed together. So if we insert a rule set containing one URL under test
+// PLUS an `immediate`-eagerness "barrier" URL, and we then wait until the
+// barrier appears in `performance.getSpeculations().navigations`, we know the
+// browser has had time to enact anything else it was going to enact from that
+// same rule set. If the URL
// under test is still absent, it's absent because the browser chose not to
// enact it — which is exactly what the negative assertion is checking.
//
-// We use `immediate` (not `eager`) because `eager` is only fired
-// immediately when the kPreloadingEagerHoverHeuristics and
-// kPreloadingEagerViewportHeuristics features are both disabled; with
-// either enabled, `eager` candidates go to standby and never fire without
-// user gesture, which would deadlock the barrier.
+// We use `immediate` to trigger the barrier immediately, without needing any
+// user interaction.
//
// Returns { barrierUrl, ruleSet } so the caller can insert the rule set
// with insertSpeculationRules(ruleSet) and later verify via
@@ -99,7 +96,7 @@
// navigations array. Any URL still absent at this point is absent because
// the browser decided not to enact it, not because of timing.
async function awaitBarrierAndSnapshot(t, barrierUrl) {
- await waitForNavigation(t, barrierUrl);
+ await waitForSpeculationMatching(t, barrierUrl);
return performance.getSpeculations().navigations;
}
```
```
The name of the file: third_party/blink/web_tests/external/wpt/speculation-rules/speculation-measurement/performance-speculations-navigations-eagerness.tentative.https.html
Insertions: 29, Deletions: 19.
@@ -11,7 +11,7 @@
<script src="support/speculation-measurement-utils.js"></script>
<body>
<script>
-// performance.getSpeculations().navigations reports the speculative
+// `performance.getSpeculations().navigations` reports the speculative
// navigations that the browser has actually started, not the set of
// candidates a site has proposed. These tests exercise that filtering per
// eagerness level:
@@ -23,7 +23,8 @@
// - once the user interacts with a link that matches the rule, the
// corresponding entry SHOULD appear.
//
-// Synchronization: all subtests use polling (waitForNavigation) or barrier
+// Synchronization: all subtests use polling (waitForSpeculationMatching) or
+// barrier
// synchronization (awaitBarrierAndSnapshot) — never fixed-duration
// timeouts — so they are fast when the state flips quickly and robust on
// slow bots.
@@ -37,7 +38,8 @@
promise_test(async t => {
// A single moderate-eagerness prefetch rule listing many URLs. Without
// any user interaction, the browser parks these on-standby and does NOT
- // prefetch. Therefore navigations must NOT list them.
+ // prefetch. Therefore `performance.getSpeculations().navigations` must NOT
+ // list them.
const moderateUrls = Array.from({length: 10},
(_, i) => uniqueNavigationUrl(`moderate-no-interaction-${i}`));
@@ -51,10 +53,12 @@
const matches = navsAfterBarrier
.filter(n => moderateUrls.includes(n.url));
assert_equals(matches.length, 0,
- "moderate-eagerness candidates must NOT appear in navigations before " +
+ "moderate-eagerness candidates must NOT appear in " +
+ "`performance.getSpeculations().navigations` before " +
"any user interaction triggers them (got: " +
matches.map(m => m.url).join(", ") + ")");
-}, "Moderate-eagerness prefetch rule alone does not populate navigations");
+}, "Moderate-eagerness prefetch rule alone does not populate " +
+ "`performance.getSpeculations().navigations`");
promise_test(async t => {
// Same shape, but conservative eagerness. Conservative candidates only
@@ -73,15 +77,16 @@
const matches = navsAfterBarrier
.filter(n => conservativeUrls.includes(n.url));
assert_equals(matches.length, 0,
- "conservative-eagerness candidates must NOT appear in navigations " +
+ "conservative-eagerness candidates must NOT appear in " +
+ "`performance.getSpeculations().navigations` " +
"before pointerdown triggers them");
}, "Conservative-eagerness prefetch rule alone does not populate " +
- "navigations");
+ "`performance.getSpeculations().navigations`");
promise_test(async t => {
// A document-source rule with a `where` predicate that matches every
- // internal anchor at moderate eagerness — the Shopify-storefront shape.
- // No user interaction. navigations must NOT include any of these.
+ // internal anchor at moderate eagerness. No user interaction, so
+ // `performance.getSpeculations().navigations` must NOT include any of these.
const targetUrls = Array.from({length: 5},
(_, i) => uniqueNavigationUrl(`doc-rule-moderate-${i}`));
@@ -115,11 +120,12 @@
const matches = navsAfterBarrier
.filter(n => targetUrls.includes(n.url));
assert_equals(matches.length, 0,
- "moderate document-source candidates must NOT appear in navigations " +
+ "moderate document-source candidates must NOT appear in " +
+ "`performance.getSpeculations().navigations` " +
"before user interaction (got: " + matches.map(m => m.url).join(", ") +
")");
}, "Moderate document-source prefetch rule with anchors does not populate " +
- "navigations without user interaction");
+ "`performance.getSpeculations().navigations` without user interaction");
promise_test(async t => {
// Mixed rule set: two URLs at eager eagerness (should appear) and two at
@@ -127,7 +133,8 @@
// per-candidate, not per-ruleset.
//
// The immediate URLs themselves serve as the barrier: once both are in
- // navigations, the browser has finished with this rule set.
+ // `performance.getSpeculations().navigations`, the browser has finished with
+ // this rule set.
const immediateUrls = [
uniqueNavigationUrl('mixed-immediate-0'),
uniqueNavigationUrl('mixed-immediate-1'),
@@ -144,14 +151,16 @@
]
});
- // Wait for both immediate URLs to land in navigations.
- await Promise.all(immediateUrls.map(u => waitForNavigation(t, u)));
+ // Wait for both immediate URLs to land in
+ // `performance.getSpeculations().navigations`.
+ await Promise.all(immediateUrls.map(u => waitForSpeculationMatching(t, u)));
const data = performance.getSpeculations();
const moderateMatches = data.navigations
.filter(n => moderateUrls.includes(n.url));
assert_equals(moderateMatches.length, 0,
- "moderate-eagerness URLs must NOT appear in navigations even after " +
+ "moderate-eagerness URLs must NOT appear in " +
+ "`performance.getSpeculations().navigations` even after " +
"sibling immediate-eagerness URLs from the same rule set have been " +
"enacted (got: " + moderateMatches.map(m => m.url).join(", ") + ")");
}, "Mixed eagerness rule: only immediate candidates appear, not moderate");
@@ -165,18 +174,18 @@
promise_test(async t => {
// Only unique candidates count. If a rule declares the same URL twice, or
// two rules declare the same URL, the URL should appear at most once in
- // navigations for a given action.
+ // `performance.getSpeculations().navigations` for a given action.
const url = uniqueNavigationUrl('dedup-test');
insertSpeculationRules({
prefetch: [
{ urls: [url, url], eagerness: 'immediate' },
- { urls: [url], eagerness: 'immediate' },
+ { urls: [url], eagerness: 'immediate' },
]
});
// Wait for the URL to appear at least once.
- await waitForNavigation(t, url);
+ await waitForSpeculationMatching(t, url);
// Then use a barrier via a fresh rule set to give any hypothetical
// extra-notification more time to arrive before we count.
@@ -188,6 +197,7 @@
assert_less_than_equal(matches.length, 1,
"duplicate declarations of the same (url, action) collapse to one " +
"navigation entry (got " + matches.length + ")");
-}, "navigations deduplicates (url, action) across duplicate declarations");
+}, "`performance.getSpeculations().navigations` deduplicates (url, action) " +
+ "across duplicate declarations");
</script>
</body>
```
```
The name of the file: third_party/blink/web_tests/external/wpt/speculation-rules/speculation-measurement/performance-speculations-navigations-pointerdown.tentative.https.html
Insertions: 1, Deletions: 1.
@@ -90,7 +90,7 @@
.send();
// Poll for the entry to show up.
- const nav = await waitForNavigation(t, url);
+ const nav = await waitForSpeculationMatching(t, url);
assert_equals(nav.eagerness, 'conservative',
"reported eagerness must be the declared eagerness");
assert_equals(nav.type, 'prefetch',
```
```
The name of the file: third_party/blink/web_tests/external/wpt/speculation-rules/speculation-measurement/performance-speculations-navigations.tentative.https.html
Insertions: 12, Deletions: 11.
@@ -49,7 +49,7 @@
}]
});
- const nav = await waitForNavigation(t, prefetchUrl);
+ const nav = await waitForSpeculationMatching(t, prefetchUrl);
assert_equals(nav.type, "prefetch", "type should be 'prefetch'");
assert_equals(nav.url, prefetchUrl, "url should match");
assert_equals(nav.eagerness, "immediate",
@@ -69,7 +69,7 @@
}]
});
- const nav = await waitForNavigation(t, prefetchUrl);
+ const nav = await waitForSpeculationMatching(t, prefetchUrl);
assert_true(Array.isArray(nav.tags), "tags should be an array");
assert_true(nav.tags.includes('my-tag'),
"tags should include the specified tag");
@@ -86,7 +86,7 @@
}]
});
- const nav = await waitForNavigation(t, prefetchUrl);
+ const nav = await waitForSpeculationMatching(t, prefetchUrl);
assert_equals(nav.tags, null, "tags should be null when not specified");
}, "Navigation without tags has null tags");
@@ -98,9 +98,9 @@
promise_test(async t => {
// A moderate-eagerness prefetch rule alone (no user interaction, no
- // pointerdown, no hover) must NOT populate `navigations`, because the
- // browser only parks such candidates on-standby and never prefetches
- // them.
+ // pointerdown, no hover) must NOT populate
+ // `performance.getSpeculations().navigations`, because the browser only parks
+ // such candidates on-standby and never prefetches them.
//
// Synchronization: include an eager barrier URL in the same rule set.
// Once the barrier appears in navigations, we know the browser has
@@ -140,7 +140,7 @@
});
document.head.appendChild(script);
- await waitForNavigation(t, prefetchUrl);
+ await waitForSpeculationMatching(t, prefetchUrl);
// Remove the speculation rules script element.
script.remove();
@@ -162,8 +162,9 @@
// prefetched at eager eagerness; both entries should end up in
// navigations even though only one rule set is active at any time.
//
- // `navigations` accumulates across rule replacement for distinct URLs.
- // Same-URL declarations dedup by (url, action).
+ // `performance.getSpeculations().navigations` accumulates across rule
+ // replacement for distinct URLs. Same-URL declarations dedup by
+ // (url, action).
const firstUrl = uniqueNavigationUrl('replace-first');
const secondUrl = uniqueNavigationUrl('replace-second');
@@ -174,7 +175,7 @@
});
document.head.appendChild(script1);
- await waitForNavigation(t, firstUrl);
+ await waitForSpeculationMatching(t, firstUrl);
// Swap rule sets: remove the first, add a rule for a different URL.
script1.remove();
@@ -187,7 +188,7 @@
document.head.appendChild(script2);
t.add_cleanup(() => script2.remove());
- await waitForNavigation(t, secondUrl);
+ await waitForSpeculationMatching(t, secondUrl);
// Both entries should coexist.
const data = performance.getSpeculations();
```
Speculation measurement - renderer source of truth
After having moved the speculation rules heuristics to the renderer,
this CL moves the speculation rules measurement to be based on these
renderer-side heuristics.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |