LibWeb: Add opt-in tracing of update_layout() calls with reason

This commit is contained in:
Andreas Kling 2025-03-05 20:50:05 +01:00 committed by Alexander Kalenik
commit c333042e63
Notes: github-actions[bot] 2025-03-08 02:39:13 +00:00
21 changed files with 137 additions and 52 deletions

View file

@ -458,7 +458,7 @@ Document::Document(JS::Realm& realm, const URL::URL& url, TemporaryDocumentForFr
if (!navigable || !navigable->is_focused())
return;
node->document().update_layout();
node->document().update_layout(UpdateLayoutReason::CursorBlinkTimer);
if (node->paintable()) {
m_cursor_blink_state = !m_cursor_blink_state;
@ -1278,7 +1278,7 @@ static void propagate_overflow_to_viewport(Element& root_element, Layout::Viewpo
overflow_origin_computed_values.set_overflow_y(CSS::Overflow::Visible);
}
void Document::update_layout()
void Document::update_layout(UpdateLayoutReason reason)
{
auto navigable = this->navigable();
if (!navigable || navigable->active_document() != this)
@ -1287,7 +1287,7 @@ void Document::update_layout()
// NOTE: If our parent document needs a relayout, we must do that *first*.
// This is necessary as the parent layout may cause our viewport to change.
if (navigable->container() && &navigable->container()->document() != this)
navigable->container()->document().update_layout();
navigable->container()->document().update_layout(reason);
update_style();
@ -1303,6 +1303,8 @@ void Document::update_layout()
auto* document_element = this->document_element();
auto viewport_rect = navigable->viewport_rect();
auto timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise);
if (!m_layout_root || needs_layout_tree_update() || child_needs_layout_tree_update() || needs_full_layout_tree_update()) {
Layout::TreeBuilder tree_builder;
m_layout_root = as<Layout::Viewport>(*tree_builder.build(*this));
@ -1313,6 +1315,10 @@ void Document::update_layout()
}
set_needs_full_layout_tree_update(false);
if constexpr (UPDATE_LAYOUT_DEBUG) {
dbgln("TREEBUILD {} µs", timer.elapsed_time().to_microseconds());
}
}
// Assign each box that establishes a formatting context a list of absolutely positioned children it should take care of during layout
@ -1388,6 +1394,10 @@ void Document::update_layout()
// after the viewport size change.
if (auto window = this->window())
window->scroll_by(0, 0);
if constexpr (UPDATE_LAYOUT_DEBUG) {
dbgln("LAYOUT {} {} µs", to_string(reason), timer.elapsed_time().to_microseconds());
}
}
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node, CSS::StyleComputer& style_computer, bool needs_inherited_style_update)
@ -5405,7 +5415,7 @@ WebIDL::ExceptionOr<void> Document::set_design_mode(String const& design_mode)
if (auto active_range = selection->range(); active_range) {
TRY(active_range->set_start(*this, 0));
TRY(active_range->set_end(*this, 0));
update_layout();
update_layout(UpdateLayoutReason::DocumentSetDesignMode);
}
}
// 3. Run the focusing steps for this's document element, if non-null.
@ -5432,7 +5442,7 @@ Element const* Document::element_from_point(double x, double y)
return nullptr;
// Ensure the layout tree exists prior to hit testing.
update_layout();
update_layout(UpdateLayoutReason::DocumentElementFromPoint);
// 2. If there is a box in the viewport that would be a target for hit testing at coordinates x,y, when applying the transforms
// that apply to the descendants of the viewport, return the associated element and terminate these steps.
@ -5474,7 +5484,7 @@ GC::RootVector<GC::Ref<Element>> Document::elements_from_point(double x, double
return sequence;
// Ensure the layout tree exists prior to hit testing.
update_layout();
update_layout(UpdateLayoutReason::DocumentElementsFromPoint);
// 3. For each box in the viewport, in paint order, starting with the topmost box, that would be a target for
// hit testing at coordinates x,y even if nothing would be overlapping it, when applying the transforms that
@ -6002,7 +6012,7 @@ void Document::set_needs_to_refresh_scroll_state(bool b)
Vector<GC::Root<DOM::Range>> Document::find_matching_text(String const& query, CaseSensitivity case_sensitivity)
{
// Ensure the layout tree exists before searching for text matches.
update_layout();
update_layout(UpdateLayoutReason::DocumentFindMatchingText);
if (!layout_node())
return {};
@ -6393,4 +6403,16 @@ void Document::set_onvisibilitychange(WebIDL::CallbackType* value)
set_event_handler_attribute(HTML::EventNames::visibilitychange, value);
}
StringView to_string(UpdateLayoutReason reason)
{
switch (reason) {
#define ENUMERATE_UPDATE_LAYOUT_REASON(e) \
case UpdateLayoutReason::e: \
return #e##sv;
ENUMERATE_UPDATE_LAYOUT_REASONS(ENUMERATE_UPDATE_LAYOUT_REASON)
#undef ENUMERATE_UPDATE_LAYOUT_REASON
}
VERIFY_NOT_REACHED();
}
}

View file

@ -50,6 +50,64 @@ enum class QuirksMode {
Yes
};
#define ENUMERATE_UPDATE_LAYOUT_REASONS(X) \
X(CanvasRenderingContext2DSetFilter) \
X(CursorBlinkTimer) \
X(Debugging) \
X(DocumentElementFromPoint) \
X(DocumentElementsFromPoint) \
X(DocumentFindMatchingText) \
X(DocumentSetDesignMode) \
X(ElementCheckVisibility) \
X(ElementClientHeight) \
X(ElementClientLeft) \
X(ElementClientTop) \
X(ElementClientWidth) \
X(ElementGetClientRects) \
X(ElementIsPotentiallyScrollable) \
X(ElementScroll) \
X(ElementScrollHeight) \
X(ElementScrollIntoView) \
X(ElementScrollLeft) \
X(ElementScrollTop) \
X(ElementScrollWidth) \
X(ElementSetScrollLeft) \
X(ElementSetScrollTop) \
X(EventHandlerHandleDoubleClick) \
X(EventHandlerHandleDragAndDrop) \
X(EventHandlerHandleMouseDown) \
X(EventHandlerHandleMouseMove) \
X(EventHandlerHandleMouseUp) \
X(EventHandlerHandleMouseWheel) \
X(HTMLElementGetTheTextSteps) \
X(HTMLElementOffsetHeight) \
X(HTMLElementOffsetLeft) \
X(HTMLElementOffsetParent) \
X(HTMLElementOffsetTop) \
X(HTMLElementOffsetWidth) \
X(HTMLEventLoopRenderingUpdate) \
X(HTMLImageElementHeight) \
X(HTMLImageElementWidth) \
X(HTMLInputElementHeight) \
X(HTMLInputElementWidth) \
X(InternalsHitTest) \
X(MediaQueryListMatches) \
X(NodeNameOrDescription) \
X(RangeGetClientRects) \
X(ResolvedCSSStyleDeclarationProperty) \
X(SVGDecodedImageDataRender) \
X(SVGGraphicsElementGetBBox) \
X(SourceSetNormalizeSourceDensities) \
X(WindowScroll)
enum class UpdateLayoutReason {
#define ENUMERATE_UPDATE_LAYOUT_REASON(e) e,
ENUMERATE_UPDATE_LAYOUT_REASONS(ENUMERATE_UPDATE_LAYOUT_REASON)
#undef ENUMERATE_UPDATE_LAYOUT_REASON
};
[[nodiscard]] StringView to_string(UpdateLayoutReason);
// https://html.spec.whatwg.org/multipage/dom.html#document-load-timing-info
struct DocumentLoadTimingInfo {
// https://html.spec.whatwg.org/multipage/dom.html#navigation-start-time
@ -258,7 +316,7 @@ public:
void obtain_theme_color();
void update_style();
void update_layout();
void update_layout(UpdateLayoutReason);
void update_paint_and_hit_testing_properties_if_needed();
void update_animated_style_if_needed();

View file

@ -994,7 +994,7 @@ GC::Ref<Geometry::DOMRectList> Element::get_client_rects() const
return Geometry::DOMRectList::create(realm(), {});
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document()).update_layout();
const_cast<Document&>(document()).update_layout(UpdateLayoutReason::ElementGetClientRects);
// 1. If the element on which it was invoked does not have an associated layout box return an empty DOMRectList
// object and stop this algorithm.
@ -1047,7 +1047,7 @@ GC::Ref<Geometry::DOMRectList> Element::get_client_rects() const
int Element::client_top() const
{
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document()).update_layout();
const_cast<Document&>(document()).update_layout(UpdateLayoutReason::ElementClientTop);
// 1. If the element has no associated CSS layout box or if the CSS layout box is inline, return zero.
if (!paintable_box())
@ -1063,7 +1063,7 @@ int Element::client_top() const
int Element::client_left() const
{
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document()).update_layout();
const_cast<Document&>(document()).update_layout(UpdateLayoutReason::ElementClientLeft);
// 1. If the element has no associated CSS layout box or if the CSS layout box is inline, return zero.
if (!paintable_box())
@ -1089,7 +1089,7 @@ int Element::client_width() const
}
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document()).update_layout();
const_cast<Document&>(document()).update_layout(UpdateLayoutReason::ElementClientWidth);
// 1. If the element has no associated CSS layout box or if the CSS layout box is inline, return zero.
if (!paintable_box())
@ -1114,7 +1114,7 @@ int Element::client_height() const
}
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document()).update_layout();
const_cast<Document&>(document()).update_layout(UpdateLayoutReason::ElementClientHeight);
// 1. If the element has no associated CSS layout box or if the CSS layout box is inline, return zero.
if (!paintable_box())
@ -1420,7 +1420,7 @@ void Element::set_tab_index(i32 tab_index)
bool Element::is_potentially_scrollable() const
{
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document()).update_layout();
const_cast<Document&>(document()).update_layout(UpdateLayoutReason::ElementIsPotentiallyScrollable);
// An element body (which will be the body element) is potentially scrollable if all of the following conditions are true:
VERIFY(is<HTML::HTMLBodyElement>(this) || is<HTML::HTMLFrameSetElement>(this));
@ -1466,7 +1466,7 @@ double Element::scroll_top() const
return window->scroll_y();
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document).update_layout();
const_cast<Document&>(document).update_layout(UpdateLayoutReason::ElementScrollTop);
// 7. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, return the value of scrollY on window.
if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable())
@ -1508,7 +1508,7 @@ double Element::scroll_left() const
return window->scroll_x();
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document).update_layout();
const_cast<Document&>(document).update_layout(UpdateLayoutReason::ElementScrollLeft);
// 7. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, return the value of scrollX on window.
if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable())
@ -1557,7 +1557,7 @@ void Element::set_scroll_left(double x)
}
// NOTE: Ensure that layout is up-to-date before looking at metrics or scrolling the page.
const_cast<Document&>(document).update_layout();
const_cast<Document&>(document).update_layout(UpdateLayoutReason::ElementSetScrollLeft);
// 9. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, invoke scroll() on window with x as first argument and scrollY on window as second argument, and terminate these steps.
if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable()) {
@ -1614,7 +1614,7 @@ void Element::set_scroll_top(double y)
}
// NOTE: Ensure that layout is up-to-date before looking at metrics or scrolling the page.
const_cast<Document&>(document).update_layout();
const_cast<Document&>(document).update_layout(UpdateLayoutReason::ElementSetScrollTop);
// 9. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, invoke scroll() on window with scrollX as first argument and y as second argument, and terminate these steps.
if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable()) {
@ -1659,7 +1659,7 @@ int Element::scroll_width() const
return max(viewport_scroll_width, viewport_width);
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document).update_layout();
const_cast<Document&>(document).update_layout(UpdateLayoutReason::ElementScrollWidth);
// 5. If the element is the body element, document is in quirks mode and the element is not potentially scrollable,
// return max(viewport scrolling area width, viewport width).
@ -1698,7 +1698,7 @@ int Element::scroll_height() const
return max(viewport_scroll_height, viewport_height);
// NOTE: Ensure that layout is up-to-date before looking at metrics.
const_cast<Document&>(document).update_layout();
const_cast<Document&>(document).update_layout(UpdateLayoutReason::ElementScrollHeight);
// 5. If the element is the body element, document is in quirks mode and the element is not potentially scrollable,
// return max(viewport scrolling area height, viewport height).
@ -2180,7 +2180,7 @@ ErrorOr<void> Element::scroll_into_view(Optional<Variant<bool, ScrollIntoViewOpt
}
// 7. If the element does not have any associated box, or is not available to user-agent features, then return.
document().update_layout();
document().update_layout(UpdateLayoutReason::ElementScrollIntoView);
if (!layout_node())
return Error::from_string_literal("Element has no associated box");
@ -2761,7 +2761,7 @@ void Element::scroll(double x, double y)
return;
// NOTE: Ensure that layout is up-to-date before looking at metrics.
document.update_layout();
document.update_layout(UpdateLayoutReason::ElementScroll);
// 8. If the element is the root element invoke scroll() on window with scrollX on window as first argument and y as second argument, and terminate these steps.
if (document.document_element() == this) {
@ -2841,7 +2841,7 @@ void Element::scroll_by(HTML::ScrollToOptions options)
bool Element::check_visibility(Optional<CheckVisibilityOptions> options)
{
// NOTE: Ensure that layout is up-to-date before looking at metrics.
document().update_layout();
document().update_layout(UpdateLayoutReason::ElementCheckVisibility);
// 1. If this does not have an associated box, return false.
if (!paintable_box())

View file

@ -2745,7 +2745,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
if (!child_node->is_element() && !child_node->is_text())
continue;
bool should_add_space = true;
const_cast<DOM::Document&>(document).update_layout();
const_cast<DOM::Document&>(document).update_layout(DOM::UpdateLayoutReason::NodeNameOrDescription);
auto const* layout_node = child_node->layout_node();
if (layout_node) {
auto display = layout_node->display();

View file

@ -1157,7 +1157,7 @@ GC::Ref<Geometry::DOMRectList> Range::get_client_rects()
if (!start_container()->document().navigable())
return Geometry::DOMRectList::create(realm(), {});
start_container()->document().update_layout();
start_container()->document().update_layout(DOM::UpdateLayoutReason::RangeGetClientRects);
update_associated_selection();
Vector<GC::Root<Geometry::DOMRect>> rects;
// FIXME: take Range collapsed into consideration