The deterministic viewport-based predictor for moderate
eagerness speculation rules
(kNavigationPredictorNewViewportFeatures +
kPreloadingModerateViewportHeuristics) is
#ifdef BUILDFLAG(IS_ANDROID)-gated and thus
disabled on desktop. As a result, web
developers using DevTools mobile emulation cannot observe or test
the predictor that ships to Android users.
This CL flips the IS_ANDROID-keyed defaults so the
predictor and its tunables behave the same on every platform
speculation rules ship to. Mobile emulation in DevTools therefore
automatically exercises the heuristic, and so does normal desktop
browsing. WebView keeps its existing opt-out via
android_webview/browser/aw_field_trials.cc, and Finch
can still disable per-platform if a field experiment wants to.
A more conservative variant (runtime-gate the heuristic on
Settings::ViewportEnabled(), which only flips under
DevTools mobile emulation on desktop) was prototyped but had too
many follow-on browser-side gates to be a tidy CL. The behavior
change for desktop end users is small (capped at two concurrent
moderate-eagerness prefetches, only on visible anchors that are
larger than their nearest runner-up by >25%), so the simpler
default-on path is the recommended fix.
The companion concern in gilbertoc@google.com's comment -- that
even when the prefetch fires under emulation, the prefetched
response may be the desktop variant of the page because
PrefetchService's outgoing request doesn't carry the
DevTools-emulated UA / Client Hints / viewport headers -- is a
separate plumbing problem and is out of scope of this CL. See
"Known follow-ups" at the bottom.
heuristic.html -- source page with
one visually-dominant "hero" anchor and three smaller runner-ups.
A <script type="speculationrules"> document
rule registers every a.anchor-card as a
moderate-eagerness prefetch candidate. The moderate viewport
heuristic, when it runs, picks the largest in-viewport anchor and
enacts that pending candidate.
target.html -- destination of the
hero. Reads PerformanceNavigationTiming on load and
prints a verdict (prefetch hit /
possibly prefetched /
cold fetch) based on
deliveryType, transferSize, and response
time.
dummy-1/2/3.html -- runner-up
destinations. Should not be picked by the heuristic when the hero
is the largest in-viewport anchor.
| Build | Heuristic fires? |
|---|---|
| Stock canary, desktop | no — this is the bug |
Stock canary, desktop, with all three feature flags forced on
via --enable-features (see "approximation"
command below)
|
yes (prove the chain works without rebuilding) |
Patched canary (branch i-420724833, commit
dbf6d0c442b96), desktop
|
yes — this is the fix |
| Any build, real Android | yes (already works today) |
google-chrome-canary --user-data-dir=/tmp/i-420724833-prepatch
F12), dock to the right, switch to
Application -> Speculative loads -> Preloads.
https://static.januschka.com/i-420724833/heuristic.html.
The Rules tab should show one ruleset with 4
moderate-eagerness prefetch candidates registered.
Space,
Page Down) so the gradient HERO is the dominant
in-viewport anchor. Wait ~600ms.
target.html's verdict box
reports NOT PREFETCHED with
transferSize > 0 and a normal RTT.
To prove the rest of the predictor pipeline works without rebuilding, you can launch any unpatched canary with all three Android-only-on defaults forced on:
google-chrome-canary \
--user-data-dir=/tmp/i-420724833-flagged \
--enable-features=NavigationPredictorNewViewportFeatures,PreloadingModerateViewportHeuristics:enact_candidates/true,NavigationPredictor:random_anchor_sampling_period/1
With these flags applied, the same scroll interaction in step A should fire the moderate viewport heuristic on the hero. If this works on your binary but the patched build doesn't, the patch isn't actually applied to the binary you launched.
cd ~/chromium/src
git fetch origin
git checkout -b i-420724833 origin/main
git cherry-pick dbf6d0c442b96 # 3-file CL
autoninja -C out/Default chrome
--enable-features:
out/Default/chrome --user-data-dir=/tmp/i-420724833-postpatch
https://static.januschka.com/i-420724833/heuristic.html.
kPreloadingModerateViewportHeuristics.delay) the
hero's row flips to Ready:
.../i-420724833/target.html?from=heuristicprefetchmoderateModerate viewport heuristictarget.html's verdict box reports
LIKELY SERVED FROM PREFETCH CACHE,
with transferSize = 0 and a non-zero
decodedBodySize.
| Check | Pre-patch | Post-patch |
|---|---|---|
| Speculative loads panel shows trigger Moderate viewport heuristic | no | yes (within ~500ms) |
target.html verdict |
cold fetch | prefetch cache hit |
| Behavior under DevTools mobile emulation | heuristic still off | heuristic on, same as without emulation |
Branch i-420724833, commit
dbf6d0c442b96, 3 files / +19 / -19.
third_party/blink/common/features.cc --
kNavigationPredictorNewViewportFeatures and
kPreloadingModerateViewportHeuristics default to
FEATURE_ENABLED_BY_DEFAULT on every platform.content/browser/preloading/preloading_decider.cc --
enact_candidates param defaults to
true, so the largest in-viewport anchor is actually
prefetched (not just logged as a prediction).third_party/blink/renderer/core/html/anchor_element_metrics_sender.cc
-- random_anchor_sampling_period default flipped to
1, so AllAnchorsSampledIn() is true
and the moderate heuristic registers as a viewport observer.out/Default/blink_unittests \
--gtest_filter='AnchorElement*:NavigationPredictor*:AnchorElementInteraction*'
-> 61/61 passed
xvfb-run -a out/Default/unit_tests \
--gtest_filter='NavigationPredictor*'
-> 31/31 passed
xvfb-run -a out/Default/content_unittests \
--gtest_filter='AnchorElementInteractionHostImplTest.*:PreloadingDeciderTest.*'
-> 21/21 passed
gilbertoc@google.com noted that even when the moderate viewport
heuristic fires under emulation, the prefetched response may be
the desktop variant of the page because
PrefetchService's outgoing request doesn't carry the
DevTools-emulated User-Agent, Client Hints, or viewport headers.
Plumbing the per-frame UA / Client Hints overrides through to the
prefetch fetcher (so the prefetched HTML matches what the
eventually-navigated request would receive) is a separate change
in content/browser/preloading/prefetch/. It is not
addressed by this CL; tracking under the same bug is the expected
follow-up.