LibWeb: Move "needs layout update" flag from DOM to layout tree

This is in preparation for allowing anonymous boxes to retain their
intrinsic size cache across layouts.
This commit is contained in:
Andreas Kling 2025-04-18 20:40:14 +02:00 committed by Andreas Kling
parent a122685896
commit 3c15fec303
Notes: github-actions[bot] 2025-04-20 22:32:22 +00:00
11 changed files with 51 additions and 44 deletions

View file

@ -968,8 +968,8 @@ void KeyframeEffect::update_computed_properties()
}
}
if (invalidation.relayout)
target->set_needs_layout_update(DOM::SetNeedsLayoutReason::KeyframeEffect);
if (invalidation.relayout && target->layout_node())
target->layout_node()->set_needs_layout_update(DOM::SetNeedsLayoutReason::KeyframeEffect);
if (invalidation.rebuild_layout_tree) {
// We mark layout tree for rebuild starting from parent element to correctly invalidate
// "display" property change to/from "contents" value.

View file

@ -147,7 +147,7 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
static_cast<Layout::TextNode&>(*layout_node).invalidate_text_for_rendering();
// We also need to relayout.
set_needs_layout_update(SetNeedsLayoutReason::CharacterDataReplaceData);
layout_node->set_needs_layout_update(SetNeedsLayoutReason::CharacterDataReplaceData);
}
document().bump_character_data_version();

View file

@ -1293,7 +1293,7 @@ void Document::update_layout(UpdateLayoutReason reason)
update_style();
if (!m_needs_layout_update && m_layout_root)
if (m_layout_root && !m_layout_root->needs_layout_update())
return;
// NOTE: If this is a document hosting <template> contents, layout is unnecessary.
@ -1332,8 +1332,7 @@ void Document::update_layout(UpdateLayoutReason reason)
if (auto dom_node = child.dom_node(); dom_node && dom_node->is_element()) {
child.set_has_size_containment(as<Element>(*dom_node).has_size_containment());
}
bool needs_layout_update = child.dom_node() && child.dom_node()->needs_layout_update();
if (needs_layout_update || child.is_anonymous()) {
if (child.needs_layout_update() || child.is_anonymous()) {
child.reset_cached_intrinsic_sizes();
}
child.clear_contained_abspos_children();
@ -1403,7 +1402,7 @@ void Document::update_layout(UpdateLayoutReason reason)
paintable()->recompute_selection_states(*range);
}
for_each_shadow_including_inclusive_descendant([](auto& node) {
m_layout_root->for_each_in_inclusive_subtree([](auto& node) {
node.reset_needs_layout_update();
return TraversalDecision::Continue;
});
@ -1440,8 +1439,8 @@ void Document::update_layout(UpdateLayoutReason reason)
}
is_display_none = static_cast<Element&>(node).computed_properties()->display().is_none();
}
if (node_invalidation.relayout) {
node.set_needs_layout_update(SetNeedsLayoutReason::StyleChange);
if (node_invalidation.relayout && node.layout_node()) {
node.layout_node()->set_needs_layout_update(SetNeedsLayoutReason::StyleChange);
}
if (node_invalidation.rebuild_layout_tree) {
// We mark layout tree for rebuild starting from parent element to correctly invalidate

View file

@ -1418,7 +1418,8 @@ void Node::set_needs_layout_tree_update(bool value)
break;
ancestor->m_child_needs_layout_tree_update = true;
}
set_needs_layout_update(SetNeedsLayoutReason::LayoutTreeUpdate);
if (auto layout_node = this->layout_node())
layout_node->set_needs_layout_update(SetNeedsLayoutReason::LayoutTreeUpdate);
}
}
@ -1438,27 +1439,6 @@ void Node::set_needs_style_update(bool value)
}
}
void Node::set_needs_layout_update(SetNeedsLayoutReason reason)
{
if (m_needs_layout_update)
return;
if constexpr (UPDATE_LAYOUT_DEBUG) {
// NOTE: We check some conditions here to avoid debug spam in documents that don't do layout.
auto navigable = this->navigable();
if (navigable && navigable->active_document() == &document())
dbgln_if(UPDATE_LAYOUT_DEBUG, "NEED LAYOUT {}", to_string(reason));
}
m_needs_layout_update = true;
for (auto* ancestor = parent_or_shadow_host(); ancestor; ancestor = ancestor->parent_or_shadow_host()) {
if (ancestor->m_needs_layout_update)
break;
ancestor->m_needs_layout_update = true;
}
}
void Node::post_connection()
{
}

View file

@ -316,10 +316,6 @@ public:
void set_needs_style_update(bool);
void set_needs_style_update_internal(bool) { m_needs_style_update = true; }
bool needs_layout_update() const { return m_needs_layout_update; }
void set_needs_layout_update(SetNeedsLayoutReason);
void reset_needs_layout_update() { m_needs_layout_update = false; }
bool child_needs_style_update() const { return m_child_needs_style_update; }
void set_child_needs_style_update(bool b) { m_child_needs_style_update = b; }
@ -557,8 +553,6 @@ protected:
bool m_child_needs_style_update { false };
bool m_entire_subtree_needs_style_update { false };
bool m_needs_layout_update { false };
UniqueNodeID m_unique_id;
// https://dom.spec.whatwg.org/#registered-observer-list

View file

@ -763,7 +763,8 @@ void HTMLImageElement::add_callbacks_to_image_request(GC::Ref<ImageRequest> imag
document().list_of_available_images().add(key, *image_data, true);
set_needs_style_update(true);
set_needs_layout_update(DOM::SetNeedsLayoutReason::HTMLImageElementUpdateTheImageData);
if (auto layout_node = this->layout_node())
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::HTMLImageElementUpdateTheImageData);
// 4. If maybe omit events is not set or previousURL is not equal to urlString, then fire an event named load at the img element.
if (!maybe_omit_events || previous_url != url_string.serialize())
@ -903,7 +904,8 @@ void HTMLImageElement::react_to_changes_in_the_environment()
image_request->prepare_for_presentation(*this);
// FIXME: This is ad-hoc, updating the layout here should probably be handled by prepare_for_presentation().
set_needs_style_update(true);
set_needs_layout_update(DOM::SetNeedsLayoutReason::HTMLImageElementReactToChangesInTheEnvironment);
if (auto layout_node = this->layout_node())
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::HTMLImageElementReactToChangesInTheEnvironment);
// 7. Fire an event named load at the img element.
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));

View file

@ -112,7 +112,8 @@ u32 HTMLVideoElement::video_height() const
void HTMLVideoElement::set_video_track(GC::Ptr<HTML::VideoTrack> video_track)
{
set_needs_style_update(true);
set_needs_layout_update(DOM::SetNeedsLayoutReason::HTMLVideoElementSetVideoTrack);
if (auto layout_node = this->layout_node())
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::HTMLVideoElementSetVideoTrack);
if (m_video_track)
m_video_track->pause_video({});

View file

@ -42,6 +42,7 @@
#include <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/Paintable.h>
@ -2165,7 +2166,8 @@ void finalize_a_cross_document_navigation(GC::Ref<Navigable> navigable, HistoryH
// AD-HOC: If we're inside a navigable container, let's trigger a relayout in the container document.
// This allows size negotiation between the containing document and SVG documents to happen.
if (auto container = navigable->container()) {
container->set_needs_layout_update(DOM::SetNeedsLayoutReason::FinalizeACrossDocumentNavigation);
if (auto layout_node = container->layout_node())
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::FinalizeACrossDocumentNavigation);
}
}
@ -2288,7 +2290,8 @@ void Navigable::set_viewport_size(CSSPixelSize size)
if (auto document = active_document()) {
// NOTE: Resizing the viewport changes the reference value for viewport-relative CSS lengths.
document->invalidate_style(DOM::StyleInvalidationReason::NavigableSetViewportSize);
document->set_needs_layout_update(DOM::SetNeedsLayoutReason::NavigableSetViewportSize);
if (auto layout_node = document->layout_node())
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::NavigableSetViewportSize);
}
if (auto document = active_document()) {

View file

@ -1281,4 +1281,25 @@ void NodeWithStyleAndBoxModelMetrics::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_continuation_of_node);
}
void Node::set_needs_layout_update(DOM::SetNeedsLayoutReason reason)
{
if (m_needs_layout_update)
return;
if constexpr (UPDATE_LAYOUT_DEBUG) {
// NOTE: We check some conditions here to avoid debug spam in documents that don't do layout.
auto navigable = this->navigable();
if (navigable && navigable->active_document() == &document())
dbgln_if(UPDATE_LAYOUT_DEBUG, "NEED LAYOUT {}", DOM::to_string(reason));
}
m_needs_layout_update = true;
for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
if (ancestor->m_needs_layout_update)
break;
ancestor->m_needs_layout_update = true;
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@ -45,6 +45,10 @@ public:
DOM::Element const* pseudo_element_generator() const;
DOM::Element* pseudo_element_generator();
bool needs_layout_update() const { return m_needs_layout_update; }
void set_needs_layout_update(DOM::SetNeedsLayoutReason);
void reset_needs_layout_update() { m_needs_layout_update = false; }
bool is_generated() const { return m_generated_for.has_value(); }
bool is_generated_for_before_pseudo_element() const { return m_generated_for == CSS::GeneratedPseudoElement::Before; }
bool is_generated_for_after_pseudo_element() const { return m_generated_for == CSS::GeneratedPseudoElement::After; }
@ -215,6 +219,8 @@ private:
bool m_has_been_wrapped_in_table_wrapper { false };
bool m_needs_layout_update { false };
Optional<CSS::GeneratedPseudoElement> m_generated_for {};
u32 m_initial_quote_nesting_level { 0 };

View file

@ -168,7 +168,8 @@ void SVGImageElement::fetch_the_document(URL::URL const& url)
m_animation_timer->start();
}
set_needs_style_update(true);
set_needs_layout_update(DOM::SetNeedsLayoutReason::SVGImageElementFetchTheDocument);
if (auto layout_node = this->layout_node())
layout_node->set_needs_layout_update(DOM::SetNeedsLayoutReason::SVGImageElementFetchTheDocument);
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));
},