blob: 295aeed752e32a417d2db3e9728b5f05e47c46ab [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_observer.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"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_delegate.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_init.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/testing/sim/sim_compositor.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
namespace {
class TestIntersectionObserverDelegate : public IntersectionObserverDelegate {
public:
TestIntersectionObserverDelegate(Document& document)
: document_(document), call_count_(0) {}
void Deliver(const HeapVector<Member<IntersectionObserverEntry>>& entries,
IntersectionObserver&) override {
call_count_++;
entries_.AppendVector(entries);
}
ExecutionContext* GetExecutionContext() const override { return document_; }
int CallCount() const { return call_count_; }
int EntryCount() const { return entries_.size(); }
const IntersectionObserverEntry* LastEntry() const { return entries_.back(); }
FloatRect LastIntersectionRect() const {
if (entries_.IsEmpty())
return FloatRect();
const IntersectionObserverEntry* entry = entries_.back();
return FloatRect(entry->intersectionRect()->x(),
entry->intersectionRect()->y(),
entry->intersectionRect()->width(),
entry->intersectionRect()->height());
}
void Trace(blink::Visitor* visitor) override {
IntersectionObserverDelegate::Trace(visitor);
visitor->Trace(document_);
visitor->Trace(entries_);
}
private:
Member<Document> document_;
HeapVector<Member<IntersectionObserverEntry>> entries_;
int call_count_;
};
} // namespace
class IntersectionObserverTest : public SimTest {};
class IntersectionObserverV2Test : public IntersectionObserverTest,
public ScopedIntersectionObserverV2ForTest {
public:
IntersectionObserverV2Test()
: IntersectionObserverTest(), ScopedIntersectionObserverV2ForTest(true) {
IntersectionObserver::SetThrottleDelayEnabledForTesting(false);
}
~IntersectionObserverV2Test() override {
IntersectionObserver::SetThrottleDelayEnabledForTesting(true);
}
};
TEST_F(IntersectionObserverTest, ObserveSchedulesFrame) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete("<div id='target'></div>");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Compositor().BeginFrame();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_TRUE(observer->takeRecords(exception_state).IsEmpty());
EXPECT_EQ(observer_delegate->CallCount(), 0);
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
observer->observe(target, exception_state);
EXPECT_TRUE(Compositor().NeedsBeginFrame());
}
TEST_F(IntersectionObserverTest, NotificationSentWhenRootRemoved) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<style>
#target {
width: 100px;
height: 100px;
}
</style>
<div id='root'>
<div id='target'></div>
</div>
)HTML");
Element* root = GetDocument().getElementById("root");
ASSERT_TRUE(root);
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
observer_init->setRoot(root);
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
observer->observe(target, exception_state);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 1);
EXPECT_EQ(observer_delegate->EntryCount(), 1);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
root->remove();
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 2);
EXPECT_EQ(observer_delegate->EntryCount(), 2);
EXPECT_FALSE(observer_delegate->LastEntry()->isIntersecting());
}
TEST_F(IntersectionObserverTest, ReportsFractionOfTargetOrRoot) {
// Place a 100x100 target element in the middle of a 200x200 main frame.
WebView().MainFrameWidget()->Resize(WebSize(200, 200));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<style>
#target {
position: absolute;
top: 50px; left: 50px; width: 100px; height: 100px;
}
</style>
<div id='target'></div>
)HTML");
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
// 100% of the target element's area intersects with the frame.
constexpr float kExpectedFractionOfTarget = 1.0f;
// 25% of the frame's area is covered by the target element.
constexpr float kExpectedFractionOfRoot = 0.25f;
TestIntersectionObserverDelegate* target_observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* target_observer =
MakeGarbageCollected<IntersectionObserver>(
*target_observer_delegate, nullptr, Vector<Length>(),
Vector<float>{kExpectedFractionOfTarget / 2},
IntersectionObserver::kFractionOfTarget, 0, false, false);
DummyExceptionStateForTesting exception_state;
target_observer->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException());
TestIntersectionObserverDelegate* root_observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* root_observer =
MakeGarbageCollected<IntersectionObserver>(
*root_observer_delegate, nullptr, Vector<Length>(),
Vector<float>{kExpectedFractionOfRoot / 2},
IntersectionObserver::kFractionOfRoot, 0, false, false);
root_observer->observe(target, exception_state);
ASSERT_FALSE(exception_state.HadException());
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(target_observer_delegate->CallCount(), 1);
EXPECT_EQ(target_observer_delegate->EntryCount(), 1);
EXPECT_TRUE(target_observer_delegate->LastEntry()->isIntersecting());
EXPECT_NEAR(kExpectedFractionOfTarget,
target_observer_delegate->LastEntry()->intersectionRatio(), 1e-6);
EXPECT_EQ(root_observer_delegate->CallCount(), 1);
EXPECT_EQ(root_observer_delegate->EntryCount(), 1);
EXPECT_TRUE(root_observer_delegate->LastEntry()->isIntersecting());
EXPECT_NEAR(kExpectedFractionOfRoot,
root_observer_delegate->LastEntry()->intersectionRatio(), 1e-6);
}
TEST_F(IntersectionObserverTest, ResumePostsTask) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<div id='leading-space' style='height: 700px;'></div>
<div id='target'></div>
<div id='trailing-space' style='height: 700px;'></div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
observer->observe(target, exception_state);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 1);
// When document is not suspended, beginFrame() will generate notifications
// and post a task to deliver them.
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 300),
kProgrammaticScroll);
Compositor().BeginFrame();
EXPECT_EQ(observer_delegate->CallCount(), 1);
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 2);
// When a document is suspended, beginFrame() will generate a notification,
// but it will not be delivered. The notification will, however, be
// available via takeRecords();
WebView().GetPage()->SetPaused(true);
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 0),
kProgrammaticScroll);
Compositor().BeginFrame();
EXPECT_EQ(observer_delegate->CallCount(), 2);
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 2);
EXPECT_FALSE(observer->takeRecords(exception_state).IsEmpty());
// Generate a notification while document is suspended; then resume document.
// Notification should happen in a post task.
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 300),
kProgrammaticScroll);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 2);
WebView().GetPage()->SetPaused(false);
EXPECT_EQ(observer_delegate->CallCount(), 2);
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 3);
}
TEST_F(IntersectionObserverTest, HitTestAfterMutation) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<div id='leading-space' style='height: 700px;'></div>
<div id='target'></div>
<div id='trailing-space' style='height: 700px;'></div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
observer->observe(target, exception_state);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 1);
GetDocument().View()->ScheduleAnimation();
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 1);
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 300),
kProgrammaticScroll);
HitTestLocation location(LayoutPoint(0, 0));
HitTestResult result(
HitTestRequest(HitTestRequest::kReadOnly | HitTestRequest::kActive |
HitTestRequest::kAllowChildFrameContent),
location);
GetDocument().View()->GetLayoutView()->HitTest(location, result);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 2);
}
TEST_F(IntersectionObserverTest, DisconnectClearsNotifications) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<div id='leading-space' style='height: 700px;'></div>
<div id='target'></div>
<div id='trailing-space' style='height: 700px;'></div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
observer->observe(target, exception_state);
Compositor().BeginFrame();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 1);
// If disconnect() is called while an observer has unsent notifications,
// those notifications should be discarded.
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 300),
kProgrammaticScroll);
Compositor().BeginFrame();
observer->disconnect();
test::RunPendingTasks();
EXPECT_EQ(observer_delegate->CallCount(), 1);
}
TEST_F(IntersectionObserverTest, RootIntersectionWithForceZeroLayoutHeight) {
WebView().GetSettings()->SetForceZeroLayoutHeight(true);
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<style>
body {
margin: 0;
height: 2000px;
}
#target {
width: 100px;
height: 100px;
position: absolute;
top: 1000px;
left: 200px;
}
</style>
<div id='target'></div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
ASSERT_TRUE(target);
observer->observe(target, exception_state);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_EQ(observer_delegate->CallCount(), 1);
EXPECT_TRUE(observer_delegate->LastIntersectionRect().IsEmpty());
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 600),
kProgrammaticScroll);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_EQ(observer_delegate->CallCount(), 2);
EXPECT_FALSE(observer_delegate->LastIntersectionRect().IsEmpty());
EXPECT_EQ(FloatRect(200, 400, 100, 100),
observer_delegate->LastIntersectionRect());
GetDocument().View()->LayoutViewport()->SetScrollOffset(ScrollOffset(0, 1200),
kProgrammaticScroll);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_EQ(observer_delegate->CallCount(), 3);
EXPECT_TRUE(observer_delegate->LastIntersectionRect().IsEmpty());
}
TEST_F(IntersectionObserverV2Test, TrackVisibilityInit) {
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
EXPECT_FALSE(observer->trackVisibility());
// This should fail because no delay is set.
observer_init->setTrackVisibility(true);
observer = IntersectionObserver::Create(observer_init, *observer_delegate,
exception_state);
EXPECT_TRUE(exception_state.HadException());
// This should fail because the delay is < 100.
exception_state.ClearException();
observer_init->setDelay(99.9);
observer = IntersectionObserver::Create(observer_init, *observer_delegate,
exception_state);
EXPECT_TRUE(exception_state.HadException());
exception_state.ClearException();
observer_init->setDelay(101.);
observer = IntersectionObserver::Create(observer_init, *observer_delegate,
exception_state);
ASSERT_FALSE(exception_state.HadException());
EXPECT_TRUE(observer->trackVisibility());
EXPECT_EQ(observer->delay(), 101.);
}
TEST_F(IntersectionObserverV2Test, BasicOcclusion) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<style>
div {
width: 100px;
height: 100px;
}
</style>
<div id='target'>
<div id='child'></div>
</div>
<div id='occluder'></div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
observer_init->setTrackVisibility(true);
observer_init->setDelay(100);
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
Element* occluder = GetDocument().getElementById("occluder");
ASSERT_TRUE(target);
observer->observe(target);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 1);
EXPECT_EQ(observer_delegate->EntryCount(), 1);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_TRUE(observer_delegate->LastEntry()->isVisible());
occluder->SetInlineStyleProperty(CSSPropertyMarginTop, "-10px");
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 2);
EXPECT_EQ(observer_delegate->EntryCount(), 2);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_FALSE(observer_delegate->LastEntry()->isVisible());
// Zero-opacity objects should not count as occluding.
occluder->SetInlineStyleProperty(CSSPropertyOpacity, "0");
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 3);
EXPECT_EQ(observer_delegate->EntryCount(), 3);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_TRUE(observer_delegate->LastEntry()->isVisible());
}
TEST_F(IntersectionObserverV2Test, BasicOpacity) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<style>
div {
width: 100px;
height: 100px;
}
</style>
<div id='transparent'>
<div id='target'></div>
</div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
observer_init->setTrackVisibility(true);
observer_init->setDelay(100);
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
Element* transparent = GetDocument().getElementById("transparent");
ASSERT_TRUE(target);
ASSERT_TRUE(transparent);
observer->observe(target);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 1);
EXPECT_EQ(observer_delegate->EntryCount(), 1);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_TRUE(observer_delegate->LastEntry()->isVisible());
transparent->SetInlineStyleProperty(CSSPropertyOpacity, "0.99");
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 2);
EXPECT_EQ(observer_delegate->EntryCount(), 2);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_FALSE(observer_delegate->LastEntry()->isVisible());
}
TEST_F(IntersectionObserverV2Test, BasicTransform) {
WebView().MainFrameWidget()->Resize(WebSize(800, 600));
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"HTML(
<style>
div {
width: 100px;
height: 100px;
}
</style>
<div id='transformed'>
<div id='target'></div>
</div>
)HTML");
IntersectionObserverInit* observer_init = IntersectionObserverInit::Create();
observer_init->setTrackVisibility(true);
observer_init->setDelay(100);
DummyExceptionStateForTesting exception_state;
TestIntersectionObserverDelegate* observer_delegate =
MakeGarbageCollected<TestIntersectionObserverDelegate>(GetDocument());
IntersectionObserver* observer = IntersectionObserver::Create(
observer_init, *observer_delegate, exception_state);
ASSERT_FALSE(exception_state.HadException());
Element* target = GetDocument().getElementById("target");
Element* transformed = GetDocument().getElementById("transformed");
ASSERT_TRUE(target);
ASSERT_TRUE(transformed);
observer->observe(target);
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 1);
EXPECT_EQ(observer_delegate->EntryCount(), 1);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_TRUE(observer_delegate->LastEntry()->isVisible());
// 2D translations and proportional upscaling is permitted.
transformed->SetInlineStyleProperty(
CSSPropertyTransform, "translateX(10px) translateY(20px) scale(2)");
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 1);
EXPECT_EQ(observer_delegate->EntryCount(), 1);
// Any other transform is not permitted.
transformed->SetInlineStyleProperty(CSSPropertyTransform, "skewX(10deg)");
Compositor().BeginFrame();
test::RunPendingTasks();
ASSERT_FALSE(Compositor().NeedsBeginFrame());
EXPECT_EQ(observer_delegate->CallCount(), 2);
EXPECT_EQ(observer_delegate->EntryCount(), 2);
EXPECT_TRUE(observer_delegate->LastEntry()->isIntersecting());
EXPECT_FALSE(observer_delegate->LastEntry()->isVisible());
}
} // namespace blink