This lets us remove the color/dimension-parsing lambdas, since these
always forwarded to the same methods. This will make it easier to later
convert those methods to take a TokenStream.
Previously, the parent CSS stylesheet, owner node and owner CSS rule
properties were not unset when removing a sheet from a StyleSheetList.
This change moves the methods for adding and removing sheets to and
from a StyleSheetList, directly into the StyleSheetList class and
ensures they are called as required by the CSSOM specification.
Rather than each element which supports dimension attributes needing to
implement parsing the attributes and setting the appropriate style, we
can generalize this functionality. This will also make each element more
closely resemble the spec text, as we will be effectively declaring, for
example, "The img element supports dimension attributes" in code.
As the parser was trying to directly unwrap an unresolved duration.
Currently we are outputting the wrong results for the serialized
duration, but this is still a step forwards.
Fixes a crash seen on: https://evaparish.com/blog/how-i-edit
Before this change, every CSS @supports rule would keep the containing
JS realm alive via a JS::Handle. This led to a GC reference cycle and
the whole realm leaked.
Since we only need the realm at construction time, we can take it as a
parameter instead, and stop storing it.
This way we can just leave it alone if the property hasn't changed.
Notably, if the play-state property has been set to 'paused', and then
the user gets the animation with JS and calls .play() on it, it should
start playing despite the play-state property value.
We don't want to always invoke the .play() method if an animation is
relevant and had a play state of running, as the play state is
essentially just "paused" or "not paused". We replace that relevancy
check with the inverse of the pause check so we don't constantly call
.play() on animations that are finished.
Also duplicates and moves the temporary context into both blocks, since
most of the time neither condition will be true.
Given a selector like `.foo .bar #baz`, we know that elements with
the class names `foo` and `bar` must be present in the ancestor chain of
the candidate element, or the selector cannot match.
By keeping track of the current ancestor chain during style computation,
and which strings are used in tag names and attribute names, we can do
a quick check before evaluating the selector itself, to see if all the
required ancestors are present.
The way this works:
1. CSS::Selector now has a cache of up to 8 strings that must be present
in the ancestor chain of a matching element. Note that we actually
store string *hashes*, not the strings themselves.
2. When Document performs a recursive style update, we now push and pop
elements to the ancestor chain stack as they are entered and exited.
3. When entering/exiting an ancestor, StyleComputer collects all the
relevant string hashes from that ancestor element and updates a
counting bloom filter.
4. Before evaluating a selector, we first check if any of the hashes
required by the selector are definitely missing from the ancestor
filter. If so, it cannot be a match, and we reject it immediately.
5. Otherwise, we carry on and evaluate the selector as usual.
I originally tried doing this with a HashMap, but we ended up losing
a huge chunk of the time saved to HashMap instead. As it turns out,
a simple counting bloom filter is way better at handling this.
The cost is a flat 8KB per StyleComputer, and since it's a bloom filter,
false positives are a thing.
This is extremely efficient, and allows us to quickly reject the
majority of selectors on many huge websites.
Some example rejection rates:
- https://amazon.com: 77%
- https://github.com/SerenityOS/serenity: 61%
- https://nytimes.com: 57%
- https://store.steampowered.com: 55%
- https://en.wikipedia.org: 45%
- https://youtube.com: 32%
- https://shopify.com: 25%
This also yields a chunky 37% speedup on StyleBench. :^)
When iterating through a @keyframes rule, it isn't possible to resolve
unresolved style properties since there are no elements. This change
allows those properties to simply pass through this helper function.
These will need to store unresolved styles as well, since they may be
built during parsing of a @keyframes rule. In that case there is no
target element or pseudo-element, and thus the value cannot be resolved.
- Compare only the animated properties
- Clone only the hash map containing animated properties, instead of
the entire StyleProperties.
Reduces `KeyframeEffect::update_style_properties()` from 10% to 3% in
GitHub profiles.
Non-recursive pseudo classes are easy to evaluate, so let's allow them
on the fast path.
Increases fast path coverage when loading our GitHub repo from 48% to
56% of all selectors evaluated.
If we determine that a selector is simple enough, we now run it using a
special matching loop that traverses up the DOM ancestor chain without
recursion.
The criteria for this fast path are:
- All combinators involved must be either descendant or child.
- Only tag name, class, ID and attribute selectors allowed.
It's definitely possible to increase the coverage of this fast path,
but this first version already provides a substantial reduction in time
spent evaluating selectors.
48% of the selectors evaluated when loading our GitHub repo are now
using this fast path.
18% speed-up on the "Descendant and child combinators" subtest of
StyleBench. :^)