Commit 5b8e4354 authored by Stefan Zager's avatar Stefan Zager Committed by Commit Bot

Tighten up conditions for IntersectionObserver tracking

The root cause of the linked bug is that IntersectionObserverController
continues to track an observer after the observer's root element is
deleted.

This CL tightens up the conditions for which objects are tracked:
  - Explicit root observers with observations, if the root is connected
  - Implicit root observations, if the target is connected

BUG=1046897
R=vmpstr@chromium.org

Change-Id: Ib98a0dd3c948e61200cd270011cfe18b8776d7fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2068860Reviewed-by: default avatarvmpstr <vmpstr@chromium.org>
Commit-Queue: Stefan Zager <szager@chromium.org>
Cr-Commit-Position: refs/heads/master@{#744795}
parent b5349fb4
...@@ -2734,7 +2734,7 @@ Node::InsertionNotificationRequest Element::InsertedInto( ...@@ -2734,7 +2734,7 @@ Node::InsertionNotificationRequest Element::InsertedInto(
if (!insertion_point.IsInTreeScope()) if (!insertion_point.IsInTreeScope())
return kInsertionDone; return kInsertionDone;
if (HasRareData()) { if (isConnected() && HasRareData()) {
ElementRareData* rare_data = GetElementRareData(); ElementRareData* rare_data = GetElementRareData();
if (ElementIntersectionObserverData* observer_data = if (ElementIntersectionObserverData* observer_data =
rare_data->IntersectionObserverData()) { rare_data->IntersectionObserverData()) {
...@@ -2749,10 +2749,8 @@ Node::InsertionNotificationRequest Element::InsertedInto( ...@@ -2749,10 +2749,8 @@ Node::InsertionNotificationRequest Element::InsertedInto(
} }
} }
if (isConnected()) { if (auto* context = rare_data->GetDisplayLockContext())
if (auto* context = rare_data->GetDisplayLockContext()) context->ElementConnected();
context->ElementConnected();
}
} }
if (isConnected()) { if (isConnected()) {
......
...@@ -44,27 +44,18 @@ void ElementIntersectionObserverData::RemoveObserver( ...@@ -44,27 +44,18 @@ void ElementIntersectionObserverData::RemoveObserver(
void ElementIntersectionObserverData::TrackWithController( void ElementIntersectionObserverData::TrackWithController(
IntersectionObserverController& controller) { IntersectionObserverController& controller) {
for (auto& entry : observations_) { for (auto& entry : observations_)
if (entry.key->RootIsImplicit()) { controller.AddTrackedObservation(*entry.value);
controller.AddTrackedObservation(*entry.value, for (auto& observer : observers_)
entry.key->trackVisibility()); controller.AddTrackedObserver(*observer);
}
}
for (auto& observer : observers_) {
controller.AddTrackedObserver(*observer, observer->trackVisibility());
}
} }
void ElementIntersectionObserverData::StopTrackingWithController( void ElementIntersectionObserverData::StopTrackingWithController(
IntersectionObserverController& controller) { IntersectionObserverController& controller) {
for (auto& entry : observations_) { for (auto& entry : observations_)
if (entry.key->RootIsImplicit()) { controller.RemoveTrackedObservation(*entry.value);
controller.RemoveTrackedObservation(*entry.value); for (auto& observer : observers_)
}
}
for (auto& observer : observers_) {
controller.RemoveTrackedObserver(*observer); controller.RemoveTrackedObserver(*observer);
}
} }
bool ElementIntersectionObserverData::ComputeIntersectionsForTarget( bool ElementIntersectionObserverData::ComputeIntersectionsForTarget(
......
...@@ -77,11 +77,7 @@ void IntersectionObservation::Disconnect() { ...@@ -77,11 +77,7 @@ void IntersectionObservation::Disconnect() {
ElementIntersectionObserverData* observer_data = ElementIntersectionObserverData* observer_data =
target_->IntersectionObserverData(); target_->IntersectionObserverData();
observer_data->RemoveObservation(*this); observer_data->RemoveObservation(*this);
// We track connected elements that are either the root of an explicit root if (target_->isConnected()) {
// 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()) {
IntersectionObserverController* controller = IntersectionObserverController* controller =
target_->GetDocument().GetIntersectionObserverController(); target_->GetDocument().GetIntersectionObserverController();
if (controller) if (controller)
......
...@@ -290,9 +290,6 @@ IntersectionObserver::IntersectionObserver( ...@@ -290,9 +290,6 @@ IntersectionObserver::IntersectionObserver(
DCHECK(root->IsElementNode()); DCHECK(root->IsElementNode());
To<Element>(root)->EnsureIntersectionObserverData().AddObserver(*this); To<Element>(root)->EnsureIntersectionObserverData().AddObserver(*this);
} }
root->GetDocument()
.EnsureIntersectionObserverController()
.AddTrackedObserver(*this, track_visibility);
} }
} }
...@@ -328,12 +325,16 @@ void IntersectionObserver::observe(Element* target, ...@@ -328,12 +325,16 @@ void IntersectionObserver::observe(Element* target,
MakeGarbageCollected<IntersectionObservation>(*this, *target); MakeGarbageCollected<IntersectionObservation>(*this, *target);
target->EnsureIntersectionObserverData().AddObservation(*observation); target->EnsureIntersectionObserverData().AddObservation(*observation);
observations_.insert(observation); observations_.insert(observation);
if (root() && root()->isConnected()) {
root()
->GetDocument()
.EnsureIntersectionObserverController()
.AddTrackedObserver(*this);
}
if (target->isConnected()) { if (target->isConnected()) {
if (RootIsImplicit()) { target->GetDocument()
target->GetDocument() .EnsureIntersectionObserverController()
.EnsureIntersectionObserverController() .AddTrackedObservation(*observation);
.AddTrackedObservation(*observation, track_visibility_);
}
if (LocalFrameView* frame_view = target_frame->View()) { if (LocalFrameView* frame_view = target_frame->View()) {
// The IntersectionObsever spec requires that at least one observation // The IntersectionObsever spec requires that at least one observation
// be recorded after observe() is called, even if the frame is throttled. // be recorded after observe() is called, even if the frame is throttled.
...@@ -362,12 +363,24 @@ void IntersectionObserver::unobserve(Element* target, ...@@ -362,12 +363,24 @@ void IntersectionObserver::unobserve(Element* target,
observation->Disconnect(); observation->Disconnect();
observations_.erase(observation); observations_.erase(observation);
if (root() && root()->isConnected() && observations_.IsEmpty()) {
root()
->GetDocument()
.EnsureIntersectionObserverController()
.RemoveTrackedObserver(*this);
}
} }
void IntersectionObserver::disconnect(ExceptionState& exception_state) { void IntersectionObserver::disconnect(ExceptionState& exception_state) {
for (auto& observation : observations_) for (auto& observation : observations_)
observation->Disconnect(); observation->Disconnect();
observations_.clear(); observations_.clear();
if (root() && root()->isConnected()) {
root()
->GetDocument()
.EnsureIntersectionObserverController()
.RemoveTrackedObserver(*this);
}
} }
HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords( HeapVector<Member<IntersectionObserverEntry>> IntersectionObserver::takeRecords(
......
...@@ -129,6 +129,7 @@ class CORE_EXPORT IntersectionObserver final ...@@ -129,6 +129,7 @@ class CORE_EXPORT IntersectionObserver final
// root just because root_ is null. Hence root_is_implicit_. // root just because root_ is null. Hence root_is_implicit_.
bool RootIsImplicit() const { return root_is_implicit_; } bool RootIsImplicit() const { return root_is_implicit_; }
bool HasObservations() const { return !observations_.IsEmpty(); }
bool AlwaysReportRootBounds() const { return always_report_root_bounds_; } bool AlwaysReportRootBounds() const { return always_report_root_bounds_; }
bool NeedsOcclusionTracking() const { bool NeedsOcclusionTracking() const {
return trackVisibility() && !observations_.IsEmpty(); return trackVisibility() && !observations_.IsEmpty();
......
...@@ -67,12 +67,15 @@ bool IntersectionObserverController::ComputeIntersections(unsigned flags) { ...@@ -67,12 +67,15 @@ bool IntersectionObserverController::ComputeIntersections(unsigned flags) {
"IntersectionObserverController::" "IntersectionObserverController::"
"computeIntersections"); "computeIntersections");
HeapVector<Member<IntersectionObserver>> observers_to_process; HeapVector<Member<IntersectionObserver>> observers_to_process;
CopyToVector(explicit_root_observers_, observers_to_process); CopyToVector(tracked_explicit_root_observers_, observers_to_process);
for (auto& observer : observers_to_process) { for (auto& observer : observers_to_process) {
needs_occlusion_tracking_ |= observer->ComputeIntersections(flags); if (observer->HasObservations())
needs_occlusion_tracking_ |= observer->ComputeIntersections(flags);
else
tracked_explicit_root_observers_.erase(observer);
} }
HeapVector<Member<IntersectionObservation>> observations_to_process; HeapVector<Member<IntersectionObservation>> observations_to_process;
CopyToVector(implicit_root_observations_, observations_to_process); CopyToVector(tracked_implicit_root_observations_, observations_to_process);
for (auto& observation : observations_to_process) { for (auto& observation : observations_to_process) {
observation->ComputeIntersection(flags); observation->ComputeIntersection(flags);
needs_occlusion_tracking_ |= observation->Observer()->trackVisibility(); needs_occlusion_tracking_ |= observation->Observer()->trackVisibility();
...@@ -82,57 +85,65 @@ bool IntersectionObserverController::ComputeIntersections(unsigned flags) { ...@@ -82,57 +85,65 @@ bool IntersectionObserverController::ComputeIntersections(unsigned flags) {
} }
void IntersectionObserverController::AddTrackedObserver( void IntersectionObserverController::AddTrackedObserver(
IntersectionObserver& observer, IntersectionObserver& observer) {
bool track_occlusion) { // We only track explicit-root observers that have active observations.
DCHECK(!observer.RootIsImplicit()); if (observer.RootIsImplicit() || !observer.HasObservations())
explicit_root_observers_.insert(&observer);
if (!track_occlusion)
return; return;
needs_occlusion_tracking_ = true; tracked_explicit_root_observers_.insert(&observer);
if (LocalFrameView* frame_view = observer.root()->GetDocument().View()) { if (observer.trackVisibility()) {
if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) { needs_occlusion_tracking_ = true;
// Set this bit as early as possible, rather than waiting for a lifecycle if (LocalFrameView* frame_view = observer.root()->GetDocument().View()) {
// update to recompute it. if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) {
frame_owner->SetNeedsOcclusionTracking(true); // Set this bit as early as possible, rather than waiting for a
// lifecycle update to recompute it.
frame_owner->SetNeedsOcclusionTracking(true);
}
} }
} }
} }
void IntersectionObserverController::RemoveTrackedObserver( void IntersectionObserverController::RemoveTrackedObserver(
IntersectionObserver& observer) { IntersectionObserver& observer) {
DCHECK(!observer.RootIsImplicit()); if (observer.RootIsImplicit())
return;
// Note that we don't try to opportunistically turn off the 'needs occlusion // Note that we don't try to opportunistically turn off the 'needs occlusion
// tracking' bit here, like the way we turn it on in AddTrackedObserver. 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 // bit will get recomputed on the next lifecycle update; there's no
// compelling reason to do it here, so we avoid the iteration through // compelling reason to do it here, so we avoid the iteration through
// observers and observations here. // observers and observations here.
explicit_root_observers_.erase(&observer); tracked_explicit_root_observers_.erase(&observer);
} }
void IntersectionObserverController::AddTrackedObservation( void IntersectionObserverController::AddTrackedObservation(
IntersectionObservation& observation, IntersectionObservation& observation) {
bool track_occlusion) { IntersectionObserver* observer = observation.Observer();
DCHECK(observation.Observer()->RootIsImplicit()); DCHECK(observer);
implicit_root_observations_.insert(&observation); if (!observer->RootIsImplicit())
if (!track_occlusion)
return; return;
needs_occlusion_tracking_ = true; tracked_implicit_root_observations_.insert(&observation);
if (LocalFrameView* frame_view = observation.Target()->GetDocument().View()) { if (observer->trackVisibility()) {
if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) { needs_occlusion_tracking_ = true;
frame_owner->SetNeedsOcclusionTracking(true); if (LocalFrameView* frame_view =
observation.Target()->GetDocument().View()) {
if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) {
frame_owner->SetNeedsOcclusionTracking(true);
}
} }
} }
} }
void IntersectionObserverController::RemoveTrackedObservation( void IntersectionObserverController::RemoveTrackedObservation(
IntersectionObservation& observation) { IntersectionObservation& observation) {
DCHECK(observation.Observer()->RootIsImplicit()); IntersectionObserver* observer = observation.Observer();
implicit_root_observations_.erase(&observation); DCHECK(observer);
if (!observer->RootIsImplicit())
return;
tracked_implicit_root_observations_.erase(&observation);
} }
void IntersectionObserverController::Trace(Visitor* visitor) { void IntersectionObserverController::Trace(Visitor* visitor) {
visitor->Trace(explicit_root_observers_); visitor->Trace(tracked_explicit_root_observers_);
visitor->Trace(implicit_root_observations_); visitor->Trace(tracked_implicit_root_observations_);
visitor->Trace(pending_intersection_observers_); visitor->Trace(pending_intersection_observers_);
ExecutionContextClient::Trace(visitor); ExecutionContextClient::Trace(visitor);
} }
......
...@@ -43,8 +43,8 @@ class IntersectionObserverController ...@@ -43,8 +43,8 @@ class IntersectionObserverController
// The second argument indicates whether the Element is a target of any // The second argument indicates whether the Element is a target of any
// observers for which observer->trackVisibility() is true. // observers for which observer->trackVisibility() is true.
void AddTrackedObserver(IntersectionObserver&, bool); void AddTrackedObserver(IntersectionObserver&);
void AddTrackedObservation(IntersectionObservation&, bool); void AddTrackedObservation(IntersectionObservation&);
void RemoveTrackedObserver(IntersectionObserver&); void RemoveTrackedObserver(IntersectionObserver&);
void RemoveTrackedObservation(IntersectionObservation&); void RemoveTrackedObservation(IntersectionObservation&);
...@@ -56,20 +56,23 @@ class IntersectionObserverController ...@@ -56,20 +56,23 @@ class IntersectionObserverController
} }
unsigned GetTrackedObserverCountForTesting() const { unsigned GetTrackedObserverCountForTesting() const {
return explicit_root_observers_.size(); return tracked_explicit_root_observers_.size();
} }
unsigned GetTrackedObservationCountForTesting() const { unsigned GetTrackedObservationCountForTesting() const {
return implicit_root_observations_.size(); return tracked_implicit_root_observations_.size();
} }
private: private:
void PostTaskToDeliverNotifications(); void PostTaskToDeliverNotifications();
private: private:
// IntersectionObserver's with an explicit root in this document. // IntersectionObserver's with a connected explicit root in this document.
HeapHashSet<WeakMember<IntersectionObserver>> explicit_root_observers_; HeapHashSet<WeakMember<IntersectionObserver>>
// IntersectionObservations with an implicit root and target in this document. tracked_explicit_root_observers_;
HeapHashSet<WeakMember<IntersectionObservation>> implicit_root_observations_; // IntersectionObservations with an implicit root and connected target in this
// document.
HeapHashSet<WeakMember<IntersectionObservation>>
tracked_implicit_root_observations_;
// IntersectionObservers for which this is the execution context of the // IntersectionObservers for which this is the execution context of the
// callback, and with unsent notifications. // callback, and with unsent notifications.
HeapHashSet<Member<IntersectionObserver>> pending_intersection_observers_; HeapHashSet<Member<IntersectionObserver>> pending_intersection_observers_;
......
...@@ -488,19 +488,14 @@ TEST_F(IntersectionObserverTest, TrackedTargetBookkeeping) { ...@@ -488,19 +488,14 @@ TEST_F(IntersectionObserverTest, TrackedTargetBookkeeping) {
Element* target = GetDocument().getElementById("target"); Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target); ASSERT_TRUE(target);
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create(); IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate = TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument()); MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer1 = IntersectionObserver::Create( IntersectionObserver* observer1 =
observer_init, *observer_delegate, exception_state); IntersectionObserver::Create(observer_init, *observer_delegate);
ASSERT_FALSE(exception_state.HadException()); observer1->observe(target);
observer1->observe(target, exception_state); IntersectionObserver* observer2 =
ASSERT_FALSE(exception_state.HadException()); IntersectionObserver::Create(observer_init, *observer_delegate);
IntersectionObserver* observer2 = IntersectionObserver::Create( observer2->observe(target);
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
observer2->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException());
ElementIntersectionObserverData* target_data = ElementIntersectionObserverData* target_data =
target->IntersectionObserverData(); target->IntersectionObserverData();
...@@ -509,14 +504,17 @@ TEST_F(IntersectionObserverTest, TrackedTargetBookkeeping) { ...@@ -509,14 +504,17 @@ TEST_F(IntersectionObserverTest, TrackedTargetBookkeeping) {
GetDocument().EnsureIntersectionObserverController(); GetDocument().EnsureIntersectionObserverController();
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 2u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 2u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
observer1->unobserve(target, exception_state);
ASSERT_FALSE(exception_state.HadException()); target->remove();
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
GetDocument().body()->AppendChild(target);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 2u);
observer1->unobserve(target);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 1u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
observer2->unobserve(target, exception_state); observer2->unobserve(target);
ASSERT_FALSE(exception_state.HadException());
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
} }
TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) { TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) {
...@@ -544,15 +542,14 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) { ...@@ -544,15 +542,14 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) {
Persistent<IntersectionObserver> observer = Persistent<IntersectionObserver> observer =
IntersectionObserver::Create(observer_init, *observer_delegate); IntersectionObserver::Create(observer_init, *observer_delegate);
// For an explicit-root observer, the root element is tracked, even when it // For an explicit-root observer, the root element is tracked only when it
// has no observations. // has observations and is connected. Target elements are not tracked.
ElementIntersectionObserverData* root_data = root->IntersectionObserverData(); ElementIntersectionObserverData* root_data = root->IntersectionObserverData();
ASSERT_TRUE(root_data); ASSERT_TRUE(root_data);
EXPECT_FALSE(root_data->IsEmpty()); EXPECT_FALSE(root_data->IsEmpty());
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u); EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// For an explicit-root observer, target elements are not tracked.
observer->observe(target); observer->observe(target);
ElementIntersectionObserverData* target_data = ElementIntersectionObserverData* target_data =
target->IntersectionObserverData(); target->IntersectionObserverData();
...@@ -561,9 +558,23 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) { ...@@ -561,9 +558,23 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) {
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u); EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// The existing observation should keep the observer alive and active, even // Root should not be tracked if it's not connected.
// when there are no other references to the observer. root->remove();
observer = nullptr; EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
GetDocument().body()->AppendChild(root);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
// Root should not be tracked if it has no observations.
observer->disconnect();
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
observer->observe(target);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
observer->unobserve(target);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
observer->observe(target);
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 1u);
// The existing observation should keep the observer alive and active.
// Flush any pending notifications, which hold a hard reference to the // 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 // observer and can prevent it from being gc'ed. The observation will be the
// only thing keeping the observer alive. // only thing keeping the observer alive.
...@@ -578,8 +589,8 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) { ...@@ -578,8 +589,8 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) {
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// When the last observation is disconnected, as a result of the target being // 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 // gc'ed, the root element should no longer be tracked after the next
// longer be tracked. // lifecycle update.
target->remove(); target->remove();
target = nullptr; target = nullptr;
target_data = nullptr; target_data = nullptr;
...@@ -590,10 +601,18 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) { ...@@ -590,10 +601,18 @@ TEST_F(IntersectionObserverTest, TrackedRootBookkeeping) {
V8GCController::CollectAllGarbageForTesting( V8GCController::CollectAllGarbageForTesting(
v8::Isolate::GetCurrent(), v8::Isolate::GetCurrent(),
v8::EmbedderHeapTracer::EmbedderStackState::kEmpty); v8::EmbedderHeapTracer::EmbedderStackState::kEmpty);
EXPECT_TRUE(root_data->IsEmpty()); Compositor().BeginFrame();
EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObserverCountForTesting(), 0u);
EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u); EXPECT_EQ(controller.GetTrackedObservationCountForTesting(), 0u);
// Removing the last reference to the observer should allow it to be dropeed
// from the root's ElementIntersectionObserverData.
observer = nullptr;
V8GCController::CollectAllGarbageForTesting(
v8::Isolate::GetCurrent(),
v8::EmbedderHeapTracer::EmbedderStackState::kEmpty);
EXPECT_TRUE(root_data->IsEmpty());
target = GetDocument().getElementById("target2"); target = GetDocument().getElementById("target2");
observer = IntersectionObserver::Create(observer_init, *observer_delegate); observer = IntersectionObserver::Create(observer_init, *observer_delegate);
observer->observe(target); observer->observe(target);
......
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