/* -*- 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<nsIObserver*>(aObs);
}
static void TestExpectedCount(nsIObserverService* svc,
const char* topic,
size_t expected) {
nsCOMPtr<nsISimpleEnumerator> 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<nsISupports> 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<nsIObserverService> svc =
do_CreateInstance(
"@mozilla.org/observer-service;1", &rv);
ASSERT_EQ(rv, NS_OK);
ASSERT_TRUE(svc);
}
TEST(ObserverService, AddObserver)
{
nsCOMPtr<nsIObserverService> svc =
do_CreateInstance(
"@mozilla.org/observer-service;1");
// Add a strong ref.
RefPtr<TestObserver> a =
new TestObserver(u
"A"_ns);
nsresult rv = svc->AddObserver(a,
"Foo",
false);
testResult(rv);
// Add a few weak ref.
RefPtr<TestObserver> b =
new TestObserver(u
"B"_ns);
rv = svc->AddObserver(b,
"Bar",
true);
testResult(rv);
}
TEST(ObserverService, RemoveObserver)
{
nsCOMPtr<nsIObserverService> svc =
do_CreateInstance(
"@mozilla.org/observer-service;1");
RefPtr<TestObserver> a =
new TestObserver(u
"A"_ns);
RefPtr<TestObserver> b =
new TestObserver(u
"B"_ns);
RefPtr<TestObserver> 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<nsIObserverService> 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<TestObserver> a =
new TestObserver(u
"A"_ns);
testResult(svc->AddObserver(a,
"Foo",
false));
TestExpectedCount(svc,
"A", 0);
}
TEST(ObserverService, Enumerate)
{
nsCOMPtr<nsIObserverService> svc =
do_CreateInstance(
"@mozilla.org/observer-service;1");
const size_t kFooCount = 10;
for (size_t i = 0; i < kFooCount; i++) {
RefPtr<TestObserver> 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<TestObserver> 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<nsIObserverService> svc =
do_CreateInstance(
"@mozilla.org/observer-service;1");
const size_t kFooCount = 10;
for (size_t i = 0; i < kFooCount; i++) {
RefPtr<TestObserver> 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<TestObserver> a =
new TestObserver(u
"A"_ns);
RefPtr<TestObserver> 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<TestObserver> a =
new TestObserver(u
"A"_ns);
testResult(svc->AddObserver(a,
"Foo",
true));
RefPtr<TestObserver> 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<nsIObserverService> svc =
do_CreateInstance(
"@mozilla.org/observer-service;1");
RefPtr<TestObserver> aObserver =
new TestObserver(u
"Observer-A"_ns);
RefPtr<TestObserver> 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);
}