blob: f6934a7ed48f8e7d35b56767cf90bde50b5c241d [file]
// Copyright 2016 The Chromium Authors
// 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/intersection_observer/intersection_observation.h"
#include "third_party/blink/renderer/core/dom/node_rare_data.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_geometry.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#define CHECK_SKIPPED_UPDATE_ON_SCROLL() DCHECK_IS_ON()
namespace blink {
IntersectionObservation::IntersectionObservation(IntersectionObserver& observer,
Element& target)
: observer_(observer), target_(&target) {}
int64_t IntersectionObservation::ComputeIntersection(
unsigned compute_flags,
gfx::Vector2dF accumulated_scroll_delta_since_last_update,
ComputeIntersectionsContext& context) {
if (!CanCompute()) {
return 0;
}
if (compute_flags & kConsumeScrollDelta) {
cached_rects_.min_scroll_delta_to_update -=
accumulated_scroll_delta_since_last_update;
accumulated_scroll_delta_since_last_update = gfx::Vector2dF();
}
// If we're processing post-layout deliveries only and we don't have a
// post-layout delivery observer, then return early. Likewise, return if we
// need to compute non-post-layout-delivery observations but the observer
// behavior is post-layout.
bool post_layout_delivery_only = compute_flags & kPostLayoutDeliveryOnly;
bool is_post_layout_delivery_observer =
Observer()->GetDeliveryBehavior() ==
IntersectionObserver::kDeliverDuringPostLayoutSteps;
if (post_layout_delivery_only != is_post_layout_delivery_observer) {
return 0;
}
bool has_pending_update = needs_update_;
if (compute_flags &
(observer_->RootIsImplicit() ? kImplicitRootObserversNeedUpdate
: kExplicitRootObserversNeedUpdate)) {
needs_update_ = true;
}
if (!CanCompute() || !ShouldCompute(compute_flags)) {
return 0;
}
if (MaybeDelayAndReschedule(compute_flags, context)) {
return 0;
}
last_run_time_ = context.GetMonotonicTime();
needs_update_ = false;
#if CHECK_SKIPPED_UPDATE_ON_SCROLL()
std::optional<IntersectionGeometry::CachedRects> cached_rects_backup;
#endif
if (!has_pending_update && (compute_flags & kScrollAndVisibilityOnly) &&
cached_rects_.min_scroll_delta_to_update.x() >
accumulated_scroll_delta_since_last_update.x() &&
cached_rects_.min_scroll_delta_to_update.y() >
accumulated_scroll_delta_since_last_update.y()) {
#if CHECK_SKIPPED_UPDATE_ON_SCROLL()
cached_rects_backup.emplace(cached_rects_);
#else
// This is equivalent to a full update.
return 1;
#endif
}
unsigned geometry_flags = GetIntersectionGeometryFlags(compute_flags);
// The policy for honoring margins is the same as that for reporting root
// bounds, so this flag can be used for both.
bool honor_margins =
geometry_flags & IntersectionGeometry::kShouldReportRootBounds;
Vector<Length> empty_margin;
IntersectionGeometry geometry(
observer_->root(), *Target(),
honor_margins ? observer_->RootMargin() : empty_margin,
observer_->thresholds(),
honor_margins ? observer_->TargetMargin() : empty_margin,
honor_margins ? observer_->ScrollMargin() : empty_margin, geometry_flags,
context.GetRootGeometry(*observer_, compute_flags), &cached_rects_);
#if CHECK_SKIPPED_UPDATE_ON_SCROLL()
if (cached_rects_backup) {
// A skipped update on scroll should generate the same result.
CHECK_EQ(last_threshold_index_, geometry.ThresholdIndex());
CHECK_EQ(last_is_visible_, geometry.IsVisible());
cached_rects_ = cached_rects_backup.value();
return 1;
}
#endif
ProcessIntersectionGeometry(geometry, context);
return geometry.DidComputeGeometry() ? 1 : 0;
}
gfx::Vector2dF IntersectionObservation::MinScrollDeltaToUpdate() const {
if (cached_rects_.valid) {
return cached_rects_.min_scroll_delta_to_update;
}
return gfx::Vector2dF();
}
void IntersectionObservation::TakeRecords(
HeapVector<Member<IntersectionObserverEntry>>& entries) {
entries.append_range(entries_);
entries_.clear();
}
void IntersectionObservation::Disconnect() {
DCHECK(Observer());
if (target_) {
DCHECK(target_->IntersectionObserverData());
ElementIntersectionObserverData* observer_data =
target_->IntersectionObserverData();
observer_data->RemoveObservation(*this);
IntersectionObserverController* controller =
target_->GetDocument().GetIntersectionObserverController();
if (controller) {
controller->RemoveTrackedObservation(*this);
}
}
entries_.clear();
observer_.Clear();
}
void IntersectionObservation::Trace(Visitor* visitor) const {
visitor->Trace(observer_);
visitor->Trace(entries_);
visitor->Trace(target_);
}
bool IntersectionObservation::CanUseCachedRectsForTesting(
bool scroll_and_visibility_only) const {
// This is to avoid the side effects of IntersectionGeometry.
IntersectionGeometry::CachedRects cached_rects_copy = cached_rects_;
std::optional<IntersectionGeometry::RootGeometry> root_geometry;
IntersectionGeometry geometry(
observer_->root(), *target_,
/* root_margin */ {},
/* thresholds */ {0},
/* target_margin */ {},
/* scroll_margin */ {},
scroll_and_visibility_only
? IntersectionGeometry::kScrollAndVisibilityOnly
: 0,
root_geometry, &cached_rects_copy);
return geometry.CanUseCachedRectsForTesting();
}
bool IntersectionObservation::CanCompute() const {
return !!target_ && !!observer_ && observer_->RootIsValid() &&
observer_->GetExecutionContext();
}
bool IntersectionObservation::ShouldCompute(unsigned flags) const {
if (!needs_update_) {
return false;
}
if (target_->isConnected() && target_->GetDocument().GetFrame() &&
Observer()->trackVisibility()) {
mojom::blink::FrameOcclusionState occlusion_state =
target_->GetDocument().GetFrame()->GetOcclusionState();
// If we're tracking visibility, and we aren't currently reporting the
// target visible, and we don't have occlusion information from our parent
// frame, then postpone computing intersections until a later lifecycle when
// the occlusion information is known.
if (!last_is_visible_ &&
occlusion_state == mojom::blink::FrameOcclusionState::kUnknown) {
return false;
}
}
return true;
}
bool IntersectionObservation::MaybeDelayAndReschedule(
unsigned flags,
ComputeIntersectionsContext& context) {
if (flags & kIgnoreDelay) {
return false;
}
if (last_run_time_.is_null()) {
return false;
}
base::TimeDelta delay = observer_->GetEffectiveDelay() -
(context.GetMonotonicTime() - last_run_time_);
if (delay.is_positive()) {
context.UpdateNextRunDelay(delay);
return true;
}
return false;
}
unsigned IntersectionObservation::GetIntersectionGeometryFlags(
unsigned compute_flags) const {
bool report_root_bounds = observer_->AlwaysReportRootBounds() ||
(compute_flags & kReportImplicitRootBounds) ||
!observer_->RootIsImplicit();
unsigned geometry_flags = IntersectionGeometry::kShouldConvertToCSSPixels;
if (report_root_bounds)
geometry_flags |= IntersectionGeometry::kShouldReportRootBounds;
if (Observer()->trackVisibility())
geometry_flags |= IntersectionGeometry::kShouldComputeVisibility;
if (Observer()->ShouldExposeOccluderNodeId()) {
geometry_flags |= IntersectionGeometry::kShouldExposeOccluderNodeId;
}
if (Observer()->trackFractionOfRoot())
geometry_flags |= IntersectionGeometry::kShouldTrackFractionOfRoot;
if (Observer()->UseOverflowClipEdge())
geometry_flags |= IntersectionGeometry::kUseOverflowClipEdge;
if (Observer()->IsInternal()) {
// TODO(wangxianzhu): Let internal clients decide whether to respect
// filters.
geometry_flags |= IntersectionGeometry::kRespectFilters;
}
if (compute_flags & kScrollAndVisibilityOnly) {
geometry_flags |= IntersectionGeometry::kScrollAndVisibilityOnly;
}
return geometry_flags;
}
void IntersectionObservation::ProcessIntersectionGeometry(
const IntersectionGeometry& geometry,
ComputeIntersectionsContext& context) {
CHECK_LT(geometry.ThresholdIndex(), kNotFound);
if (last_threshold_index_ != geometry.ThresholdIndex() ||
last_is_visible_ != geometry.IsVisible()) {
entries_.push_back(MakeGarbageCollected<IntersectionObserverEntry>(
geometry, context.GetTimeStamp(*Observer()), Target()));
Observer()->ReportUpdates(*this);
last_threshold_index_ = geometry.ThresholdIndex();
last_is_visible_ = geometry.IsVisible();
}
}
} // namespace blink