diff options
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.cc | 242 |
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( |