The "paintable" state in Layout::Box was actually not safe to access
until after layout had been performed.
As a first step towards making this harder to mess up accidentally,
this patch moves painting information from Layout::Box to a new class:
Painting::Box. Every layout can have a corresponding paint box, and
it holds the final used metrics determined by layout.
The paint box is created and populated by FormattingState::commit().
I've also added DOM::Node::paint_box() as a convenient way to access
the paint box (if available) of a given DOM node.
Going forward, I believe this will allow us to better separate data
that belongs to layout vs painting, and also open up opportunities
for naturally invalidating caches in the paint box (since it's
reconstituted by every layout.)
Let's get this right before trying to make it fast. This patch removes
the code that tried to do less work when an element's style changes,
and instead simply invalidates the entire document.
Note that invalidations are still coalesced, and will not be
synchronized until update_style() and/or update_layout() is used.
When recomputing the style for an element that previously didn't have
a corresponding layout node, it may become necessary to create a layout
node for it.
However, we should not do this if it's within a subtree that can't have
layout children. Nor should we do it for elements who have an ancestor
with display:none.
This means we can instantiate them for pseudo-elements, which don't have
an associated Element. They all pass it to their parent as a
`Layout::Node*` and handle a lack of `layout_node()` already so this
won't affect any functionality.
getClientRects supposed to return a list of bounding DOMRect
for each box fragment of Element's layout, but most elements have
only one box fragment, so implementing it with getBoundingClientRect
is useful.
Style updates are lazy since late last year, so the StyleInvalidator is
actually hurting us more than it's helping by running the entire CSS
selector machine on the whole DOM for every attribute change.
Instead, simply mark the entire DOM dirty and let the lazy style update
mechanism run *once* on next event loop iteration.
Instead of making each Layout::Node compute style for itself, we now
compute it in TreeBuilder before even calling create_layout_node().
For non-element DOM nodes, we create the style and layout tree node
in TreeBuilder. This allows us to move create_layout_node() from
DOM::Node to DOM::Element.
Rather than following the spec exactly and creating lowercase strings,
we can simply do a case-insensitive string comparison. The caveat is
that creating attributes must follow the spec by creating the attribute
name with a lowercase string.
Note our Attribute class is what the spec refers to as just "Attr". The
main differences between the existing implementation and the spec are
just that the spec defines more fields.
Attributes can contain namespace URIs and prefixes. However, note that
these are not parsed in HTML documents unless the document content-type
is XML. So for now, these are initialized to null. Web pages are able to
set the namespace via JavaScript (setAttributeNS), so these fields may
be filled in when the corresponding APIs are implemented.
The main change to be aware of is that an attribute is a node. This has
implications on how attributes are stored in the Element class. Nodes
are non-copyable and non-movable because these constructors are deleted
by the EventTarget base class. This means attributes cannot be stored in
a Vector or HashMap as these containers assume copyability / movability.
So for now, the Vector holding attributes is changed to hold RefPtrs to
attributes instead. This might change when attribute storage is
implemented according to the spec (by way of NamedNodeMap).
'static' for a function means that the symbol shall not be made public
for the result of the current compilation unit. This does not make sense
in a header, especially not if it's a large function that is used in
more than one place and not that performance-sensitive.
There's a subtle difference here. A "block box" in the spec is a
block-level box, while a "block container" is a box whose children are
either all inline-level boxes in an IFC, or all block-level boxes
participating in a BFC.
Notably, an "inline-block" box is a "block container" but not a "block
box" since it is itself inline-level.
Until now, we've internally thought of the CSS "display" property as a
single-value property. In practice, "display" is a much more complex
property that comes in a number of configurations.
The most interesting one is the two-part format that describes the
outside and inside behavior of a box. Switching our own internal
representation towards this model will allow for much cleaner
abstractions around layout and the various formatting contexts.
Note that we don't *parse* two-part "display" yet, this is only about
changing the internal representation of the property.
Spec: https://drafts.csswg.org/css-display
Instead of doing layout synchronously whenever something changes,
we now use a basic event loop timer to defer and coalesce relayouts.
If you did something that requires a relayout of the page, make sure
to call Document::set_needs_layout() and it will get coalesced with all
the other layout updates.
There's lots of room for improvement here, but this already makes many
web pages significantly snappier. :^)
Also, note that this exposes a number of layout bugs where we have been
relying on multiple relayouts to calculate the correct dimensions for
things. Now that we only do a single layout in many cases, these kind of
problems are much more noticeable. That should also make them easier to
figure out and fix. :^)
Resolved style is a spec concept that refers to the weird mix of
computed style and used style reflected by getComputedStyle().
The purpose of this class is to produce the *computed* style for a given
element, so let's call it StyleComputer.
The original name was based on the window.getComputedStyle() API.
However, "Computed" in "getComputedStyle" is actually a misnomer that
the platform is stuck with due to backwards compatibility.
What getComputedStyle() returns is actually a mix of computed and used
values. The spec calls it the "resolved" values. So let's call this
declaration subclass "ResolvedCSSStyleDeclaration" to match.
This also moves getElementsByTagName to ParentNode to remove the code
duplication between Document and Element. This additionally fixes a bug
where getElementsByTagName did not check if the element was a
descendant, meaning it would also include the context element if the
condition matched.
The previous implementation was about a half implementation and was
tied to Element::innerHTML. This separates it and puts it into
HTMLDocumentParser, as this is in the parsing section of the spec.
This provides a near finished HTML fragment serialisation algorithm,
bar namespaces in attributes and the `is` value.
This roughly models the "queue an element task" algorithm from the spec.
For safety, this captures a strong reference to the element, and then
bundles that with a callback into a HTML::Task (that we then queue up.)
This commit changes inline CSS loaded from style attributes of HTML
elements to be loaded as CSS::ElementInlineCSSStyleDeclaration instead
of CSS::CSSStyleDeclaration, fixing a crash when the style of that
element is changed from JavaScript.
Change all the places that were including the deprecated parser, to
include the new one instead, and then delete the old parser code.
`ParentNode::query_selector[_all]()` now treat their input as a
comma-separated list of selectors, instead of just one, and return
elements that match any of the selectors in that list. This is according
to these specs:
- querySelector/querySelectorAll:
https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselector%E2%91%A0
- selector matching algorithm:
https://www.w3.org/TR/selectors-4/#match-against-tree
Problem:
- New `any_of` implementation takes the entire container so the user
does not need to pass explicit begin/end iterators. This is unused
except is in tests.
Solution:
- Make use of the new and more user-friendly version where possible.