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/. */
6
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 "nsString.h"
28
29 static const uint32_t kMinTelemetryNotifyObserversLatencyMs = 1;
30
31 // Log module for nsObserverService logging...
32 //
33 // To enable logging (see prlog.h for full details):
34 //
35 // set MOZ_LOG=ObserverService:5
36 // set MOZ_LOG_FILE=service.log
37 //
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)
42
43 using namespace mozilla;
44
45 NS_IMETHODIMP
CollectReports(nsIHandleReportCallback * aHandleReport,nsISupports * aData,bool aAnonymize)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) {}
51 const char* mTopic;
52 size_t mReferentCount;
53 };
54
55 size_t totalNumStrong = 0;
56 size_t totalNumWeakAlive = 0;
57 size_t totalNumWeakDead = 0;
58 nsTArray<SuspectObserver> suspectObservers;
59
60 for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) {
61 nsObserverList* observerList = iter.Get();
62 if (!observerList) {
63 continue;
64 }
65
66 size_t topicNumStrong = 0;
67 size_t topicNumWeakAlive = 0;
68 size_t topicNumWeakDead = 0;
69
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();
74 if (ref) {
75 topicNumWeakAlive++;
76 } else {
77 topicNumWeakDead++;
78 }
79 } else {
80 topicNumStrong++;
81 }
82 }
83
84 totalNumStrong += topicNumStrong;
85 totalNumWeakAlive += topicNumWeakAlive;
86 totalNumWeakDead += topicNumWeakDead;
87
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);
94 }
95 }
96
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)",
101 suspect.mTopic);
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."),
109 aData);
110 }
111
112 MOZ_COLLECT_REPORT(
113 "observer-service/referent/strong", KIND_OTHER, UNITS_COUNT,
114 totalNumStrong,
115 "The number of strong references held by the observer service.");
116
117 MOZ_COLLECT_REPORT(
118 "observer-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
119 totalNumWeakAlive,
120 "The number of weak references held by the observer service that are "
121 "still alive.");
122
123 MOZ_COLLECT_REPORT(
124 "observer-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
125 totalNumWeakDead,
126 "The number of weak references held by the observer service that are "
127 "dead.");
128
129 return NS_OK;
130 }
131
132 ////////////////////////////////////////////////////////////////////////////////
133 // nsObserverService Implementation
134
NS_IMPL_ISUPPORTS(nsObserverService,nsIObserverService,nsObserverService,nsIMemoryReporter)135 NS_IMPL_ISUPPORTS(nsObserverService, nsIObserverService, nsObserverService,
136 nsIMemoryReporter)
137
138 nsObserverService::nsObserverService() : mShuttingDown(false) {}
139
~nsObserverService(void)140 nsObserverService::~nsObserverService(void) { Shutdown(); }
141
RegisterReporter()142 void nsObserverService::RegisterReporter() { RegisterWeakMemoryReporter(this); }
143
Shutdown()144 void nsObserverService::Shutdown() {
145 if (mShuttingDown) {
146 return;
147 }
148
149 mShuttingDown = true;
150 UnregisterWeakMemoryReporter(this);
151 mObserverTopicTable.Clear();
152 }
153
Create(nsISupports * aOuter,const nsIID & aIID,void ** aInstancePtr)154 nsresult nsObserverService::Create(nsISupports* aOuter, const nsIID& aIID,
155 void** aInstancePtr) {
156 LOG(("nsObserverService::Create()"));
157
158 RefPtr<nsObserverService> os = new nsObserverService();
159
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));
166
167 return os->QueryInterface(aIID, aInstancePtr);
168 }
169
EnsureValidCall() const170 nsresult nsObserverService::EnsureValidCall() const {
171 if (!NS_IsMainThread()) {
172 MOZ_CRASH("Using observer service off the main thread!");
173 return NS_ERROR_UNEXPECTED;
174 }
175
176 if (mShuttingDown) {
177 NS_ERROR("Using observer service after XPCOM shutdown!");
178 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
179 }
180
181 return NS_OK;
182 }
183
FilterHttpOnTopics(const char * aTopic)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 nsCOMPtr<nsIConsoleService> console(
192 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
193 nsCOMPtr<nsIScriptError> error(
194 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
195 error->Init(u"http-on-* observers only work in the parent process"_ns,
196 u""_ns, u""_ns, 0, 0, nsIScriptError::warningFlag,
197 "chrome javascript", false /* from private window */,
198 true /* from chrome context */);
199 console->LogMessage(error);
200
201 return NS_ERROR_NOT_IMPLEMENTED;
202 }
203
204 return NS_OK;
205 }
206
207 NS_IMETHODIMP
AddObserver(nsIObserver * aObserver,const char * aTopic,bool aOwnsWeak)208 nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic,
209 bool aOwnsWeak) {
210 LOG(("nsObserverService::AddObserver(%p: %s, %s)", (void*)aObserver, aTopic,
211 aOwnsWeak ? "weak" : "strong"));
212
213 MOZ_TRY(EnsureValidCall());
214 if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
215 return NS_ERROR_INVALID_ARG;
216 }
217
218 MOZ_TRY(FilterHttpOnTopics(aTopic));
219
220 nsObserverList* observerList = mObserverTopicTable.PutEntry(aTopic);
221 if (!observerList) {
222 return NS_ERROR_OUT_OF_MEMORY;
223 }
224
225 return observerList->AddObserver(aObserver, aOwnsWeak);
226 }
227
228 NS_IMETHODIMP
RemoveObserver(nsIObserver * aObserver,const char * aTopic)229 nsObserverService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) {
230 LOG(("nsObserverService::RemoveObserver(%p: %s)", (void*)aObserver, aTopic));
231
232 if (mShuttingDown) {
233 // The service is shutting down. Let's ignore this call.
234 return NS_OK;
235 }
236
237 MOZ_TRY(EnsureValidCall());
238 if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) {
239 return NS_ERROR_INVALID_ARG;
240 }
241
242 nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
243 if (!observerList) {
244 return NS_ERROR_FAILURE;
245 }
246
247 return observerList->RemoveObserver(aObserver);
248 }
249
250 NS_IMETHODIMP
EnumerateObservers(const char * aTopic,nsISimpleEnumerator ** anEnumerator)251 nsObserverService::EnumerateObservers(const char* aTopic,
252 nsISimpleEnumerator** anEnumerator) {
253 LOG(("nsObserverService::EnumerateObservers(%s)", aTopic));
254
255 MOZ_TRY(EnsureValidCall());
256 if (NS_WARN_IF(!anEnumerator) || NS_WARN_IF(!aTopic)) {
257 return NS_ERROR_INVALID_ARG;
258 }
259
260 nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
261 if (!observerList) {
262 return NS_NewEmptyEnumerator(anEnumerator);
263 }
264
265 observerList->GetObserverList(anEnumerator);
266 return NS_OK;
267 }
268
269 // Enumerate observers of aTopic and call Observe on each.
NotifyObservers(nsISupports * aSubject,const char * aTopic,const char16_t * aSomeData)270 NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports* aSubject,
271 const char* aTopic,
272 const char16_t* aSomeData) {
273 LOG(("nsObserverService::NotifyObservers(%s)", aTopic));
274
275 MOZ_TRY(EnsureValidCall());
276 if (NS_WARN_IF(!aTopic)) {
277 return NS_ERROR_INVALID_ARG;
278 }
279
280 MOZ_ASSERT(AppShutdown::IsNoOrLegalShutdownTopic(aTopic));
281
282 mozilla::TimeStamp start = TimeStamp::Now();
283
284 AUTO_PROFILER_MARKER_TEXT("NotifyObservers", OTHER, MarkerStack::Capture(),
285 nsDependentCString(aTopic));
286 AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
287 "nsObserverService::NotifyObservers", OTHER, aTopic);
288
289 nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic);
290 if (observerList) {
291 observerList->NotifyObservers(aSubject, aTopic, aSomeData);
292 }
293
294 uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
295 if (latencyMs >= kMinTelemetryNotifyObserversLatencyMs) {
296 Telemetry::Accumulate(Telemetry::NOTIFY_OBSERVERS_LATENCY_MS,
297 nsDependentCString(aTopic), latencyMs);
298 }
299
300 return NS_OK;
301 }
302
303 NS_IMETHODIMP
UnmarkGrayStrongObservers()304 nsObserverService::UnmarkGrayStrongObservers() {
305 MOZ_TRY(EnsureValidCall());
306
307 nsCOMArray<nsIObserver> strongObservers;
308 for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) {
309 nsObserverList* aObserverList = iter.Get();
310 if (aObserverList) {
311 aObserverList->AppendStrongObservers(strongObservers);
312 }
313 }
314
315 for (uint32_t i = 0; i < strongObservers.Length(); ++i) {
316 xpc_TryUnmarkWrappedGrayObject(strongObservers[i]);
317 }
318
319 return NS_OK;
320 }
321
322 namespace {
323
324 class NotifyWhenScriptSafeRunnable : public mozilla::Runnable {
325 public:
NotifyWhenScriptSafeRunnable(nsIObserverService * aObs,nsISupports * aSubject,const char * aTopic,const char16_t * aData)326 NotifyWhenScriptSafeRunnable(nsIObserverService* aObs, nsISupports* aSubject,
327 const char* aTopic, const char16_t* aData)
328 : mozilla::Runnable("NotifyWhenScriptSafeRunnable"),
329 mObs(aObs),
330 mSubject(aSubject),
331 mTopic(aTopic) {
332 if (aData) {
333 mData.Assign(aData);
334 } else {
335 mData.SetIsVoid(true);
336 }
337 }
338
Run()339 NS_IMETHOD Run() {
340 const char16_t* data = mData.IsVoid() ? nullptr : mData.get();
341 return mObs->NotifyObservers(mSubject, mTopic.get(), data);
342 }
343
344 private:
345 nsCOMPtr<nsIObserverService> mObs;
346 nsCOMPtr<nsISupports> mSubject;
347 nsCString mTopic;
348 nsString mData;
349 };
350
351 } // namespace
352
NotifyWhenScriptSafe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)353 nsresult nsIObserverService::NotifyWhenScriptSafe(nsISupports* aSubject,
354 const char* aTopic,
355 const char16_t* aData) {
356 if (nsContentUtils::IsSafeToRunScript()) {
357 return NotifyObservers(aSubject, aTopic, aData);
358 }
359
360 nsContentUtils::AddScriptRunner(MakeAndAddRef<NotifyWhenScriptSafeRunnable>(
361 this, aSubject, aTopic, aData));
362 return NS_OK;
363 }
364