1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Logging.h"
8 #include "nsComponentManagerUtils.h"
9 #include "nsContentUtils.h"
10 #include "nsIConsoleService.h"
11 #include "nsIObserverService.h"
12 #include "nsIObserver.h"
13 #include "nsIScriptError.h"
14 #include "nsObserverService.h"
15 #include "nsObserverList.h"
16 #include "nsServiceManagerUtils.h"
17 #include "nsThreadUtils.h"
18 #include "nsEnumeratorUtils.h"
19 #include "xpcpublic.h"
20 #include "mozilla/AppShutdown.h"
21 #include "mozilla/net/NeckoCommon.h"
22 #include "mozilla/ProfilerLabels.h"
23 #include "mozilla/ProfilerMarkers.h"
24 #include "mozilla/ResultExtensions.h"
25 #include "mozilla/Telemetry.h"
26 #include "mozilla/TimeStamp.h"
27 #include "mozilla/Try.h"
30 // Log module for nsObserverService logging...
32 // To enable logging (see prlog.h for full details):
34 // set MOZ_LOG=ObserverService:5
35 // set MOZ_LOG_FILE=service.log
37 // This enables LogLevel::Debug level information and places all output in
38 // the file service.log.
39 static mozilla::LazyLogModule
sObserverServiceLog("ObserverService");
40 #define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x)
42 using namespace mozilla
;
45 nsObserverService::CollectReports(nsIHandleReportCallback
* aHandleReport
,
46 nsISupports
* aData
, bool aAnonymize
) {
47 struct SuspectObserver
{
48 SuspectObserver(const char* aTopic
, size_t aReferentCount
)
49 : mTopic(aTopic
), mReferentCount(aReferentCount
) {}
51 size_t mReferentCount
;
54 size_t totalNumStrong
= 0;
55 size_t totalNumWeakAlive
= 0;
56 size_t totalNumWeakDead
= 0;
57 nsTArray
<SuspectObserver
> suspectObservers
;
59 for (auto iter
= mObserverTopicTable
.Iter(); !iter
.Done(); iter
.Next()) {
60 nsObserverList
* observerList
= iter
.Get();
65 size_t topicNumStrong
= 0;
66 size_t topicNumWeakAlive
= 0;
67 size_t topicNumWeakDead
= 0;
69 nsMaybeWeakPtrArray
<nsIObserver
>& observers
= observerList
->mObservers
;
70 for (uint32_t i
= 0; i
< observers
.Length(); i
++) {
71 if (observers
[i
].IsWeak()) {
72 nsCOMPtr
<nsIObserver
> ref
= observers
[i
].GetValue();
83 totalNumStrong
+= topicNumStrong
;
84 totalNumWeakAlive
+= topicNumWeakAlive
;
85 totalNumWeakDead
+= topicNumWeakDead
;
87 // Keep track of topics that have a suspiciously large number
88 // of referents (symptom of leaks).
89 size_t topicTotal
= topicNumStrong
+ topicNumWeakAlive
+ topicNumWeakDead
;
90 if (topicTotal
> kSuspectReferentCount
) {
91 SuspectObserver
suspect(observerList
->GetKey(), topicTotal
);
92 suspectObservers
.AppendElement(suspect
);
96 // These aren't privacy-sensitive and so don't need anonymizing.
97 for (uint32_t i
= 0; i
< suspectObservers
.Length(); i
++) {
98 SuspectObserver
& suspect
= suspectObservers
[i
];
99 nsPrintfCString
suspectPath("observer-service-suspect/referent(topic=%s)",
101 aHandleReport
->Callback(
102 /* process */ ""_ns
, suspectPath
, KIND_OTHER
, UNITS_COUNT
,
103 suspect
.mReferentCount
,
104 nsLiteralCString("A topic with a suspiciously large number of "
105 "referents. This may be symptomatic of a leak "
106 "if the number of referents is high with "
107 "respect to the number of windows."),
112 "observer-service/referent/strong", KIND_OTHER
, UNITS_COUNT
,
114 "The number of strong references held by the observer service.");
117 "observer-service/referent/weak/alive", KIND_OTHER
, UNITS_COUNT
,
119 "The number of weak references held by the observer service that are "
123 "observer-service/referent/weak/dead", KIND_OTHER
, UNITS_COUNT
,
125 "The number of weak references held by the observer service that are "
131 ////////////////////////////////////////////////////////////////////////////////
132 // nsObserverService Implementation
134 NS_IMPL_ISUPPORTS(nsObserverService
, nsIObserverService
, nsObserverService
,
137 nsObserverService::nsObserverService() : mShuttingDown(false) {}
139 nsObserverService::~nsObserverService(void) { Shutdown(); }
141 void nsObserverService::RegisterReporter() { RegisterWeakMemoryReporter(this); }
143 void nsObserverService::Shutdown() {
148 mShuttingDown
= true;
149 UnregisterWeakMemoryReporter(this);
150 mObserverTopicTable
.Clear();
153 nsresult
nsObserverService::Create(const nsIID
& aIID
, void** aInstancePtr
) {
154 LOG(("nsObserverService::Create()"));
156 RefPtr
<nsObserverService
> os
= new nsObserverService();
158 // The memory reporter can not be immediately registered here because
159 // the nsMemoryReporterManager may attempt to get the nsObserverService
160 // during initialization, causing a recursive GetService.
161 NS_DispatchToCurrentThread(
162 NewRunnableMethod("nsObserverService::RegisterReporter", os
,
163 &nsObserverService::RegisterReporter
));
165 return os
->QueryInterface(aIID
, aInstancePtr
);
168 nsresult
nsObserverService::EnsureValidCall() const {
169 if (!NS_IsMainThread()) {
170 MOZ_CRASH("Using observer service off the main thread!");
171 return NS_ERROR_UNEXPECTED
;
175 NS_ERROR("Using observer service after XPCOM shutdown!");
176 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
182 nsresult
nsObserverService::FilterHttpOnTopics(const char* aTopic
) {
183 // Specifically allow some http-on-* observer notifications in the child
185 if (mozilla::net::IsNeckoChild() && !strncmp(aTopic
, "http-on-", 8) &&
186 strcmp(aTopic
, "http-on-before-stop-request") &&
187 strcmp(aTopic
, "http-on-failed-opening-request") &&
188 // TODO: Merge cache response notifications (bug 1919218)
189 strcmp(aTopic
, "http-on-image-cache-response") &&
190 strcmp(aTopic
, "http-on-stylesheet-cache-response") &&
191 strcmp(aTopic
, "http-on-resource-cache-response") &&
192 strcmp(aTopic
, "http-on-opening-request") &&
193 strcmp(aTopic
, "http-on-stop-request")) {
194 nsCOMPtr
<nsIConsoleService
> console(
195 do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
196 nsCOMPtr
<nsIScriptError
> error(
197 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
198 error
->Init(u
"http-on-* observers only work in the parent process"_ns
,
199 ""_ns
, 0, 0, nsIScriptError::warningFlag
,
200 "chrome javascript"_ns
, false /* from private window */,
201 true /* from chrome context */);
202 console
->LogMessage(error
);
204 return NS_ERROR_NOT_IMPLEMENTED
;
211 nsObserverService::AddObserver(nsIObserver
* aObserver
, const char* aTopic
,
213 LOG(("nsObserverService::AddObserver(%p: %s, %s)", (void*)aObserver
, aTopic
,
214 aOwnsWeak
? "weak" : "strong"));
216 MOZ_TRY(EnsureValidCall());
217 if (NS_WARN_IF(!aObserver
) || NS_WARN_IF(!aTopic
)) {
218 return NS_ERROR_INVALID_ARG
;
221 MOZ_TRY(FilterHttpOnTopics(aTopic
));
223 nsObserverList
* observerList
= mObserverTopicTable
.PutEntry(aTopic
);
225 return NS_ERROR_OUT_OF_MEMORY
;
228 return observerList
->AddObserver(aObserver
, aOwnsWeak
);
232 nsObserverService::RemoveObserver(nsIObserver
* aObserver
, const char* aTopic
) {
233 LOG(("nsObserverService::RemoveObserver(%p: %s)", (void*)aObserver
, aTopic
));
236 // The service is shutting down. Let's ignore this call.
240 MOZ_TRY(EnsureValidCall());
241 if (NS_WARN_IF(!aObserver
) || NS_WARN_IF(!aTopic
)) {
242 return NS_ERROR_INVALID_ARG
;
245 nsObserverList
* observerList
= mObserverTopicTable
.GetEntry(aTopic
);
247 return NS_ERROR_FAILURE
;
250 return observerList
->RemoveObserver(aObserver
);
254 nsObserverService::EnumerateObservers(const char* aTopic
,
255 nsISimpleEnumerator
** anEnumerator
) {
256 LOG(("nsObserverService::EnumerateObservers(%s)", aTopic
));
258 MOZ_TRY(EnsureValidCall());
259 if (NS_WARN_IF(!anEnumerator
) || NS_WARN_IF(!aTopic
)) {
260 return NS_ERROR_INVALID_ARG
;
263 nsObserverList
* observerList
= mObserverTopicTable
.GetEntry(aTopic
);
265 return NS_NewEmptyEnumerator(anEnumerator
);
268 observerList
->GetObserverList(anEnumerator
);
272 // Enumerate observers of aTopic and call Observe on each.
273 NS_IMETHODIMP
nsObserverService::NotifyObservers(nsISupports
* aSubject
,
275 const char16_t
* aSomeData
) {
276 LOG(("nsObserverService::NotifyObservers(%s)", aTopic
));
278 MOZ_TRY(EnsureValidCall());
279 if (NS_WARN_IF(!aTopic
)) {
280 return NS_ERROR_INVALID_ARG
;
283 MOZ_ASSERT(AppShutdown::IsNoOrLegalShutdownTopic(aTopic
));
285 AUTO_PROFILER_MARKER_TEXT("NotifyObservers", OTHER
, MarkerStack::Capture(),
286 nsDependentCString(aTopic
));
287 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
288 "nsObserverService::NotifyObservers", OTHER
, aTopic
);
290 nsObserverList
* observerList
= mObserverTopicTable
.GetEntry(aTopic
);
292 observerList
->NotifyObservers(aSubject
, aTopic
, aSomeData
);
299 nsObserverService::UnmarkGrayStrongObservers() {
300 MOZ_TRY(EnsureValidCall());
302 nsCOMArray
<nsIObserver
> strongObservers
;
303 for (auto iter
= mObserverTopicTable
.Iter(); !iter
.Done(); iter
.Next()) {
304 nsObserverList
* aObserverList
= iter
.Get();
306 aObserverList
->AppendStrongObservers(strongObservers
);
310 for (uint32_t i
= 0; i
< strongObservers
.Length(); ++i
) {
311 xpc_TryUnmarkWrappedGrayObject(strongObservers
[i
]);
317 bool nsObserverService::HasObservers(const char* aTopic
) {
318 return mObserverTopicTable
.Contains(aTopic
);
323 class NotifyWhenScriptSafeRunnable
: public mozilla::Runnable
{
325 NotifyWhenScriptSafeRunnable(nsIObserverService
* aObs
, nsISupports
* aSubject
,
326 const char* aTopic
, const char16_t
* aData
)
327 : mozilla::Runnable("NotifyWhenScriptSafeRunnable"),
334 mData
.SetIsVoid(true);
338 NS_IMETHOD
Run() override
{
339 const char16_t
* data
= mData
.IsVoid() ? nullptr : mData
.get();
340 return mObs
->NotifyObservers(mSubject
, mTopic
.get(), data
);
344 nsCOMPtr
<nsIObserverService
> mObs
;
345 nsCOMPtr
<nsISupports
> mSubject
;
352 nsresult
nsIObserverService::NotifyWhenScriptSafe(nsISupports
* aSubject
,
354 const char16_t
* aData
) {
355 if (nsContentUtils::IsSafeToRunScript()) {
356 return NotifyObservers(aSubject
, aTopic
, aData
);
359 nsContentUtils::AddScriptRunner(MakeAndAddRef
<NotifyWhenScriptSafeRunnable
>(
360 this, aSubject
, aTopic
, aData
));