← Speculation Rules - moderate viewport heuristics controls

v152 · origin trial · speculation rules grammar

moderate_viewport_heuristics

The ruleset-level key that carries the heuristic overrides. An optional JSON object at the top level of a speculation-rules rule set, with up to three numeric fields — distance_from_pointer_down, largest_anchor_threshold, and delay — that retune Chrome’s Android “moderate”-eagerness viewport heuristic for the document. Experimental, origin-trial-gated, and not planned to ship as is.

Origin trial — experiment-only grammar

This key only has an effect in documents opted into the SpeculationRulesModerateViewportHeuristicsControl origin trial (Chrome 152–157, Android only; registration and local-flag steps on the overview). There is no specification; every contract on this page is anchored to the explainer and the linked Chromium source files, and the grammar may change or disappear without a deprecation path.

syntax

Formal position: a top-level member of the rule-set object, a sibling of prefetch, prerender, and tag — not a member of any individual rule:

rule-set = {
  "moderate_viewport_heuristics": {
    "distance_from_pointer_down": [ low, high ],   // optional; two numbers
    "largest_anchor_threshold": number,            // optional
    "delay": milliseconds                          // optional; number
  },                                // ^ the whole object is optional
  "prefetch": [ … ],   // and/or "prerender"
}

The key is valid in inline <script type="speculationrules"> blocks and in external rule sets fetched via Speculation-Rules headers alike — parsing happens in the shared rule-set parser (speculation_rule_set.cc), which makes no inline/external distinction for this key.

Source: explainer — proposal; speculation_rule_set.cc; host grammar: Speculation Rules spec

distance_from_pointer_down

TypeTwo-element array of numbers [low, high]
MeaningThe inclusive band a candidate link’s vertical distance from the user’s last pointerdown must fall inside, expressed as a fraction of screen height. Negative values are above the tap point, positive below. Links outside the band are discarded before the dominance test runs
Default[-0.3, 0.0] — the P12.5/P87.5 bounds of the middle 75% of distance_from_ptr_down_ratio values observed for clicked anchors (per the implementation comments); the compiled values are Finch feature parameters and can be adjusted server-side
ClampingEach end is clamped to [-1, 1]; after clamping, high is raised to at least low (high = max(low, high)), so an inverted band degenerates to a single value rather than an empty interval
Malformed inputIgnored unless the value is exactly a two-element array whose entries both convert to numbers; a malformed value leaves the default in place and never fails the rule set
Effect of wideningA wider band (e.g. [-0.5, 0.2]) lets more links pass the distance filter — more speculation, more candidate ambiguity for the dominance test to resolve. A band of [0, 0] restricts candidates to links level with the last tap
Source: explainer — field table; anchor_element_interaction_tracker.cc — defaults, clamping; speculation_rule_set.cc — two-number parse rule

largest_anchor_threshold

TypeNumber (a ratio, dimensionless)
MeaningHow much bigger the largest in-viewport link must be than the second largest before it is picked: the heuristic requires (size(largest) − size(second)) / size(second) > threshold, with size measured as visible area. If the two biggest links are too close in size, no candidate is chosen — the browser does not guess between near-equal targets
Default0.25 — the implementation comment records this default “was selected arbitrarily and hasn’t been tuned”, which is precisely the kind of value this experiment exists to improve
ClampingNegative values are clamped up to 0 (max(value, 0)); there is no upper clamp
Malformed inputIgnored unless the value converts to a number; the default then applies and the rule set is unaffected
Effect of changing0 always accepts the largest link even when a near-tie exists (more speculation, more wrong guesses); large values make the picker increasingly conservative, suppressing speculation on dense link grids
Source: explainer — field table + dominance test; anchor_element_interaction_tracker.cc — default rationale + clamp; speculation_rule_set.cc — numeric parse rule

delay

TypeNumber, milliseconds
MeaningThe dwell time the browser waits after picking a candidate before it triggers the speculative load. If the user scrolls again during the delay, the candidate is dropped — this is the filter that keeps fling-scrolling from wasting fetches on links that merely whip through the viewport
Default500 ms — like the threshold, the implementation comment records this default “was selected arbitrarily and hasn’t been tuned”
Clamping10–5000 ms. The implementation enforces a nonzero 10 ms floor “per privacy review” so authors cannot configure the heuristic to fire essentially immediately. The explainer’s field table documents a 05000 range; the implementation floor postdates or supersedes it and is the authoritative contract (2026-07-29)
Malformed inputIgnored unless the value converts to a number; the default then applies
Effect of changingLower values speculate sooner after a scroll stop (better perceived latency, more fling false-positives); higher values waste fewer fetches but arrive later relative to the user’s next tap
Source: anchor_element_interaction_tracker.cc — floor, ceiling, defaults; explainer — field table (documents 0–5000); speculation_rule_set.cc — numeric parse rule

parse and error semantics

The object is parsed unconditionally — including in documents with no origin-trial token — so that a third-party token registered after parsing can still take effect on the next heuristic run without a re-parse; effectiveness is gated separately at application time. Error handling is deliberately forgiving, and the implementation comment states the design rule explicitly: unknown sub-keys and malformed values are ignored; they never cause the whole rule set to fail.

Input situationBehavior
Key absentNo overrides; the document uses the (Finch-adjustable) default configuration
Field absent from the objectThat parameter keeps the default; present fields still override (fields are independently optional)
Unknown sub-key inside the objectIgnored
Wrong type (string, object, boolean…)That field is ignored; the default applies; the rule set is unaffected
distance_from_pointer_down not a 2-element number arrayBoth distance values are ignored; the default band applies
Out-of-range numbersClamped at application time (per-field rules above) — never a parse error
Source: speculation_rule_set.cc — unconditional parse + ignored-malformed rule; speculation_rule_set.h — optional fields fall back to defaults, clamped when applied; anchor_element_interaction_tracker.cc — clamping

context and exposure

Source: explainer; anchor_element_interaction_tracker.cc — application gating; runtime flag record — OS + third-party token rules

lifecycle

  1. Parse (page load / rule-set insertion): the object is read into an optional per-rule-set params struct; malformed fields are dropped here, but nothing is clamped or applied yet and no metric is recorded.
  2. Selection (document level): when the heuristic needs the configuration, the document consults its rule sets in order and uses the first rule set that carries the object; objects on later rule sets are ignored (no merging), a deliberate simplification for an experiment-only mechanism.
  3. Application (each scroll stop): if and only if the origin trial is enabled for the document, the params are clamped and layered onto the default config; the use counter kSpeculationRulesModerateViewportHeuristicsControl is recorded at this point so the metric reflects params that actually took effect.
  4. Action: the retuned distance filter, dominance test, and dwell delay govern the next candidate pick; the candidate dies if the user scrolls during the delay, and the delay timer resets on new position updates.
  5. Teardown: nothing persists — params live and die with the document’s rule sets; navigating away discards all state.
Source: speculation_rule_set.cc — parse; document_speculation_rules.cc — first-rule-set-wins; anchor_element_interaction_tracker.cc — apply, use counter, timer reset

examples

All three fields set (the explainer’s proposal example):

<script type="speculationrules">
{
  "moderate_viewport_heuristics": {
    "distance_from_pointer_down": [-0.5, 0.2],
    "largest_anchor_threshold": 0.1,
    "delay": 200
  },
  "prefetch": [
    { "source": "document", "where": { "href_matches": "/*" }, "eagerness": "moderate" }
  ]
}
</script>

Conservative experiment — only admit links nearly level with the tap, require a clear size winner, wait longer:

<script type="speculationrules">
{
  "moderate_viewport_heuristics": {
    "distance_from_pointer_down": [-0.1, 0.1],
    "largest_anchor_threshold": 0.5,
    "delay": 1500
  },
  "prefetch": [
    { "source": "document", "where": { "href_matches": "/articles/*" }, "eagerness": "moderate" }
  ]
}
</script>

Two rule sets — demonstrating first-wins: the second object is ignored, so delay stays 300, not 900:

<script type="speculationrules">
{
  "moderate_viewport_heuristics": { "delay": 300 },
  "prefetch": [ { "source": "document", "where": { "href_matches": "/*" }, "eagerness": "moderate" } ]
}
</script>
<script type="speculationrules">
{
  "moderate_viewport_heuristics": { "delay": 900 },
  "prerender": [ { "source": "document", "where": { "href_matches": "/checkout/*" }, "eagerness": "moderate" } ]
}
</script>
Source: first example verbatim from the explainer; first-wins behavior per document_speculation_rules.cc

compatibility

Engine / browserSupportEvidence
Chrome for Android 152–157Behind origin trial (first- and third-party tokens); parsed unconditionally, effective only with the tokenmilestone=152 listing; API-owner LGTM M152–M157; runtime flag record
Chrome desktop / WebViewNo effect (viewport heuristic is Android-only; trial restricted to Android)Intent; runtime flag record
Firefox / SafariNo signal; key ignored as unknown speculation-rules contentIntent vendor views
BCD / webstatusNo entries (checked 2026-07-29) — this table is the interim recordBCD api/; webstatus.dev query
Source: milestone=152 listing; Intent; API-owner LGTM; runtime flag record; BCD api/; webstatus.dev query — compiled 2026-07-29

security and privacy

Source: anchor_element_interaction_tracker.cc; runtime flag record; speculation_rule_set.cc — fetched 2026-07-29