Skip to content

Commit 41a12ef

Browse files
committed
LibWeb: Build accumulated visual contexts lazily
Layout commits rebuilt the accumulated visual context tree immediately, even when callers requested transform-independent metrics such as offsetWidth. Repeated mutation and forced-layout cycles therefore walked the full paint tree after every layout. Keep the tree dirty after layout and resolve it through the viewport paintable accessor when transforms, painting, or hit testing consume it. Preserve non-mutating presence checks for lifecycle-sensitive visual viewport updates. Add an internals counter and regression test showing that layout-only metric reads perform no builds. The first transform-aware read coalesces the pending work into one build.
1 parent c7de739 commit 41a12ef

10 files changed

Lines changed: 75 additions & 17 deletions

File tree

Libraries/LibWeb/DOM/Document.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,7 +1805,6 @@ void Document::after_layout_commit(LayoutTreeChanged layout_tree_changed)
18051805
unsafe_paintable()->reassign_scroll_frames();
18061806

18071807
set_needs_accumulated_visual_contexts_update(true);
1808-
update_paint_and_hit_testing_properties_if_needed();
18091808

18101809
// Selection state lives on paintable fragments, which the commit has rebuilt.
18111810
if (auto range = get_selection()->range())
@@ -2879,7 +2878,7 @@ static CSSPixelPoint compute_mouse_event_offset(CSSPixelPoint position, Painting
28792878
{
28802879
auto inverse_transform_point = [](Painting::Paintable const& paintable_box, CSSPixelPoint position) -> Optional<CSSPixelPoint> {
28812880
auto viewport_paintable = paintable_box.document().unsafe_paintable();
2882-
if (!viewport_paintable || !viewport_paintable->has_visual_context_tree())
2881+
if (!viewport_paintable)
28832882
return {};
28842883
auto pixel_ratio = static_cast<float>(paintable_box.document().page().client().device_pixels_per_css_pixel());
28852884
auto const& visual_context_tree = viewport_paintable->visual_context_tree();

Libraries/LibWeb/Internals/Internals.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,14 @@ WebIDL::UnsignedLongLong Internals::full_layout_count()
574574
return window().associated_document().full_layout_count();
575575
}
576576

577+
WebIDL::UnsignedLongLong Internals::accumulated_visual_context_tree_build_count()
578+
{
579+
auto paintable = window().associated_document().unsafe_paintable();
580+
if (!paintable)
581+
return 0;
582+
return paintable->accumulated_visual_context_tree_build_count();
583+
}
584+
577585
void Internals::set_autoplay_policy(Utf16String const& policy)
578586
{
579587
if (auto parsed = HTML::autoplay_policy_from_string(policy.utf16_view()); parsed.has_value())

Libraries/LibWeb/Internals/Internals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class WEB_API Internals final : public InternalsBase {
9292
void set_content_blocking_enabled(bool enabled);
9393
WebIDL::UnsignedLongLong partial_layout_count();
9494
WebIDL::UnsignedLongLong full_layout_count();
95+
WebIDL::UnsignedLongLong accumulated_visual_context_tree_build_count();
9596
void set_autoplay_policy(Utf16String const& policy);
9697

9798
Utf16String get_computed_role(DOM::Element& element);

Libraries/LibWeb/Internals/Internals.idl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ interface Internals {
8282
undefined setContentBlockingEnabled(boolean enabled);
8383
unsigned long long partialLayoutCount();
8484
unsigned long long fullLayoutCount();
85+
unsigned long long accumulatedVisualContextTreeBuildCount();
8586
undefined setAutoplayPolicy(Utf16DOMString policy);
8687

8788
Utf16DOMString getComputedRole(Element element);

Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,8 @@ bool update_accumulated_visual_context_values(ViewportPaintable& viewport_painta
625625

626626
void update_visual_viewport_accumulated_visual_context(ViewportPaintable& viewport_paintable)
627627
{
628-
viewport_paintable.visual_context_tree().set_visual_viewport_transform(visual_viewport_transform_data(viewport_paintable.document()));
628+
VERIFY(viewport_paintable.m_visual_context_tree.has_value());
629+
viewport_paintable.m_visual_context_tree->set_visual_viewport_transform(visual_viewport_transform_data(viewport_paintable.document()));
629630
}
630631

631632
VisualContextIndex AccumulatedVisualContextTree::append(VisualContextData data, VisualContextIndex parent_index)

Libraries/LibWeb/Painting/Paintable.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2578,7 +2578,7 @@ BorderRadiiData Paintable::normalized_border_radii_data(ShrinkRadiiForBorders sh
25782578
Optional<CSSPixelPoint> Paintable::transform_point_to_local(CSSPixelPoint screen_position) const
25792579
{
25802580
auto viewport_paintable = document().paintable();
2581-
if (!viewport_paintable || !viewport_paintable->has_visual_context_tree())
2581+
if (!viewport_paintable)
25822582
return screen_position;
25832583
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
25842584
auto const& scroll_state = viewport_paintable->scroll_state_snapshot();
@@ -2592,7 +2592,7 @@ Optional<CSSPixelPoint> Paintable::transform_point_to_local(CSSPixelPoint screen
25922592
Optional<CSSPixelPoint> Paintable::transform_point_to_local_for_descendants(CSSPixelPoint screen_position) const
25932593
{
25942594
auto viewport_paintable = document().paintable();
2595-
if (!viewport_paintable || !viewport_paintable->has_visual_context_tree())
2595+
if (!viewport_paintable)
25962596
return screen_position;
25972597
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
25982598
auto const& scroll_state = viewport_paintable->scroll_state_snapshot();
@@ -2606,7 +2606,7 @@ Optional<CSSPixelPoint> Paintable::transform_point_to_local_for_descendants(CSSP
26062606
CSSPixelRect Paintable::transform_rect_to_viewport(CSSPixelRect const& rect, AccumulatedVisualContextTree::IncludeVisualViewportTransform include_visual_viewport_transform) const
26072607
{
26082608
auto viewport_paintable = document().paintable();
2609-
if (!viewport_paintable || !viewport_paintable->has_visual_context_tree())
2609+
if (!viewport_paintable)
26102610
return rect;
26112611
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
26122612
auto const& scroll_state = viewport_paintable->scroll_state_snapshot();
@@ -2618,7 +2618,7 @@ CSSPixelRect Paintable::transform_rect_to_viewport(CSSPixelRect const& rect, Acc
26182618
CSSPixelPoint Paintable::inverse_transform_point(CSSPixelPoint screen_position) const
26192619
{
26202620
auto viewport_paintable = document().paintable();
2621-
if (!viewport_paintable || !viewport_paintable->has_visual_context_tree())
2621+
if (!viewport_paintable)
26222622
return screen_position;
26232623
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
26242624
auto const& visual_context_tree = viewport_paintable->visual_context_tree();

Libraries/LibWeb/Painting/ViewportPaintable.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ ViewportPaintable::ViewportPaintable(Layout::Viewport const& layout_viewport)
4242

4343
ViewportPaintable::~ViewportPaintable() = default;
4444

45+
void ViewportPaintable::ensure_visual_context_tree() const
46+
{
47+
const_cast<DOM::Document&>(document()).update_paint_and_hit_testing_properties_if_needed();
48+
}
49+
50+
AccumulatedVisualContextTree const& ViewportPaintable::visual_context_tree() const
51+
{
52+
ensure_visual_context_tree();
53+
VERIFY(m_visual_context_tree.has_value());
54+
return *m_visual_context_tree;
55+
}
56+
57+
AccumulatedVisualContextTree& ViewportPaintable::visual_context_tree()
58+
{
59+
ensure_visual_context_tree();
60+
VERIFY(m_visual_context_tree.has_value());
61+
return *m_visual_context_tree;
62+
}
63+
4564
struct BlockingWheelEventRegionState {
4665
bool has_blocking_wheel_event_listeners { false };
4766
bool has_blocking_wheel_event_region_covering_viewport { false };
@@ -247,6 +266,7 @@ void ViewportPaintable::assign_scroll_frames()
247266
void ViewportPaintable::assign_accumulated_visual_contexts()
248267
{
249268
auto visual_context_tree = build_accumulated_visual_context_tree(*this);
269+
++m_accumulated_visual_context_tree_build_count;
250270
auto is_compatible = m_visual_context_tree.has_value() && visual_context_tree.is_compatible_with(*m_visual_context_tree);
251271
if (m_force_incompatible_visual_context_tree_rebuild_for_testing) {
252272
m_force_incompatible_visual_context_tree_rebuild_for_testing = false;

Libraries/LibWeb/Painting/ViewportPaintable.h

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class WEB_API ViewportPaintable final : public PaintableWithLines {
3939
void did_update_visual_context_tree_in_compositor() { m_visual_context_tree_needs_compositor_update = false; }
4040
void set_force_incompatible_visual_context_tree_rebuild_for_testing() { m_force_incompatible_visual_context_tree_rebuild_for_testing = true; }
4141
bool has_visual_context_tree() const { return m_visual_context_tree.has_value(); }
42+
u64 accumulated_visual_context_tree_build_count() const { return m_accumulated_visual_context_tree_build_count; }
4243

4344
GC::Ptr<Selection::Selection> selection() const;
4445
void recompute_selection_states(DOM::Range&);
@@ -58,20 +59,15 @@ class WEB_API ViewportPaintable final : public PaintableWithLines {
5859
void set_paintable_boxes_with_auto_content_visibility(Vector<WeakPtr<Paintable>> paintable_boxes) { m_paintable_boxes_with_auto_content_visibility = move(paintable_boxes); }
5960
Vector<WeakPtr<Paintable>> const& paintable_boxes_with_auto_content_visibility() const { return m_paintable_boxes_with_auto_content_visibility; }
6061

61-
AccumulatedVisualContextTree const& visual_context_tree() const
62-
{
63-
VERIFY(m_visual_context_tree.has_value());
64-
return *m_visual_context_tree;
65-
}
66-
AccumulatedVisualContextTree& visual_context_tree()
67-
{
68-
VERIFY(m_visual_context_tree.has_value());
69-
return *m_visual_context_tree;
70-
}
62+
AccumulatedVisualContextTree const& visual_context_tree() const;
63+
AccumulatedVisualContextTree& visual_context_tree();
7164

7265
private:
66+
friend void update_visual_viewport_accumulated_visual_context(ViewportPaintable&);
67+
7368
virtual bool is_viewport_paintable() const override { return true; }
7469

70+
void ensure_visual_context_tree() const;
7571
void build_stacking_context_tree();
7672
void clear_scroll_state();
7773
void precompute_sticky_constraints(ScrollFrameIndex, Paintable const&);
@@ -85,6 +81,7 @@ class WEB_API ViewportPaintable final : public PaintableWithLines {
8581
Vector<WeakPtr<Paintable>> m_paintable_boxes_with_auto_content_visibility;
8682

8783
Optional<AccumulatedVisualContextTree> m_visual_context_tree;
84+
u64 m_accumulated_visual_context_tree_build_count { 0 };
8885
bool m_visual_context_tree_needs_compositor_update { false };
8986
bool m_force_incompatible_visual_context_tree_rebuild_for_testing { false };
9087
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
offset width: 110
2+
offset width: 120
3+
offset width: 130
4+
builds after layout-only reads: 0
5+
bounding rect width: 130
6+
builds after transform-sensitive read: 1
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<script src="include.js"></script>
3+
<div id="target" style="width: 100px; height: 100px"></div>
4+
<script>
5+
test(() => {
6+
const target = document.getElementById('target');
7+
8+
target.getBoundingClientRect();
9+
const initialBuildCount = internals.accumulatedVisualContextTreeBuildCount();
10+
const lines = [];
11+
12+
for (const width of [110, 120, 130]) {
13+
target.style.width = `${width}px`;
14+
lines.push(`offset width: ${target.offsetWidth}`);
15+
}
16+
17+
lines.push(`builds after layout-only reads: ${internals.accumulatedVisualContextTreeBuildCount() - initialBuildCount}`);
18+
19+
lines.push(`bounding rect width: ${target.getBoundingClientRect().width}`);
20+
lines.push(`builds after transform-sensitive read: ${internals.accumulatedVisualContextTreeBuildCount() - initialBuildCount}`);
21+
22+
for (const line of lines)
23+
println(line);
24+
});
25+
</script>

0 commit comments

Comments
 (0)