Skip to content

Commit

Permalink
[IntersectionObserver] Refactor IntersectionGeometry
Browse files Browse the repository at this point in the history
When IntersectionGeometry was carved out as a class, it was thought
that it would be useful in contexts other than IntersectionObserver.
That has since proved to be mistaken; a number of bugs cropped up
due to IntersectionGeometry being used at the wrong time in the
document lifecycle.

This patch adds more IntersectionObserver-specific functionality into
IntersectionGeometry, and moves the source into the same directory
as the other IntersectionObserver-related classes, where it belongs.

No functional change.

Change-Id: Ib5583b5ed5ac2a053ee53b9df11f05012c6d15c3
Reviewed-on: https://chromium-review.googlesource.com/c/1461832
Commit-Queue: Stefan Zager <szager@chromium.org>
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#631506}
  • Loading branch information
szager-chromium authored and Commit Bot committed Feb 13, 2019
1 parent 4387ac5 commit f725794
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
#include "third_party/blink/renderer/core/html/track/video_track_list.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/intersection_geometry.h"
#include "third_party/blink/renderer/core/layout/layout_media.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/layout/intersection_geometry.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ blink_core_sources("intersection_observer") {
sources = [
"element_intersection_observer_data.cc",
"element_intersection_observer_data.h",
"intersection_geometry.cc",
"intersection_geometry.h",
"intersection_observation.cc",
"intersection_observation.h",
"intersection_observer.cc",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/layout/intersection_geometry.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_geometry.h"

#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h"
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
Expand Down Expand Up @@ -66,26 +67,33 @@ LayoutView* LocalRootView(Element& element) {

} // namespace

IntersectionGeometry::IntersectionGeometry(Element* root,
Element& target,
IntersectionGeometry::IntersectionGeometry(Element* root_element,
Element& target_element,
const Vector<Length>& root_margin,
bool should_report_root_bounds)
: root_(root ? root->GetLayoutObject() : LocalRootView(target)),
target_(target.GetLayoutObject()),
const Vector<float>& thresholds,
unsigned flags)
: root_(root_element ? root_element->GetLayoutObject()
: LocalRootView(target_element)),
target_(target_element.GetLayoutObject()),
root_margin_(root_margin),
does_intersect_(0),
should_report_root_bounds_(should_report_root_bounds),
root_is_implicit_(!root),
can_compute_geometry_(InitializeCanComputeGeometry(root, target)) {
if (can_compute_geometry_)
InitializeGeometry();
thresholds_(thresholds),
flags_(flags),
intersection_ratio_(0),
threshold_index_(0) {
DCHECK(root_margin.IsEmpty() || root_margin.size() == 4);
if (root_element)
flags_ &= ~kRootIsImplicit;
else
flags_ |= kRootIsImplicit;
flags_ &= ~kIsVisible;
if (CanComputeGeometry(root_element, target_element))
ComputeGeometry();
}

IntersectionGeometry::~IntersectionGeometry() = default;

bool IntersectionGeometry::InitializeCanComputeGeometry(Element* root,
Element& target) const {
DCHECK(root_margin_.IsEmpty() || root_margin_.size() == 4);
bool IntersectionGeometry::CanComputeGeometry(Element* root,
Element& target) const {
if (root && !root->isConnected())
return false;
if (!root_ || !root_->IsBox())
Expand All @@ -99,12 +107,6 @@ bool IntersectionGeometry::InitializeCanComputeGeometry(Element* root,
return true;
}

void IntersectionGeometry::InitializeGeometry() {
InitializeTargetRect();
intersection_rect_ = target_rect_;
InitializeRootRect();
}

void IntersectionGeometry::InitializeTargetRect() {
if (target_->IsBox()) {
target_rect_ =
Expand Down Expand Up @@ -153,41 +155,36 @@ void IntersectionGeometry::ApplyRootMargin() {
root_rect_.SetHeight(root_rect_.Height() + top_margin + bottom_margin);
}

void IntersectionGeometry::ClipToRoot() {
unsigned IntersectionGeometry::FirstThresholdGreaterThan(float ratio) const {
unsigned result = 0;
while (result < thresholds_.size() && thresholds_[result] <= ratio)
++result;
return result;
}

bool IntersectionGeometry::ClipToRoot() {
// Map and clip rect into root element coordinates.
// TODO(szager): the writing mode flipping needs a test.
LayoutBox* local_ancestor = nullptr;
if (!RootIsImplicit() || root_->GetDocument().IsInMainFrame())
local_ancestor = ToLayoutBox(root_);
// If we're throttled, then we can't guarantee that geometry mapper is up to
// date, so we fall back to the slow path. If we're unthrottled, then ensure
// that prepaint has run and the frame view doesn't need a paint property
// update.
#if DCHECK_IS_ON()
{
auto* frame_view = target_->GetFrameView();
auto* layout_view = frame_view->GetLayoutView();
DCHECK(frame_view->ShouldThrottleRendering() || !layout_view ||
!(layout_view->NeedsPaintPropertyUpdate() ||
layout_view->DescendantNeedsPaintPropertyUpdate()));

LayoutView* layout_view = target_->GetDocument().GetLayoutView();

unsigned flags = kDefaultVisualRectFlags | kEdgeInclusive;
if (!layout_view->NeedsPaintPropertyUpdate() &&
!layout_view->DescendantNeedsPaintPropertyUpdate()) {
flags |= kUseGeometryMapper;
}
#endif

VisualRectFlags use_geometry_mapper =
target_->GetFrameView()->ShouldThrottleRendering()
? kDefaultVisualRectFlags
: kUseGeometryMapper;
VisualRectFlags flags =
static_cast<VisualRectFlags>(use_geometry_mapper | kEdgeInclusive);
does_intersect_ = target_->MapToVisualRectInAncestorSpace(
local_ancestor, intersection_rect_, flags);
if (!does_intersect_ || !local_ancestor)
return;
bool does_intersect = target_->MapToVisualRectInAncestorSpace(
local_ancestor, intersection_rect_, static_cast<VisualRectFlags>(flags));
if (!does_intersect || !local_ancestor)
return does_intersect;
if (local_ancestor->HasOverflowClip())
intersection_rect_.Move(-local_ancestor->ScrolledContentOffset());
LayoutRect root_clip_rect(root_rect_);
local_ancestor->FlipForWritingMode(root_clip_rect);
does_intersect_ &= intersection_rect_.InclusiveIntersect(root_clip_rect);
return does_intersect & intersection_rect_.InclusiveIntersect(root_clip_rect);
}

void IntersectionGeometry::MapTargetRectToTargetFrameCoordinates() {
Expand Down Expand Up @@ -218,20 +215,79 @@ void IntersectionGeometry::MapIntersectionRectToTargetFrameCoordinates() {
}

void IntersectionGeometry::ComputeGeometry() {
if (!CanComputeGeometry())
return;
InitializeTargetRect();
intersection_rect_ = target_rect_;
InitializeRootRect();
DCHECK(root_);
DCHECK(target_);
ClipToRoot();
DCHECK(!target_->GetDocument().View()->NeedsLayout());
bool does_intersect = ClipToRoot();
MapTargetRectToTargetFrameCoordinates();
if (does_intersect_)
if (does_intersect)
MapIntersectionRectToTargetFrameCoordinates();
else
intersection_rect_ = LayoutRect();
// Small optimization: if we're not going to report root bounds, don't bother
// transforming them to the frame.
if (ShouldReportRootBounds())
MapRootRectToRootFrameCoordinates();

// Some corner cases for threshold index:
// - If target rect is zero area, because it has zero width and/or zero
// height,
// only two states are recognized:
// - 0 means not intersecting.
// - 1 means intersecting.
// No other threshold crossings are possible.
// - Otherwise:
// - If root and target do not intersect, the threshold index is 0.

// - If root and target intersect but the intersection has zero-area
// (i.e., they have a coincident edge or corner), we consider the
// intersection to have "crossed" a zero threshold, but not crossed
// any non-zero threshold.

if (does_intersect) {
const LayoutRect comparison_rect =
ShouldTrackFractionOfRoot() ? RootRect() : TargetRect();
if (comparison_rect.IsEmpty()) {
intersection_ratio_ = 1;
} else {
const LayoutSize& intersection_size = IntersectionRect().Size();
const float intersection_area = intersection_size.Width().ToFloat() *
intersection_size.Height().ToFloat();
const LayoutSize& comparison_size = comparison_rect.Size();
const float area_of_interest = comparison_size.Width().ToFloat() *
comparison_size.Height().ToFloat();
intersection_ratio_ = intersection_area / area_of_interest;
}
threshold_index_ = FirstThresholdGreaterThan(intersection_ratio_);
} else {
intersection_ratio_ = 0;
threshold_index_ = 0;
}
ComputeVisibility();
}

void IntersectionGeometry::ComputeVisibility() {
if (!IsIntersecting() || !ShouldComputeVisibility())
return;
DCHECK(RuntimeEnabledFeatures::IntersectionObserverV2Enabled());
if (target_->GetDocument()
.GetFrame()
->LocalFrameRoot()
.MayBeOccludedOrObscuredByRemoteAncestor()) {
return;
}
if (target_->HasDistortingVisualEffects())
return;
// TODO(layout-dev): This should hit-test the intersection rect, not the
// target rect; it's not helpful to know that the portion of the target that
// is clipped is also occluded. To do that, the intersection rect must be
// mapped down to the local space of the target element.
HitTestResult result(target_->HitTestForOcclusion(TargetRect()));
if (!result.InnerNode() || result.InnerNode() == target_->GetNode())
flags_ |= kIsVisible;
}

LayoutRect IntersectionGeometry::UnZoomedTargetRect() const {
Expand All @@ -258,4 +314,18 @@ LayoutRect IntersectionGeometry::UnZoomedRootRect() const {
return LayoutRect(rect);
}

IntersectionObserverEntry* IntersectionGeometry::CreateEntry(
Element* target,
DOMHighResTimeStamp timestamp) {
FloatRect root_bounds(UnZoomedRootRect());
FloatRect* root_bounds_pointer =
ShouldReportRootBounds() ? &root_bounds : nullptr;
IntersectionObserverEntry* entry =
MakeGarbageCollected<IntersectionObserverEntry>(
timestamp, IntersectionRatio(), FloatRect(UnZoomedTargetRect()),
root_bounds_pointer, FloatRect(UnZoomedIntersectionRect()),
IsIntersecting(), IsVisible(), target);
return entry;
}

} // namespace blink
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_INTERSECTION_GEOMETRY_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_INTERSECTION_GEOMETRY_H_
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_INTERSECTION_OBSERVER_INTERSECTION_GEOMETRY_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_INTERSECTION_OBSERVER_INTERSECTION_GEOMETRY_H_

#include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
#include "third_party/blink/renderer/platform/geometry/layout_rect.h"
#include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
Expand All @@ -13,25 +14,47 @@
namespace blink {

class Element;
class IntersectionObserverEntry;
class LayoutObject;

// Computes the intersection between an ancestor (root) element and a
// descendant (target) element, with overflow and CSS clipping applied, but not
// paint occlusion.
// descendant (target) element, with overflow and CSS clipping applied.
// Optionally also checks whether the target is occluded or has visual
// effects applied.
//
// If the root argument to the constructor is null, computes the intersection
// of the target with the top-level frame viewport (AKA the "implicit root").
class IntersectionGeometry {
STACK_ALLOCATED();

public:
enum Flags {
// These flags should passed to the constructor
kShouldReportRootBounds = 1 << 0,
kShouldComputeVisibility = 1 << 1,
kShouldTrackFractionOfRoot = 1 << 2,

// These flags will be computed
kRootIsImplicit = 1 << 3,
kIsVisible = 1 << 4
};

IntersectionGeometry(Element* root,
Element& target,
const Vector<Length>& root_margin,
bool should_report_root_bounds);
const Vector<float>& thresholds,
unsigned flags);
~IntersectionGeometry();

void ComputeGeometry();
bool ShouldReportRootBounds() const {
return flags_ & kShouldReportRootBounds;
}
bool ShouldComputeVisibility() const {
return flags_ & kShouldComputeVisibility;
}
bool ShouldTrackFractionOfRoot() const {
return flags_ & kShouldTrackFractionOfRoot;
}

LayoutObject* Root() const { return root_; }
LayoutObject* Target() const { return target_; }
Expand All @@ -51,45 +74,47 @@ class IntersectionGeometry {
// Root rect in CSS pixels
LayoutRect UnZoomedRootRect() const;

bool DoesIntersect() const { return does_intersect_; }

IntRect IntersectionIntRect() const {
return PixelSnappedIntRect(intersection_rect_);
}

IntRect TargetIntRect() const { return PixelSnappedIntRect(target_rect_); }

IntRect RootIntRect() const { return PixelSnappedIntRect(root_rect_); }

double IntersectionRatio() const { return intersection_ratio_; }
unsigned ThresholdIndex() const { return threshold_index_; }

bool RootIsImplicit() const { return flags_ & kRootIsImplicit; }
bool IsIntersecting() const { return threshold_index_ > 0; }
bool IsVisible() const { return flags_ & kIsVisible; }

IntersectionObserverEntry* CreateEntry(Element* target,
DOMHighResTimeStamp timestamp);

private:
bool InitializeCanComputeGeometry(Element* root, Element& target) const;
void InitializeGeometry();
void ComputeGeometry();
bool CanComputeGeometry(Element* root, Element& target) const;
void InitializeTargetRect();
void InitializeRootRect();
void ClipToRoot();
bool ClipToRoot();
void MapTargetRectToTargetFrameCoordinates();
void MapRootRectToRootFrameCoordinates();
void MapIntersectionRectToTargetFrameCoordinates();
void ApplyRootMargin();

// Returns true iff it's possible to compute an intersection between root
// and target.
bool CanComputeGeometry() const { return can_compute_geometry_; }
bool RootIsImplicit() const { return root_is_implicit_; }
bool ShouldReportRootBounds() const { return should_report_root_bounds_; }
unsigned FirstThresholdGreaterThan(float ratio) const;
void ComputeVisibility();

LayoutObject* root_;
LayoutObject* target_;
const Vector<Length> root_margin_;
const Vector<Length>& root_margin_;
const Vector<float>& thresholds_;
LayoutRect target_rect_;
LayoutRect intersection_rect_;
LayoutRect root_rect_;
unsigned does_intersect_ : 1;
const unsigned should_report_root_bounds_ : 1;
const unsigned root_is_implicit_ : 1;
const unsigned can_compute_geometry_ : 1;
unsigned flags_;
double intersection_ratio_;
unsigned threshold_index_;
};

} // namespace blink

#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_INTERSECTION_GEOMETRY_H_
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_INTERSECTION_OBSERVER_INTERSECTION_GEOMETRY_H_
Loading

0 comments on commit f725794

Please sign in to comment.