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"
29 static const uint32_t kMinTelemetryNotifyObserversLatencyMs
= 1;
31 // Log module for nsObserverService logging...
33 // To enable logging (see prlog.h for full details):
35 // set MOZ_LOG=ObserverService:5
36 // set MOZ_LOG_FILE=service.log
38 // This enables LogLevel::Debug level information and places all output in
39 // the file service.log.
40 static mozilla::LazyLogModule
sObserverServiceLog("ObserverService");
41 #define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x)
43 using namespace mozilla
;
46 nsObserverService::CollectReports(nsIHandleReportCallback
* aHandleReport
,
47 nsISupports
* aData
, bool aAnonymize
) {
48 struct SuspectObserver
{
49 SuspectObserver(const char* aTopic
, size_t aReferentCount
)
50 : mTopic(aTopic
), mReferentCount(aReferentCount
) {}
52 size_t mReferentCount
;
55 size_t totalNumStrong
= 0;
56 size_t totalNumWeakAlive
= 0;
57 size_t totalNumWeakDead
= 0;
58 nsTArray
<SuspectObserver
> suspectObservers
;
60 for (auto iter
= mObserverTopicTable
.Iter(); !iter
.Done(); iter
.Next()) {
61 nsObserverList
* observerList
= iter
.Get();
66 size_t topicNumStrong
= 0;
67 size_t topicNumWeakAlive
= 0;
68 size_t topicNumWeakDead
= 0;
70 nsMaybeWeakPtrArray
<nsIObserver
>& observers
= observerList
->mObservers
;
71 for (uint32_t i
= 0; i
< observers
.Length(); i
++) {
72 if (observers
[i
].IsWeak()) {
73 nsCOMPtr
<nsIObserver
> ref
= observers
[i
].GetValue();
84 totalNumStrong
+= topicNumStrong
;
85 totalNumWeakAlive
+= topicNumWeakAlive
;
86 totalNumWeakDead
+= topicNumWeakDead
;
88 // Keep track of topics that have a suspiciously large number
89 // of referents (symptom of leaks).
90 size_t topicTotal
= topicNumStrong
+ topicNumWeakAlive
+ topicNumWeakDead
;
91 if (topicTotal
> kSuspectReferentCount
) {
92 SuspectObserver
suspect(observerList
->GetKey(), topicTotal
);
93 suspectObservers
.AppendElement(suspect
);
97 // These aren't privacy-sensitive and so don't need anonymizing.
98 for (uint32_t i
= 0; i
< suspectObservers
.Length(); i
++) {
99 SuspectObserver
& suspect
= suspectObservers
[i
];
100 nsPrintfCString
suspectPath("observer-service-suspect/referent(topic=%s)",
102 aHandleReport
->Callback(
103 /* process */ ""_ns
, suspectPath
, KIND_OTHER
, UNITS_COUNT
,
104 suspect
.mReferentCount
,
105 nsLiteralCString("A topic with a suspiciously large number of "
106 "referents. This may be symptomatic of a leak "
107 "if the number of referents is high with "
108 "respect to the number of windows."),
113 "observer-service/referent/strong", KIND_OTHER
, UNITS_COUNT
,
115 "The number of strong references held by the observer service.");
118 "observer-service/referent/weak/alive", KIND_OTHER
, UNITS_COUNT
,
120 "The number of weak references held by the observer service that are "
124 "observer-service/referent/weak/dead", KIND_OTHER
, UNITS_COUNT
,
126 "The number of weak references held by the observer service that are "
132 ////////////////////////////////////////////////////////////////////////////////
133 // nsObserverService Implementation
135 NS_IMPL_ISUPPORTS(nsObserverService
, nsIObserverService
, nsObserverService
,
138 nsObserverService::nsObserverService() : mShuttingDown(false) {}
140 nsObserverService::~nsObserverService(void) { Shutdown(); }
142 void nsObserverService::RegisterReporter() { RegisterWeakMemoryReporter(this); }
144 void nsObserverService::Shutdown() {
149 mShuttingDown
= true;
150 UnregisterWeakMemoryReporter(this);
151 mObserverTopicTable
.Clear();
154 nsresult
nsObserverService::Create(nsISupports
* aOuter
, const nsIID
& aIID
,
155 void** aInstancePtr
) {
156 LOG(("nsObserverService::Create()"));
158 RefPtr
<nsObserverService
> os
= new nsObserverService();
160 // The memory reporter can not be immediately registered here because
161 // the nsMemoryReporterManager may attempt to get the nsObserverService
162 // during initialization, causing a recursive GetService.
163 NS_DispatchToCurrentThread(
164 NewRunnableMethod("nsObserverService::RegisterReporter", os
,
165 &nsObserverService::RegisterReporter
));
167 return os
->QueryInterface(aIID
, aInstancePtr
);
170 nsresult
nsObserverService::EnsureValidCall() const {
171 if (!NS_IsMainThread()) {
172 MOZ_CRASH("Using observer service off the main thread!");
173 return NS_ERROR_UNEXPECTED
;
177 NS_ERROR("Using observer service after XPCOM shutdown!");
178 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
184 nsresult
nsObserverService::FilterHttpOnTopics(const char* aTopic
) {
185 // Specifically allow http-on-opening-request and http-on-stop-request in the
186 // child process; see bug 1269765.
187 if (mozilla::net::IsNeckoChild() && !strncmp(aTopic
, "http-on-", 8) &&
188 strcmp(aTopic
, "http-on-failed-opening-request") &&
189 strcmp(aTopic
, "http-on-opening-request") &&
190 strcmp(aTopic
, "http-on-stop-request") &&
191 strcmp(aTopic
, "http-on-image-cache-response")) {
192 nsCOMPtr
<nsIConsoleService
> console(
193 do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
194 nsCOMPtr
<nsIScriptError
> error(
195 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
196 error
->Init(u
"http-on-* observers only work in the parent process"_ns
,
197 u
""_ns
, u
""_ns
, 0, 0, nsIScriptError::warningFlag
,
198 "chrome javascript", false /* from private window */,
199 true /* from chrome context */);
200 console
->LogMessage(error
);
202 return NS_ERROR_NOT_IMPLEMENTED
;
209 nsObserverService::AddObserver(nsIObserver
* aObserver
, const char* aTopic
,
211 LOG(("nsObserverService::AddObserver(%p: %s, %s)", (void*)aObserver
, aTopic
,
212 aOwnsWeak
? "weak" : "strong"));
214 MOZ_TRY(EnsureValidCall());
215 if (NS_WARN_IF(!aObserver
) || NS_WARN_IF(!aTopic
)) {
216 return NS_ERROR_INVALID_ARG
;
219 MOZ_TRY(FilterHttpOnTopics(aTopic
));
221 nsObserverList
* observerList
= mObserverTopicTable
.PutEntry(aTopic
);
223 return NS_ERROR_OUT_OF_MEMORY
;
226 return observerList
->AddObserver(aObserver
, aOwnsWeak
);
230 nsObserverService::RemoveObserver(nsIObserver
* aObserver
, const char* aTopic
) {
231 LOG(("nsObserverService::RemoveObserver(%p: %s)", (void*)aObserver
, aTopic
));
234 // The service is shutting down. Let's ignore this call.
238 MOZ_TRY(EnsureValidCall());
239 if (NS_WARN_IF(!aObserver
) || NS_WARN_IF(!aTopic
)) {
240 return NS_ERROR_INVALID_ARG
;
243 nsObserverList
* observerList
= mObserverTopicTable
.GetEntry(aTopic
);
245 return NS_ERROR_FAILURE
;
248 return observerList
->RemoveObserver(aObserver
);
252 nsObserverService::EnumerateObservers(const char* aTopic
,
253 nsISimpleEnumerator
** anEnumerator
) {
254 LOG(("nsObserverService::EnumerateObservers(%s)", aTopic
));
256 MOZ_TRY(EnsureValidCall());
257 if (NS_WARN_IF(!anEnumerator
) || NS_WARN_IF(!aTopic
)) {
258 return NS_ERROR_INVALID_ARG
;
261 nsObserverList
* observerList
= mObserverTopicTable
.GetEntry(aTopic
);
263 return NS_NewEmptyEnumerator(anEnumerator
);
266 observerList
->GetObserverList(anEnumerator
);
270 // Enumerate observers of aTopic and call Observe on each.
271 NS_IMETHODIMP
nsObserverService::NotifyObservers(nsISupports
* aSubject
,
273 const char16_t
* aSomeData
) {
274 LOG(("nsObserverService::NotifyObservers(%s)", aTopic
));
276 MOZ_TRY(EnsureValidCall());
277 if (NS_WARN_IF(!aTopic
)) {
278 return NS_ERROR_INVALID_ARG
;
281 MOZ_ASSERT(AppShutdown::IsNoOrLegalShutdownTopic(aTopic
));
283 mozilla::TimeStamp start
= TimeStamp::Now();
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
);
295 uint32_t latencyMs
= round((TimeStamp::Now() - start
).ToMilliseconds());
296 if (latencyMs
>= kMinTelemetryNotifyObserversLatencyMs
) {
297 Telemetry::Accumulate(Telemetry::NOTIFY_OBSERVERS_LATENCY_MS
,
298 nsDependentCString(aTopic
), latencyMs
);
305 nsObserverService::UnmarkGrayStrongObservers() {
306 MOZ_TRY(EnsureValidCall());
308 nsCOMArray
<nsIObserver
> strongObservers
;
309 for (auto iter
= mObserverTopicTable
.Iter(); !iter
.Done(); iter
.Next()) {
310 nsObserverList
* aObserverList
= iter
.Get();
312 aObserverList
->AppendStrongObservers(strongObservers
);
316 for (uint32_t i
= 0; i
< strongObservers
.Length(); ++i
) {
317 xpc_TryUnmarkWrappedGrayObject(strongObservers
[i
]);
325 class NotifyWhenScriptSafeRunnable
: public mozilla::Runnable
{
327 NotifyWhenScriptSafeRunnable(nsIObserverService
* aObs
, nsISupports
* aSubject
,
328 const char* aTopic
, const char16_t
* aData
)
329 : mozilla::Runnable("NotifyWhenScriptSafeRunnable"),
336 mData
.SetIsVoid(true);
341 const char16_t
* data
= mData
.IsVoid() ? nullptr : mData
.get();
342 return mObs
->NotifyObservers(mSubject
, mTopic
.get(), data
);
346 nsCOMPtr
<nsIObserverService
> mObs
;
347 nsCOMPtr
<nsISupports
> mSubject
;
354 nsresult
nsIObserverService::NotifyWhenScriptSafe(nsISupports
* aSubject
,
356 const char16_t
* aData
) {
357 if (nsContentUtils::IsSafeToRunScript()) {
358 return NotifyObservers(aSubject
, aTopic
, aData
);
361 nsContentUtils::AddScriptRunner(MakeAndAddRef
<NotifyWhenScriptSafeRunnable
>(
362 this, aSubject
, aTopic
, aData
));