| // 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 "core/dom/ScriptedAnimationController.h" |
| |
| #include <memory> |
| #include "core/dom/Document.h" |
| #include "core/dom/FrameRequestCallbackCollection.h" |
| #include "core/dom/events/Event.h" |
| #include "core/dom/events/EventListener.h" |
| #include "core/dom/events/EventTarget.h" |
| #include "core/testing/DummyPageHolder.h" |
| #include "platform/heap/Handle.h" |
| #include "platform/wtf/Functional.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace blink { |
| |
| class ScriptedAnimationControllerTest : public ::testing::Test { |
| protected: |
| void SetUp() override; |
| |
| Document& GetDocument() const { return dummy_page_holder_->GetDocument(); } |
| ScriptedAnimationController& Controller() { return *controller_; } |
| |
| private: |
| std::unique_ptr<DummyPageHolder> dummy_page_holder_; |
| Persistent<ScriptedAnimationController> controller_; |
| }; |
| |
| void ScriptedAnimationControllerTest::SetUp() { |
| dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600)); |
| |
| // Note: The document doesn't know about this ScriptedAnimationController |
| // instance, and will create another if |
| // Document::ensureScriptedAnimationController is called. |
| controller_ = |
| WrapPersistent(ScriptedAnimationController::Create(&GetDocument())); |
| } |
| |
| namespace { |
| |
| class TaskOrderObserver { |
| public: |
| base::RepeatingClosure CreateTask(int id) { |
| return WTF::BindRepeating(&TaskOrderObserver::RunTask, |
| WTF::Unretained(this), id); |
| } |
| const Vector<int>& Order() const { return order_; } |
| |
| private: |
| void RunTask(int id) { order_.push_back(id); } |
| Vector<int> order_; |
| }; |
| |
| } // anonymous namespace |
| |
| TEST_F(ScriptedAnimationControllerTest, EnqueueOneTask) { |
| TaskOrderObserver observer; |
| |
| Controller().EnqueueTask(observer.CreateTask(1)); |
| EXPECT_EQ(0u, observer.Order().size()); |
| |
| Controller().ServiceScriptedAnimations(0); |
| EXPECT_EQ(1u, observer.Order().size()); |
| EXPECT_EQ(1, observer.Order()[0]); |
| } |
| |
| TEST_F(ScriptedAnimationControllerTest, EnqueueTwoTasks) { |
| TaskOrderObserver observer; |
| |
| Controller().EnqueueTask(observer.CreateTask(1)); |
| Controller().EnqueueTask(observer.CreateTask(2)); |
| EXPECT_EQ(0u, observer.Order().size()); |
| |
| Controller().ServiceScriptedAnimations(0); |
| EXPECT_EQ(2u, observer.Order().size()); |
| EXPECT_EQ(1, observer.Order()[0]); |
| EXPECT_EQ(2, observer.Order()[1]); |
| } |
| |
| namespace { |
| |
| void EnqueueTask(ScriptedAnimationController* controller, |
| TaskOrderObserver* observer, |
| int id) { |
| controller->EnqueueTask(observer->CreateTask(id)); |
| } |
| |
| } // anonymous namespace |
| |
| // A task enqueued while running tasks should not be run immediately after, but |
| // the next time tasks are run. |
| TEST_F(ScriptedAnimationControllerTest, EnqueueWithinTask) { |
| TaskOrderObserver observer; |
| |
| Controller().EnqueueTask(observer.CreateTask(1)); |
| Controller().EnqueueTask(WTF::Bind(&EnqueueTask, |
| WrapPersistent(&Controller()), |
| WTF::Unretained(&observer), 2)); |
| Controller().EnqueueTask(observer.CreateTask(3)); |
| EXPECT_EQ(0u, observer.Order().size()); |
| |
| Controller().ServiceScriptedAnimations(0); |
| EXPECT_EQ(2u, observer.Order().size()); |
| EXPECT_EQ(1, observer.Order()[0]); |
| EXPECT_EQ(3, observer.Order()[1]); |
| |
| Controller().ServiceScriptedAnimations(0); |
| EXPECT_EQ(3u, observer.Order().size()); |
| EXPECT_EQ(1, observer.Order()[0]); |
| EXPECT_EQ(3, observer.Order()[1]); |
| EXPECT_EQ(2, observer.Order()[2]); |
| } |
| |
| namespace { |
| |
| class RunTaskEventListener final : public EventListener { |
| public: |
| RunTaskEventListener(base::RepeatingClosure task) |
| : EventListener(kCPPEventListenerType), task_(std::move(task)) {} |
| void handleEvent(ExecutionContext*, Event*) override { task_.Run(); } |
| bool operator==(const EventListener& other) const override { |
| return this == &other; |
| } |
| |
| private: |
| base::RepeatingClosure task_; |
| }; |
| |
| } // anonymous namespace |
| |
| // Tasks should be run after events are dispatched, even if they were enqueued |
| // first. |
| TEST_F(ScriptedAnimationControllerTest, EnqueueTaskAndEvent) { |
| TaskOrderObserver observer; |
| |
| Controller().EnqueueTask(observer.CreateTask(1)); |
| GetDocument().addEventListener( |
| "test", new RunTaskEventListener(observer.CreateTask(2))); |
| Event* event = Event::Create("test"); |
| event->SetTarget(&GetDocument()); |
| Controller().EnqueueEvent(event); |
| EXPECT_EQ(0u, observer.Order().size()); |
| |
| Controller().ServiceScriptedAnimations(0); |
| EXPECT_EQ(2u, observer.Order().size()); |
| EXPECT_EQ(2, observer.Order()[0]); |
| EXPECT_EQ(1, observer.Order()[1]); |
| } |
| |
| namespace { |
| |
| class RunTaskCallback final |
| : public FrameRequestCallbackCollection::FrameCallback { |
| public: |
| RunTaskCallback(base::RepeatingClosure task) : task_(std::move(task)) {} |
| void Invoke(double) override { task_.Run(); } |
| |
| private: |
| base::RepeatingClosure task_; |
| }; |
| |
| } // anonymous namespace |
| |
| // Animation frame callbacks should be run after tasks, even if they were |
| // enqueued first. |
| TEST_F(ScriptedAnimationControllerTest, RegisterCallbackAndEnqueueTask) { |
| TaskOrderObserver observer; |
| |
| Event* event = Event::Create("test"); |
| event->SetTarget(&GetDocument()); |
| |
| Controller().RegisterCallback(new RunTaskCallback(observer.CreateTask(1))); |
| Controller().EnqueueTask(observer.CreateTask(2)); |
| EXPECT_EQ(0u, observer.Order().size()); |
| |
| Controller().ServiceScriptedAnimations(0); |
| EXPECT_EQ(2u, observer.Order().size()); |
| EXPECT_EQ(2, observer.Order()[0]); |
| EXPECT_EQ(1, observer.Order()[1]); |
| } |
| |
| } // namespace blink |