Moves pseudo class matching helpers into Element methods, so they don't
have to be duplicated between SelectorEngine and function that checks if
element is included in invalidation set.
The current implementation of `:has()` style invalidation is divided
into two cases:
- When used in subject position (e.g., `.a:has(.b)`).
- When in a non-subject position (e.g., `.a > .b:has(.c)`).
This change focuses on improving the first case. For non-subject usage,
we still perform a full tree traversal and invalidate all elements
affected by the `:has()` pseudo-class invalidation set.
We already optimize subject `:has()` invalidations by limiting
invalidated elements to ones that were tested against `has()` selectors
during selector matching. However, selectors like `div:has(.a)`
currently cause every div element in the document to be invalidated.
By modifying the invalidation traversal to consider only ancestor nodes
(and, optionally, their siblings), we can drastically reduce the number
of invalidated elements for broad selectors like the example above.
On Discord, when scrolling through message history, this change allows
to reduce number of invalidated elements from ~1k to ~5.
Before this change, tasks associated with a destroyed document would get
stuck in the task queue forever, since document-associated tasks are not
allowed to run when their document isn't fully active (and destroyed
documents never become fully active again). This caused everything
captured by task callbacks to leak.
We now treat tasks for destroyed documents as runnable immediately,
which gets them out of the queue.
This fixes another massive GC leak on Speedometer.
Before this change, Agent held on to all of the live MutationObserver
objects via GC::Root. This prevented them from ever getting
garbage-collected.
Instead of roots, we now use a simple IntrusiveList and remove them
from it in the finalizer for MutationObserver.
This fixes a massive GC leak on Speedometer.
f7a3f78 made the layout tree invalidate only the inserted nodes
themselves, but it turned out that CSS containment invalidation relies
on the parent being invalidated as well.
There is no need for this invalidation because taking care of siblings
is already done by invalidation with `NodeInsertBefore` reason. Parent
element itself (without subtree) is always invalidated by
`Node::children_changed()` hook, so `:empty` pseudo-class invalidation
is already covered.
When checking whether an early return is possible because some ancestor
already has the whole subtree invalidation flag set, the check should
begin with the current node's parent rather than with the node itself.
Otherwise, if a node already has the whole subtree invalidation flag
set and is subsequently invalidated for the reason `NodeInsertBefore`
or `NodeRemove`, we will skip the sibling invalidation required for
these operations
This fix is required for optimizations in subsequent commits.
With this change, siblings of an inserted node are no longer invalidated
unless the insertion could potentially affect their style. By
"potentially affected," we mean elements that are evaluated against the
following selectors during matching:
- Sibling combinators (+ or ~)
- Pseudo-classes :first-child and :last-child
- Pseudo-classes :nth-child, :nth-last-child, :nth-of-type, and
:nth-last-of-type
This is not really a context, but more of a set of parameters for
creating a Parser. So, treat it as such: Rename it to ParsingParams,
and store its values and methods directly in the Parser instead of
keeping the ParsingContext around.
This has a nice side-effect of not including DOM/Document.h everywhere
that needs a Parser.
While keyword_to_foo() does return Optional<Foo>, in practice the
invalid keywords get rejected at parse-time, so we don't have to worry
about them here. This simplifies the user code quite a bit.
This ad-hoc code informs the client of a potentially changed page title.
But because we always update the title element (either the SVG or HTML
title) the client was already informed, causing the code to run twice.
Before this change, checking if fast selector matching could be used was
only enabled in style recalculation and hover invalidation. With this
change it's enabled for all callers of SelectorEngine::matches() by
default. This way APIs like `Element.matches()` and `querySelector()`
could take advantage of this optimization.
This is "update document for history step application" but that's too
long for the commit title. :^)
No code changes, just adding more FIXME comments for the new steps.
(And indented step 7's substeps for clarity.)
Corresponds to https://github.com/whatwg/html/pull/10910
Before this change, `m_needs_repaint` was reset in
`Document::record_display_list()` only when the cached display list was
absent. This meant that if the last triggered repaint used the cached
display list, we would keep repainting indefinitely until the display
list was invalidated (We schedule a task that checks if repainting is
required 60/s).
This change also moves `m_needs_repaint` from Document to
TraversableNavigable as we only ever need to repaint a document that
belongs to traversable.
Previous name for misleading because it checks if box could be scrolled
by user input event which is diffent from checking if box is scrollable.
For example box with `overflow: hidden` is scrollable but it can't be
scrolled by user input event.
Currently, this metadata is only provided on the insertion steps,
though I believe it would be useful to extend to the other cases
as well. This metadata can aid in making optimizations for these
steps by providing extra context into the type of change which
was made on the child.
Several interfaces that return a high resolution time require that
time to be coarsened, in order to prevent timing attacks. This
implementation simply reduces the resolution of the returned timestamp
to the minimum values given in the specification. Further work may be
needed to make our implementation more robust to the kind of attacks
that this mechanism is designed to prevent.
...until Document::update_style(). This allows to avoid doing full
document DOM tree traversal on each Node::invalidate_style() call.
Fixes performance regression on wpt.fyi
Now that we have implemented most of the element types, this dbgln
is a lot less useful for detecting issues on live sites, and is in
my experience more likely to log cases that are not a FIXME. It also
cleans up some of the log spam from running tests.
Prior to this change, we invalidated all elements in the document if it
used any selectors with :has(). This change aims to improve that by
applying a combination of techniques:
- Collect metadata for each element if it was matched against a selector
with :has() in the subject position. This is needed to invalidate all
elements that could be affected by selectors like `div:has(.a:empty)`
because they are not covered by the invalidation sets.
- Use invalidation sets to invalidate elements that are affected by
selectors with :has() in a non-subject position.
Selectors like `.a:has(.b) + .c` still cause whole-document invalidation
because invalidation sets cover only descendants, not siblings. As a
result, there is no performance improvement on github.com due to this
limitation. However, youtube.com and discord.com benefit from this
change.
...by replacing existing method to check if an element is affected by
invalidation property. It turned out there is no need to check if an
element is affected only by some specific property, so it's more
convenient to have a method that accepts the whole set.
...in hover style invalidation. Instead, pass down a flag that indicates
all subsequent nodes in tree traversal have to be marked for inherited
style update.
By using ancestor filters some selectors could be early rejected
skipping selector engine invocation. According to my measurements it's
30-80% hover selectors depending on the website.
Instead of allocating 3 vectors with size equal to the number of
elements potentially affected by hover:
- for the elements themselves
- for selector match state of each element before hovered node change
- for selector match state of each element after hovered node change
now we allocate none of them, but mark element for style recalculation
as we traverse the tree.
We have an optimization that allows us to invalidate only the style of
the element itself and mark descendants for inherited properties update
when the "style" attribute changes (unless there are any CSS rules that
use the "style" attribute, then we also invalidate all descendants that
might be affected by those rules). This optimization was not taking into
account that when the inline style has custom properties, we also need
to invalidate all descendants whose style might be affected by them.
This change fixes this bug by saving a flag in Element that indicates
whether its style depends on any custom properties and then invalidating
all descendants with this flag set when the "style" attribute changes.
Unlike font relative lengths invalidation, for elements that depend on
custom properties, we need to actually recompute the style, instead of
individual properties, because values without expanded custom properties
are gone after cascading, and it has to be done again.
The test added for this change is a version of an existing test we had
restructured such that it doesn't trigger aggressive style invalidation
caused by DOM structured changes until the last moment when test results
are printed.