| // 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 |