The upcoming generated types will match those for pseudo-classes: A
PseudoElementSelector type, that then holds a PseudoElement enum
defining what it is. That enum will be at the top level in the Web::CSS
namespace.
In order to keep the diffs clearer, this commit renames and moves the
types, and then a following one will replace the handwritten enum with
a generated one.
This also implements the `:high-value` and `:low-value` that are in the
spec.
Same note as before about this being based on the very-drafty CSS Forms
spec. In fact, some of this isn't even in that spec yet. Specifically,
the `:suboptimal-value` and `:even-less-good-value` names are undecided
and subject to change. However, it's clear that this is a pseudo-class
situation, not a pseudo-element one, so I think this is still an
improvement, as it allows styling of the `::fill` pseudo-element
regardless of what state it is in.
Relevant spec issue: https://github.com/openui/open-ui/issues/1130
If an element is affected only by selectors using the direct sibling
combinator `+`, we can calculate the maximum invalidation distance and
use it to limit style invalidation. For example, the selector
`.a + .b + .c` has a maximum invalidation distance of 2, meaning we can
skip invalidating any element affected by this selector if it's more
than two siblings away from the element that triggered the style
invalidation.
This change results in visible performance improvement when hovering
PR list on GitHub.
Instead of checking all elements in a document for containment in
`:has()` invalidation set, we could narrow this down to ancestors and
ancestor siblings, like we already do for subject `:has()` invalidation.
This change brings great improvement on GitHub that has selectors with
non-subject `:has()` and sibling combinators (e.g., `.a:has(.b) ~ .c`)
which prior to this change meant style invalidation for whole document.
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.
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
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.
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.
Instead of recalculating styles for all nodes in the common ancestor of
the new and old hovered nodes' subtrees, this change introduces the
following approach:
- While calculating ComputedProperties, a flag is saved if any rule
applied to an element is affected by the hover state during the
execution of SelectorEngine::matches().
- When the hovered element changes, styles are marked for recalculation
only if the flag saved in ComputedProperties indicates that the
element could be affected by the hover state.
This change fixes selector matching for non-HTML elements that have
mixed-case names — such as the SVG foreignObject element.
Otherwise, without this change, attempting to use a selector to match
such an element — e.g., document.querySelector("foreignObject") — fails.
In conformance with the requirements of the spec PR at
https://github.com/whatwg/html/pull/9546, this change adds support for
the “switch” attribute for type=checkbox “input” elements — which is
shipping in Safari (since Safari 17.4). This change also implements
support for exposing it to AT users with role=switch.
There are essentially 3 URL parsing AOs defined by the spec:
1. Parse a URL
2. Encoding parse a URL
3. Encoding parse a URL and serialize the result
Further, these are replicated between the Document and the ESO.
This patch defines these methods in accordance with the spec and updates
existing users to invoke the correct method. In places where the correct
method is ambiguous, we use the encoding parser to preserve existing ad-
hoc behavior.
The DOM spec defines what it means for an element to be an "editing
host", and the Editing spec does the same for the "editable" concept.
Replace our `Node::is_editable()` implementation with these
spec-compliant algorithms.
An editing host is an element that has the properties to make its
contents effectively editable. Editable elements are descendants of an
editing host. Concepts like the inheritable contenteditable attribute
are propagated through the editable algorithm.
Fixes an issue where selectors inside a shadow root could incorrectly
match their shadow host directly using selectors like #host,
instead of requiring :host pseudo-class selectors.
Fixes issue #2319
Resulting in a massive rename across almost everywhere! Alongside the
namespace change, we now have the following names:
* JS::NonnullGCPtr -> GC::Ref
* JS::GCPtr -> GC::Ptr
* JS::HeapFunction -> GC::Function
* JS::CellImpl -> GC::Cell
* JS::Handle -> GC::Root
Attempt 2! Reverts 2a5dbedad4
This time, set up a different combinator when producing a relative
invalid selector rather than a standalone one. This fixes the crash.
Original description below for simplicity because it still applies.
---
Selectors like `:is(.valid, &!?!?!invalid)` need to keep the invalid
part around, even though it will never match, for a couple of reasons:
- Serialization needs to include them
- For nesting, we care if a `&` appeared anywhere in the selector, even
in an invalid part.
So this patch introduces an `Invalid` simple selector type, which simply
holds its original ComponentValues. We search through these looking for
`&`, and we dump them out directly when asked to serialize.
Selectors like `:is(.valid, &!?!?!invalid)` need to keep the invalid
part around, even though it will never match, for a couple of reasons:
- Serialization needs to include them
- For nesting, we care if a `&` appeared anywhere in the selector, even
in an invalid part.
So this patch introduces an `Invalid` simple selector type, which simply
holds its original ComponentValues. We search through these looking for
`&`, and we dump them out directly when asked to serialize.
Note that this is the old CSS2 syntax, we don't support the CSS3 syntax
just yet. Also we don't actually implement the pseudo-elements, this is
really just to make the selectors distinct from the same ones without
these pseudo-elements.
This enables a nice warning in case a function becomes dead code. Also, in the
case of {Event,Node}WrapperFactory.cpp, the corresponding header was forgotten.
This would cause an issue later when we enable -Wmissing-declarations.
Is my clang-format misconfigured? Why is the diff for NodeWrapperFactory.cpp
so large?
LibWeb keeps growing and the Web namespace is filling up fast.
Let's put DOM stuff into Web::DOM, just like we already started doing
with SVG stuff in Web::SVG.
To prepare for fully qualified tag names, let's call this local_name.
Note that we still keep an Element::tag_name() around since that's what
the JS bindings end up calling into for the Element.tagName property.
Instead of creating extremely common FlyStrings like "id" and "class"
on demand every time they are needed, we now have AttributeNames.h,
which provides Web::HTML::AttributeNames::{id,class_}
This avoids a bunch of string allocations during selector matching.
It's still only a dummy as LibWeb doesn't have focused elements yet, but
at least now we don't treat "selector:focus" as just "selector".
This fixes an issue on google.com which was mostly grey - coming from
some menu item focus styles :^)