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