/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "nsISupports.h" #include "nsIObserverService.h" #include "nsIObserver.h" #include "nsISimpleEnumerator.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsString.h" #include "nsWeakReference.h" #include "mozilla/gtest/MozAssertions.h" #include "mozilla/RefPtr.h" #include "gtest/gtest.h" static void testResult(nsresult rv) { EXPECT_TRUE(NS_SUCCEEDED(rv)) << "0x" << std::hex << (int)rv; } class TestObserver final : public nsIObserver, public nsSupportsWeakReference { public: explicit TestObserver(const nsAString& name) : mName(name), mObservations(0) {} NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER nsString mName; int mObservations; static int sTotalObservations; nsString mExpectedData; private: ~TestObserver() = default; }; NS_IMPL_ISUPPORTS(TestObserver, nsIObserver, nsISupportsWeakReference) int TestObserver::sTotalObservations; NS_IMETHODIMP TestObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* someData) { mObservations++; sTotalObservations++; if (!mExpectedData.IsEmpty()) { EXPECT_TRUE(mExpectedData.Equals(someData)); } return NS_OK; } static nsISupports* ToSupports(TestObserver* aObs) { return static_cast(aObs); } static void TestExpectedCount(nsIObserverService* svc, const char* topic, size_t expected) { nsCOMPtr e; nsresult rv = svc->EnumerateObservers(topic, getter_AddRefs(e)); testResult(rv); EXPECT_TRUE(e); bool hasMore = false; rv = e->HasMoreElements(&hasMore); testResult(rv); if (expected == 0) { EXPECT_FALSE(hasMore); return; } size_t count = 0; while (hasMore) { count++; // Grab the element. nsCOMPtr supports; e->GetNext(getter_AddRefs(supports)); ASSERT_TRUE(supports); // Move on. rv = e->HasMoreElements(&hasMore); testResult(rv); } EXPECT_EQ(count, expected); } TEST(ObserverService, Creation) { nsresult rv; nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1", &rv); ASSERT_EQ(rv, NS_OK); ASSERT_TRUE(svc); } TEST(ObserverService, AddObserver) { nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1"); // Add a strong ref. RefPtr a = new TestObserver(u"A"_ns); nsresult rv = svc->AddObserver(a, "Foo", false); testResult(rv); // Add a few weak ref. RefPtr b = new TestObserver(u"B"_ns); rv = svc->AddObserver(b, "Bar", true); testResult(rv); } TEST(ObserverService, RemoveObserver) { nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1"); RefPtr a = new TestObserver(u"A"_ns); RefPtr b = new TestObserver(u"B"_ns); RefPtr c = new TestObserver(u"C"_ns); svc->AddObserver(a, "Foo", false); svc->AddObserver(b, "Foo", true); // Remove from non-existent topic. nsresult rv = svc->RemoveObserver(a, "Bar"); ASSERT_NS_FAILED(rv); // Remove a. testResult(svc->RemoveObserver(a, "Foo")); // Remove b. testResult(svc->RemoveObserver(b, "Foo")); // Attempt to remove c. rv = svc->RemoveObserver(c, "Foo"); ASSERT_NS_FAILED(rv); } TEST(ObserverService, EnumerateEmpty) { nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1"); // Try with no observers. TestExpectedCount(svc, "A", 0); // Now add an observer and enumerate an unobserved topic. RefPtr a = new TestObserver(u"A"_ns); testResult(svc->AddObserver(a, "Foo", false)); TestExpectedCount(svc, "A", 0); } TEST(ObserverService, Enumerate) { nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1"); const size_t kFooCount = 10; for (size_t i = 0; i < kFooCount; i++) { RefPtr a = new TestObserver(u"A"_ns); testResult(svc->AddObserver(a, "Foo", false)); } const size_t kBarCount = kFooCount / 2; for (size_t i = 0; i < kBarCount; i++) { RefPtr a = new TestObserver(u"A"_ns); testResult(svc->AddObserver(a, "Bar", false)); } // Enumerate "Foo". TestExpectedCount(svc, "Foo", kFooCount); // Enumerate "Bar". TestExpectedCount(svc, "Bar", kBarCount); } TEST(ObserverService, EnumerateWeakRefs) { nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1"); const size_t kFooCount = 10; for (size_t i = 0; i < kFooCount; i++) { RefPtr a = new TestObserver(u"A"_ns); testResult(svc->AddObserver(a, "Foo", true)); } // All refs are out of scope, expect enumeration to be empty. TestExpectedCount(svc, "Foo", 0); // Now test a mixture. for (size_t i = 0; i < kFooCount; i++) { RefPtr a = new TestObserver(u"A"_ns); RefPtr b = new TestObserver(u"B"_ns); // Register a as weak for "Foo". testResult(svc->AddObserver(a, "Foo", true)); // Register b as strong for "Foo". testResult(svc->AddObserver(b, "Foo", false)); } // Expect the b instances to stick around. TestExpectedCount(svc, "Foo", kFooCount); // Now add a couple weak refs, but don't go out of scope. RefPtr a = new TestObserver(u"A"_ns); testResult(svc->AddObserver(a, "Foo", true)); RefPtr b = new TestObserver(u"B"_ns); testResult(svc->AddObserver(b, "Foo", true)); // Expect all the observers from before and the two new ones. TestExpectedCount(svc, "Foo", kFooCount + 2); } TEST(ObserverService, TestNotify) { nsCString topicA; topicA.Assign("topic-A"); nsCString topicB; topicB.Assign("topic-B"); nsCOMPtr svc = do_CreateInstance("@mozilla.org/observer-service;1"); RefPtr aObserver = new TestObserver(u"Observer-A"_ns); RefPtr bObserver = new TestObserver(u"Observer-B"_ns); // Add two observers for topicA. testResult(svc->AddObserver(aObserver, topicA.get(), false)); testResult(svc->AddObserver(bObserver, topicA.get(), false)); // Add one observer for topicB. testResult(svc->AddObserver(bObserver, topicB.get(), false)); // Notify topicA. const char16_t* dataA = u"Testing Notify(observer-A, topic-A)"; aObserver->mExpectedData = dataA; bObserver->mExpectedData = dataA; nsresult rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); testResult(rv); ASSERT_EQ(aObserver->mObservations, 1); ASSERT_EQ(bObserver->mObservations, 1); // Notify topicB. const char16_t* dataB = u"Testing Notify(observer-B, topic-B)"; bObserver->mExpectedData = dataB; rv = svc->NotifyObservers(ToSupports(bObserver), topicB.get(), dataB); testResult(rv); ASSERT_EQ(aObserver->mObservations, 1); ASSERT_EQ(bObserver->mObservations, 2); // Remove one of the topicA observers, make sure it's not notified. testResult(svc->RemoveObserver(aObserver, topicA.get())); // Notify topicA, only bObserver is expected to be notified. bObserver->mExpectedData = dataA; rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); testResult(rv); ASSERT_EQ(aObserver->mObservations, 1); ASSERT_EQ(bObserver->mObservations, 3); // Remove the other topicA observer, make sure none are notified. testResult(svc->RemoveObserver(bObserver, topicA.get())); rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); testResult(rv); ASSERT_EQ(aObserver->mObservations, 1); ASSERT_EQ(bObserver->mObservations, 3); }