When the regular HTML parser is blocked on an external script, the
speculative parser scans ahead and pre-fetches discoverable
sub-resources. Previously those fetches were tracked only in the
parser's own URL list and never registered in the document's preload
map, so when the regular parser later reached each element fetch()'s
consume_a_preloaded_resource() lookup found nothing and issued a
duplicate request — every parser-blocked sub-resource was fetched
twice.
issue_speculative_fetch now creates a PreloadEntry, registers it
under create_a_preload_key(request) in the document's preload map,
and supplies a processResponseConsumeBody callback that populates
the entry. The map insertion happens after fetch() starts because
fetch() runs consume_a_preloaded_resource() synchronously, so
registering the entry beforehand would short-circuit the
speculative fetch itself.
The body-handling steps (1, 2, 5 of the preload algorithm's
processResponseConsumeBody) are factored into a shared
deliver_preload_response helper used by both the speculative parser
and HTMLLinkElement::preload.
IFrame geometry changes and object representation changes directly
selected style invalidation reasons from their HTML element classes.
Move those mappings into a new
CSS::Invalidation::EmbeddedContentInvalidator.
The HTML elements continue to own their loading, representation, and
layout-tree side effects. CSS invalidation now owns the style dirtiness
associated with those embedded-content changes.
Element::set_shadow_root directly selected the style invalidation reason
used when a shadow root changes. Move that mapping into
CSS::Invalidation::ElementStateInvalidator.
Element still owns the shadow-root state transition and the required
layout-tree invalidation. CSS invalidation now owns the style dirtiness
for that state change.
Element::set_being_activated directly selected the style invalidation
reason used for :active changes. Move that mapping into
CSS::Invalidation::ElementStateInvalidator.
The element continues to own its activation state. CSS invalidation now
owns the style invalidation work associated with changing that state.
CustomStateSet directly selected the style invalidation reason used when
its JS-visible set is modified. Move that mapping into
CSS::Invalidation::CustomElementInvalidator.
This keeps the custom-state container focused on its set contents while
CSS invalidation owns the style work required by :state() selectors.
HTMLInputElement still mapped its picker open-state change directly to a
style invalidation reason. Move that mapping into
CSS::Invalidation::ElementStateInvalidator alongside the matching select
open-state helper.
This keeps another element-state invalidation decision out of the HTML
implementation without changing the invalidation behavior.
Several HTML element state changes directly selected style invalidation
reasons from their element implementations. Move those mappings into a
new CSS::Invalidation::ElementStateInvalidator helper.
This keeps details, dialog, option, and select code focused on their own
state changes while CSS invalidation owns the style work those changes
require. The existing invalidation breadth is preserved.
Document.cpp still flushed pending :has() invalidation by walking the
document and shadow-root style scopes directly. Move that CSS-specific
flush into CSS::Invalidation::HasMutationInvalidator.
Document continues to own the flag that says a :has() flush is needed.
The helper now owns the style-scope work needed to invalidate elements
affected by pending :has() mutations.
Document.cpp still handled CSS fallout from stylesheet media query match
changes directly. Move active stylesheet evaluation, rule-cache
invalidation, shadow-root fallout, and slot propagation into the CSS
invalidation helper.
Document continues to decide when media queries should be evaluated.
The helper now owns the style invalidation consequences when stylesheet
media queries change match state.
Document.cpp still knew how style changes on a slot propagate to
assigned light-DOM nodes. Move that flat-tree inheritance invalidation
into CSS::Invalidation::SlotInvalidator.
The style update walk continues to decide when an element's style
changed. The helper now owns the ::slotted() consequence of dirtying
assigned slottables for a changed slot.
AdoptedStyleSheets.cpp still handled CSS-side fallout from adopted sheet
list mutations directly. Move sheet attachment and media-query setup to
a CSS invalidation helper, along with rule-cache invalidation.
The observable array callbacks continue to validate JS values and
same-document construction rules. The helper now owns the CSS work for
when a constructed stylesheet becomes visible to, or is removed from, a
Document or ShadowRoot.
Slottable.cpp still handled the style invalidation fallout from slot
assignment changes directly. Move that ::slotted()-related policy into
CSS::Invalidation::SlotInvalidator.
Slot assignment remains DOM bookkeeping. The helper now owns the
choice to dirty element slottables when they gain or lose assignment
to a slot.
CharacterData.cpp still handled the style invalidation fallout from text
mutations under dir=auto ancestors directly. Move that behavior into the
CSS language invalidation helper.
CharacterData continues to own text replacement and layout text updates.
The helper now owns the inherited :dir() restyle and :has(:dir(...))
ancestor scheduling that can follow from text content changes.
Element.cpp still handled the style invalidation fallout from part and
exportparts attribute changes directly. Move that ::part-related policy
into CSS::Invalidation::PartInvalidator.
Element continues to update the DOM token state for part attributes. The
helper now owns the style dirtiness for elements targeted through ::part
and for shadow-tree descendants exposed through exportparts.
Element.cpp still handled the CSS invalidation fallout from dir and lang
attribute changes directly. Move the descendant style dirtiness and
:has(:dir/:lang) ancestor scheduling into CSS::Invalidation.
Element continues to parse and store the DOM-facing attribute state. The
new helper owns the inherited style invalidation behavior that follows
from language and directionality changes.
Node.cpp still contained the CSS policy for full style invalidation and
property-based invalidation plans. Move that logic into
CSS::Invalidation::NodeInvalidator.
Node remains the public DOM entry point for callers that need to
invalidate style. The helper now owns the :has() metadata probing,
style-scope plan lookup, and subtree/sibling invalidation scheduling.
Document.cpp contained the CSS rule-cache matching used to decide which
elements need style updates when hover, focus, or target state changes.
Move that logic into CSS::Invalidation::PseudoClassInvalidator.
Document still owns the current state slots and chooses when a state
transition happens. The helper now owns the selector matching and
recursive invalidation pass for those pseudo-class transitions.
StyleInvalidator applies CSS invalidation plans and matches selector
features while walking DOM nodes. Move the class from DOM into the
CSS::Invalidation namespace alongside the other invalidation helpers.
Document still owns the invalidator and DOM nodes still expose the state
that gets marked, but the policy for applying invalidation plans now has
a home with the rest of the CSS invalidation code.
Element.cpp still contained the CSS logic for deciding whether an
invalidation set references features present on an element. Move that
matcher into CSS::Invalidation::InvalidationSetMatcher.
The helper uses Element's public API for classes, id, attributes,
pseudo-class state, and removed-attribute tracking. This keeps Element
focused on DOM state while CSS::Invalidation owns selector feature
matching.
Element.cpp still spelled out the :defined pseudo-class invalidation set
when custom element state changed. Move that selector policy into
CustomElementInvalidator.
This keeps Element responsible for the state transition, while
CSS::Invalidation owns the affected selector feature.
HTMLInputElement had two call sites spelling out the same checked and
unchecked pseudo-class invalidation set. Move that selector policy into
FormControlInvalidator.
This keeps the input element responsible for detecting state changes,
while CSS::Invalidation owns the affected selector features.
HTML and SVG link elements both encoded the same pseudo-class list for
hyperlink state changes. Move that CSS policy into LinkInvalidator and
have both call sites delegate to it.
This keeps element-specific code focused on detecting hyperlink state
changes, while the helper owns the affected selector features.
Element.cpp still encoded the CSS consequences of attribute changes:
class/id invalidation keys, pseudo-class triggers, and shadow-host
stylesheet fallout. Move that policy into AttributeInvalidator.
Element now reports attribute changes to the helper and exposes a small
state hook to remember removed attributes while invalidation is pending.
Element exposed a small method that encoded how :has()-affected elements
are marked dirty. Move that policy into CSS::Invalidation alongside the
rest of the :has() mutation invalidation helpers.
This keeps Element focused on DOM state while preserving the existing
subject and non-subject :has() invalidation behavior.
Node.cpp still contained selector-specific policy for sibling and
same-parent-move structural invalidation. Move that logic into
CSS::Invalidation::StructuralMutationInvalidator so DOM mutation code
can delegate structural selector dependency handling.
This is a behavior-preserving extraction. It keeps the existing
previous-sibling walk guard, sibling-distance checks, shadow-root
marking, and ancestor child-needs-style propagation.
Node.cpp still contained the policy for deciding when a DOM mutation
should schedule pending :has() invalidation work. Move that into
CSS::Invalidation::HasMutationInvalidator, next to the mutation feature
collector it depends on.
This keeps DOM mutation code focused on reporting that a mutation
happened, while CSS invalidation code owns the selector-specific checks
for :has() metadata and sibling-combinator sensitivity.
Node.cpp currently knows too much about selector invalidation metadata
when deciding whether subtree mutations can affect :has() selectors.
Pull that logic into CSS/Invalidation/HasMutationFeatureCollector so DOM
mutation code can ask a focused helper instead of inspecting
StyleInvalidationData directly.
This is a behavior-preserving extraction. It keeps the existing
conservative fallbacks for featureless subtree-sensitive selectors and
still uses the existing element property matching helper for
pseudo-class metadata.
In the future we should switch to using a better file format for this,
i.e. one that supports directly pasting CSS grammar production blocks
(https://drafts.csswg.org/css-values-4/#css-grammar-production-block)
and has support for inline comments, but we use JSON for now for
simplicity's sake.
Every DOM mutation that may affect a :has() selector enqueues an
entry in StyleScope keyed by an ancestor node. The entries were
previously stored in a Vector and linearly scanned on every insert to
deduplicate by node. We now use an OrderedHashMap instead, eliminating
the quadratic deduplication.
When an inline-relative is split by block-level descendants, the rect
computation only looked at one anonymous wrapper and returned empty
for everything else, sending abspos placement back to the initial
containing block. On Reddit this let an inline-relative ad host's <a>
overlay the entire viewport and steal clicks from the post gallery's
pager buttons.
Walk every descendant of the inline's real block container instead,
collecting fragments from any InlineNode part of the inline plus the
border-box rects of its in-flow Box descendants. Matches what other
engines expose via getClientRects() for split inlines.
While here, narrow the inline-CB detection to the triggers that
actually apply on non-atomic inlines: `position`, `filter`,
`backdrop-filter` (and their will-change hints). transform, contain
and the rest don't apply per their specs - the broader check would
have started routing the WPT contain-paint/contain-layout ib-split
tests through the inline path once the rect computation began
returning non-empty results.
Per "find flattened slotables", a <slot> whose root is a shadow root is
recursed through, not appended to the result. ::slotted() in an outer
shadow must therefore not match such an intermediate slot.
Fixes the gallery on Reddit comment pages: a re-slotted <slot> was
picking up `::slotted(:not([slot])) { display: grid }` from the inner
shadow, which made the <ul> size to its content rather than the flex
container, leaving the carousel's "next" button with a 0px translate.
When an insert or remove flips a sibling's match for :first-child,
:last-child, :nth-child, :nth-of-type, or a + / ~ combinator, only the
sibling's own match changes. Descendants only need rechecking when a
selector ties them to the sibling's position.
invalidate_structurally_affected_siblings used to mark the affected
sibling's entire subtree, so a sibling with N descendants turned a
match-state flip into N+1 element recomputes. Reuse the per-element
flags introduced for the same-parent-move optimization: when neither
affected_by_structural_pseudo_class_in_non_subject_position nor
affected_by_sibling_combinator_in_non_subject_position is set, mark
only the sibling root, and let the existing inherited-update path
reach descendants if the sibling's own style changes.
Rebaseline the impacted tests. styleInvalidations gains one per
affected sibling because set_needs_style_update increments the counter
(entire-subtree marks did not), but elementStyleRecomputations falls
accordingly.
Most paintable boxes do not expose scrollbars or resize handles. Check
for scrollable axes and resizability before constructing chrome metrics
or creating scrollbar widgets.
This keeps the common no-scrollbar case from creating a `HitTestResult`,
fetching ChromeMetrics, allocating `Scrollbar` objects, and recomputing
scrollbar geometry unnecessarily.
Previously, `Document::notify_css_background_image_loaded()` walked the
entire `PaintableBox` subtree and cleared each box's paintable cache
whenever any CSS image finished loading.
Replace this with per-image observers owned by the layout node. During
`apply_style`, each node registers as an `ImageStyleValue::Client` for
the images its style references. On load, only the affected layout
node's paintables are invalidated.
Introduce IncrementalDocumentParser, which streams the response body
through a TextCodec::StreamingDecoder into the HTMLTokenizer one chunk
at a time. The tokenizer pauses when it runs out of input and resumes
once the next chunk is appended; when the body closes we close the
tokenizer's input stream so it can finish the parse.
DocumentLoading routes HTML responses through the new parser instead of
buffering the full body before handing it to HTMLParser.
Pull the post-parse-action setup, run loop, and post-parse invocation
out of HTMLParser::run(URL, ...) into a new run_until_completion()
method. The URL overload still calls it; behavior is unchanged. The
incremental parser will use this entry point directly without going
through the URL-setting overload.
Add a ScriptCreatedParser flag plumbed through HTMLParser's constructor
and create_for_scripting(). Only document.open()'s parser sets it to
Yes. Document::close() step 3 now checks is_script_created() so it
correctly skips parsers that weren't created via document.open(),
matching the spec.
Previously the check was just `if (!m_parser)`, which incorrectly let
document.close() insert an EOF into a network-driven parser. The bug
was mostly latent because the network parser used to finish quickly,
but it matters once the network parser stays alive for the duration of
a streamed parse.
Add can_run_out_of_characters() and use it in the
NamedCharacterReference state and consume_next_if_match() so that an
open input stream gets the same code-point-at-a-time treatment as an
active document.write insertion point. Without this, a network chunk
that ends partway through a named character reference or a
multi-character match would make the tokenizer commit to a "no match"
decision before the remaining bytes arrive.
No behavior change for existing callers: the new helper still returns
false once the input stream is closed (which the StringView constructor
sets immediately).
Add an explicit "input stream closed" flag plus the streaming-input API
(append_to_input_stream, close_input_stream, is_input_stream_closed) to
let a future incremental driver feed bytes as they arrive. Rewrite
should_pause_before_next_input_character so the tokenizer pauses when
the buffer is exhausted but more bytes may still arrive, including the
case where a chunk ends in CR (CRLF normalization needs one code point
of lookahead).
Existing call sites are unaffected: the StringView constructor
immediately marks the input stream closed, and insert_eof() now also
closes the stream so document.close() drives the same exit path.
This is the streaming counterpart to TextDecoder, used by sites that
process responses with `pipeThrough(new TextDecoderStream())` or that
otherwise consume a Response body as decoded text.
The transform algorithm appends each chunk's bytes to the I/O queue
and decodes them via the underlying LibTextCodec decoder, holding back
any trailing partial UTF-8 sequence so chunk boundaries don't produce
spurious replacement characters. The flush algorithm emits a single
replacement character if a partial sequence was still pending. For
non-UTF-8 encodings the underlying decoders are stateless across
calls, so each chunk is decoded in full with nothing carried over.
This mirrors the existing TextEncoderCommon split and lets a future
TextDecoderStream share the same encoding/fatal/ignoreBOM state with
TextDecoder. The state (decoder reference, encoding name, error mode,
ignore-BOM flag, and BOM-seen flag) all moves into a
TextDecoderCommonMixin base class so both interfaces can inherit it.
CSSRuleList::evaluate_media_queries previously compared
CSSMediaRule::condition_matches() (which reads MediaQuery::m_matches,
default false) against the freshly-computed result. A brand-new @media
rule whose condition matches would therefore look like a false->true
flip the very first time it was evaluated, the same shape as the
CSSStyleSheet outer-MediaList bug fixed in the previous commit.
In practice all known paths that introduce a new @media rule
(StyleSheetList::add_sheet, AdoptedStyleSheets on_set,
CSSStyleSheet::invalidate_owners, CSSImportRule::set_style_sheet) call
through CSSStyleSheet::evaluate_media_queries eagerly and absorb the
flip before the next Document::evaluate_media_rules pass, so this
change does not move counters in the existing tests. It does make the
inner-@media handling consistent with the outer one, and protects any
future path (e.g. CSSGroupingRule::insert_rule into a nested rule
list) where a new @media rule might be evaluated for the first time
during the regular media-rule pass.
Track per-rule whether evaluate has been called yet via a sticky
m_did_evaluate flag on CSSMediaRule, and only record a flip on
subsequent evaluations.
CSSStyleSheet::evaluate_media_queries previously flagged "no recorded
result yet" as a match-state change, so every freshly-loaded sheet
fired MediaQueryChangedMatchState on the first pass through
Document::evaluate_media_rules. For sheets added through
adoptedStyleSheets that piled an extra full-document style invalidation
on top of the AdoptedStyleSheetsList one, recomputing every element a
second time for nothing.
Drop the !has_value() leg so the very first evaluation establishes the
baseline silently. The sheet's rules already entered the cascade through
StyleSheetListAddSheet, AdoptedStyleSheetsList, or invalidate_owners,
each of which performs its own targeted invalidation.
Two callers relied on the implicit "first eval forces a refresh"
behavior to handle freshly-mutated state:
- invalidate_owners resets m_did_match, then leans on the next eval to
repopulate it. With the new semantics it must also re-evaluate the
sheet eagerly so MediaList::matches() and inner @media state are
fresh before the next rule cache build reads them.
- The adoptedStyleSheets on_set callback didn't evaluate at all,
relying on Document::evaluate_media_rules to populate
MediaList::m_matches. That worked accidentally because the false
flip retriggered invalidate_rule_cache after the matches had been
populated. Mirror StyleSheetList::add_sheet by evaluating the sheet
at adopt time so the rule cache build sees the correct match state
even if it runs first (e.g. via a :has() invalidation pass).
Before applying an effect context (filter, opacity, blend-mode), the
display list player runs a culling check by tentatively switching to the
effect's parent first. When the effect was already on the stack (i.e.
the current context was a descendant of the target) that walk restored
the saveLayer prematurely. The filter was then applied to a partial
batch of commands, and subsequent commands opened a fresh saveLayer for
a second, independent application, producing visibly wrong compositing.
Fold the culling check into switch_to_context: while walking down to
apply contexts, check the bounding rect right before each EffectsData
node and abort if the command is fully clipped. Walks that only go up
never traverse an EffectsData node, so an already-applied effect is
never torn down.
This regressed in cd0705334b.
When a :has() mutation is known to come from a specific subtree, use
that subtree as the mutation root while walking observed ancestors.
Before dirtying an anchor and its non-subject descendants, check whether
any cached :has() rule for that anchor can observe the changed subtree.
This keeps unrelated descendant mutations from invalidating every rule
that merely contains :has().