summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc242
1 files changed, 174 insertions, 68 deletions
diff --git a/chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc b/chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
index ac40b7c5ef5..586abe8f69a 100644
--- a/chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
+++ b/chromium/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
@@ -17,6 +17,7 @@
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
namespace blink {
@@ -121,7 +122,8 @@ PhysicalRect InitializeRootRect(const LayoutObject* root,
const Vector<Length>& margin) {
DCHECK(margin.IsEmpty() || margin.size() == 4);
PhysicalRect result;
- if (root->IsLayoutView() && root->GetDocument().IsInMainFrame()) {
+ auto* layout_view = DynamicTo<LayoutView>(root);
+ if (layout_view && root->GetDocument().IsInMainFrame()) {
// The main frame is a bit special as the scrolling viewport can differ in
// size from the LayoutView itself. There's two situations this occurs in:
// 1) The ForceZeroLayoutHeight quirk setting is used in Android WebView for
@@ -130,7 +132,7 @@ PhysicalRect InitializeRootRect(const LayoutObject* root,
// testing. Use the FrameView geometry instead.
// 2) An element wider than the ICB can cause us to resize the FrameView so
// we can zoom out to fit the entire element width.
- result = ToLayoutView(root)->OverflowClipRect(PhysicalOffset());
+ result = layout_view->OverflowClipRect(PhysicalOffset());
} else if (root->IsBox() && root->HasOverflowClip()) {
result = ToLayoutBox(root)->PhysicalContentBoxRect();
} else {
@@ -157,12 +159,21 @@ LayoutObject* GetTargetLayoutObject(const Element& target_element) {
return target;
}
+bool CanUseGeometryMapper(const LayoutObject* object) {
+ // This checks for cases where we didn't just complete a successful lifecycle
+ // update, e.g., if the frame is throttled.
+ LayoutView* layout_view = object->GetDocument().GetLayoutView();
+ return layout_view && !layout_view->NeedsPaintPropertyUpdate() &&
+ !layout_view->DescendantNeedsPaintPropertyUpdate();
+}
+
static const unsigned kConstructorFlagsMask =
IntersectionGeometry::kShouldReportRootBounds |
IntersectionGeometry::kShouldComputeVisibility |
IntersectionGeometry::kShouldTrackFractionOfRoot |
IntersectionGeometry::kShouldUseReplacedContentRect |
- IntersectionGeometry::kShouldConvertToCSSPixels;
+ IntersectionGeometry::kShouldConvertToCSSPixels |
+ IntersectionGeometry::kShouldUseCachedRects;
} // namespace
@@ -178,100 +189,157 @@ IntersectionGeometry::RootGeometry::RootGeometry(const LayoutObject* root,
root_to_document_transform = transform_state.AccumulatedTransform();
}
-// If root_element is non-null, it is treated as the explicit root of an
+// If root_node is non-null, it is treated as the explicit root of an
// IntersectionObserver; if it is valid, its LayoutObject is returned.
//
-// If root_element is null, returns the object to be used as the implicit root
+// If root_node is null, returns the object to be used as the implicit root
// for a given target.
//
// https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-root
const LayoutObject* IntersectionGeometry::GetRootLayoutObjectForTarget(
- const Element* root_element,
- LayoutObject* target) {
- if (!root_element)
+ const Node* root_node,
+ LayoutObject* target,
+ bool check_containing_block_chain) {
+ if (!root_node)
return target ? LocalRootView(*target) : nullptr;
- if (!root_element->isConnected())
+ if (!root_node->isConnected())
return nullptr;
LayoutObject* root = nullptr;
if (RuntimeEnabledFeatures::
IntersectionObserverDocumentScrollingElementRootEnabled() &&
- root_element == root_element->GetDocument().scrollingElement()) {
- root = root_element->GetDocument().GetLayoutView();
+ root_node->IsDocumentNode()) {
+ root = To<Document>(root_node)->GetLayoutView();
} else {
- root = root_element->GetLayoutObject();
- if (target && !IsContainingBlockChainDescendant(target, root))
+ root = root_node->GetLayoutObject();
+ if (target && check_containing_block_chain &&
+ !IsContainingBlockChainDescendant(target, root)) {
root = nullptr;
+ }
}
return root;
}
-IntersectionGeometry::IntersectionGeometry(const Element* root_element,
+IntersectionGeometry::IntersectionGeometry(const Node* root_node,
const Element& target_element,
const Vector<Length>& root_margin,
const Vector<float>& thresholds,
- unsigned flags)
+ unsigned flags,
+ CachedRects* cached_rects)
: flags_(flags & kConstructorFlagsMask),
intersection_ratio_(0),
threshold_index_(0) {
- if (!root_element)
+ if (cached_rects)
+ cached_rects->valid = false;
+ if (!root_node)
flags_ |= kRootIsImplicit;
LayoutObject* target = GetTargetLayoutObject(target_element);
if (!target)
return;
- const LayoutObject* root = GetRootLayoutObjectForTarget(root_element, target);
+ const LayoutObject* root =
+ GetRootLayoutObjectForTarget(root_node, target, !ShouldUseCachedRects());
if (!root)
return;
RootGeometry root_geometry(root, root_margin);
- ComputeGeometry(root_geometry, root, target, thresholds);
+ ComputeGeometry(root_geometry, root, target, thresholds, cached_rects);
}
IntersectionGeometry::IntersectionGeometry(const RootGeometry& root_geometry,
- const Element& explicit_root,
+ const Node& explicit_root,
const Element& target_element,
const Vector<float>& thresholds,
- unsigned flags)
+ unsigned flags,
+ CachedRects* cached_rects)
: flags_(flags & kConstructorFlagsMask),
intersection_ratio_(0),
threshold_index_(0) {
+ if (cached_rects)
+ cached_rects->valid = false;
LayoutObject* target = GetTargetLayoutObject(target_element);
if (!target)
return;
- const LayoutObject* root =
- GetRootLayoutObjectForTarget(&explicit_root, target);
+ const LayoutObject* root = GetRootLayoutObjectForTarget(
+ &explicit_root, target, !ShouldUseCachedRects());
if (!root)
return;
- ComputeGeometry(root_geometry, root, target, thresholds);
+ ComputeGeometry(root_geometry, root, target, thresholds, cached_rects);
}
-IntersectionGeometry::~IntersectionGeometry() = default;
-
void IntersectionGeometry::ComputeGeometry(const RootGeometry& root_geometry,
const LayoutObject* root,
const LayoutObject* target,
- const Vector<float>& thresholds) {
+ const Vector<float>& thresholds,
+ CachedRects* cached_rects) {
+ DCHECK(cached_rects || !ShouldUseCachedRects());
// Initially:
// target_rect_ is in target's coordinate system
- // intersection_rect_ is in target's coordinate system
// root_rect_ is in root's coordinate system
- target_rect_ = InitializeTargetRect(target, flags_);
- intersection_rect_ = target_rect_;
+ // The coordinate system for unclipped_intersection_rect_ depends on whether
+ // or not we can use previously cached geometry...
+ if (ShouldUseCachedRects()) {
+ target_rect_ = cached_rects->local_target_rect;
+ // The cached intersection rect has already been mapped/clipped up to the
+ // root, except that the root's scroll offset and overflow clip have not
+ // been applied.
+ unclipped_intersection_rect_ =
+ cached_rects->unscrolled_unclipped_intersection_rect;
+ } else {
+ target_rect_ = InitializeTargetRect(target, flags_);
+ // We have to map/clip target_rect_ up to the root, so we begin with the
+ // intersection rect in target's coordinate system. After ClipToRoot, it
+ // will be in root's coordinate system.
+ unclipped_intersection_rect_ = target_rect_;
+ }
+ if (cached_rects)
+ cached_rects->local_target_rect = target_rect_;
root_rect_ = root_geometry.local_root_rect;
- // This maps intersection_rect_ up to root's coordinate system
bool does_intersect =
- ClipToRoot(root, target, root_rect_, intersection_rect_);
-
- // Map target_rect_ to absolute coordinates for target's document
- target_rect_ = target->LocalToAncestorRect(target_rect_, nullptr);
+ ClipToRoot(root, target, root_rect_, unclipped_intersection_rect_,
+ intersection_rect_, cached_rects);
+
+ // Map target_rect_ to absolute coordinates for target's document.
+ // GeometryMapper is faster, so we use it when possible; otherwise, fall back
+ // to LocalToAncestorRect.
+ PropertyTreeState container_properties = PropertyTreeState::Uninitialized();
+ const LayoutObject* property_container =
+ CanUseGeometryMapper(target)
+ ? target->GetPropertyContainer(nullptr, &container_properties)
+ : nullptr;
+ if (property_container) {
+ LayoutRect target_layout_rect = target_rect_.ToLayoutRect();
+ target_layout_rect.Move(
+ target->FirstFragment().PaintOffset().ToLayoutSize());
+ GeometryMapper::SourceToDestinationRect(container_properties.Transform(),
+ target->GetDocument()
+ .GetLayoutView()
+ ->FirstFragment()
+ .LocalBorderBoxProperties()
+ .Transform(),
+ target_layout_rect);
+ target_rect_ = PhysicalRect(target_layout_rect);
+ } else {
+ target_rect_ = target->LocalToAncestorRect(target_rect_, nullptr);
+ }
if (does_intersect) {
if (RootIsImplicit()) {
+ // Generate matrix to transform from the space of the implicit root to
+ // the absolute coordinates of the target document.
+ TransformState implicit_root_to_target_document_transform(
+ TransformState::kUnapplyInverseTransformDirection);
+ target->GetDocument().GetLayoutView()->MapAncestorToLocal(
+ nullptr, implicit_root_to_target_document_transform,
+ kTraverseDocumentBoundaries | kApplyRemoteRootFrameOffset);
+ TransformationMatrix matrix =
+ implicit_root_to_target_document_transform.AccumulatedTransform()
+ .Inverse();
+ intersection_rect_ = PhysicalRect::EnclosingRect(
+ matrix.ProjectQuad(FloatRect(intersection_rect_)).BoundingBox());
+ unclipped_intersection_rect_ = PhysicalRect::EnclosingRect(
+ matrix.ProjectQuad(FloatRect(unclipped_intersection_rect_))
+ .BoundingBox());
// intersection_rect_ is in the coordinate system of the implicit root;
// map it down the to absolute coordinates for the target's document.
- intersection_rect_ =
- target->GetDocument().GetLayoutView()->AbsoluteToLocalRect(
- intersection_rect_,
- kTraverseDocumentBoundaries | kApplyRemoteRootFrameOffset);
} else {
// intersection_rect_ is in root's coordinate system; map it up to
// absolute coordinates for target's containing document (which is the
@@ -280,6 +348,10 @@ void IntersectionGeometry::ComputeGeometry(const RootGeometry& root_geometry,
root_geometry.root_to_document_transform
.MapQuad(FloatQuad(FloatRect(intersection_rect_)))
.BoundingBox());
+ unclipped_intersection_rect_ = PhysicalRect::EnclosingRect(
+ root_geometry.root_to_document_transform
+ .MapQuad(FloatQuad(FloatRect(unclipped_intersection_rect_)))
+ .BoundingBox());
}
} else {
intersection_rect_ = PhysicalRect();
@@ -326,8 +398,9 @@ void IntersectionGeometry::ComputeGeometry(const RootGeometry& root_geometry,
threshold_index_ = 0;
}
if (IsIntersecting() && ShouldComputeVisibility() &&
- ComputeIsVisible(target, target_rect_))
+ ComputeIsVisible(target, target_rect_)) {
flags_ |= kIsVisible;
+ }
if (flags_ & kShouldConvertToCSSPixels) {
FloatRect target_float_rect(target_rect_);
@@ -340,49 +413,82 @@ void IntersectionGeometry::ComputeGeometry(const RootGeometry& root_geometry,
AdjustForAbsoluteZoom::AdjustFloatRect(root_float_rect, *root);
root_rect_ = PhysicalRect::EnclosingRect(root_float_rect);
}
+
+ if (cached_rects)
+ cached_rects->valid = true;
}
bool IntersectionGeometry::ClipToRoot(const LayoutObject* root,
const LayoutObject* target,
const PhysicalRect& root_rect,
- PhysicalRect& intersection_rect) {
+ PhysicalRect& unclipped_intersection_rect,
+ PhysicalRect& intersection_rect,
+ CachedRects* cached_rects) {
// Map and clip rect into root element coordinates.
// TODO(szager): the writing mode flipping needs a test.
const LayoutBox* local_ancestor = nullptr;
if (!RootIsImplicit() || root->GetDocument().IsInMainFrame())
local_ancestor = ToLayoutBox(root);
- const LayoutView* layout_view = target->GetDocument().GetLayoutView();
-
- unsigned flags = kDefaultVisualRectFlags | kEdgeInclusive;
- if (!layout_view->NeedsPaintPropertyUpdate() &&
- !layout_view->DescendantNeedsPaintPropertyUpdate()) {
+ unsigned flags = kDefaultVisualRectFlags | kEdgeInclusive |
+ kDontApplyMainFrameOverflowClip;
+ if (CanUseGeometryMapper(target))
flags |= kUseGeometryMapper;
+
+ bool does_intersect;
+ if (ShouldUseCachedRects()) {
+ does_intersect = cached_rects->does_intersect;
+ } else {
+ does_intersect = target->MapToVisualRectInAncestorSpace(
+ local_ancestor, unclipped_intersection_rect,
+ static_cast<VisualRectFlags>(flags));
}
- bool does_intersect = target->MapToVisualRectInAncestorSpace(
- local_ancestor, intersection_rect, static_cast<VisualRectFlags>(flags));
-
- // Note that this early-return for (!local_ancestor) skips clipping to the
- // root_rect. That's ok because the only scenario where local_ancestor is
- // null is an implicit root and running inside an OOPIF, in which case there
- // can't be any root margin applied to root_rect (root margin is disallowed
- // for implicit-root cross-origin observation). So the default behavior of
- // MapToVisualRectInAncestorSpace will have already done the right thing WRT
- // clipping to the implicit root.
- if (!does_intersect || !local_ancestor)
- return does_intersect;
-
- if (local_ancestor->HasOverflowClip()) {
- intersection_rect.Move(
- -PhysicalOffset(LayoutPoint(local_ancestor->ScrollOrigin()) +
- local_ancestor->PixelSnappedScrolledContentOffset()));
+ if (cached_rects) {
+ cached_rects->unscrolled_unclipped_intersection_rect =
+ unclipped_intersection_rect;
+ cached_rects->does_intersect = does_intersect;
}
- LayoutRect root_clip_rect = root_rect.ToLayoutRect();
- // TODO(szager): This flipping seems incorrect because root_rect is already
- // physical.
- local_ancestor->DeprecatedFlipForWritingMode(root_clip_rect);
- return does_intersect &
- intersection_rect.InclusiveIntersect(PhysicalRect(root_clip_rect));
+
+ intersection_rect = PhysicalRect();
+
+ // If the target intersects with the unclipped root, calculate the clipped
+ // intersection.
+ if (does_intersect) {
+ intersection_rect = unclipped_intersection_rect;
+ if (local_ancestor) {
+ if (local_ancestor->HasOverflowClip()) {
+ PhysicalOffset scroll_offset = -PhysicalOffset(
+ LayoutPoint(local_ancestor->ScrollOrigin()) +
+ local_ancestor->PixelSnappedScrolledContentOffset());
+ intersection_rect.Move(scroll_offset);
+ unclipped_intersection_rect.Move(scroll_offset);
+ }
+ LayoutRect root_clip_rect = root_rect.ToLayoutRect();
+ // TODO(szager): This flipping seems incorrect because root_rect is
+ // already physical.
+ local_ancestor->DeprecatedFlipForWritingMode(root_clip_rect);
+ does_intersect &=
+ intersection_rect.InclusiveIntersect(PhysicalRect(root_clip_rect));
+ } else {
+ // Note that we don't clip to root_rect here. That's ok because
+ // (!local_ancestor) implies that the root is implicit and the
+ // main frame is remote, in which case there can't be any root margin
+ // applied to root_rect (root margin is disallowed for implicit-root
+ // cross-origin observation). We still need to apply the remote main
+ // frame's overflow clip here, because the
+ // kDontApplyMainFrameOverflowClip flag above, means it hasn't been
+ // done yet.
+ LocalFrame* local_root_frame = root->GetDocument().GetFrame();
+ IntRect clip_rect(local_root_frame->RemoteViewportIntersection());
+ // Map clip_rect from the coordinate system of the local root frame to
+ // the coordinate system of the remote main frame.
+ clip_rect.MoveBy(IntPoint(local_root_frame->RemoteViewportOffset()));
+ does_intersect &=
+ intersection_rect.InclusiveIntersect(PhysicalRect(clip_rect));
+ }
+ }
+
+ return does_intersect;
}
unsigned IntersectionGeometry::FirstThresholdGreaterThan(