Commit 32c30787 authored by Morten Stenshorne's avatar Morten Stenshorne Committed by Commit Bot

[LayoutNG] Hit-test by traversing the fragment tree.

This adds support for hit-testing by traversing the fragment tree,
rather than traversing the LayoutObject tree.

All behind the LayoutNGFragmentTraversal runtime flag.

Testing: virtual/layout_ng_fragment_traversal/ contain some hit-testing
tests, and will now run with the new code. (Adding fast/events/ to the
virtual testuite would increase coverage by a lot, but the tests there
are so flaky I decided against it.)

Bug: 1043787
Change-Id: Id1e1d524afa071c80098274b2c61a3339c8032e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2041597
Commit-Queue: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#739346}
parent 5485843a
...@@ -240,6 +240,41 @@ Vector<PhysicalRect> BuildBackplate(NGInlineCursor* descendants, ...@@ -240,6 +240,41 @@ Vector<PhysicalRect> BuildBackplate(NGInlineCursor* descendants,
return backplates.paragraph_backplates; return backplates.paragraph_backplates;
} }
bool HitTestAllPhasesInFragment(const NGPhysicalBoxFragment& fragment,
const HitTestLocation& hit_test_location,
PhysicalOffset accumulated_offset,
HitTestResult* result) {
// Hit test all phases of inline blocks, inline tables, replaced elements and
// non-positioned floats as if they created their own (pseudo- [1]) stacking
// context. https://www.w3.org/TR/CSS22/zindex.html#painting-order
//
// [1] As if it creates a new stacking context, but any positioned descendants
// and descendants which actually create a new stacking context should be
// considered part of the parent stacking context, not this new one.
if (!fragment.CanTraverse()) {
return fragment.GetMutableLayoutObject()->HitTestAllPhases(
*result, hit_test_location, accumulated_offset);
}
return NGBoxFragmentPainter(To<NGPhysicalBoxFragment>(fragment))
.HitTestAllPhases(*result, hit_test_location, accumulated_offset);
}
bool NodeAtPointInFragment(const NGPhysicalBoxFragment& fragment,
const HitTestLocation& hit_test_location,
PhysicalOffset accumulated_offset,
HitTestAction action,
HitTestResult* result) {
if (!fragment.CanTraverse()) {
return fragment.GetMutableLayoutObject()->NodeAtPoint(
*result, hit_test_location, accumulated_offset, action);
}
return NGBoxFragmentPainter(fragment).NodeAtPoint(*result, hit_test_location,
accumulated_offset, action);
}
} // anonymous namespace } // anonymous namespace
const NGBorderEdges& NGBoxFragmentPainter::BorderEdges() const { const NGBorderEdges& NGBoxFragmentPainter::BorderEdges() const {
...@@ -1657,6 +1692,42 @@ bool NGBoxFragmentPainter::NodeAtPoint(const HitTestContext& hit_test, ...@@ -1657,6 +1692,42 @@ bool NGBoxFragmentPainter::NodeAtPoint(const HitTestContext& hit_test,
return false; return false;
} }
bool NGBoxFragmentPainter::HitTestAllPhases(
HitTestResult& result,
const HitTestLocation& hit_test_location,
const PhysicalOffset& accumulated_offset,
HitTestFilter hit_test_filter) {
// Logic taken from LayoutObject::HitTestAllPhases().
HitTestContext hit_test(kHitTestForeground, hit_test_location,
accumulated_offset, &result);
bool inside = false;
if (hit_test_filter != kHitTestSelf) {
// First test the foreground layer (lines and inlines).
inside = NodeAtPoint(hit_test, accumulated_offset);
// Test floats next.
if (!inside) {
hit_test.action = kHitTestFloat;
inside = NodeAtPoint(hit_test, accumulated_offset);
}
// Finally test to see if the mouse is in the background (within a child
// block's background).
if (!inside) {
hit_test.action = kHitTestChildBlockBackgrounds;
inside = NodeAtPoint(hit_test, accumulated_offset);
}
}
// See if the pointer is inside us but not any of our descendants.
if (hit_test_filter != kHitTestDescendants && !inside) {
hit_test.action = kHitTestChildBlockBackground;
inside = NodeAtPoint(hit_test, accumulated_offset);
}
return inside;
}
bool NGBoxFragmentPainter::VisibleToHitTestRequest( bool NGBoxFragmentPainter::VisibleToHitTestRequest(
const HitTestRequest& request) const { const HitTestRequest& request) const {
return FragmentVisibleToHitTestRequest(box_fragment_, request); return FragmentVisibleToHitTestRequest(box_fragment_, request);
...@@ -1783,8 +1854,6 @@ bool NGBoxFragmentPainter::HitTestChildBoxFragment( ...@@ -1783,8 +1854,6 @@ bool NGBoxFragmentPainter::HitTestChildBoxFragment(
return false; return false;
if (!FragmentRequiresLegacyFallback(fragment)) { if (!FragmentRequiresLegacyFallback(fragment)) {
// TODO(layout-dev): Implement HitTestAllPhases in NG after we stop
// falling back to legacy for child atomic inlines and floats.
DCHECK(!fragment.IsAtomicInline()); DCHECK(!fragment.IsAtomicInline());
DCHECK(!fragment.IsFloating()); DCHECK(!fragment.IsFloating());
if (const NGPaintFragment* paint_fragment = cursor.CurrentPaintFragment()) { if (const NGPaintFragment* paint_fragment = cursor.CurrentPaintFragment()) {
...@@ -1816,24 +1885,15 @@ bool NGBoxFragmentPainter::HitTestChildBoxFragment( ...@@ -1816,24 +1885,15 @@ bool NGBoxFragmentPainter::HitTestChildBoxFragment(
if (fragment.IsInline() && hit_test.action != kHitTestForeground) if (fragment.IsInline() && hit_test.action != kHitTestForeground)
return false; return false;
if (fragment.IsAtomicInline() || fragment.IsFloating()) if (fragment.IsAtomicInline() || fragment.IsFloating()) {
return HitTestAllPhases(hit_test, fragment, physical_offset); return HitTestAllPhasesInFragment(fragment, hit_test.location,
physical_offset, hit_test.result);
}
return fragment.GetMutableLayoutObject()->NodeAtPoint( return fragment.GetMutableLayoutObject()->NodeAtPoint(
*hit_test.result, hit_test.location, physical_offset, hit_test.action); *hit_test.result, hit_test.location, physical_offset, hit_test.action);
} }
bool NGBoxFragmentPainter::HitTestAllPhases(
const HitTestContext& hit_test,
const NGPhysicalFragment& fragment,
const PhysicalOffset& accumulated_offset) {
// Hit test all phases of inline blocks, inline tables, replaced elements and
// non-positioned floats as if they created their own stacking contexts.
// https://www.w3.org/TR/CSS22/zindex.html#painting-order
return fragment.GetMutableLayoutObject()->HitTestAllPhases(
*hit_test.result, hit_test.location, accumulated_offset);
}
bool NGBoxFragmentPainter::HitTestChildBoxItem( bool NGBoxFragmentPainter::HitTestChildBoxItem(
const HitTestContext& hit_test, const HitTestContext& hit_test,
const NGFragmentItem& item, const NGFragmentItem& item,
...@@ -1900,6 +1960,16 @@ bool NGBoxFragmentPainter::HitTestChildren( ...@@ -1900,6 +1960,16 @@ bool NGBoxFragmentPainter::HitTestChildren(
NGInlineCursor cursor(*items_); NGInlineCursor cursor(*items_);
return HitTestChildren(hit_test, cursor, accumulated_offset); return HitTestChildren(hit_test, cursor, accumulated_offset);
} }
if (box_fragment_.CanTraverse()) {
DCHECK(!box_fragment_.ChildrenInline());
if (hit_test.action == kHitTestFloat) {
return box_fragment_.HasFloatingDescendantsForPaint() &&
HitTestFloatingChildren(hit_test, box_fragment_,
accumulated_offset);
}
return HitTestBlockChildren(*hit_test.result, hit_test.location,
accumulated_offset, hit_test.action);
}
NOTREACHED(); NOTREACHED();
return false; return false;
} }
...@@ -1916,6 +1986,31 @@ bool NGBoxFragmentPainter::HitTestChildren( ...@@ -1916,6 +1986,31 @@ bool NGBoxFragmentPainter::HitTestChildren(
return false; return false;
} }
bool NGBoxFragmentPainter::HitTestBlockChildren(
HitTestResult& result,
const HitTestLocation& hit_test_location,
PhysicalOffset accumulated_offset,
HitTestAction action) {
if (action == kHitTestChildBlockBackgrounds)
action = kHitTestChildBlockBackground;
auto children = box_fragment_.Children();
for (const NGLink& child : base::Reversed(children)) {
const auto& block_child = To<NGPhysicalBoxFragment>(*child);
if (block_child.HasSelfPaintingLayer() || block_child.IsFloating())
continue;
const PhysicalOffset child_offset = accumulated_offset + child.offset;
// TODO(mstensho): If we hit a child that is an anonymous block, we need to
// provide the hit test result with the node here.
if (NodeAtPointInFragment(block_child, hit_test_location, child_offset,
action, &result))
return true;
}
return false;
}
bool NGBoxFragmentPainter::HitTestPaintFragmentChildren( bool NGBoxFragmentPainter::HitTestPaintFragmentChildren(
const HitTestContext& hit_test, const HitTestContext& hit_test,
const NGInlineCursor& children, const NGInlineCursor& children,
...@@ -2016,7 +2111,9 @@ bool NGBoxFragmentPainter::HitTestFloatingChildren( ...@@ -2016,7 +2111,9 @@ bool NGBoxFragmentPainter::HitTestFloatingChildren(
const PhysicalOffset child_offset = accumulated_offset + child.offset; const PhysicalOffset child_offset = accumulated_offset + child.offset;
if (child_fragment.IsFloating()) { if (child_fragment.IsFloating()) {
if (HitTestAllPhases(hit_test, child_fragment, child_offset)) if (HitTestAllPhasesInFragment(To<NGPhysicalBoxFragment>(child_fragment),
hit_test.location, child_offset,
hit_test.result))
return true; return true;
continue; continue;
} }
......
...@@ -63,6 +63,11 @@ class NGBoxFragmentPainter : public BoxPainterBase { ...@@ -63,6 +63,11 @@ class NGBoxFragmentPainter : public BoxPainterBase {
const PhysicalOffset& physical_offset, const PhysicalOffset& physical_offset,
HitTestAction); HitTestAction);
bool HitTestAllPhases(HitTestResult&,
const HitTestLocation&,
const PhysicalOffset& accumulated_offset,
HitTestFilter = kHitTestAll);
protected: protected:
LayoutRectOutsets ComputeBorders() const override; LayoutRectOutsets ComputeBorders() const override;
LayoutRectOutsets ComputePadding() const override; LayoutRectOutsets ComputePadding() const override;
...@@ -216,6 +221,10 @@ class NGBoxFragmentPainter : public BoxPainterBase { ...@@ -216,6 +221,10 @@ class NGBoxFragmentPainter : public BoxPainterBase {
bool HitTestChildren(const HitTestContext& hit_test, bool HitTestChildren(const HitTestContext& hit_test,
const NGInlineCursor& children, const NGInlineCursor& children,
const PhysicalOffset& physical_offset); const PhysicalOffset& physical_offset);
bool HitTestBlockChildren(HitTestResult&,
const HitTestLocation&,
PhysicalOffset,
HitTestAction);
bool HitTestPaintFragmentChildren(const HitTestContext& hit_test, bool HitTestPaintFragmentChildren(const HitTestContext& hit_test,
const NGInlineCursor& children, const NGInlineCursor& children,
const PhysicalOffset& physical_offset); const PhysicalOffset& physical_offset);
...@@ -225,10 +234,6 @@ class NGBoxFragmentPainter : public BoxPainterBase { ...@@ -225,10 +234,6 @@ class NGBoxFragmentPainter : public BoxPainterBase {
const NGPhysicalContainerFragment& container, const NGPhysicalContainerFragment& container,
const PhysicalOffset& accumulated_offset); const PhysicalOffset& accumulated_offset);
bool HitTestAllPhases(const HitTestContext& hit_test,
const NGPhysicalFragment& fragment,
const PhysicalOffset& accumulated_offset);
// Hit tests a box fragment, which is a child of either |box_fragment_|, or // Hit tests a box fragment, which is a child of either |box_fragment_|, or
// one of its child line box fragments. // one of its child line box fragments.
// @param physical_offset Physical offset of the given box fragment in the // @param physical_offset Physical offset of the given box fragment in the
......
...@@ -77,6 +77,7 @@ ...@@ -77,6 +77,7 @@
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
#include "third_party/blink/renderer/core/paint/filter_effect_builder.h" #include "third_party/blink/renderer/core/paint/filter_effect_builder.h"
#include "third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.h"
#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
#include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer_paint_order_iterator.h" #include "third_party/blink/renderer/core/paint/paint_layer_paint_order_iterator.h"
...@@ -2310,8 +2311,8 @@ bool PaintLayer::HitTestContentsForFragments( ...@@ -2310,8 +2311,8 @@ bool PaintLayer::HitTestContentsForFragments(
inside_clip_rect = true; inside_clip_rect = true;
PhysicalOffset fragment_offset = offset; PhysicalOffset fragment_offset = offset;
fragment_offset += fragment.layer_bounds.offset; fragment_offset += fragment.layer_bounds.offset;
if (HitTestContents(result, fragment_offset, hit_test_location, if (HitTestContents(result, fragment.physical_fragment, fragment_offset,
hit_test_filter)) hit_test_location, hit_test_filter))
return true; return true;
} }
...@@ -2394,12 +2395,23 @@ PaintLayer* PaintLayer::HitTestLayerByApplyingTransform( ...@@ -2394,12 +2395,23 @@ PaintLayer* PaintLayer::HitTestLayerByApplyingTransform(
} }
bool PaintLayer::HitTestContents(HitTestResult& result, bool PaintLayer::HitTestContents(HitTestResult& result,
const NGPhysicalBoxFragment* physical_fragment,
const PhysicalOffset& fragment_offset, const PhysicalOffset& fragment_offset,
const HitTestLocation& hit_test_location, const HitTestLocation& hit_test_location,
HitTestFilter hit_test_filter) const { HitTestFilter hit_test_filter) const {
DCHECK(IsSelfPaintingLayer() || HasSelfPaintingLayerDescendant()); DCHECK(IsSelfPaintingLayer() || HasSelfPaintingLayerDescendant());
if (!GetLayoutObject().HitTestAllPhases(result, hit_test_location,
fragment_offset, hit_test_filter)) { bool did_hit;
if (physical_fragment) {
did_hit = NGBoxFragmentPainter(*physical_fragment)
.HitTestAllPhases(result, hit_test_location, fragment_offset,
hit_test_filter);
} else {
did_hit = GetLayoutObject().HitTestAllPhases(
result, hit_test_location, fragment_offset, hit_test_filter);
}
if (!did_hit) {
// It's wrong to set innerNode, but then claim that you didn't hit anything, // It's wrong to set innerNode, but then claim that you didn't hit anything,
// unless it is a list-based test. // unless it is a list-based test.
DCHECK(!result.InnerNode() || (result.GetHitTestRequest().ListBased() && DCHECK(!result.InnerNode() || (result.GetHitTestRequest().ListBased() &&
......
...@@ -1197,6 +1197,7 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient { ...@@ -1197,6 +1197,7 @@ class CORE_EXPORT PaintLayer : public DisplayItemClient {
const PhysicalOffset& translation_offset = PhysicalOffset()) const; const PhysicalOffset& translation_offset = PhysicalOffset()) const;
bool HitTestContents(HitTestResult&, bool HitTestContents(HitTestResult&,
const NGPhysicalBoxFragment*,
const PhysicalOffset& fragment_offset, const PhysicalOffset& fragment_offset,
const HitTestLocation&, const HitTestLocation&,
HitTestFilter) const; HitTestFilter) const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment