Commit f7909ba3 authored by Stefan Zager's avatar Stefan Zager Committed by Commit Bot

[IntersectionObserver] Fix tracked element bookkeeping

This patch caused a leak in IntersectionObserverController:

https://chromium-review.googlesource.com/c/chromium/src/+/1774244

In particular, if an explicit-root IntersectionObserver was destroyed
because it had no observations and no javascript references to it,
then IntersectionObserverController would continue to track the root
Element.

This patch overhauls the way the tracking bookkeeping is handled for
explicit-root observers, and guarantees two important cases:

- If the observer dies, the root will no longer be tracked.
- If the root element dies, any remaining observations will be
disconnected.

Change-Id: Ieb3822a4735850c7baae4ef32e7826f3eda10fb7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2008528
Commit-Queue: Stefan Zager <szager@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarvmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734206}
parent 26cbd380
......@@ -2736,12 +2736,9 @@ Node::InsertionNotificationRequest Element::InsertedInto(
if (ElementIntersectionObserverData* observer_data =
rare_data->IntersectionObserverData()) {
observer_data->InvalidateCachedRects();
if (observer_data->IsTargetOfImplicitRootObserver() ||
observer_data->IsRoot()) {
GetDocument().EnsureIntersectionObserverController().AddTrackedElement(
*this, observer_data->NeedsOcclusionTracking());
}
if (observer_data->IsTarget() || observer_data->IsRoot()) {
observer_data->TrackWithController(
GetDocument().EnsureIntersectionObserverController());
if (!observer_data->IsEmpty()) {
if (LocalFrameView* frame_view = GetDocument().View()) {
frame_view->SetIntersectionObservationState(
LocalFrameView::kRequired);
......@@ -2847,8 +2844,8 @@ void Element::RemovedFrom(ContainerNode& insertion_point) {
IntersectionObservation::kExplicitRootObserversNeedUpdate |
IntersectionObservation::kImplicitRootObserversNeedUpdate |
IntersectionObservation::kIgnoreDelay);
GetDocument().EnsureIntersectionObserverController().RemoveTrackedElement(
*this);
data->IntersectionObserverData()->StopTrackingWithController(
GetDocument().EnsureIntersectionObserverController());
}
if (auto* context = data->GetDisplayLockContext())
......@@ -4754,18 +4751,6 @@ ElementIntersectionObserverData& Element::EnsureIntersectionObserverData() {
return EnsureElementRareData().EnsureIntersectionObserverData();
}
bool Element::ComputeIntersectionsForLifecycleUpdate(unsigned flags) {
if (ElementIntersectionObserverData* data = IntersectionObserverData())
return data->ComputeIntersectionsForLifecycleUpdate(flags);
return false;
}
bool Element::NeedsOcclusionTracking() const {
if (ElementIntersectionObserverData* data = IntersectionObserverData())
return data->NeedsOcclusionTracking();
return false;
}
HeapHashMap<Member<ResizeObserver>, Member<ResizeObservation>>*
Element::ResizeObserverData() const {
if (HasRareData())
......
......@@ -931,10 +931,6 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable {
ElementIntersectionObserverData* IntersectionObserverData() const;
ElementIntersectionObserverData& EnsureIntersectionObserverData();
bool ComputeIntersectionsForLifecycleUpdate(unsigned flags);
// Returns true if the Element is being observed by an IntersectionObserver
// for which trackVisibility() is true.
bool NeedsOcclusionTracking() const;
HeapHashMap<Member<ResizeObserver>, Member<ResizeObservation>>*
ResizeObserverData() const;
......
......@@ -15,8 +15,8 @@ ElementIntersectionObserverData::ElementIntersectionObserverData() = default;
IntersectionObservation* ElementIntersectionObserverData::GetObservationFor(
IntersectionObserver& observer) {
auto i = intersection_observations_.find(&observer);
if (i == intersection_observations_.end())
auto i = observations_.find(&observer);
if (i == observations_.end())
return nullptr;
return i->value;
}
......@@ -24,59 +24,61 @@ IntersectionObservation* ElementIntersectionObserverData::GetObservationFor(
void ElementIntersectionObserverData::AddObservation(
IntersectionObservation& observation) {
DCHECK(observation.Observer());
intersection_observations_.insert(observation.Observer(), &observation);
observations_.insert(observation.Observer(), &observation);
}
void ElementIntersectionObserverData::AddObserver(
IntersectionObserver& observer) {
intersection_observers_.insert(&observer);
observers_.insert(&observer);
}
bool ElementIntersectionObserverData::IsTargetOfImplicitRootObserver() const {
for (auto& entry : intersection_observations_) {
if (entry.key->RootIsImplicit())
return true;
}
return false;
void ElementIntersectionObserverData::RemoveObservation(
IntersectionObservation& observation) {
observations_.erase(observation.Observer());
}
void ElementIntersectionObserverData::RemoveObservation(
void ElementIntersectionObserverData::RemoveObserver(
IntersectionObserver& observer) {
intersection_observations_.erase(&observer);
observers_.erase(&observer);
}
bool ElementIntersectionObserverData::ComputeIntersectionsForTarget(
unsigned flags) {
bool needs_occlusion_tracking = false;
for (auto& entry : intersection_observations_) {
needs_occlusion_tracking |= entry.key->NeedsOcclusionTracking();
entry.value->ComputeIntersection(flags);
void ElementIntersectionObserverData::TrackWithController(
IntersectionObserverController& controller) {
for (auto& entry : observations_) {
if (entry.key->RootIsImplicit()) {
controller.AddTrackedObservation(*entry.value,
entry.key->trackVisibility());
}
}
for (auto& observer : observers_) {
controller.AddTrackedObserver(*observer, observer->trackVisibility());
}
return needs_occlusion_tracking;
}
bool ElementIntersectionObserverData::ComputeIntersectionsForLifecycleUpdate(
unsigned flags) {
bool needs_occlusion_tracking = false;
// Process explicit-root observers for which this element is root.
for (auto& observer : intersection_observers_) {
needs_occlusion_tracking |= observer->NeedsOcclusionTracking();
if (flags & IntersectionObservation::kExplicitRootObserversNeedUpdate) {
observer->ComputeIntersections(flags);
void ElementIntersectionObserverData::StopTrackingWithController(
IntersectionObserverController& controller) {
for (auto& entry : observations_) {
if (entry.key->RootIsImplicit()) {
controller.RemoveTrackedObservation(*entry.value);
}
}
for (auto& observer : observers_) {
controller.RemoveTrackedObserver(*observer);
}
}
// Process implicit-root observations for which this element is target.
unsigned implicit_root_flags =
flags & ~IntersectionObservation::kExplicitRootObserversNeedUpdate;
needs_occlusion_tracking |=
ComputeIntersectionsForTarget(implicit_root_flags);
bool ElementIntersectionObserverData::ComputeIntersectionsForTarget(
unsigned flags) {
bool needs_occlusion_tracking = false;
for (auto& entry : observations_) {
needs_occlusion_tracking |= entry.key->NeedsOcclusionTracking();
entry.value->ComputeIntersection(flags);
}
return needs_occlusion_tracking;
}
bool ElementIntersectionObserverData::NeedsOcclusionTracking() const {
for (auto& entry : intersection_observations_) {
for (auto& entry : observations_) {
if (entry.key->trackVisibility())
return true;
}
......@@ -84,15 +86,15 @@ bool ElementIntersectionObserverData::NeedsOcclusionTracking() const {
}
void ElementIntersectionObserverData::InvalidateCachedRects() {
for (auto& observer : intersection_observers_)
for (auto& observer : observers_)
observer->InvalidateCachedRects();
for (auto& entry : intersection_observations_)
for (auto& entry : observations_)
entry.value->InvalidateCachedRects();
}
void ElementIntersectionObserverData::Trace(blink::Visitor* visitor) {
visitor->Trace(intersection_observations_);
visitor->Trace(intersection_observers_);
visitor->Trace(observations_);
visitor->Trace(observers_);
}
} // namespace blink
......@@ -14,6 +14,7 @@ namespace blink {
class IntersectionObservation;
class IntersectionObserver;
class IntersectionObserverController;
class CORE_EXPORT ElementIntersectionObserverData final
: public GarbageCollected<ElementIntersectionObserverData>,
......@@ -21,21 +22,25 @@ class CORE_EXPORT ElementIntersectionObserverData final
public:
ElementIntersectionObserverData();
// If the argument observer is observing this Element, this method will return
// the observation.
IntersectionObservation* GetObservationFor(IntersectionObserver&);
// Add an implicit-root observation with this element as target.
void AddObservation(IntersectionObservation&);
// Add an explicit-root observer with this element as root.
void AddObserver(IntersectionObserver&);
void RemoveObservation(IntersectionObserver&);
bool IsTarget() const { return !intersection_observations_.IsEmpty(); }
bool IsTargetOfImplicitRootObserver() const;
bool IsRoot() const { return !intersection_observers_.IsEmpty(); }
void RemoveObservation(IntersectionObservation&);
void RemoveObserver(IntersectionObserver&);
bool IsEmpty() const {
return observations_.IsEmpty() && observers_.IsEmpty();
}
void TrackWithController(IntersectionObserverController&);
void StopTrackingWithController(IntersectionObserverController&);
// Run the IntersectionObserver algorithm for all observations for which this
// element is target.
bool ComputeIntersectionsForTarget(unsigned flags);
// Run the IntersectionObserver algorithm for all implicit-root observations
// for which this element is target; and all explicit-root observers for which
// this element is root. Returns true if any observer needs occlusion
// tracking.
bool ComputeIntersectionsForLifecycleUpdate(unsigned flags);
bool NeedsOcclusionTracking() const;
// Indicates that geometry information cached during the previous run of the
// algorithm is invalid and must be recomputed.
......@@ -49,11 +54,11 @@ class CORE_EXPORT ElementIntersectionObserverData final
private:
// IntersectionObservations for which the Node owning this data is target.
HeapHashMap<Member<IntersectionObserver>, Member<IntersectionObservation>>
intersection_observations_;
observations_;
// IntersectionObservers for which the Node owning this data is root.
// Weak because once an observer is unreachable from javascript and has no
// active observations, it should be allowed to die.
HeapHashSet<WeakMember<IntersectionObserver>> intersection_observers_;
HeapHashSet<WeakMember<IntersectionObserver>> observers_;
};
} // namespace blink
......
......@@ -76,18 +76,16 @@ void IntersectionObservation::Disconnect() {
DCHECK(target_->IntersectionObserverData());
ElementIntersectionObserverData* observer_data =
target_->IntersectionObserverData();
observer_data->RemoveObservation(*Observer());
observer_data->RemoveObservation(*this);
// We track connected elements that are either the root of an explicit root
// observer, or the target of an implicit root observer. If the target was
// previously being tracked, but no longer needs to be tracked, then remove
// it.
if (target_->isConnected() && Observer()->RootIsImplicit() &&
!observer_data->IsTargetOfImplicitRootObserver() &&
!observer_data->IsRoot()) {
if (target_->isConnected() && Observer()->RootIsImplicit()) {
IntersectionObserverController* controller =
target_->GetDocument().GetIntersectionObserverController();
if (controller)
controller->RemoveTrackedElement(*target_);
controller->RemoveTrackedObservation(*this);
}
}
entries_.clear();
......
......@@ -280,16 +280,17 @@ IntersectionObserver::IntersectionObserver(
root->EnsureIntersectionObserverData().AddObserver(*this);
root->GetDocument()
.EnsureIntersectionObserverController()
.AddTrackedElement(*root, track_visibility);
.AddTrackedObserver(*this, track_visibility);
}
}
void IntersectionObserver::ProcessCustomWeakness(const WeakCallbackInfo& info) {
if (RootIsImplicit() || (root() && info.IsHeapObjectAlive(root())))
return;
DummyExceptionStateForTesting exception_state;
disconnect(exception_state);
root_ = nullptr;
// For explicit-root observers, if the root element disappears for any reason,
// any remaining obsevations must be dismantled.
if (root() && !info.IsHeapObjectAlive(root()))
root_ = nullptr;
if (!RootIsImplicit() && !root())
disconnect();
}
bool IntersectionObserver::RootIsValid() const {
......@@ -319,7 +320,7 @@ void IntersectionObserver::observe(Element* target,
if (RootIsImplicit()) {
target->GetDocument()
.EnsureIntersectionObserverController()
.AddTrackedElement(*target, track_visibility_);
.AddTrackedObservation(*observation, track_visibility_);
}
if (LocalFrameView* frame_view = target_frame->View()) {
// The IntersectionObsever spec requires that at least one observation
......
......@@ -72,11 +72,11 @@ class CORE_EXPORT IntersectionObserver final
static IntersectionObserver* Create(const IntersectionObserverInit*,
IntersectionObserverDelegate&,
ExceptionState&);
ExceptionState& = ASSERT_NO_EXCEPTION);
static IntersectionObserver* Create(ScriptState*,
V8IntersectionObserverCallback*,
const IntersectionObserverInit*,
ExceptionState&);
ExceptionState& = ASSERT_NO_EXCEPTION);
// Creates an IntersectionObserver that monitors changes to the intersection
// between its target element relative to its implicit root and notifies via
......@@ -110,7 +110,8 @@ class CORE_EXPORT IntersectionObserver final
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&);
HeapVector<Member<IntersectionObserverEntry>> takeRecords(
ExceptionState& = ASSERT_NO_EXCEPTION);
// API attributes.
Element* root() const { return root_.Get(); }
......
......@@ -66,23 +66,30 @@ bool IntersectionObserverController::ComputeIntersections(unsigned flags) {
TRACE_EVENT0("blink",
"IntersectionObserverController::"
"computeIntersections");
HeapVector<Member<Element>> elements_to_process;
CopyToVector(tracked_elements_, elements_to_process);
for (auto& element : elements_to_process) {
needs_occlusion_tracking_ |=
element->ComputeIntersectionsForLifecycleUpdate(flags);
HeapVector<Member<IntersectionObserver>> observers_to_process;
CopyToVector(explicit_root_observers_, observers_to_process);
for (auto& observer : observers_to_process) {
needs_occlusion_tracking_ |= observer->ComputeIntersections(flags);
}
HeapVector<Member<IntersectionObservation>> observations_to_process;
CopyToVector(implicit_root_observations_, observations_to_process);
for (auto& observation : observations_to_process) {
observation->ComputeIntersection(flags);
needs_occlusion_tracking_ |= observation->Observer()->trackVisibility();
}
}
return needs_occlusion_tracking_;
}
void IntersectionObserverController::AddTrackedElement(Element& element,
bool track_occlusion) {
tracked_elements_.insert(&element);
void IntersectionObserverController::AddTrackedObserver(
IntersectionObserver& observer,
bool track_occlusion) {
DCHECK(!observer.RootIsImplicit());
explicit_root_observers_.insert(&observer);
if (!track_occlusion)
return;
needs_occlusion_tracking_ = true;
if (LocalFrameView* frame_view = element.GetDocument().View()) {
if (LocalFrameView* frame_view = observer.root()->GetDocument().View()) {
if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) {
// Set this bit as early as possible, rather than waiting for a lifecycle
// update to recompute it.
......@@ -91,17 +98,41 @@ void IntersectionObserverController::AddTrackedElement(Element& element,
}
}
void IntersectionObserverController::RemoveTrackedElement(Element& target) {
void IntersectionObserverController::RemoveTrackedObserver(
IntersectionObserver& observer) {
DCHECK(!observer.RootIsImplicit());
// Note that we don't try to opportunistically turn off the 'needs occlusion
// tracking' bit here, like the way we turn it on in AddTrackedTarget. The
// tracking' bit here, like the way we turn it on in AddTrackedObserver. The
// bit will get recomputed on the next lifecycle update; there's no
// compelling reason to do it here, so we avoid the iteration through targets
// and observations here.
tracked_elements_.erase(&target);
// compelling reason to do it here, so we avoid the iteration through
// observers and observations here.
explicit_root_observers_.erase(&observer);
}
void IntersectionObserverController::AddTrackedObservation(
IntersectionObservation& observation,
bool track_occlusion) {
DCHECK(observation.Observer()->RootIsImplicit());
implicit_root_observations_.insert(&observation);
if (!track_occlusion)
return;
needs_occlusion_tracking_ = true;
if (LocalFrameView* frame_view = observation.Target()->GetDocument().View()) {
if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) {
frame_owner->SetNeedsOcclusionTracking(true);
}
}
}
void IntersectionObserverController::RemoveTrackedObservation(
IntersectionObservation& observation) {
DCHECK(observation.Observer()->RootIsImplicit());
implicit_root_observations_.erase(&observation);
}
void IntersectionObserverController::Trace(blink::Visitor* visitor) {
visitor->Trace(tracked_elements_);
visitor->Trace(explicit_root_observers_);
visitor->Trace(implicit_root_observations_);
visitor->Trace(pending_intersection_observers_);
ContextClient::Trace(visitor);
}
......
......@@ -40,29 +40,37 @@ class IntersectionObserverController
// communicates whether observer->trackVisibility() is true for any tracked
// observer.
bool ComputeIntersections(unsigned flags);
// The second argument indicates whether the Element is a target of any
// observers for which observer->trackVisibility() is true.
void AddTrackedElement(Element&, bool);
void RemoveTrackedElement(Element&);
void AddTrackedObserver(IntersectionObserver&, bool);
void AddTrackedObservation(IntersectionObservation&, bool);
void RemoveTrackedObserver(IntersectionObserver&);
void RemoveTrackedObservation(IntersectionObservation&);
bool NeedsOcclusionTracking() const { return needs_occlusion_tracking_; }
void Trace(blink::Visitor*) override;
const char* NameInHeapSnapshot() const override {
return "IntersectionObserverController";
}
unsigned GetTrackedTargetCountForTesting() const {
return tracked_elements_.size();
unsigned GetTrackedObserverCountForTesting() const {
return explicit_root_observers_.size();
}
unsigned GetTrackedObservationCountForTesting() const {
return implicit_root_observations_.size();
}
private:
void PostTaskToDeliverNotifications();
private:
// Elements in this document which are the target of an IntersectionObserver
// with implicit root; or the explicit root of an IntersectionObserver.
HeapHashSet<WeakMember<Element>> tracked_elements_;
// IntersectionObserver's with an explicit root in this document.
HeapHashSet<WeakMember<IntersectionObserver>> explicit_root_observers_;
// IntersectionObservations with an implicit root and target in this document.
HeapHashSet<WeakMember<IntersectionObservation>> implicit_root_observations_;
// IntersectionObservers for which this is the execution context of the
// callback.
// callback, and with unsent notifications.
HeapHashSet<Member<IntersectionObserver>> pending_intersection_observers_;
// This is 'true' if any tracked element is the target of an observer for
// which observer->trackVisibility() is true.
......
......@@ -6,6 +6,10 @@
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
#include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/script_source_code.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
......@@ -46,6 +50,10 @@ class TestIntersectionObserverDelegate : public IntersectionObserverDelegate {
int CallCount() const { return call_count_; }
int EntryCount() const { return entries_.size(); }
const IntersectionObserverEntry* LastEntry() const { return entries_.back(); }
void Clear() {
entries_.clear();
call_count_ = 0;
}
PhysicalRect LastIntersectionRect() const {
if (entries_.IsEmpty())
return PhysicalRect();
......@@ -380,11 +388,11 @@ TEST_F(IntersectionObserverTest, DisconnectClearsNotifications) {
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
IntersectionObserverController& controller =
GetDocument().EnsureIntersectionObserverController();
observer->observe(target, exception_state);
EXPECT_EQ(GetDocument()
.EnsureIntersectionObserverController()
.GetTrackedTargetCountForTesting(),
1u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 1u);
Compositor().BeginFrame();
test::RunPendingTasks();
......@@ -396,10 +404,8 @@ TEST_F(IntersectionObserverTest, DisconnectClearsNotifications) {
kProgrammaticScroll);
Compositor().BeginFrame();
observer->disconnect();
EXPECT_EQ(GetDocument()
.EnsureIntersectionObserverController()
.GetTrackedTargetCountForTesting(),
0u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 1);
}
......@@ -488,15 +494,122 @@ TEST_F(IntersectionObserverTest, TrackedTargetBookkeeping) {
observer2->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException());
ElementIntersectionObserverData* target_data =
target->IntersectionObserverData();
ASSERT_TRUE(target_data);
IntersectionObserverController& controller =
GetDocument().EnsureIntersectionObserverController();
EXPECT_EQ(controller.GetTrackedTargetCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 2u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
observer1->unobserve(target, exception_state);
ASSERT_FALSE(exception_state.HadException());
EXPECT_EQ(controller.GetTrackedTargetCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
observer2->unobserve(target, exception_state);
ASSERT_FALSE(exception_state.HadException());
EXPECT_EQ(controller.GetTrackedTargetCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
}
TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<div id='root'>
<div id='target1'></div>
<div id='target2'></div>
</div>
)HTML");
IntersectionObserverController& controller =
GetDocument().EnsureIntersectionObserverController();
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
Persistent<Element> root = GetDocument().getElementById("root");
Persistent<Element> target = GetDocument().getElementById("target1");
Persistent<IntersectionObserverInit> observer_init =
IntersectionObserverInit::Create();
observer_init->setRoot(root);
Persistent<TestIntersectionObserverDelegate> observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
Persistent<IntersectionObserver> observer =
IntersectionObserver::Create(observer_init, *observer_delegate);
// For an explicit-root observer, the root element is tracked, even when it
// has no observations.
ElementIntersectionObserverData* root_data = root->IntersectionObserverData();
ASSERT_TRUE(root_data);
EXPECT_FALSE(root_data->IsEmpty());
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// For an explicit-root observer, target elements are not tracked.
observer->observe(target);
ElementIntersectionObserverData* target_data =
target->IntersectionObserverData();
ASSERT_TRUE(target_data);
EXPECT_FALSE(target_data->IsEmpty());
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// The existing observation should keep the observer alive and active, even
// when there are no other references to the observer.
observer = nullptr;
// Flush any pending notifications, which hold a hard reference to the
// observer and can prevent it from being gc'ed. The observation will be the
// only thing keeping the observer alive.
test::RunPendingTasks();
observer_delegate->Clear();
V8GCController::CollectAllGarbageForTesting(
v8::Isolate::GetCurrent(),
v8::EmbedderHeapTracer::EmbedderStackState::kEmpty);
EXPECT_FALSE(root_data->IsEmpty());
EXPECT_FALSE(target_data->IsEmpty());
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// When the last observation is disconnected, as a result of the target being
// gc'ed, the observer should also get gc'ed and the root element should no
// longer be tracked.
target->remove();
target = nullptr;
target_data = nullptr;
// Removing the target from the DOM tree forces a notification to be
// queued, so flush it out.
test::RunPendingTasks();
observer_delegate->Clear();
V8GCController::CollectAllGarbageForTesting(
v8::Isolate::GetCurrent(),
v8::EmbedderHeapTracer::EmbedderStackState::kEmpty);
EXPECT_TRUE(root_data->IsEmpty());
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
target = GetDocument().getElementById("target2");
observer = IntersectionObserver::Create(observer_init, *observer_delegate);
observer->observe(target);
target_data = target->IntersectionObserverData();
ASSERT_TRUE(target_data);
// If the explicit root of an observer goes away, any existing observations
// should be disconnected.
target->remove();
root->remove();
root = nullptr;
test::RunPendingTasks();
observer_delegate->Clear();
observer_delegate = nullptr;
observer_init = nullptr;
// Removing the target from the tree is not enough to disconnect the
// observation.
EXPECT_FALSE(target_data->IsEmpty());
V8GCController::CollectAllGarbageForTesting(
v8::Isolate::GetCurrent(),
v8::EmbedderHeapTracer::EmbedderStackState::kEmpty);
EXPECT_TRUE(target_data->IsEmpty());
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
}
TEST_F(IntersectionObserverTest, RootMarginDevicePixelRatio) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment