Previously, we were accessing the performance through the current
window object. Thus caused a crash when `animate()` was called on an
element within a document with no associated window object. The global
object is now used to access the performance object in places where
a window object is not guaranteed to exist.
Once we have built up a cache, we can use that internally for operations
on the collection, instead of copying over the list of elements every
time.
On a synthentic benchmark of a page with ~500 link elements, this
results in a 45% percent speedup on my machine.
```html
<body>
<ul>
<li><a href="#">Link 1</a></li>
...
<li><a href="#">Link N</a></li>
</ul>
<script>
window.onload = function() {
const startTime = performance.now();
for (let i = 0; i < 1_000_000; ++i) {
const numLinks = document.links.length;
}
const endTime = performance.now();
const timeTaken = endTime - startTime;
console.log(timeTaken);
};
</script>
</body>
</html>
```
These changes do not solve hanging `location.reload()` and
`location.go()` but only align implementation with the latest edits in
the specification.
`WindowProxy-Get-after-detaching-from-browsing-context` test output is
affected because `iframe.remove();` no longer synchronously does
destruction of a document, but queues a task on event loop.
Co-Authored-By: Andrew Kaster <akaster@serenityos.org>
Our implementation was errantly matching HTML tags other than the list
specified by the spec. For example, a <meta name=title> tag would be a
match for document.title.
For example, bandcamp will dynamically update its title when audio is
played as follows:
document.title = "▶︎ " + document.title;
And bandcamp also has a <meta name=title> tag. The result was that the
title would become "▶︎ [object HTMLMetaElement]".
Implements the "top layer" concept from "CSS Positioned Layout Module
Level 4" specification.
- The tree builder is modified to ensure that layout nodes created by
top layer elements are children of the viewport.
- Implements missing steps in `showModal()` to add an element top top
layer.
- Implements missing steps in `close()` to remove an element from top
layer.
Further steps could be:
- Add support for `::backdrop` pseudo-element.
- Implement the "inert" concept from HTML spec to block hit-testing
when element from top layer is displayed.
Since the introduction of `AbortSignal::any()`, the specification says
`AbortSignal::create_dependent_abort_signal()` should be used where
`AbortSignal::follow` was previously.
`Node::shadow_including_root()` was missing a null check, which caused
a crash when manipulating a select element, whose option elements were
initially detached.
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. :^)
Instead of invalidating animated style properties whenever
`Document::update_style()` is called, now we only do that when
animations might have actually progressed. We still have to ensure
animated properties are up-to-date in `update_style()` to ensure that
JS methods can access updated style properties.
Before this change, we ran style and layout updates from both event
loop processing and update timers. This could have caused missed resize
observer updates and unnecessary updating of style or layout more than
once before repaint.
Also, we can now be sure unnecessary style or layout updates won't
happen in `EventLoop::spin_processing_tasks_with_source_until()`.
Use the new DOM tree version mechanism to allow HTMLCollection to
remember its internal list of elements instead of rebuilding it on
every access.
This avoids thousands of full DOM walks while loading our GitHub repo.
~15% speed-up on jQuery subtests in Speedometer 3.0 :^)
This patch adds a u64 version counter to DOM::Document that increments
whenever the tree structure changes (via node insertion or removal),
or an element attribute is changed somehow.
This will be used as a crude invalidation mechanism for HTMLCollection
to cache its elements.
- 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.
This URL library ends up being a relatively fundamental base library of
the system, as LibCore depends on LibURL.
This change has two main benefits:
* Moving AK back more towards being an agnostic library that can
be used between the kernel and userspace. URL has never really fit
that description - and is not used in the kernel.
* URL _should_ depend on LibUnicode, as it needs punnycode support.
However, it's not really possible to do this inside of AK as it can't
depend on any external library. This change brings us a little closer
to being able to do that, but unfortunately we aren't there quite
yet, as the code generators depend on LibCore.
CharacterData nodes and their subclasses (most commonly Text) don't have
style, as style is specific to Elements. So there's no need to mark them
for a style update when their content is programmatically changed.
This commit introduces a WEB_SET_PROTOTYPE_FOR_INTERFACE macro that
caches the interface name in a local static FlyString. This means that
we only pay for FlyString-from-literal lookup once per browser lifetime
instead of every time the interface is instantiated.
Document::navigable() can be unpleasantly slow, since we don't have a
direct link between documents and navigables at the moment. So let's not
call it twice when once is enough.
When matching a CSS attribute selector against an HTML element, the
attribute name is case-insensitive. Before this change, that meant we
had to call equals_ignoring_ascii_case() on all the attribute names.
We now cache the attribute name lowercased on each Attr node, which
allows us to do FlyString-to-FlyString comparison (simple pointer
comparison).
This brings attribute selector matching from 6% to <1% when loading our
GitHub repo at https://github.com/SerenityOS/serenity
Patch up existing style properties instead of using the regular style
invalidation path, which requires rule matching for each element in the
invalidated subtree.
- !important properties: this change introduces a flag used to skip the
update of animated properties overridden by !important.
- inherited animated properties: for now, these are invalidated by
traversing animated element's subtree to propagate the update.
- StyleProperties has a separate array for animated properties that
allows the removal animated properties after animation has ended,
without requiring full style invalidation.
By following the spec more closely, we can actually make this function
a bit more efficient (by comparing the parent against the document
instead of looking for the first element child of the document).