/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/StopGapEventTarget.h" #include "mozilla/RefPtr.h" #include "mozilla/gtest/MozAssertions.h" #include "nsIRunnable.h" #include "nsThreadUtils.h" #include "Helpers.h" using mozilla::CancelableRunnable; using mozilla::MakeRefPtr; using mozilla::StopGapEventTarget; namespace TestStopGapEventTarget { static void Enqueue(nsIEventTarget* target, std::function&& aCallable) { nsresult rv = target->Dispatch( NS_NewRunnableFunction("SGQ GTest", std::move(aCallable))); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); } class TestCancelable final : public CancelableRunnable { public: explicit TestCancelable() : CancelableRunnable("TestCancelable") {} NS_IMETHOD Run() override { mHasRun = true; return NS_OK; } nsresult Cancel() override { mHasCanceled = true; return NS_OK; } bool mHasRun = false; bool mHasCanceled = false; private: virtual ~TestCancelable() = default; }; } // namespace TestStopGapEventTarget using namespace TestStopGapEventTarget; using testing::RunnableQueue; TEST(StopGapEventTarget, SimpleDispatch) { std::string log; RefPtr stopgap = MakeRefPtr(); Enqueue(stopgap, [&]() { log += 'a'; }); ASSERT_EQ(log, ""); auto base = MakeRefPtr(); stopgap->SetRealEventTarget(base); ASSERT_NS_SUCCEEDED(base->Run()); ASSERT_EQ(log, "a"); ASSERT_TRUE(base->IsEmpty()); } TEST(StopGapEventTarget, RunnableNeverRun) { std::string log; RefPtr stopgap = MakeRefPtr(); Enqueue(stopgap, [&]() { log += 'a'; }); ASSERT_EQ(log, ""); stopgap = nullptr; ASSERT_EQ(log, ""); } TEST(StopGapEventTarget, CancelableIsRun) { RefPtr stopgap = MakeRefPtr(); RefPtr cancelable(new TestCancelable); stopgap->Dispatch(do_AddRef(cancelable)); ASSERT_FALSE(cancelable->mHasRun); ASSERT_FALSE(cancelable->mHasCanceled); auto base = MakeRefPtr(); stopgap->SetRealEventTarget(base); ASSERT_NS_SUCCEEDED(base->Run()); stopgap = nullptr; ASSERT_TRUE(cancelable->mHasRun); ASSERT_FALSE(cancelable->mHasCanceled); } TEST(StopGapEventTarget, CancelableNeverRun) { RefPtr stopgap = MakeRefPtr(); RefPtr cancelable(new TestCancelable); stopgap->Dispatch(do_AddRef(cancelable)); ASSERT_FALSE(cancelable->mHasRun); ASSERT_FALSE(cancelable->mHasCanceled); stopgap = nullptr; ASSERT_FALSE(cancelable->mHasRun); ASSERT_TRUE(cancelable->mHasCanceled); } TEST(StopGapEventTarget, EnqueueFromRun) { std::string log; auto base = MakeRefPtr(); RefPtr stopgap = MakeRefPtr(); Enqueue(base, [&]() { log += 'a'; }); Enqueue(stopgap, [&]() { log += 'b'; Enqueue(stopgap, [&]() { log += 'c'; }); Enqueue(base, [&]() { log += 'd'; }); }); Enqueue(stopgap, [&]() { log += 'e'; }); ASSERT_NS_SUCCEEDED(base->Run()); ASSERT_EQ(log, "a"); ASSERT_TRUE(base->IsEmpty()); stopgap->SetRealEventTarget(base); ASSERT_EQ(base->Length(), 2U); ASSERT_NS_SUCCEEDED(base->Run()); ASSERT_EQ(log, "abecd"); ASSERT_TRUE(base->IsEmpty()); } TEST(StopGapEventTarget, RunFromRun) { std::string log; auto base = MakeRefPtr(); RefPtr stopgap = MakeRefPtr(); Enqueue(base, [&]() { log += 'a'; }); // Running the event queue from within an event (i.e., a nested event loop) // does not stall the StopGapEventTarget. Enqueue(stopgap, [&]() { log += '('; Enqueue(stopgap, [&]() { log += 'b'; }); // This should run subsequent events from stopgap. ASSERT_NS_SUCCEEDED(base->Run()); log += ')'; }); Enqueue(stopgap, [&]() { log += 'c'; }); ASSERT_EQ(log, ""); stopgap->SetRealEventTarget(base); ASSERT_NS_SUCCEEDED(base->Run()); ASSERT_EQ(log, "a(cb)"); ASSERT_TRUE(base->IsEmpty()); } TEST(StopGapEventTarget, SetEventTargetFromRun) { std::string log; auto base = MakeRefPtr(); RefPtr stopgap = MakeRefPtr(); Enqueue(stopgap, [&]() { // This is running during the first Run() call, and enqueueing these will // result in them being run too during this call log += 'd'; Enqueue(stopgap, [&]() { log += 'e'; }); Enqueue(stopgap, [&]() { log += 'f'; }); }); Enqueue(base, [&]() { log += 'a'; // This will enqueue the d task from above; Run() is currently running... stopgap->SetRealEventTarget(base); log += 'b'; // ...but we call Run inside Run; *this* call to Run will take over, running // d, then e and f ASSERT_NS_SUCCEEDED(base->Run()); log += 'c'; }); ASSERT_EQ(log, ""); ASSERT_NS_SUCCEEDED(base->Run()); ASSERT_EQ(log, "abdefc"); ASSERT_TRUE(base->IsEmpty()); } TEST(StopGapEventTarget, DropWhileRunning) { std::string log; auto base = MakeRefPtr(); // If we drop the event queue while it still has events, they still run. { RefPtr stopgap = MakeRefPtr(); Enqueue(stopgap, [&]() { log += 'a'; }); stopgap->SetRealEventTarget(base); } ASSERT_EQ(log, ""); ASSERT_NS_SUCCEEDED(base->Run()); ASSERT_EQ(log, "a"); }