blob: a1300591df2dc5ee7734ff87388ac96c24503385 [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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_H_
#include <optional>
#include "base/functional/callback.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observation.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/geometry/length.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_linked_hash_set.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
#include "third_party/blink/renderer/platform/heap/forward.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
class Document;
class Element;
class ExceptionState;
class IntersectionObserverDelegate;
class IntersectionObserverEntry;
class IntersectionObserverInit;
class Node;
class ScriptState;
class V8IntersectionObserverCallback;
class CORE_EXPORT IntersectionObserver final
: public ScriptWrappable,
public ActiveScriptWrappable<IntersectionObserver>,
public ExecutionContextClient {
DEFINE_WRAPPERTYPEINFO();
public:
using EventCallback = base::RepeatingCallback<void(
const HeapVector<Member<IntersectionObserverEntry>>&)>;
// The IntersectionObserver can be configured to notify based on changes to
// how much of the target element's area intersects with the root, or based on
// changes to how much of the root element's area intersects with the
// target. Examples illustrating the distinction:
//
// 1.0 of target, 0.5 of target, 1.0 of target,
// 0.25 of root 0.5 of root 1.0 of root
// +------------------+ +------------------+ *~~~~~~~~~~~~~~~~~~*
// | ////////// | | | ;//////////////////;
// | ////////// | | | ;//////////////////;
// | ////////// | ;//////////////////; ;//////////////////;
// | | ;//////////////////; ;//////////////////;
// +------------------+ *~~~~~~~~~~~~~~~~~~* *~~~~~~~~~~~~~~~~~~*
// ////////////////////
// ////////////////////
// ////////////////////
enum ThresholdInterpretation { kFractionOfTarget, kFractionOfRoot };
// This value can be used to detect transitions between non-intersecting or
// edge-adjacent (i.e., zero area) state, and intersecting by any non-zero
// number of pixels.
static constexpr float kMinimumThreshold =
IntersectionGeometry::kMinimumThreshold;
// Used to specify when callbacks should be invoked with new notifications.
// Blink-internal users of IntersectionObserver will have their callbacks
// invoked synchronously either at the end of a lifecycle update or in the
// middle of the lifecycle post layout. Javascript observers will PostTask to
// invoke their callbacks.
enum DeliveryBehavior {
kDeliverDuringPostLayoutSteps,
kDeliverDuringPostLifecycleSteps,
kPostTaskToDeliver
};
// Used to specify whether the margins apply to the root element or the source
// element. The effect of the root element margins is that intermediate
// scrollers clip content by its bounding box without considering margins.
// That is, margins only apply to the last scroller (root). The effect of
// source element margins is that the margins apply to the first / deepest
// clipper, but do not apply to any other clippers. Note that in a case of a
// single clipper, the two approaches are equivalent.
//
// Note that the percentage margin is resolved against the root rect, even
// when the margin is applied to the target.
enum MarginTarget { kApplyMarginToRoot, kApplyMarginToTarget };
static IntersectionObserver* Create(
const IntersectionObserverInit*,
IntersectionObserverDelegate&,
std::optional<LocalFrameUkmAggregator::MetricId> ukm_metric_id,
ExceptionState& = ASSERT_NO_EXCEPTION);
static IntersectionObserver* Create(ScriptState*,
V8IntersectionObserverCallback*,
const IntersectionObserverInit*,
ExceptionState& = ASSERT_NO_EXCEPTION);
struct Params {
STACK_ALLOCATED();
public:
Node* root;
Vector<Length> margin;
MarginTarget margin_target = kApplyMarginToRoot;
Vector<Length> scroll_margin;
// Elements should be in the range [0,1], and are interpreted according to
// the given `semantics`.
Vector<float> thresholds;
ThresholdInterpretation semantics = kFractionOfTarget;
DeliveryBehavior behavior = kDeliverDuringPostLifecycleSteps;
// Specifies the minimum period between change notifications.
base::TimeDelta delay;
bool track_visibility = false;
bool always_report_root_bounds = false;
// Indicates whether the overflow clip edge should be used instead of the
// bounding box if appropriate.
bool use_overflow_clip_edge = false;
// Indicates whether we should compute and expose the occluder node Id.
// This only works if you've already set true `track_visibility`.
bool expose_occluder_id = false;
};
// Creates an IntersectionObserver that monitors changes to the intersection
// and notifies via the given |callback|.
static IntersectionObserver* Create(
const Document& document,
EventCallback callback,
std::optional<LocalFrameUkmAggregator::MetricId> ukm_metric_id,
Params&& params);
IntersectionObserver(
IntersectionObserverDelegate& delegate,
std::optional<LocalFrameUkmAggregator::MetricId> ukm_metric_id,
Params&& params);
// API methods.
void observe(Element*, ExceptionState& = ASSERT_NO_EXCEPTION);
void unobserve(Element*, ExceptionState& = ASSERT_NO_EXCEPTION);
void disconnect(ExceptionState& = ASSERT_NO_EXCEPTION);
HeapVector<Member<IntersectionObserverEntry>> takeRecords(
ExceptionState& = ASSERT_NO_EXCEPTION);
// API attributes.
Node* root() const { return root_.Get(); }
String rootMargin() const;
String scrollMargin() const;
const Vector<float>& thresholds() const { return thresholds_; }
DOMHighResTimeStamp delay() const { return delay_.InMilliseconds(); }
bool trackVisibility() const { return track_visibility_; }
bool trackFractionOfRoot() const { return track_fraction_of_root_; }
// An observer can either track intersections with an explicit root Node,
// or with the the top-level frame's viewport (the "implicit root"). When
// tracking the implicit root, root_ will be null, but because root_ is a
// weak pointer, we cannot surmise that this observer tracks the implicit
// root just because root_ is null. Hence root_is_implicit_.
bool RootIsImplicit() const { return root_is_implicit_; }
bool HasObservations() const { return !observations_.empty(); }
bool AlwaysReportRootBounds() const { return always_report_root_bounds_; }
bool NeedsOcclusionTracking() const {
return trackVisibility() && !observations_.empty();
}
bool ShouldExposeOccluderNodeId() const {
return trackVisibility() && expose_occluder_id_;
}
base::TimeDelta GetEffectiveDelay() const;
Vector<Length> RootMargin() const {
return margin_target_ == kApplyMarginToRoot ? margin_ : Vector<Length>();
}
Vector<Length> TargetMargin() const {
return margin_target_ == kApplyMarginToTarget ? margin_ : Vector<Length>();
}
Vector<Length> ScrollMargin() const { return scroll_margin_; }
bool IsInternal() const;
// The metric id for tracking update time via UpdateTime metrics, or null for
// internal intersection observers without explicit metrics.
std::optional<LocalFrameUkmAggregator::MetricId> GetUkmMetricId() const {
return ukm_metric_id_;
}
void ReportUpdates(IntersectionObservation&);
DeliveryBehavior GetDeliveryBehavior() const;
void Deliver();
// Returns false if this observer has an explicit root node which has been
// deleted; true otherwise.
bool RootIsValid() const;
bool UseOverflowClipEdge() const { return use_overflow_clip_edge_ == 1; }
// ScriptWrappable override:
bool HasPendingActivity() const override;
void Trace(Visitor*) const override;
// Enable/disable throttling of visibility checking, so we don't have to add
// sleep() calls to tests to wait for notifications to show up.
static void SetThrottleDelayEnabledForTesting(bool);
const HeapLinkedHashSet<WeakMember<IntersectionObservation>>& Observations() {
return observations_;
}
private:
bool NeedsDelivery() const { return !active_observations_.empty(); }
void ProcessCustomWeakness(const LivenessBroker&);
const Member<IntersectionObserverDelegate> delegate_;
// See: `GetUkmMetricId()`.
const std::optional<LocalFrameUkmAggregator::MetricId> ukm_metric_id_;
// We use UntracedMember<> here to do custom weak processing.
UntracedMember<Node> root_;
HeapLinkedHashSet<WeakMember<IntersectionObservation>> observations_;
// Observations that have updates waiting to be delivered
HeapHashSet<Member<IntersectionObservation>> active_observations_;
const Vector<float> thresholds_;
const base::TimeDelta delay_;
const Vector<Length> margin_;
const Vector<Length> scroll_margin_;
const MarginTarget margin_target_;
const unsigned root_is_implicit_ : 1;
const unsigned track_visibility_ : 1;
const unsigned track_fraction_of_root_ : 1;
const unsigned always_report_root_bounds_ : 1;
const unsigned use_overflow_clip_edge_ : 1;
const unsigned expose_occluder_id_ : 1;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_H_