blob: cfc46069efa896a1c3271b497dbd2c6a1d1c4e83 [file] [log] [blame]
// 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/element_rare_data.h"
#include "third_party/blink/renderer/core/frame/local_frame.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"
namespace blink {
namespace {
Document& TrackingDocument(const IntersectionObservation* observation) {
if (observation->Observer()->RootIsImplicit())
return observation->Target()->GetDocument();
return (observation->Observer()->root()->GetDocument());
}
} // namespace
IntersectionObservation::IntersectionObservation(IntersectionObserver& observer,
Element& target)
: observer_(observer),
target_(&target),
last_run_time_(-observer.GetEffectiveDelay()),
last_is_visible_(false),
// Note that the spec says the initial value of last_threshold_index_
// should be -1, but since last_threshold_index_ is unsigned, we use a
// different sentinel value.
last_threshold_index_(kMaxThresholdIndex - 1) {
if (!observer.RootIsImplicit())
cached_rects_ = std::make_unique<IntersectionGeometry::CachedRects>();
}
int64_t IntersectionObservation::ComputeIntersection(
const IntersectionGeometry::RootGeometry& root_geometry,
unsigned compute_flags,
absl::optional<base::TimeTicks>& monotonic_time) {
DCHECK(Observer());
if (compute_flags &
(observer_->RootIsImplicit() ? kImplicitRootObserversNeedUpdate
: kExplicitRootObserversNeedUpdate)) {
needs_update_ = true;
}
if (!ShouldCompute(compute_flags))
return 0;
if (!monotonic_time.has_value())
monotonic_time = base::DefaultTickClock::GetInstance()->NowTicks();
DOMHighResTimeStamp timestamp = observer_->GetTimeStamp(*monotonic_time);
if (MaybeDelayAndReschedule(compute_flags, timestamp))
return 0;
DCHECK(observer_->root());
unsigned geometry_flags = GetIntersectionGeometryFlags(compute_flags);
IntersectionGeometry geometry(
root_geometry, *observer_->root(), *Target(), observer_->thresholds(),
observer_->TargetMargin(), geometry_flags, cached_rects_.get());
ProcessIntersectionGeometry(geometry, timestamp);
last_run_time_ = timestamp;
needs_update_ = false;
return geometry.DidComputeGeometry() ? 1 : 0;
}
int64_t IntersectionObservation::ComputeIntersection(
unsigned compute_flags,
absl::optional<base::TimeTicks>& monotonic_time) {
DCHECK(Observer());
if (compute_flags &
(observer_->RootIsImplicit() ? kImplicitRootObserversNeedUpdate
: kExplicitRootObserversNeedUpdate)) {
needs_update_ = true;
}
if (!ShouldCompute(compute_flags))
return 0;
if (!monotonic_time.has_value())
monotonic_time = base::DefaultTickClock::GetInstance()->NowTicks();
DOMHighResTimeStamp timestamp = observer_->GetTimeStamp(*monotonic_time);
if (MaybeDelayAndReschedule(compute_flags, timestamp))
return 0;
unsigned geometry_flags = GetIntersectionGeometryFlags(compute_flags);
IntersectionGeometry geometry(
observer_->root(), *Target(), observer_->RootMargin(),
observer_->thresholds(), observer_->TargetMargin(), geometry_flags);
ProcessIntersectionGeometry(geometry, timestamp);
last_run_time_ = timestamp;
needs_update_ = false;
return geometry.DidComputeGeometry() ? 1 : 0;
}
void IntersectionObservation::TakeRecords(
HeapVector<Member<IntersectionObserverEntry>>& entries) {
entries.AppendVector(entries_);
entries_.clear();
}
void IntersectionObservation::Disconnect() {
DCHECK(Observer());
if (target_) {
DCHECK(target_->IntersectionObserverData());
ElementIntersectionObserverData* observer_data =
target_->IntersectionObserverData();
observer_data->RemoveObservation(*this);
if (target_->isConnected()) {
IntersectionObserverController* controller =
target_->GetDocument().GetIntersectionObserverController();
if (controller)
controller->RemoveTrackedObservation(*this);
}
}
entries_.clear();
observer_.Clear();
}
void IntersectionObservation::InvalidateCachedRects() {
if (cached_rects_)
cached_rects_->valid = false;
}
void IntersectionObservation::Trace(Visitor* visitor) const {
visitor->Trace(observer_);
visitor->Trace(entries_);
visitor->Trace(target_);
}
bool IntersectionObservation::ShouldCompute(unsigned flags) const {
if (!target_ || !observer_->RootIsValid() ||
!observer_->GetExecutionContext())
return false;
// 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 = flags & kPostLayoutDeliveryOnly;
bool is_post_layout_delivery_observer =
Observer()->GetDeliveryBehavior() ==
IntersectionObserver::kDeliverDuringPostLayoutSteps;
if (post_layout_delivery_only != is_post_layout_delivery_observer)
return false;
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 don't have occlusion information
// from our parent frame, then postpone computing intersections until a
// later lifecycle when the occlusion information is known.
if (occlusion_state == mojom::blink::FrameOcclusionState::kUnknown)
return false;
}
return true;
}
bool IntersectionObservation::MaybeDelayAndReschedule(
unsigned flags,
DOMHighResTimeStamp timestamp) {
if (timestamp == -1)
return true;
base::TimeDelta delay = base::Milliseconds(observer_->GetEffectiveDelay() -
(timestamp - last_run_time_));
if (!(flags & kIgnoreDelay) && delay.is_positive()) {
TrackingDocument(this).View()->ScheduleAnimation(delay);
return true;
}
return false;
}
bool IntersectionObservation::CanUseCachedRects() const {
if (!cached_rects_ || !cached_rects_->valid ||
!observer_->CanUseCachedRects()) {
return false;
}
// Cached rects can only be used if there are no scrollable objects in the
// hierarchy between target and root (a scrollable root is ok). The reason is
// that a scroll change in an intermediate scroller would change the
// intersection geometry, but it would not properly trigger an invalidation of
// the cached rects.
if (LayoutObject* target = target_->GetLayoutObject()) {
PaintLayer* root_layer = target->GetDocument().GetLayoutView()->Layer();
if (!root_layer)
return false;
if (LayoutBox* scroller = target->EnclosingScrollableBox()) {
if (scroller->GetNode() == observer_->root())
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()->trackFractionOfRoot())
geometry_flags |= IntersectionGeometry::kShouldTrackFractionOfRoot;
if (CanUseCachedRects())
geometry_flags |= IntersectionGeometry::kShouldUseCachedRects;
if (Observer()->UseOverflowClipEdge())
geometry_flags |= IntersectionGeometry::kUseOverflowClipEdge;
return geometry_flags;
}
void IntersectionObservation::ProcessIntersectionGeometry(
const IntersectionGeometry& geometry,
DOMHighResTimeStamp timestamp) {
CHECK_LT(geometry.ThresholdIndex(), kMaxThresholdIndex - 1);
if (last_threshold_index_ != geometry.ThresholdIndex() ||
last_is_visible_ != geometry.IsVisible()) {
entries_.push_back(MakeGarbageCollected<IntersectionObserverEntry>(
geometry, timestamp, Target()));
Observer()->ReportUpdates(*this);
SetLastThresholdIndex(geometry.ThresholdIndex());
SetWasVisible(geometry.IsVisible());
}
}
} // namespace blink