blob: b1cebeee4779b570049560b6ff00ac59ced2f8b3 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// 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_observer.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/intersection_geometry.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
namespace blink {
namespace {
bool IsOccluded(const Element& element, const IntersectionGeometry& geometry) {
DCHECK(RuntimeEnabledFeatures::IntersectionObserverV2Enabled());
if (element.GetDocument()
.GetFrame()
->LocalFrameRoot()
.MayBeOccludedOrObscuredByRemoteAncestor()) {
return true;
}
// 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(
element.GetLayoutObject()->HitTestForOcclusion(geometry.TargetRect()));
return result.InnerNode() && result.InnerNode() != &element;
}
} // 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) {}
void IntersectionObservation::Compute(unsigned flags) {
DCHECK(Observer());
if (!target_ || !observer_->RootIsValid() | !observer_->GetExecutionContext())
return;
if (flags &
(observer_->RootIsImplicit() ? kImplicitRootObserversNeedUpdate
: kExplicitRootObserversNeedUpdate)) {
needs_update_ = true;
}
if (!needs_update_)
return;
DOMHighResTimeStamp timestamp = observer_->GetTimeStamp();
if (timestamp == -1)
return;
if (timestamp - last_run_time_ < observer_->GetEffectiveDelay()) {
// TODO(crbug.com/915495): Need to eventually notify the observer of the
// updated intersection because there's currently nothing to guarantee this
// Compute() method will be called again after the delay period has passed.
return;
}
last_run_time_ = timestamp;
needs_update_ = 0;
Vector<Length> root_margin(4);
root_margin[0] = observer_->TopMargin();
root_margin[1] = observer_->RightMargin();
root_margin[2] = observer_->BottomMargin();
root_margin[3] = observer_->LeftMargin();
bool report_root_bounds = observer_->AlwaysReportRootBounds() ||
(flags & kReportImplicitRootBounds) ||
!observer_->RootIsImplicit();
IntersectionGeometry geometry(observer_->root(), *Target(), root_margin,
report_root_bounds);
geometry.ComputeGeometry();
// 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.
unsigned new_threshold_index;
float new_visible_ratio;
bool is_visible = false;
if (geometry.DoesIntersect()) {
const LayoutRect comparison_rect = observer_->trackFractionOfRoot()
? geometry.RootRect()
: geometry.TargetRect();
if (comparison_rect.IsEmpty()) {
new_visible_ratio = 1;
} else {
const LayoutSize& intersection_size = geometry.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();
new_visible_ratio = intersection_area / area_of_interest;
}
new_threshold_index =
Observer()->FirstThresholdGreaterThan(new_visible_ratio);
if (RuntimeEnabledFeatures::IntersectionObserverV2Enabled() &&
Observer()->trackVisibility()) {
is_visible = new_threshold_index > 0 &&
!Target()->GetLayoutObject()->HasDistortingVisualEffects() &&
!IsOccluded(*Target(), geometry);
}
} else {
new_visible_ratio = 0;
new_threshold_index = 0;
}
// TODO(tkent): We can't use CHECK_LT due to a compile error.
CHECK(new_threshold_index < kMaxThresholdIndex - 1);
if (last_threshold_index_ != new_threshold_index ||
last_is_visible_ != is_visible) {
FloatRect root_bounds(geometry.UnZoomedRootRect());
FloatRect* root_bounds_pointer =
report_root_bounds ? &root_bounds : nullptr;
IntersectionObserverEntry* new_entry =
MakeGarbageCollected<IntersectionObserverEntry>(
timestamp, new_visible_ratio,
FloatRect(geometry.UnZoomedTargetRect()), root_bounds_pointer,
FloatRect(geometry.UnZoomedIntersectionRect()),
new_threshold_index > 0, is_visible, Target());
entries_.push_back(new_entry);
To<Document>(Observer()->GetExecutionContext())
->EnsureIntersectionObserverController()
.ScheduleIntersectionObserverForDelivery(*Observer());
SetLastThresholdIndex(new_threshold_index);
SetWasVisible(is_visible);
}
}
void IntersectionObservation::TakeRecords(
HeapVector<Member<IntersectionObserverEntry>>& entries) {
entries.AppendVector(entries_);
entries_.clear();
}
void IntersectionObservation::Disconnect() {
DCHECK(Observer());
if (target_)
Target()->EnsureIntersectionObserverData().RemoveObservation(*Observer());
entries_.clear();
observer_.Clear();
}
void IntersectionObservation::Trace(blink::Visitor* visitor) {
visitor->Trace(observer_);
visitor->Trace(entries_);
visitor->Trace(target_);
}
} // namespace blink