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 file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "ProcessPriorityManager.h"
8 #include "mozilla/ClearOnShutdown.h"
9 #include "mozilla/dom/ContentParent.h"
10 #include "mozilla/dom/Element.h"
11 #include "mozilla/dom/BrowserHost.h"
12 #include "mozilla/dom/BrowserParent.h"
13 #include "mozilla/Hal.h"
14 #include "mozilla/IntegerPrintfMacros.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/Services.h"
17 #include "mozilla/StaticPrefs_dom.h"
18 #include "mozilla/Telemetry.h"
19 #include "mozilla/Unused.h"
20 #include "mozilla/Logging.h"
21 #include "nsPrintfCString.h"
22 #include "nsXULAppAPI.h"
23 #include "nsFrameLoader.h"
24 #include "nsINamed.h"
25 #include "nsIObserverService.h"
26 #include "StaticPtr.h"
27 #include "nsIObserver.h"
28 #include "nsITimer.h"
29 #include "nsIPropertyBag2.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsCRT.h"
32 #include "nsTHashtable.h"
33 #include "nsQueryObject.h"
34 
35 using namespace mozilla;
36 using namespace mozilla::dom;
37 using namespace mozilla::hal;
38 
39 #ifdef XP_WIN
40 #  include <process.h>
41 #  define getpid _getpid
42 #else
43 #  include <unistd.h>
44 #endif
45 
46 #ifdef LOG
47 #  undef LOG
48 #endif
49 
50 // Use LOGP inside a ParticularProcessPriorityManager method; use LOG
51 // everywhere else.  LOGP prints out information about the particular process
52 // priority manager.
53 //
54 // (Wow, our logging story is a huge mess.)
55 
56 // #define ENABLE_LOGGING 1
57 
58 #if defined(ANDROID) && defined(ENABLE_LOGGING)
59 #  include <android/log.h>
60 #  define LOG(fmt, ...)                                                        \
61     __android_log_print(ANDROID_LOG_INFO, "Gecko:ProcessPriorityManager", fmt, \
62                         ##__VA_ARGS__)
63 #  define LOGP(fmt, ...)                                                \
64     __android_log_print(                                                \
65         ANDROID_LOG_INFO, "Gecko:ProcessPriorityManager",               \
66         "[%schild-id=%" PRIu64 ", pid=%d] " fmt, NameWithComma().get(), \
67         static_cast<uint64_t>(ChildID()), Pid(), ##__VA_ARGS__)
68 
69 #elif defined(ENABLE_LOGGING)
70 #  define LOG(fmt, ...) \
71     printf("ProcessPriorityManager - " fmt "\n", ##__VA_ARGS__)
72 #  define LOGP(fmt, ...)                                                   \
73     printf("ProcessPriorityManager[%schild-id=%" PRIu64 ", pid=%d] - " fmt \
74            "\n",                                                           \
75            NameWithComma().get(), static_cast<uint64_t>(ChildID()), Pid(), \
76            ##__VA_ARGS__)
77 #else
GetPPMLog()78 static LogModule* GetPPMLog() {
79   static LazyLogModule sLog("ProcessPriorityManager");
80   return sLog;
81 }
82 #  define LOG(fmt, ...)                   \
83     MOZ_LOG(GetPPMLog(), LogLevel::Debug, \
84             ("ProcessPriorityManager - " fmt, ##__VA_ARGS__))
85 #  define LOGP(fmt, ...)                                                      \
86     MOZ_LOG(GetPPMLog(), LogLevel::Debug,                                     \
87             ("ProcessPriorityManager[%schild-id=%" PRIu64 ", pid=%d] - " fmt, \
88              NameWithComma().get(), static_cast<uint64_t>(ChildID()), Pid(),  \
89              ##__VA_ARGS__))
90 #endif
91 
92 namespace {
93 
94 class ParticularProcessPriorityManager;
95 
96 /**
97  * This singleton class does the work to implement the process priority manager
98  * in the main process.  This class may not be used in child processes.  (You
99  * can call StaticInit, but it won't do anything, and GetSingleton() will
100  * return null.)
101  *
102  * ProcessPriorityManager::CurrentProcessIsForeground() and
103  * ProcessPriorityManager::AnyProcessHasHighPriority() which can be called in
104  * any process, are handled separately, by the ProcessPriorityManagerChild
105  * class.
106  */
107 class ProcessPriorityManagerImpl final : public nsIObserver,
108                                          public nsSupportsWeakReference {
109  public:
110   /**
111    * If we're in the main process, get the ProcessPriorityManagerImpl
112    * singleton.  If we're in a child process, return null.
113    */
114   static ProcessPriorityManagerImpl* GetSingleton();
115 
116   static void StaticInit();
117   static bool PrefsEnabled();
118   static bool TestMode();
119 
120   NS_DECL_ISUPPORTS
121   NS_DECL_NSIOBSERVER
122 
123   /**
124    * This function implements ProcessPriorityManager::SetProcessPriority.
125    */
126   void SetProcessPriority(ContentParent* aContentParent,
127                           ProcessPriority aPriority);
128 
129   /**
130    * If a magic testing-only pref is set, notify the observer service on the
131    * given topic with the given data.  This is used for testing
132    */
133   void FireTestOnlyObserverNotification(
134       const char* aTopic, const nsACString& aData = EmptyCString());
135 
136   /**
137    * This must be called by a ParticularProcessPriorityManager when it changes
138    * its priority.
139    */
140   void NotifyProcessPriorityChanged(
141       ParticularProcessPriorityManager* aParticularManager,
142       hal::ProcessPriority aOldPriority);
143 
144   void TabActivityChanged(BrowserParent* aBrowserParent, bool aIsActive);
145 
146  private:
147   static bool sPrefListenersRegistered;
148   static bool sInitialized;
149   static StaticRefPtr<ProcessPriorityManagerImpl> sSingleton;
150 
151   static void PrefChangedCallback(const char* aPref, void* aClosure);
152 
153   ProcessPriorityManagerImpl();
154   ~ProcessPriorityManagerImpl();
155   DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManagerImpl);
156 
157   void Init();
158 
159   already_AddRefed<ParticularProcessPriorityManager>
160   GetParticularProcessPriorityManager(ContentParent* aContentParent);
161 
162   void ObserveContentParentCreated(nsISupports* aContentParent);
163   void ObserveContentParentDestroyed(nsISupports* aSubject);
164 
165   nsDataHashtable<nsUint64HashKey, RefPtr<ParticularProcessPriorityManager> >
166       mParticularManagers;
167 
168   /** Contains the PIDs of child processes holding high-priority wakelocks */
169   nsTHashtable<nsUint64HashKey> mHighPriorityChildIDs;
170 };
171 
172 /**
173  * This singleton class implements the parts of the process priority manager
174  * that are available from all processes.
175  */
176 class ProcessPriorityManagerChild final : public nsIObserver {
177  public:
178   static void StaticInit();
179   static ProcessPriorityManagerChild* Singleton();
180 
181   NS_DECL_ISUPPORTS
182   NS_DECL_NSIOBSERVER
183 
184   bool CurrentProcessIsForeground();
185 
186  private:
187   static StaticRefPtr<ProcessPriorityManagerChild> sSingleton;
188 
189   ProcessPriorityManagerChild();
190   ~ProcessPriorityManagerChild() = default;
191   DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManagerChild);
192 
193   void Init();
194 
195   hal::ProcessPriority mCachedPriority;
196 };
197 
198 /**
199  * This class manages the priority of one particular process.  It is
200  * main-process only.
201  */
202 class ParticularProcessPriorityManager final : public WakeLockObserver,
203                                                public nsIObserver,
204                                                public nsITimerCallback,
205                                                public nsINamed,
206                                                public nsSupportsWeakReference {
207   ~ParticularProcessPriorityManager();
208 
209  public:
210   explicit ParticularProcessPriorityManager(ContentParent* aContentParent);
211 
212   NS_DECL_ISUPPORTS
213   NS_DECL_NSIOBSERVER
214   NS_DECL_NSITIMERCALLBACK
215 
216   virtual void Notify(const WakeLockInformation& aInfo) override;
217   void Init();
218 
219   int32_t Pid() const;
220   uint64_t ChildID() const;
221 
222   /**
223    * Used in logging, this method returns the ContentParent's name followed by
224    * ", ".  If we can't get the ContentParent's name for some reason, it
225    * returns an empty string.
226    *
227    * The reference returned here is guaranteed to be live until the next call
228    * to NameWithComma() or until the ParticularProcessPriorityManager is
229    * destroyed, whichever comes first.
230    */
231   const nsAutoCString& NameWithComma();
232 
233   void OnRemoteBrowserFrameShown(nsISupports* aSubject);
234   void OnBrowserParentDestroyed(nsISupports* aSubject);
235 
236   ProcessPriority CurrentPriority();
237   ProcessPriority ComputePriority();
238 
239   enum TimeoutPref {
240     BACKGROUND_PERCEIVABLE_GRACE_PERIOD,
241     BACKGROUND_GRACE_PERIOD,
242   };
243 
244   void ScheduleResetPriority(TimeoutPref aTimeoutPref);
245   void ResetPriority();
246   void ResetPriorityNow();
247   void SetPriorityNow(ProcessPriority aPriority);
248 
249   void TabActivityChanged(BrowserParent* aBrowserParent, bool aIsActive);
250 
251   void ShutDown();
252 
GetName(nsACString & aName)253   NS_IMETHOD GetName(nsACString& aName) override {
254     aName.AssignLiteral("ParticularProcessPriorityManager");
255     return NS_OK;
256   }
257 
258  private:
259   void FireTestOnlyObserverNotification(
260       const char* aTopic, const nsACString& aData = EmptyCString());
261 
262   void FireTestOnlyObserverNotification(const char* aTopic,
263                                         const char* aData = nullptr);
264 
265   bool IsHoldingWakeLock(const nsAString& aTopic);
266 
267   ContentParent* mContentParent;
268   uint64_t mChildID;
269   ProcessPriority mPriority;
270   bool mHoldsCPUWakeLock;
271   bool mHoldsHighPriorityWakeLock;
272   bool mHoldsPlayingAudioWakeLock;
273   bool mHoldsPlayingVideoWakeLock;
274 
275   /**
276    * Used to implement NameWithComma().
277    */
278   nsAutoCString mNameWithComma;
279 
280   nsCOMPtr<nsITimer> mResetPriorityTimer;
281 
282   // This hashtable contains the list of active TabId for this process.
283   nsTHashtable<nsUint64HashKey> mActiveBrowserParents;
284 };
285 
286 /* static */
287 bool ProcessPriorityManagerImpl::sInitialized = false;
288 /* static */
289 bool ProcessPriorityManagerImpl::sPrefListenersRegistered = false;
290 /* static */
291 StaticRefPtr<ProcessPriorityManagerImpl> ProcessPriorityManagerImpl::sSingleton;
292 
293 NS_IMPL_ISUPPORTS(ProcessPriorityManagerImpl, nsIObserver,
294                   nsISupportsWeakReference);
295 
296 /* static */
PrefChangedCallback(const char * aPref,void * aClosure)297 void ProcessPriorityManagerImpl::PrefChangedCallback(const char* aPref,
298                                                      void* aClosure) {
299   StaticInit();
300   if (!PrefsEnabled() && sSingleton) {
301     sSingleton = nullptr;
302     sInitialized = false;
303   }
304 }
305 
306 /* static */
PrefsEnabled()307 bool ProcessPriorityManagerImpl::PrefsEnabled() {
308   return StaticPrefs::dom_ipc_processPriorityManager_enabled() &&
309          hal::SetProcessPrioritySupported() &&
310          !StaticPrefs::dom_ipc_tabs_disabled();
311 }
312 
313 /* static */
TestMode()314 bool ProcessPriorityManagerImpl::TestMode() {
315   return StaticPrefs::dom_ipc_processPriorityManager_testMode();
316 }
317 
318 /* static */
StaticInit()319 void ProcessPriorityManagerImpl::StaticInit() {
320   if (sInitialized) {
321     return;
322   }
323 
324   // The process priority manager is main-process only.
325   if (!XRE_IsParentProcess()) {
326     sInitialized = true;
327     return;
328   }
329 
330   // If IPC tabs aren't enabled at startup, don't bother with any of this.
331   if (!PrefsEnabled()) {
332     LOG("InitProcessPriorityManager bailing due to prefs.");
333 
334     // Run StaticInit() again if the prefs change.  We don't expect this to
335     // happen in normal operation, but it happens during testing.
336     if (!sPrefListenersRegistered) {
337       sPrefListenersRegistered = true;
338       Preferences::RegisterCallback(PrefChangedCallback,
339                                     "dom.ipc.processPriorityManager.enabled");
340       Preferences::RegisterCallback(PrefChangedCallback,
341                                     "dom.ipc.tabs.disabled");
342     }
343     return;
344   }
345 
346   sInitialized = true;
347 
348   sSingleton = new ProcessPriorityManagerImpl();
349   sSingleton->Init();
350   ClearOnShutdown(&sSingleton);
351 }
352 
353 /* static */
GetSingleton()354 ProcessPriorityManagerImpl* ProcessPriorityManagerImpl::GetSingleton() {
355   if (!sSingleton) {
356     StaticInit();
357   }
358 
359   return sSingleton;
360 }
361 
ProcessPriorityManagerImpl()362 ProcessPriorityManagerImpl::ProcessPriorityManagerImpl() {
363   MOZ_ASSERT(XRE_IsParentProcess());
364 }
365 
366 ProcessPriorityManagerImpl::~ProcessPriorityManagerImpl() = default;
367 
Init()368 void ProcessPriorityManagerImpl::Init() {
369   LOG("Starting up.  This is the master process.");
370 
371   // The master process's priority never changes; set it here and then forget
372   // about it.  We'll manage only subprocesses' priorities using the process
373   // priority manager.
374   hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER);
375 
376   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
377   if (os) {
378     os->AddObserver(this, "ipc:content-created", /* ownsWeak */ true);
379     os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ true);
380   }
381 }
382 
383 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)384 ProcessPriorityManagerImpl::Observe(nsISupports* aSubject, const char* aTopic,
385                                     const char16_t* aData) {
386   nsDependentCString topic(aTopic);
387   if (topic.EqualsLiteral("ipc:content-created")) {
388     ObserveContentParentCreated(aSubject);
389   } else if (topic.EqualsLiteral("ipc:content-shutdown")) {
390     ObserveContentParentDestroyed(aSubject);
391   } else {
392     MOZ_ASSERT(false);
393   }
394 
395   return NS_OK;
396 }
397 
398 already_AddRefed<ParticularProcessPriorityManager>
GetParticularProcessPriorityManager(ContentParent * aContentParent)399 ProcessPriorityManagerImpl::GetParticularProcessPriorityManager(
400     ContentParent* aContentParent) {
401   uint64_t cpId = aContentParent->ChildID();
402   auto entry = mParticularManagers.LookupForAdd(cpId);
403   RefPtr<ParticularProcessPriorityManager> pppm =
404       entry.OrInsert([aContentParent]() {
405         return new ParticularProcessPriorityManager(aContentParent);
406       });
407 
408   if (!entry) {
409     // We created a new entry.
410     pppm->Init();
411     FireTestOnlyObserverNotification("process-created",
412                                      nsPrintfCString("%" PRIu64, cpId));
413   }
414 
415   return pppm.forget();
416 }
417 
SetProcessPriority(ContentParent * aContentParent,ProcessPriority aPriority)418 void ProcessPriorityManagerImpl::SetProcessPriority(
419     ContentParent* aContentParent, ProcessPriority aPriority) {
420   MOZ_ASSERT(aContentParent);
421   RefPtr<ParticularProcessPriorityManager> pppm =
422       GetParticularProcessPriorityManager(aContentParent);
423   if (pppm) {
424     pppm->SetPriorityNow(aPriority);
425   }
426 }
427 
ObserveContentParentCreated(nsISupports * aContentParent)428 void ProcessPriorityManagerImpl::ObserveContentParentCreated(
429     nsISupports* aContentParent) {
430   // Do nothing; it's sufficient to get the PPPM.  But assign to nsRefPtr so we
431   // don't leak the already_AddRefed object.
432   RefPtr<ContentParent> cp = do_QueryObject(aContentParent);
433   RefPtr<ParticularProcessPriorityManager> pppm =
434       GetParticularProcessPriorityManager(cp);
435 }
436 
ObserveContentParentDestroyed(nsISupports * aSubject)437 void ProcessPriorityManagerImpl::ObserveContentParentDestroyed(
438     nsISupports* aSubject) {
439   nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
440   NS_ENSURE_TRUE_VOID(props);
441 
442   uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN;
443   props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
444   NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN);
445 
446   if (auto entry = mParticularManagers.Lookup(childID)) {
447     entry.Data()->ShutDown();
448     mHighPriorityChildIDs.RemoveEntry(childID);
449     entry.Remove();
450   }
451 }
452 
NotifyProcessPriorityChanged(ParticularProcessPriorityManager * aParticularManager,ProcessPriority aOldPriority)453 void ProcessPriorityManagerImpl::NotifyProcessPriorityChanged(
454     ParticularProcessPriorityManager* aParticularManager,
455     ProcessPriority aOldPriority) {
456   ProcessPriority newPriority = aParticularManager->CurrentPriority();
457 
458   if (newPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH &&
459       aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH) {
460     mHighPriorityChildIDs.PutEntry(aParticularManager->ChildID());
461   } else if (newPriority < PROCESS_PRIORITY_FOREGROUND_HIGH &&
462              aOldPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
463     mHighPriorityChildIDs.RemoveEntry(aParticularManager->ChildID());
464   }
465 }
466 
TabActivityChanged(BrowserParent * aBrowserParent,bool aIsActive)467 void ProcessPriorityManagerImpl::TabActivityChanged(
468     BrowserParent* aBrowserParent, bool aIsActive) {
469   RefPtr<ParticularProcessPriorityManager> pppm =
470       GetParticularProcessPriorityManager(aBrowserParent->Manager());
471   if (!pppm) {
472     return;
473   }
474 
475   Telemetry::ScalarAdd(
476       Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_CHANGE_CONSIDERED, 1);
477 
478   pppm->TabActivityChanged(aBrowserParent, aIsActive);
479 }
480 
481 NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, nsIObserver,
482                   nsITimerCallback, nsISupportsWeakReference, nsINamed);
483 
ParticularProcessPriorityManager(ContentParent * aContentParent)484 ParticularProcessPriorityManager::ParticularProcessPriorityManager(
485     ContentParent* aContentParent)
486     : mContentParent(aContentParent),
487       mChildID(aContentParent->ChildID()),
488       mPriority(PROCESS_PRIORITY_UNKNOWN),
489       mHoldsCPUWakeLock(false),
490       mHoldsHighPriorityWakeLock(false),
491       mHoldsPlayingAudioWakeLock(false),
492       mHoldsPlayingVideoWakeLock(false) {
493   MOZ_ASSERT(XRE_IsParentProcess());
494   LOGP("Creating ParticularProcessPriorityManager.");
495 }
496 
Init()497 void ParticularProcessPriorityManager::Init() {
498   RegisterWakeLockObserver(this);
499 
500   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
501   if (os) {
502     os->AddObserver(this, "remote-browser-shown", /* ownsWeak */ true);
503     os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true);
504   }
505 
506   // This process may already hold the CPU lock; for example, our parent may
507   // have acquired it on our behalf.
508   mHoldsCPUWakeLock = IsHoldingWakeLock(NS_LITERAL_STRING("cpu"));
509   mHoldsHighPriorityWakeLock =
510       IsHoldingWakeLock(NS_LITERAL_STRING("high-priority"));
511   mHoldsPlayingAudioWakeLock =
512       IsHoldingWakeLock(NS_LITERAL_STRING("audio-playing"));
513   mHoldsPlayingVideoWakeLock =
514       IsHoldingWakeLock(NS_LITERAL_STRING("video-playing"));
515 
516   LOGP(
517       "Done starting up.  mHoldsCPUWakeLock=%d, "
518       "mHoldsHighPriorityWakeLock=%d, mHoldsPlayingAudioWakeLock=%d, "
519       "mHoldsPlayingVideoWakeLock=%d",
520       mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock, mHoldsPlayingAudioWakeLock,
521       mHoldsPlayingVideoWakeLock);
522 }
523 
IsHoldingWakeLock(const nsAString & aTopic)524 bool ParticularProcessPriorityManager::IsHoldingWakeLock(
525     const nsAString& aTopic) {
526   WakeLockInformation info;
527   GetWakeLockInfo(aTopic, &info);
528   return info.lockingProcesses().Contains(ChildID());
529 }
530 
~ParticularProcessPriorityManager()531 ParticularProcessPriorityManager::~ParticularProcessPriorityManager() {
532   LOGP("Destroying ParticularProcessPriorityManager.");
533 
534   // Unregister our wake lock observer if ShutDown hasn't been called.  (The
535   // wake lock observer takes raw refs, so we don't want to take chances here!)
536   // We don't call UnregisterWakeLockObserver unconditionally because the code
537   // will print a warning if it's called unnecessarily.
538 
539   if (mContentParent) {
540     UnregisterWakeLockObserver(this);
541   }
542 }
543 
544 /* virtual */
Notify(const WakeLockInformation & aInfo)545 void ParticularProcessPriorityManager::Notify(
546     const WakeLockInformation& aInfo) {
547   if (!mContentParent) {
548     // We've been shut down.
549     return;
550   }
551 
552   bool* dest = nullptr;
553   if (aInfo.topic().EqualsLiteral("cpu")) {
554     dest = &mHoldsCPUWakeLock;
555   } else if (aInfo.topic().EqualsLiteral("high-priority")) {
556     dest = &mHoldsHighPriorityWakeLock;
557   } else if (aInfo.topic().EqualsLiteral("audio-playing")) {
558     dest = &mHoldsPlayingAudioWakeLock;
559   } else if (aInfo.topic().EqualsLiteral("video-playing")) {
560     dest = &mHoldsPlayingVideoWakeLock;
561   }
562 
563   if (dest) {
564     bool thisProcessLocks = aInfo.lockingProcesses().Contains(ChildID());
565     if (thisProcessLocks != *dest) {
566       *dest = thisProcessLocks;
567       LOGP(
568           "Got wake lock changed event. "
569           "Now mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d, "
570           "mHoldsPlayingAudioWakeLock=%d, mHoldsPlayingVideoWakeLock=%d",
571           mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock,
572           mHoldsPlayingAudioWakeLock, mHoldsPlayingVideoWakeLock);
573       ResetPriority();
574     }
575   }
576 }
577 
578 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)579 ParticularProcessPriorityManager::Observe(nsISupports* aSubject,
580                                           const char* aTopic,
581                                           const char16_t* aData) {
582   if (!mContentParent) {
583     // We've been shut down.
584     return NS_OK;
585   }
586 
587   nsDependentCString topic(aTopic);
588 
589   if (topic.EqualsLiteral("remote-browser-shown")) {
590     OnRemoteBrowserFrameShown(aSubject);
591   } else if (topic.EqualsLiteral("ipc:browser-destroyed")) {
592     OnBrowserParentDestroyed(aSubject);
593   } else {
594     MOZ_ASSERT(false);
595   }
596 
597   return NS_OK;
598 }
599 
ChildID() const600 uint64_t ParticularProcessPriorityManager::ChildID() const {
601   // We have to cache mContentParent->ChildID() instead of getting it from the
602   // ContentParent each time because after ShutDown() is called, mContentParent
603   // is null.  If we didn't cache ChildID(), then we wouldn't be able to run
604   // LOGP() after ShutDown().
605   return mChildID;
606 }
607 
Pid() const608 int32_t ParticularProcessPriorityManager::Pid() const {
609   return mContentParent ? mContentParent->Pid() : -1;
610 }
611 
NameWithComma()612 const nsAutoCString& ParticularProcessPriorityManager::NameWithComma() {
613   mNameWithComma.Truncate();
614   if (!mContentParent) {
615     return mNameWithComma;  // empty string
616   }
617 
618   nsAutoString name;
619   mContentParent->FriendlyName(name);
620   if (name.IsEmpty()) {
621     return mNameWithComma;  // empty string
622   }
623 
624   mNameWithComma = NS_ConvertUTF16toUTF8(name);
625   mNameWithComma.AppendLiteral(", ");
626   return mNameWithComma;
627 }
628 
OnRemoteBrowserFrameShown(nsISupports * aSubject)629 void ParticularProcessPriorityManager::OnRemoteBrowserFrameShown(
630     nsISupports* aSubject) {
631   RefPtr<nsFrameLoader> fl = do_QueryObject(aSubject);
632   NS_ENSURE_TRUE_VOID(fl);
633 
634   BrowserParent* tp = BrowserParent::GetFrom(fl);
635   NS_ENSURE_TRUE_VOID(tp);
636 
637   MOZ_ASSERT(XRE_IsParentProcess());
638   if (tp->Manager() != mContentParent) {
639     return;
640   }
641 
642   // Ignore notifications that aren't from a Browser
643   if (fl->OwnerIsMozBrowserFrame()) {
644     ResetPriority();
645   }
646 
647   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
648   if (os) {
649     os->RemoveObserver(this, "remote-browser-shown");
650   }
651 }
652 
OnBrowserParentDestroyed(nsISupports * aSubject)653 void ParticularProcessPriorityManager::OnBrowserParentDestroyed(
654     nsISupports* aSubject) {
655   nsCOMPtr<nsIRemoteTab> remoteTab = do_QueryInterface(aSubject);
656   NS_ENSURE_TRUE_VOID(remoteTab);
657   BrowserHost* browserHost = BrowserHost::GetFrom(remoteTab.get());
658 
659   MOZ_ASSERT(XRE_IsParentProcess());
660   if (browserHost->GetContentParent() &&
661       browserHost->GetContentParent() != mContentParent) {
662     return;
663   }
664 
665   mActiveBrowserParents.RemoveEntry(browserHost->GetTabId());
666 
667   ResetPriority();
668 }
669 
ResetPriority()670 void ParticularProcessPriorityManager::ResetPriority() {
671   ProcessPriority processPriority = ComputePriority();
672   if (mPriority == PROCESS_PRIORITY_UNKNOWN || mPriority > processPriority) {
673     // Apps set at a perceivable background priority are often playing media.
674     // Most media will have short gaps while changing tracks between songs,
675     // switching videos, etc.  Give these apps a longer grace period so they
676     // can get their next track started, if there is one, before getting
677     // downgraded.
678     if (mPriority == PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) {
679       ScheduleResetPriority(BACKGROUND_PERCEIVABLE_GRACE_PERIOD);
680     } else {
681       ScheduleResetPriority(BACKGROUND_GRACE_PERIOD);
682     }
683     return;
684   }
685 
686   SetPriorityNow(processPriority);
687 }
688 
ResetPriorityNow()689 void ParticularProcessPriorityManager::ResetPriorityNow() {
690   SetPriorityNow(ComputePriority());
691 }
692 
ScheduleResetPriority(TimeoutPref aTimeoutPref)693 void ParticularProcessPriorityManager::ScheduleResetPriority(
694     TimeoutPref aTimeoutPref) {
695   if (mResetPriorityTimer) {
696     LOGP("ScheduleResetPriority bailing; the timer is already running.");
697     return;
698   }
699 
700   uint32_t timeout = 0;
701   switch (aTimeoutPref) {
702     case BACKGROUND_PERCEIVABLE_GRACE_PERIOD:
703       timeout = StaticPrefs::
704           dom_ipc_processPriorityManager_backgroundPerceivableGracePeriodMS();
705       break;
706     case BACKGROUND_GRACE_PERIOD:
707       timeout =
708           StaticPrefs::dom_ipc_processPriorityManager_backgroundGracePeriodMS();
709       break;
710     default:
711       MOZ_ASSERT(false, "Unrecognized timeout pref");
712       break;
713   }
714 
715   LOGP("Scheduling reset timer to fire in %dms.", timeout);
716   NS_NewTimerWithCallback(getter_AddRefs(mResetPriorityTimer), this, timeout,
717                           nsITimer::TYPE_ONE_SHOT);
718 }
719 
720 NS_IMETHODIMP
Notify(nsITimer * aTimer)721 ParticularProcessPriorityManager::Notify(nsITimer* aTimer) {
722   LOGP("Reset priority timer callback; about to ResetPriorityNow.");
723   ResetPriorityNow();
724   mResetPriorityTimer = nullptr;
725   return NS_OK;
726 }
727 
CurrentPriority()728 ProcessPriority ParticularProcessPriorityManager::CurrentPriority() {
729   return mPriority;
730 }
731 
ComputePriority()732 ProcessPriority ParticularProcessPriorityManager::ComputePriority() {
733   if (!mActiveBrowserParents.IsEmpty() ||
734       mContentParent->GetRemoteType().EqualsLiteral(EXTENSION_REMOTE_TYPE) ||
735       mHoldsPlayingAudioWakeLock) {
736     return PROCESS_PRIORITY_FOREGROUND;
737   }
738 
739   if (mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock ||
740       mHoldsPlayingVideoWakeLock) {
741     return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE;
742   }
743 
744   return PROCESS_PRIORITY_BACKGROUND;
745 }
746 
SetPriorityNow(ProcessPriority aPriority)747 void ParticularProcessPriorityManager::SetPriorityNow(
748     ProcessPriority aPriority) {
749   if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
750     MOZ_ASSERT(false);
751     return;
752   }
753 
754   if (!ProcessPriorityManagerImpl::PrefsEnabled() || !mContentParent ||
755       mPriority == aPriority) {
756     return;
757   }
758 
759   if (mPriority == aPriority) {
760     hal::SetProcessPriority(Pid(), mPriority);
761     return;
762   }
763 
764   LOGP("Changing priority from %s to %s.", ProcessPriorityToString(mPriority),
765        ProcessPriorityToString(aPriority));
766 
767   ProcessPriority oldPriority = mPriority;
768 
769   mPriority = aPriority;
770 
771   // We skip incrementing the DOM_CONTENTPROCESS_OS_PRIORITY_RAISED if we're
772   // transitioning from the PROCESS_PRIORITY_UNKNOWN level, which is where
773   // we initialize at.
774   if (oldPriority < mPriority && oldPriority != PROCESS_PRIORITY_UNKNOWN) {
775     Telemetry::ScalarAdd(
776         Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_RAISED, 1);
777   } else if (oldPriority > mPriority) {
778     Telemetry::ScalarAdd(
779         Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_LOWERED, 1);
780   }
781 
782   hal::SetProcessPriority(Pid(), mPriority);
783 
784   if (oldPriority != mPriority) {
785     ProcessPriorityManagerImpl::GetSingleton()->NotifyProcessPriorityChanged(
786         this, oldPriority);
787 
788     Unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
789   }
790 
791   FireTestOnlyObserverNotification("process-priority-set",
792                                    ProcessPriorityToString(mPriority));
793 }
794 
TabActivityChanged(BrowserParent * aBrowserParent,bool aIsActive)795 void ParticularProcessPriorityManager::TabActivityChanged(
796     BrowserParent* aBrowserParent, bool aIsActive) {
797   MOZ_ASSERT(aBrowserParent);
798 
799   if (!aIsActive) {
800     mActiveBrowserParents.RemoveEntry(aBrowserParent->GetTabId());
801   } else {
802     mActiveBrowserParents.PutEntry(aBrowserParent->GetTabId());
803   }
804 
805   ResetPriority();
806 }
807 
ShutDown()808 void ParticularProcessPriorityManager::ShutDown() {
809   MOZ_ASSERT(mContentParent);
810 
811   UnregisterWakeLockObserver(this);
812 
813   if (mResetPriorityTimer) {
814     mResetPriorityTimer->Cancel();
815     mResetPriorityTimer = nullptr;
816   }
817 
818   mContentParent = nullptr;
819 }
820 
FireTestOnlyObserverNotification(const char * aTopic,const nsACString & aData)821 void ProcessPriorityManagerImpl::FireTestOnlyObserverNotification(
822     const char* aTopic, const nsACString& aData /* = EmptyCString() */) {
823   if (!TestMode()) {
824     return;
825   }
826 
827   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
828   NS_ENSURE_TRUE_VOID(os);
829 
830   nsPrintfCString topic("process-priority-manager:TEST-ONLY:%s", aTopic);
831 
832   LOG("Notifying observer %s, data %s", topic.get(),
833       PromiseFlatCString(aData).get());
834   os->NotifyObservers(nullptr, topic.get(), NS_ConvertUTF8toUTF16(aData).get());
835 }
836 
FireTestOnlyObserverNotification(const char * aTopic,const char * aData)837 void ParticularProcessPriorityManager::FireTestOnlyObserverNotification(
838     const char* aTopic, const char* aData /* = nullptr */) {
839   if (!ProcessPriorityManagerImpl::TestMode()) {
840     return;
841   }
842 
843   nsAutoCString data;
844   if (aData) {
845     data.AppendASCII(aData);
846   }
847 
848   FireTestOnlyObserverNotification(aTopic, data);
849 }
850 
FireTestOnlyObserverNotification(const char * aTopic,const nsACString & aData)851 void ParticularProcessPriorityManager::FireTestOnlyObserverNotification(
852     const char* aTopic, const nsACString& aData /* = EmptyCString() */) {
853   if (!ProcessPriorityManagerImpl::TestMode()) {
854     return;
855   }
856 
857   nsAutoCString data(nsPrintfCString("%" PRIu64, ChildID()));
858   if (!aData.IsEmpty()) {
859     data.Append(':');
860     data.Append(aData);
861   }
862 
863   // ProcessPriorityManagerImpl::GetSingleton() is guaranteed not to return
864   // null, since ProcessPriorityManagerImpl is the only class which creates
865   // ParticularProcessPriorityManagers.
866 
867   ProcessPriorityManagerImpl::GetSingleton()->FireTestOnlyObserverNotification(
868       aTopic, data);
869 }
870 
871 StaticRefPtr<ProcessPriorityManagerChild>
872     ProcessPriorityManagerChild::sSingleton;
873 
874 /* static */
StaticInit()875 void ProcessPriorityManagerChild::StaticInit() {
876   if (!sSingleton) {
877     sSingleton = new ProcessPriorityManagerChild();
878     sSingleton->Init();
879     ClearOnShutdown(&sSingleton);
880   }
881 }
882 
883 /* static */
Singleton()884 ProcessPriorityManagerChild* ProcessPriorityManagerChild::Singleton() {
885   StaticInit();
886   return sSingleton;
887 }
888 
NS_IMPL_ISUPPORTS(ProcessPriorityManagerChild,nsIObserver)889 NS_IMPL_ISUPPORTS(ProcessPriorityManagerChild, nsIObserver)
890 
891 ProcessPriorityManagerChild::ProcessPriorityManagerChild() {
892   if (XRE_IsParentProcess()) {
893     mCachedPriority = PROCESS_PRIORITY_MASTER;
894   } else {
895     mCachedPriority = PROCESS_PRIORITY_UNKNOWN;
896   }
897 }
898 
Init()899 void ProcessPriorityManagerChild::Init() {
900   // The process priority should only be changed in child processes; don't even
901   // bother listening for changes if we're in the main process.
902   if (!XRE_IsParentProcess()) {
903     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
904     NS_ENSURE_TRUE_VOID(os);
905     os->AddObserver(this, "ipc:process-priority-changed", /* weak = */ false);
906   }
907 }
908 
909 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)910 ProcessPriorityManagerChild::Observe(nsISupports* aSubject, const char* aTopic,
911                                      const char16_t* aData) {
912   MOZ_ASSERT(!strcmp(aTopic, "ipc:process-priority-changed"));
913 
914   nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
915   NS_ENSURE_TRUE(props, NS_OK);
916 
917   int32_t priority = static_cast<int32_t>(PROCESS_PRIORITY_UNKNOWN);
918   props->GetPropertyAsInt32(NS_LITERAL_STRING("priority"), &priority);
919   NS_ENSURE_TRUE(ProcessPriority(priority) != PROCESS_PRIORITY_UNKNOWN, NS_OK);
920 
921   mCachedPriority = static_cast<ProcessPriority>(priority);
922 
923   return NS_OK;
924 }
925 
CurrentProcessIsForeground()926 bool ProcessPriorityManagerChild::CurrentProcessIsForeground() {
927   return mCachedPriority == PROCESS_PRIORITY_UNKNOWN ||
928          mCachedPriority >= PROCESS_PRIORITY_FOREGROUND;
929 }
930 
931 }  // namespace
932 
933 namespace mozilla {
934 
935 /* static */
Init()936 void ProcessPriorityManager::Init() {
937   ProcessPriorityManagerImpl::StaticInit();
938   ProcessPriorityManagerChild::StaticInit();
939 }
940 
941 /* static */
SetProcessPriority(ContentParent * aContentParent,ProcessPriority aPriority)942 void ProcessPriorityManager::SetProcessPriority(ContentParent* aContentParent,
943                                                 ProcessPriority aPriority) {
944   MOZ_ASSERT(aContentParent);
945 
946   ProcessPriorityManagerImpl* singleton =
947       ProcessPriorityManagerImpl::GetSingleton();
948   if (singleton) {
949     singleton->SetProcessPriority(aContentParent, aPriority);
950   }
951 }
952 
953 /* static */
CurrentProcessIsForeground()954 bool ProcessPriorityManager::CurrentProcessIsForeground() {
955   return ProcessPriorityManagerChild::Singleton()->CurrentProcessIsForeground();
956 }
957 
958 /* static */
TabActivityChanged(BrowserParent * aBrowserParent,bool aIsActive)959 void ProcessPriorityManager::TabActivityChanged(BrowserParent* aBrowserParent,
960                                                 bool aIsActive) {
961   MOZ_ASSERT(aBrowserParent);
962 
963   ProcessPriorityManagerImpl* singleton =
964       ProcessPriorityManagerImpl::GetSingleton();
965   if (!singleton) {
966     return;
967   }
968 
969   singleton->TabActivityChanged(aBrowserParent, aIsActive);
970 }
971 
972 }  // namespace mozilla
973