Previously when one child of an element changed we would iterate over
every child to check whether they needed to be invalidated because they
relied on tree counting functions.
We now skip this in most cases by only doing it when at least one child
relies on tree counting functions.
Tree counting functions should be resolved at style computation time -
to do this we will need to know the element's sibling count and index.
This commit computes that information and propagates it to the various
`StyleValue::to_computed_value` methods.
This function used layout node pointer to check if it's corresponding to
viewport. There is no need for that, since `is_viewport_paintable()`
does exactly the same check without going through layout node.
Before this change, you could only scroll the current hovered scroll
container, even if it was at the beginning or end and thus having no
effect.
Now, if it doesn't update, it will not be classed as handled and will
move onto the next scroll container.
Before this change, we always updated paint-only properties for every
single paintable after layout or style changes.
This could get very expensive in large documents, so this patch makes
it something we can do partially based on "repaint" invalidations.
This cuts down time spent in paint-only property update when scrolling
https://imdb.com/ from 19% to 5%.
We now also more closely follow the spec when computing values for
font-weight and we now:
- Support relative lengths in `calc()`s
- Properly clamp `calc()`s
- Support relative keywords (e.g. lighter, bolder)
- Respect that font-weight can be a non-integer number.
This does expose a few false positives in the font-weight-computed.html
WPT test. This is because we don't recompute non-inherited font-weight
within `recompute_inherited_style` which means that relative keyword
values can fall out of sync with their parent's value. These previously
passed as we treated `bolder` and `lighter` as aliases for `bold` and
`normal` respectively.
`StyleValue`s stored within `ComputedProperties` should be in their
computed forms, this is for various reasons including:
- Inheritance should be of computed values
- Animations should work on computed values
- Triggering transitions should work on computed values
Currently we store `StyleValue`s in an absolutized version of the
specified value - this is equivalent to the computed form in many cases
which is why this hasn't been causing significant issues but there are
some cases - such as `border-*-width` keywords where this is not the
case.
No functionality change as we are yet to implement any properties
The StyleValue stored in m_property_values is already in it's computed
form and it's trivial to pull the underlying value out so there is no
need to store this separately.
Also removes unnecessary handling of percentage values in
`absolutize_values` - this is already handled within `compute_font`.
And make it a DOM::Node, not DOM::Element. This makes everything flow
much better, such as spec texts that explicitly mention "focused area"
as the fact that we don't necessarily need to traverse a tree of
elements, since a Node can be focusable as well.
Eventually this will need to be a struct with a separate "focused area"
and "DOM anchor", but this change will make it easier to achieve that.
As `recompute_inherited_style` works in-place rather than building
ComputedProperties from scratch we need to keep track of which animated
properties are inherited to know whether we should remove them when we
have no more inherited value.
When a subtree is projected through a slot, its root now inherits style
from the slot's parent, rather than the parent of the unprojected root.
This fixes a ton of subtle issues, and is very noticeable on Reddit.
This was a misinterpretation of the spec; we should only indicate focus
if the form associated element supports keyboard input, for which
FormAssociatedTextControlElement is a much better match.
This reverts 0e3487b9ab.
Back when I made that change, I thought we could make our StyleValue
classes match the typed-om definitions directly. However, they have
different requirements. Typed-om types need to be mutable and GCed,
whereas StyleValues are immutable and ideally wouldn't require a JS VM.
While I was already making such a cataclysmic change, I've moved it into
the StyleValues directory, because it *not* being there has bothered me
for a long time. 😅
This has quite a lot of fall out. But the majority of it is just type or
UDL substitution, where the changes just fall through to other function
calls.
By changing property key storage to UTF-16, the main affected areas are:
* NativeFunction names must now be UTF-16
* Bytecode identifiers must now be UTF-16
* Module/binding names must now be UTF-16
Before this change, whenever element's attributes changed, we would add
a flag to "pending invalidation", indicating that all descendants whose
style uses CSS custom properties needed to be recomputed. This resulted
in severe overinvalidation, because we would run invalidation regardless
of whether any custom property on affected element actually changed.
This change takes another approach, and now we decide whether
descendant's style needs to be recomputed based on whether ancestor's
style recomputation results in a change of custom properties, though
this approach adds a little overhead to style computation as now we have
to compare old vs new hashmap of custom properties.
This brings substantial improvement on discord and x.com where, before
this change, advantage of using invalidation sets was lost and we had
to recompute all descendants, because almost all of them use custom
properties.
This porting effort makes it pretty clear we will want a UTF-16-aware
GenericLexer. But for now, we can actually make ASCII assumptions about
what we are parsing, and act accordingly.
`AnimationTimeline` visits pointers of all registered animations, so if
element is removed from DOM tree but its animations remain registered in
timeline, then `Animation` and owner `Element` will be kept alive until
`AnimationTimeline` is destroyed.
...and setter. We had lots of places where we check if pseudo-element
type is specified and then use `pseudo_element_computed_properties()` or
`computed_properties()`. This change moves these checks from caller side
to the getter and setter.
Whenever we end up with a scrollable overflow rect that goes beyond
either of its axes (i.e. the rect has a negative X or Y position
relative to its parent's absolute padding box position), we need to clip
that rect to prevent going into the "unreachable scrollable overflow".
This fixes the horizontal scrolling on https://ladybird.org (gets more
pronounced if you make the window very narrow).
This stops `::before` and `::after` nodes showing up for every single
element in the inspector tree. Unfortunately there's no way for us to
detect that one of these doesn't exist in layout but has *some* style
specified for it, but that seems like a rare use case.
Corresponds to dad91f49ef
The spec text doesn't actually require any changes from us, but I
noticed we were incorrectly calling `is_shadow_including_ancestor_of()`
instead of `is_shadow_including_inclusive_ancestor_of()`, so that's
fixed.
There are multiple things happening here which are interconnected:
- We now use AbstractElement to refer to the source of a counter, which
means we also need to pass that around to compute `content`.
- Give AbstractElement new helper methods that are needed by
CountersSet, so it doesn't have to care whether it's dealing with a
true Element or PseudoElement.
- The CountersSet algorithms now walk the layout tree instead of DOM
tree, so TreeBuilder needs to wait until the layout node exists
before it resolves counters for it.
- Resolve counters when creating a pseudo-element's layout node. We
awkwardly compute the `content` value up to twice: Once to figure out
what kind of node we need to make, and then if it's a string, we do
so again after counters are resolved so we can get the true value of
any `counter()` functions. This will need adjusting in the future but
it works for now.
This is one of those cases where the spec says "element" and
means "element or pseudo-element". The easiest way to handle both is to
make these be free functions that take an AbstractElement, and then
give AbstractElement some helper methods so that the caller doesn't
have to care which it's dealing with.
There are some FIXMEs here because PseudoElement doesn't have a
CountersSet yet, and because the CountersSet currently uses a
UniqueNodeID to identify counter sources, which doesn't support
pseudo-elements.
All other viewport-related dimensions are referenced to by 'viewport',
so let's rename the member that stores the viewport size to prevent
further confusion.
`Element::ordinal_value` is called for every `li` element in
a list (ul, ol, menu).
Before:
`ordinal_value` iterates through all of the children of the list
owner. It is called once for each element: complexity $O(n^2)$.
After:
- Save the result of the first calculation in `m_ordinal_value`
then return it in subsequent calls.
- Tree modifications are intercepted and trigger invalidation
of the first node's `m_ordinal_value`:
- insert_before
- append
- remove
Results in noticeable performance improvement rendering' large
lists: from 20s to 4s for 20K elements.
Before:
`is<HTMLOLListElement>` and other similar calls in this commit
are resolved to `dynamic_cast`, which incurs runtime overhead
for resolving the type. The Performance hit becomes apparent
when rendering large lists. Callgrind analysis points to a
significant performance hit from calls to `is<...>` in
`Element::list_owner`.
Reference: Michael Gibbs and Bjarne Stroustrup (2006) Fast dynamic
casting. Softw. Pract. Exper. 2006; 36:139–156
After:
Implement inline `fast_is` virtual method that immediately
resolves the type. Results in noticeable performance improvement
2x-ish for lists with 20K entries.
Bonus: Convert starting value for LI elements to signed integer
The spec requires the start attribute and starting value to be
"integer". Firefox and Chrome support a negative start attribute.
FIXME: At the time of this PR, about 134 other objects resolve
`is<...>` to `dynamic_cast`. It may be a good idea to coordinate
similar changes to at least [some of] the ones that my have impact
on performance (maybe open a new issue?).
Previously we would just throw it away and construct a new (empty) one
when required. This doesn't work as any existing references to the old
instance will contain out of date information. Now we retain and update
the existing instance instead.
Which has an optmization if both size of the string being passed
through are FlyStrings, which actually ends up being the case
in some places during selector matching comparing attribute names.
Instead of maintaining more overloads of
Infra::is_ascii_case_insensitive_match, switch
everything over to equals_ignoring_ascii_case instead.
The play_or_cancel_animations_after_display_property_change() helper
was being called by Node::inserted() and Node::removed_from() and then
recursing into the shadow-including subtree.
This had quadratic complexity since inserted() and removed_from() are
themselves already invoked recursively for everything in the
shadow-including subtree.
Only one caller of this API actually needed the recursive behavior,
so this patch moves that responsibility to the caller and puts the logic
in style recomputation instead.
1.02x speedup on Speedometer's TodoMVC-jQuery.
After f7a3f785a8, sibling nodes' styles
were no longer invalidated after a node was removed. This reuses the
flag for `:first-child` and `:last-child` to indicate that a node's
style might be affected by any structural change in its siblings.
Fixes#4631.
Resolves the `:only-child` ACID3 failure as documented in #1231.
For attributes like Element.ariaControlsElements, which are a reflection
of FrozenArray<Element>, we must return the same JS::Array object every
time the attribute is invoked - until its contents have changed. This
patch implements caching of the reflected array in accordance with the
spec.
To allow for adding the concept of a WorkerAgent to be reused
between shared and dedicated workers. An event loop is the
commonality between the different agent types, though, there
are some differences between those event loops which we customize
on the construction of the HTML::EventLoop.
We are meant to store a weak reference to the element indicated by this
attribute, rather than a GC-protected strong reference. This also hoists
the "get the attr-associated element" AO into its own function, rather
than being hidden in IDL, to match "get the attr-associated elements".
There are ARIA attributes, e.g. ariaControlsElements, which refer to a
list of elements by their ID. For example:
<div aria-controls="item1 item2">
The div.ariaControlsElements attribute would be a list of elements whose
ID matches the values in the aria-controls attribute.
For unscrolled elements (already at 0, 0) that aren't eligible to be the
Document.scrollingElement, we can short-circuit here and avoid doing a
synchronous relayout of the page.
This avoids a bunch of synchronous layouts on Speedometer3's NewsSite
subtests.
Before this change, we were going through the chain of base classes for
each IDL interface object and having them set the prototype to their
prototype.
Instead of doing that, reorder things so that we set the right prototype
immediately in Foo::initialize(), and then don't bother in all the base
class overrides.
This knocks off a ~1% profile item on Speedometer 3.
Many elements never end up needing this string, so instead of eagerly
generating it in the Element constructor, let's defer it until someone
actually requests it.
Knocks off a ~1% profile item on Speedometer3's jQuery test.
We achieve this by keeping track of all checked pseudo class selectors
in the SelectorEngine code. We also give StyleComputer per-pseudo-class
rule caches.
These callbacks are evaluated synchronously via JS::Call. We do not need
to construct an expensive RootVector container just to immediately
invoke the callbacks.
Stylistically, this also helps indicate where the actual arguments start
at the call sites, by wrapping the arguments in braces.
Before this change, we'd skip storing the new ComputedProperties in
Element::recompute_style() if there was no invalidation needed.
This caused us to lose the information about which properties are
inherited and/or important (which is also carried by ComputedProperties,
but doesn't affect invalidation).
Consequently, we'd then fail to recompute inherited styles, since that
mechanism depends on this data.
The fix is simply to always store the new ComputedProperties.