diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 2932287e31..c2edf7e473 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -6151,7 +6151,7 @@ static CSSPixelRect compute_intersection(GC::Ref target, CSSPixelRect t auto overflow_y = container->computed_values().overflow_y(); bool has_content_clip = overflow_x != CSS::Overflow::Visible || overflow_y != CSS::Overflow::Visible; if (has_content_clip) { - auto clip_rect = container->transform_rect_to_viewport(container->absolute_padding_box_rect()); + auto clip_rect = container->transform_rect_to_viewport(container->absolute_padding_box_rect(), Painting::AccumulatedVisualContextTree::IncludeVisualViewportTransform::No); // Apply scroll margin to expand the scrollport for scroll containers. auto& scroll_margin = observer.scroll_margin_values(); diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 63606e8d69..ca72eb46ad 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -1639,7 +1639,7 @@ static Vector compute_client_rects_assuming_layout_clean(Element c Vector rects; if (auto paintable_box = element.paintable_box()) { auto absolute_rect = paintable_box->absolute_border_box_rect(); - rects.append(paintable_box->transform_rect_to_viewport(absolute_rect)); + rects.append(paintable_box->transform_rect_to_viewport(absolute_rect, Painting::AccumulatedVisualContextTree::IncludeVisualViewportTransform::No)); } else if (element.paintable()) { dbgln("FIXME: Failed to get client rects for element ({})", element.debug_description()); } diff --git a/Libraries/LibWeb/IntersectionObserver/IntersectionObserver.cpp b/Libraries/LibWeb/IntersectionObserver/IntersectionObserver.cpp index de073c0c49..001f1cd9f2 100644 --- a/Libraries/LibWeb/IntersectionObserver/IntersectionObserver.cpp +++ b/Libraries/LibWeb/IntersectionObserver/IntersectionObserver.cpp @@ -285,9 +285,12 @@ CSSPixelRect IntersectionObserver::root_intersection_rectangle() const // Since the spec says that this is only reach if the document is fully active, that means it must have a navigable. VERIFY(document->navigable()); - // NOTE: This rect is the *size* of the viewport. The viewport *offset* is not relevant, - // as intersections are computed using viewport-relative element rects. - rect = CSSPixelRect { CSSPixelPoint { 0, 0 }, document->viewport_rect().size() }; + // NOTE: This rect is in the same layout viewport coordinate space as + // Element::getBoundingClientRect(). + rect = CSSPixelRect { + CSSPixelPoint { 0, 0 }, + document->viewport_rect().size(), + }; } else { VERIFY(intersection_root.has>()); auto element = intersection_root.get>(); diff --git a/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp b/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp index 170589dd20..29a2058dbd 100644 --- a/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp +++ b/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp @@ -607,32 +607,34 @@ Gfx::FloatPoint AccumulatedVisualContextTree::inverse_transform_point(VisualCont return point; } -Gfx::FloatRect AccumulatedVisualContextTree::transform_rect_to_viewport(VisualContextIndex index, Gfx::FloatRect const& source_rect, ScrollStateSnapshot const& scroll_state) const +Gfx::FloatRect AccumulatedVisualContextTree::transform_rect_to_viewport(VisualContextIndex index, Gfx::FloatRect const& source_rect, ScrollStateSnapshot const& scroll_state, IncludeVisualViewportTransform include_visual_viewport_transform) const { auto rect = source_rect; for (size_t i = index.value();; i = m_nodes[i].parent_index.value()) { auto const& node = m_nodes[i]; - node.data.visit( - [&](TransformData const& transform) { - auto affine = Gfx::extract_2d_affine_transform(transform.matrix); - rect.translate_by(-transform.origin); - rect = affine.map(rect); - rect.translate_by(transform.origin); - }, - [&](PerspectiveData const& perspective) { - auto affine = Gfx::extract_2d_affine_transform(perspective.matrix); - rect = affine.map(rect); - }, - [&](ScrollData const& scroll) { - rect.translate_by(scroll_state.device_offset_for_index(scroll.scroll_frame_index)); - }, - [&](ScrollCompensation const& compensation) { - auto offset = scroll_state.device_offset_for_index(compensation.scroll_frame_index); - rect.translate_by(-offset); - }, - [&](ClipData const&) { /* clips don't affect rect coordinates */ }, - [&](ClipPathData const&) { /* clip paths don't affect rect coordinates */ }, - [&](EffectsData const&) { /* effects don't affect rect coordinates */ }); + if (i != VISUAL_VIEWPORT_NODE_INDEX.value() || include_visual_viewport_transform == IncludeVisualViewportTransform::Yes) { + node.data.visit( + [&](TransformData const& transform) { + auto affine = Gfx::extract_2d_affine_transform(transform.matrix); + rect.translate_by(-transform.origin); + rect = affine.map(rect); + rect.translate_by(transform.origin); + }, + [&](PerspectiveData const& perspective) { + auto affine = Gfx::extract_2d_affine_transform(perspective.matrix); + rect = affine.map(rect); + }, + [&](ScrollData const& scroll) { + rect.translate_by(scroll_state.device_offset_for_index(scroll.scroll_frame_index)); + }, + [&](ScrollCompensation const& compensation) { + auto offset = scroll_state.device_offset_for_index(compensation.scroll_frame_index); + rect.translate_by(-offset); + }, + [&](ClipData const&) { /* clips don't affect rect coordinates */ }, + [&](ClipPathData const&) { /* clip paths don't affect rect coordinates */ }, + [&](EffectsData const&) { /* effects don't affect rect coordinates */ }); + } if (i == VISUAL_VIEWPORT_NODE_INDEX.value()) break; } diff --git a/Libraries/LibWeb/Painting/AccumulatedVisualContext.h b/Libraries/LibWeb/Painting/AccumulatedVisualContext.h index 3a6a16a19c..9ef4930c6f 100644 --- a/Libraries/LibWeb/Painting/AccumulatedVisualContext.h +++ b/Libraries/LibWeb/Painting/AccumulatedVisualContext.h @@ -93,6 +93,11 @@ struct AccumulatedVisualContextNode { class AccumulatedVisualContextTree { public: + enum class IncludeVisualViewportTransform { + No, + Yes, + }; + static AccumulatedVisualContextTree create(); static AccumulatedVisualContextTree create(TransformData visual_viewport_transform); @@ -113,7 +118,7 @@ public: VisualContextIndex find_common_ancestor(VisualContextIndex a, VisualContextIndex b) const; Optional transform_point_for_hit_test(VisualContextIndex, Gfx::FloatPoint, ScrollStateSnapshot const&) const; Gfx::FloatPoint inverse_transform_point(VisualContextIndex, Gfx::FloatPoint) const; - Gfx::FloatRect transform_rect_to_viewport(VisualContextIndex, Gfx::FloatRect const&, ScrollStateSnapshot const&) const; + Gfx::FloatRect transform_rect_to_viewport(VisualContextIndex, Gfx::FloatRect const&, ScrollStateSnapshot const&, IncludeVisualViewportTransform = IncludeVisualViewportTransform::Yes) const; void dump(VisualContextIndex, StringBuilder&) const; bool has_empty_effective_clip(VisualContextIndex i) const { return m_nodes[i.value()].has_empty_effective_clip; } diff --git a/Libraries/LibWeb/Painting/PaintableBox.cpp b/Libraries/LibWeb/Painting/PaintableBox.cpp index 5f30ccf27a..5e78cd1da5 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -1768,7 +1768,7 @@ Optional PaintableBox::transform_point_to_local_for_descendants(C return (*result / pixel_ratio).to_type(); } -CSSPixelRect PaintableBox::transform_rect_to_viewport(CSSPixelRect const& rect) const +CSSPixelRect PaintableBox::transform_rect_to_viewport(CSSPixelRect const& rect, AccumulatedVisualContextTree::IncludeVisualViewportTransform include_visual_viewport_transform) const { auto viewport_paintable = document().paintable(); if (!viewport_paintable || !viewport_paintable->has_visual_context_tree()) @@ -1776,7 +1776,7 @@ CSSPixelRect PaintableBox::transform_rect_to_viewport(CSSPixelRect const& rect) auto pixel_ratio = static_cast(document().page().client().device_pixels_per_css_pixel()); auto const& scroll_state = viewport_paintable->scroll_state_snapshot(); auto const& visual_context_tree = viewport_paintable->visual_context_tree(); - auto result = visual_context_tree.transform_rect_to_viewport(m_accumulated_visual_context_index, rect.to_type() * pixel_ratio, scroll_state); + auto result = visual_context_tree.transform_rect_to_viewport(m_accumulated_visual_context_index, rect.to_type() * pixel_ratio, scroll_state, include_visual_viewport_transform); return (result * (1.f / pixel_ratio)).to_type(); } diff --git a/Libraries/LibWeb/Painting/PaintableBox.h b/Libraries/LibWeb/Painting/PaintableBox.h index 75e623f83f..99d0537d51 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Libraries/LibWeb/Painting/PaintableBox.h @@ -300,7 +300,7 @@ public: Optional transform_point_to_local(CSSPixelPoint screen_position) const; Optional transform_point_to_local_for_descendants(CSSPixelPoint screen_position) const; - CSSPixelRect transform_rect_to_viewport(CSSPixelRect const& rect) const; + CSSPixelRect transform_rect_to_viewport(CSSPixelRect const& rect, AccumulatedVisualContextTree::IncludeVisualViewportTransform = AccumulatedVisualContextTree::IncludeVisualViewportTransform::Yes) const; CSSPixelPoint inverse_transform_point(CSSPixelPoint screen_position) const; static constexpr size_t paint_phase_count = to_underlying(PaintPhase::Overlay) + 1; diff --git a/Tests/LibWeb/Text/expected/get-bounding-client-rect-pinch-zoom.txt b/Tests/LibWeb/Text/expected/get-bounding-client-rect-pinch-zoom.txt new file mode 100644 index 0000000000..dcf4669314 --- /dev/null +++ b/Tests/LibWeb/Text/expected/get-bounding-client-rect-pinch-zoom.txt @@ -0,0 +1,4 @@ +before: 0.000,0.000 100.000x100.000 +visualViewport scale: 1.500 +after: 0.000,0.000 100.000x100.000 +size unchanged: PASS diff --git a/Tests/LibWeb/Text/expected/intersection-observer-visual-viewport-geometry.txt b/Tests/LibWeb/Text/expected/intersection-observer-visual-viewport-geometry.txt new file mode 100644 index 0000000000..38a1781253 --- /dev/null +++ b/Tests/LibWeb/Text/expected/intersection-observer-visual-viewport-geometry.txt @@ -0,0 +1,11 @@ +visualViewport: 533.333x400.000 scale=1.500 +visualViewport offset: 33.328,33.328 +top-left target origin: 0.000,0.000 +top-left target size: 100.000x100.000 +top-left rootBounds origin: 0.000,0.000 +top-left rootBounds size: 800.000x600.000 +top-left intersectionRect origin: 0.000,0.000 +top-left intersectionRect size: 100.000x100.000 +top-left intersectionRatio: 1.000 +right target isIntersecting: PASS +right target intersectionRatio: 1.000 diff --git a/Tests/LibWeb/Text/expected/intersection-observer-visual-viewport-root.txt b/Tests/LibWeb/Text/expected/intersection-observer-visual-viewport-root.txt new file mode 100644 index 0000000000..2423291001 --- /dev/null +++ b/Tests/LibWeb/Text/expected/intersection-observer-visual-viewport-root.txt @@ -0,0 +1,3 @@ +visualViewport: 533.333x400.000 +rootBounds: 800.000x600.000 +rootBounds match layout viewport: PASS diff --git a/Tests/LibWeb/Text/input/get-bounding-client-rect-pinch-zoom.html b/Tests/LibWeb/Text/input/get-bounding-client-rect-pinch-zoom.html new file mode 100644 index 0000000000..b4e986bccd --- /dev/null +++ b/Tests/LibWeb/Text/input/get-bounding-client-rect-pinch-zoom.html @@ -0,0 +1,33 @@ + + +
+ + diff --git a/Tests/LibWeb/Text/input/intersection-observer-visual-viewport-geometry.html b/Tests/LibWeb/Text/input/intersection-observer-visual-viewport-geometry.html new file mode 100644 index 0000000000..0e042eb811 --- /dev/null +++ b/Tests/LibWeb/Text/input/intersection-observer-visual-viewport-geometry.html @@ -0,0 +1,62 @@ + + +
+
+ + diff --git a/Tests/LibWeb/Text/input/intersection-observer-visual-viewport-root.html b/Tests/LibWeb/Text/input/intersection-observer-visual-viewport-root.html new file mode 100644 index 0000000000..b0a53ca880 --- /dev/null +++ b/Tests/LibWeb/Text/input/intersection-observer-visual-viewport-root.html @@ -0,0 +1,37 @@ + + +
+ +