Bug 834059 - Send processses into the foreground when they first launch, so they don't get killed quite so quickly. r=cjones, a=blocking-tef
authorJustin Lebar <justin.lebar@gmail.com>
Sun, 27 Jan 2013 00:41:06 -0500
changeset 118352 5c9f6113c3fa0f387839ebb1fb50e738503954c9
parent 118351 918a01c807797abc65e1df6f7a4329fdf79fe85d
child 118353 dc3e991c5ef0830c8186e01fd5a69984f53fb958
push id3
push userjlebar@mozilla.com
push dateSun, 27 Jan 2013 05:51:52 +0000
reviewerscjones, blocking-tef
bugs834059
milestone18.0
Bug 834059 - Send processses into the foreground when they first launch, so they don't get killed quite so quickly. r=cjones, a=blocking-tef This patch also sends the preallocated process into the background immediately after it's launched.
b2g/app/b2g.js
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/ProcessPriorityManager.cpp
dom/ipc/ProcessPriorityManager.h
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -546,17 +546,18 @@ pref("dom.workers.mem.gc_allocation_thre
 
 // Show/Hide scrollbars when active/inactive
 pref("ui.showHideScrollbars", 1);
 
 // Enable the ProcessPriorityManager, and give processes with no visible
 // documents a 1s grace period before they're eligible to be marked as
 // background.
 pref("dom.ipc.processPriorityManager.enabled", true);
-pref("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
+pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
+pref("dom.ipc.processPriorityManager.temporaryPriorityMS", 5000);
 
 // Kernel parameters for how processes are killed on low-memory.
 pref("gonk.systemMemoryPressureRecoveryPollMS", 5000);
 pref("hal.processPriorityManager.gonk.masterOomScoreAdjust", 0);
 pref("hal.processPriorityManager.gonk.masterKillUnderMB", 1);
 pref("hal.processPriorityManager.gonk.foregroundOomScoreAdjust", 67);
 pref("hal.processPriorityManager.gonk.foregroundKillUnderMB", 4);
 pref("hal.processPriorityManager.gonk.backgroundPerceivableOomScoreAdjust", 134);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -103,22 +103,24 @@
 #include "ProcessUtils.h"
 #include "StructuredCloneUtils.h"
 #include "URIUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsContentUtils.h"
 #include "nsIPrincipal.h"
 #include "nsDeviceStorage.h"
 #include "AudioChannelService.h"
+#include "ProcessPriorityManager.h"
 
 using namespace base;
 using namespace mozilla;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::devicestorage;
+using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::sms;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
@@ -290,23 +292,18 @@ ContentChild::Init(MessageLoop* aIOLoop,
                                        info->len,
                                        info->offset));
         info++;
     }
     crashreporter->SendAddLibraryMappings(mappings);
 #endif
 #endif
 
-    bool startBackground = true;
-    SendGetProcessAttributes(&mID, &startBackground,
-                             &mIsForApp, &mIsForBrowser);
-    hal::SetProcessPriority(
-        GetCurrentProcId(),
-        startBackground ? hal::PROCESS_PRIORITY_BACKGROUND:
-                          hal::PROCESS_PRIORITY_FOREGROUND);
+    SendGetProcessAttributes(&mID, &mIsForApp, &mIsForBrowser);
+
     if (mIsForApp && !mIsForBrowser) {
         SetProcessName(NS_LITERAL_STRING("(Preallocated app)"));
     } else {
         SetProcessName(NS_LITERAL_STRING("Browser"));
     }
 
     return true;
 }
@@ -514,22 +511,29 @@ static void FirstIdle(void)
     sFirstIdleTask = nullptr;
     ContentChild::GetSingleton()->SendFirstIdle();
 }
 
 PBrowserChild*
 ContentChild::AllocPBrowser(const IPCTabContext& aContext,
                             const uint32_t& aChromeFlags)
 {
-    static bool firstIdleTaskPosted = false;
-    if (!firstIdleTaskPosted) {
+    static bool hasRunOnce = false;
+    if (!hasRunOnce) {
+        hasRunOnce = true;
+
         MOZ_ASSERT(!sFirstIdleTask);
         sFirstIdleTask = NewRunnableFunction(FirstIdle);
         MessageLoop::current()->PostIdleTask(FROM_HERE, sFirstIdleTask);
-        firstIdleTaskPosted = true;
+
+        // If we are the preallocated process transforming into an app process,
+        // we'll have background priority at this point.  Give ourselves a
+        // priority boost for a few seconds, so we don't get killed while we're
+        // loading our first TabChild.
+        TemporarilySetProcessPriorityToForeground();
     }
 
     // We'll happily accept any kind of IPCTabContext here; we don't need to
     // check that it's of a certain type for security purposes, because we
     // believe whatever the parent process tells us.
 
     nsRefPtr<TabChild> child = TabChild::Create(TabContext(aContext), aChromeFlags);
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -25,16 +25,17 @@
 #include "mozIApplication.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/StorageParent.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
 #include "SmsParent.h"
+#include "mozilla/Hal.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
@@ -119,17 +120,17 @@ static NS_DEFINE_CID(kCClipboardCID, NS_
 static const char* sClipboardTextFlavors[] = { kUnicodeMime };
 
 using base::ChildPrivileges;
 using base::KillProcess;
 using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::sms;
 using namespace mozilla::dom::indexedDB;
-using namespace mozilla::hal_sandbox;
+using namespace mozilla::hal;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 
 namespace mozilla {
 namespace dom {
 
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
@@ -848,34 +849,44 @@ ContentParent::ContentParent(const nsASt
     // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
     // PID along with the warning.
     nsDebugImpl::SetMultiprocessMode("Parent");
 
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
     mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content,
                                             aOSPrivileges);
 
-    bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
-    if (useOffMainThreadCompositing) {
-        // We need the subprocess's ProcessHandle to create the
-        // PCompositor channel below.  Block just until we have that.
-        mSubprocess->LaunchAndWaitForProcessHandle();
-    } else {
-        mSubprocess->AsyncLaunch();
+    mSubprocess->LaunchAndWaitForProcessHandle();
+
+    // Set the subprocess's priority (bg if we're a preallocated process, fg
+    // otherwise).  We do this first because we're likely /lowering/ its CPU and
+    // memory priority, which it has inherited from this process.
+    if (Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
+        ProcessPriority priority;
+        if (aAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL) {
+            priority = PROCESS_PRIORITY_BACKGROUND;
+        } else {
+            priority = PROCESS_PRIORITY_FOREGROUND;
+        }
+
+        SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
+                           priority);
     }
+
     Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
 
     // NB: internally, this will send an IPC message to the child
     // process to get it to create the CompositorChild.  This
     // message goes through the regular IPC queue for this
     // channel, so delivery will happen-before any other messages
     // we send.  The CompositorChild must be created before any
     // PBrowsers are created, because they rely on the Compositor
     // already being around.  (Creation is async, so can't happen
     // on demand.)
+    bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
     if (useOffMainThreadCompositing) {
         DebugOnly<bool> opened = PCompositor::Open(this);
         MOZ_ASSERT(opened);
 
         if (Preferences::GetBool("layers.async-video.enabled",false)) {
             opened = PImageBridge::Open(this);
             MOZ_ASSERT(opened);
         }
@@ -1337,22 +1348,20 @@ ContentParent::AllocPCompositor(mozilla:
 PImageBridgeParent*
 ContentParent::AllocPImageBridge(mozilla::ipc::Transport* aTransport,
                                  base::ProcessId aOtherProcess)
 {
     return ImageBridgeParent::Create(aTransport, aOtherProcess);
 }
 
 bool
-ContentParent::RecvGetProcessAttributes(uint64_t* aId, bool* aStartBackground,
+ContentParent::RecvGetProcessAttributes(uint64_t* aId,
                                         bool* aIsForApp, bool* aIsForBrowser)
 {
     *aId = mChildID = gContentChildID++;
-    *aStartBackground =
-        (mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL);
     *aIsForApp = IsForApp();
     *aIsForBrowser = mIsForBrowser;
 
     return true;
 }
 
 bool
 ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline)
@@ -1629,24 +1638,24 @@ ContentParent::RecvPCrashReporterConstru
 
 bool
 ContentParent::DeallocPCrashReporter(PCrashReporterParent* crashreporter)
 {
   delete crashreporter;
   return true;
 }
 
-PHalParent*
+hal_sandbox::PHalParent*
 ContentParent::AllocPHal()
 {
-    return CreateHalParent();
+    return hal_sandbox::CreateHalParent();
 }
 
 bool
-ContentParent::DeallocPHal(PHalParent* aHal)
+ContentParent::DeallocPHal(hal_sandbox::PHalParent* aHal)
 {
     delete aHal;
     return true;
 }
 
 PIndexedDBParent*
 ContentParent::AllocPIndexedDB()
 {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -198,17 +198,16 @@ private:
     PCompositorParent*
     AllocPCompositor(mozilla::ipc::Transport* aTransport,
                      base::ProcessId aOtherProcess) MOZ_OVERRIDE;
     PImageBridgeParent*
     AllocPImageBridge(mozilla::ipc::Transport* aTransport,
                       base::ProcessId aOtherProcess) MOZ_OVERRIDE;
 
     virtual bool RecvGetProcessAttributes(uint64_t* aId,
-                                          bool* aStartBackground,
                                           bool* aIsForApp,
                                           bool* aIsForBrowser) MOZ_OVERRIDE;
     virtual bool RecvGetXPCOMProcessAttributes(bool* aIsOffline) MOZ_OVERRIDE;
 
     virtual PBrowserParent* AllocPBrowser(const IPCTabContext& aContext,
                                           const uint32_t& aChromeFlags);
     virtual bool DeallocPBrowser(PBrowserParent* frame);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -327,18 +327,17 @@ parent:
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
      * isForBrowser|, we're loading <browser> for an app.  When
      * |isForBrowser|, we're loading <browser>.  When |!isForApp &&
      * !isForBrowser|, we're probably loading <xul:browser remote>.
      */
     sync GetProcessAttributes()
-        returns (uint64_t id, bool startBackground,
-                 bool isForApp, bool isForBrowser);
+        returns (uint64_t id, bool isForApp, bool isForBrowser);
     sync GetXPCOMProcessAttributes()
         returns (bool isOffline);
 
     PAudio(int32_t aNumChannels, int32_t aRate, int32_t aFormat);
 
     PDeviceStorageRequest(DeviceStorageParams params);
 
     sync PCrashReporter(NativeThreadId tid, uint32_t processType);
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/Hal.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/TimeStamp.h"
 #include "AudioChannelService.h"
 #include "prlog.h"
+#include "nsPrintfCString.h"
 #include "nsWeakPtr.h"
 #include "nsXULAppAPI.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsITimer.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIDocument.h"
 #include "nsIDOMEventListener.h"
@@ -127,111 +128,154 @@ IsBackgroundPriority(ProcessPriority aPr
  * and document into W.  If not for our grace period, this transition could
  * cause us to inform hal that this process quickly transitioned from
  * foreground to background to foreground again.
  *
  */
 class ProcessPriorityManager MOZ_FINAL
   : public nsIObserver
   , public nsIDOMEventListener
+  , public nsITimerCallback
 {
 public:
   ProcessPriorityManager();
   void Init();
 
   NS_DECL_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIDOMEVENTLISTENER
 
   ProcessPriority GetPriority() const { return mProcessPriority; }
 
+  /**
+   * If this process is not already in the foreground, move it into the
+   * foreground and set a timer to call ResetPriorityNow() in a few seconds.
+   */
+  void TemporarilySetIsForeground();
+
+  /**
+   * Recompute this process's priority and apply it, potentially after a brief
+   * delay.
+   *
+   * If the new priority is FOREGROUND, it takes effect immediately.
+   *
+   * If the new priority is a BACKGROUND* priority and this process's priority
+   * is currently a BACKGROUND* priority, the new priority takes effect
+   * immediately.
+   *
+   * But if the new priority is a BACKGROUND* priority and this process is not
+   * currently in the background, we schedule a timer and run
+   * ResetPriorityNow() after a short period of time.
+   */
+  void ResetPriority();
+
+  /**
+   * Recompute this process's priority and apply it immediately.
+   */
+  void ResetPriorityNow();
+
 private:
-  void SetPriority(ProcessPriority aPriority);
-  void OnAudioChannelAgentChanged();
   void OnContentDocumentGlobalCreated(nsISupports* aOuterWindow);
-  void OnInnerWindowDestroyed();
-  void OnGracePeriodTimerFired();
-  void RecomputeNumVisibleWindows();
+
+  /**
+   * Compute whether this process is in the foreground and return the result.
+   */
+  bool ComputeIsInForeground();
+
+  /**
+   * Set this process's priority to FOREGROUND immediately.
+   */
+  void SetIsForeground();
 
-  // mProcessPriority tracks the priority we've given this process in hal,
-  // except that, when the grace period timer is active, mProcessPriority ==
-  // BACKGROUND or HOMESCREEN_BACKGROUND even though hal still thinks we're a
-  // foreground process.
+  /**
+   * Set this process's priority to the appropriate BACKGROUND* priority
+   * immediately.
+   */
+  void SetIsBackgroundNow();
+
+  /**
+   * If mResetPriorityTimer is null (i.e., not running), create a timer and set
+   * it to invoke ResetPriorityNow() after
+   * dom.ipc.processPriorityManager.aTimeoutPref ms.
+   */
+  void
+  ScheduleResetPriority(const char* aTimeoutPref);
+
+  // mProcessPriority tracks the priority we've given this process in hal.
   ProcessPriority mProcessPriority;
 
   nsTArray<nsWeakPtr> mWindows;
-  nsCOMPtr<nsITimer> mGracePeriodTimer;
+
+  // When this timer expires, we set mResetPriorityTimer to null and run
+  // ResetPriorityNow().
+  nsCOMPtr<nsITimer> mResetPriorityTimer;
+
   nsWeakPtr mMemoryMinimizerRunnable;
-  TimeStamp mStartupTime;
 };
 
 NS_IMPL_ISUPPORTS2(ProcessPriorityManager, nsIObserver, nsIDOMEventListener);
 
 ProcessPriorityManager::ProcessPriorityManager()
-  : mProcessPriority(PROCESS_PRIORITY_FOREGROUND)
-  , mStartupTime(TimeStamp::Now())
+  : mProcessPriority(ProcessPriority(-1))
 {
+  // When our parent process forked us, it set our priority either to
+  // FOREGROUND (if our parent launched this process to meet an immediate need)
+  // or one of the BACKGROUND priorities (if our parent launched this process
+  // to meet a future need).
+  //
+  // We don't know which situation we're in, so we set mProcessPriority to -1
+  // so that the next time ResetPriorityNow is run, we'll definitely call into
+  // hal and set our priority.
 }
 
 void
 ProcessPriorityManager::Init()
 {
   LOG("Starting up.");
 
   // We can't do this in the constructor because we need to hold a strong ref
   // to |this| before calling these methods.
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   os->AddObserver(this, "content-document-global-created", /* ownsWeak = */ false);
   os->AddObserver(this, "inner-window-destroyed", /* ownsWeak = */ false);
   os->AddObserver(this, "audio-channel-agent-changed", /* ownsWeak = */ false);
-
-  SetPriority(PROCESS_PRIORITY_FOREGROUND);
 }
 
 NS_IMETHODIMP
 ProcessPriorityManager::Observe(
   nsISupports* aSubject,
   const char* aTopic,
   const PRUnichar* aData)
 {
   if (!strcmp(aTopic, "content-document-global-created")) {
     OnContentDocumentGlobalCreated(aSubject);
-  } else if (!strcmp(aTopic, "inner-window-destroyed")) {
-    OnInnerWindowDestroyed();
-  } else if (!strcmp(aTopic, "timer-callback")) {
-    OnGracePeriodTimerFired();
-  } else if (!strcmp(aTopic, "audio-channel-agent-changed")) {
-    OnAudioChannelAgentChanged();
+  } else if (!strcmp(aTopic, "inner-window-destroyed") ||
+             !strcmp(aTopic, "audio-channel-agent-changed")) {
+    ResetPriority();
   } else {
     MOZ_ASSERT(false);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ProcessPriorityManager::HandleEvent(
   nsIDOMEvent* aEvent)
 {
   LOG("Got visibilitychange.");
-  RecomputeNumVisibleWindows();
+  ResetPriority();
   return NS_OK;
 }
 
 void
-ProcessPriorityManager::OnAudioChannelAgentChanged()
-{
-  if (IsBackgroundPriority(mProcessPriority)) {
-    SetPriority(GetBackgroundPriority());
-  }
-}
-
-void
 ProcessPriorityManager::OnContentDocumentGlobalCreated(
   nsISupports* aOuterWindow)
 {
+  LOG("DocumentGlobalCreated");
   // Get the inner window (the topic of content-document-global-created is
   // the /outer/ window!).
   nsCOMPtr<nsPIDOMWindow> outerWindow = do_QueryInterface(aOuterWindow);
   NS_ENSURE_TRUE_VOID(outerWindow);
   nsCOMPtr<nsPIDOMWindow> innerWindow = outerWindow->GetCurrentInnerWindow();
   NS_ENSURE_TRUE_VOID(innerWindow);
 
   // We're only interested in top-level windows.
@@ -253,27 +297,45 @@ ProcessPriorityManager::OnContentDocumen
   }
 
   target->AddSystemEventListener(NS_LITERAL_STRING("mozvisibilitychange"),
                                  this,
                                  /* useCapture = */ false,
                                  /* wantsUntrusted = */ false);
 
   mWindows.AppendElement(weakWin);
-  RecomputeNumVisibleWindows();
+  ResetPriority();
 }
 
 void
-ProcessPriorityManager::OnInnerWindowDestroyed()
+ProcessPriorityManager::ResetPriority()
 {
-  RecomputeNumVisibleWindows();
+  if (ComputeIsInForeground()) {
+    SetIsForeground();
+  } else if (IsBackgroundPriority(mProcessPriority)) {
+    // If we're already in the background, recompute our background priority
+    // and set it immediately.
+    SetIsBackgroundNow();
+  } else {
+    ScheduleResetPriority("backgroundGracePeriodMS");
+  }
 }
 
 void
-ProcessPriorityManager::RecomputeNumVisibleWindows()
+ProcessPriorityManager::ResetPriorityNow()
+{
+  if (ComputeIsInForeground()) {
+    SetIsForeground();
+  } else {
+    SetIsBackgroundNow();
+  }
+}
+
+bool
+ProcessPriorityManager::ComputeIsInForeground()
 {
   // We could try to be clever and count the number of visible windows, instead
   // of iterating over mWindows every time one window's visibility state changes.
   // But experience suggests that iterating over the windows is prone to fewer
   // errors (and one mistake doesn't mess you up for the entire session).
   // Moreover, mWindows should be a very short list, since it contains only
   // top-level content windows.
 
@@ -302,78 +364,48 @@ ProcessPriorityManager::RecomputeNumVisi
 
     allHidden = allHidden && hidden;
 
     // We could break out early from this loop if
     //   !hidden && mProcessPriority == BACKGROUND,
     // but then we might not clean up all the weak refs.
   }
 
-  SetPriority(allHidden ?
-              GetBackgroundPriority() :
-              PROCESS_PRIORITY_FOREGROUND);
+  return !allHidden;
 }
 
 void
-ProcessPriorityManager::SetPriority(ProcessPriority aPriority)
+ProcessPriorityManager::SetIsForeground()
 {
-  if (aPriority == mProcessPriority) {
+  if (mProcessPriority == PROCESS_PRIORITY_FOREGROUND) {
     return;
   }
 
-  if (IsBackgroundPriority(aPriority)) {
-    // If this is a foreground --> background transition, give ourselves a
-    // grace period before informing hal.
-    uint32_t gracePeriodMS = Preferences::GetUint("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
-    if (mGracePeriodTimer) {
-      LOG("Grace period timer already active.");
-      return;
-    }
-
-    LOG("Initializing grace period timer.");
-    mProcessPriority = aPriority;
-    mGracePeriodTimer = do_CreateInstance("@mozilla.org/timer;1");
-    mGracePeriodTimer->Init(this, gracePeriodMS, nsITimer::TYPE_ONE_SHOT);
+  // Cancel the memory minimization procedure we might have started.
+  nsCOMPtr<nsICancelableRunnable> runnable =
+    do_QueryReferent(mMemoryMinimizerRunnable);
+  if (runnable) {
+    runnable->Cancel();
+  }
 
-  } else if (aPriority == PROCESS_PRIORITY_FOREGROUND) {
-    // If this is a background --> foreground transition, do it immediately, and
-    // cancel the outstanding grace period timer, if there is one.
-    if (mGracePeriodTimer) {
-      mGracePeriodTimer->Cancel();
-      mGracePeriodTimer = nullptr;
-    }
-
-    // Cancel the memory minimization procedure we might have started.
-    nsCOMPtr<nsICancelableRunnable> runnable =
-      do_QueryReferent(mMemoryMinimizerRunnable);
-    if (runnable) {
-      runnable->Cancel();
-    }
-
-    LOG("Setting priority to %d.", aPriority);
-    mProcessPriority = aPriority;
-    hal::SetProcessPriority(getpid(), aPriority);
-
-  } else {
-    MOZ_ASSERT(false);
-  }
+  LOG("Setting priority to FOREGROUND.");
+  mProcessPriority = PROCESS_PRIORITY_FOREGROUND;
+  hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_FOREGROUND);
 }
 
 void
-ProcessPriorityManager::OnGracePeriodTimerFired()
+ProcessPriorityManager::SetIsBackgroundNow()
 {
-  LOG("Grace period timer fired; setting priority to %d.",
-      mProcessPriority);
+  ProcessPriority backgroundPriority = GetBackgroundPriority();
+  if (mProcessPriority == backgroundPriority) {
+    return;
+  }
 
-  // mProcessPriority should already be one of the BACKGROUND values: We set it
-  // in SetPriority(BACKGROUND), and we canceled this timer if there was an
-  // intervening SetPriority(FOREGROUND) call.
-  MOZ_ASSERT(IsBackgroundPriority(mProcessPriority));
-
-  mGracePeriodTimer = nullptr;
+  mProcessPriority = backgroundPriority;
+  LOG("Setting priority to BACKGROUND (type %d)", mProcessPriority);
   hal::SetProcessPriority(getpid(), mProcessPriority);
 
   // We're in the background; dump as much memory as we can.
   nsCOMPtr<nsIMemoryReporterManager> mgr =
     do_GetService("@mozilla.org/memory-reporter-manager;1");
   if (mgr) {
     nsCOMPtr<nsICancelableRunnable> runnable =
       do_QueryReferent(mMemoryMinimizerRunnable);
@@ -384,28 +416,69 @@ ProcessPriorityManager::OnGracePeriodTim
     }
 
     mgr->MinimizeMemoryUsage(/* callback = */ nullptr,
                              getter_AddRefs(runnable));
     mMemoryMinimizerRunnable = do_GetWeakReference(runnable);
   }
 }
 
+void
+ProcessPriorityManager::ScheduleResetPriority(const char* aTimeoutPref)
+{
+  if (mResetPriorityTimer) {
+    // The timer is already running.
+    return;
+  }
+
+  uint32_t timeout = Preferences::GetUint(
+    nsPrintfCString("dom.ipc.processPriorityManager.%s", aTimeoutPref).get());
+  LOG("Scheduling reset timer to fire in %dms.", timeout);
+  mResetPriorityTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mResetPriorityTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+}
+
+NS_IMETHODIMP
+ProcessPriorityManager::Notify(nsITimer* aTimer)
+{
+  LOG("Reset priority timer callback; about to ResetPriorityNow.");
+  ResetPriorityNow();
+  mResetPriorityTimer = nullptr;
+  return NS_OK;
+}
+
+void
+ProcessPriorityManager::TemporarilySetIsForeground()
+{
+  LOG("TemporarilySetIsForeground");
+  SetIsForeground();
+
+  // Each call to TemporarilySetIsForeground guarantees us temporaryPriorityMS
+  // in the foreground.  So cancel our timer if it's running (which is due to a
+  // previous call to either TemporarilySetIsForeground() or ResetPriority()).
+  if (mResetPriorityTimer) {
+    mResetPriorityTimer->Cancel();
+    mResetPriorityTimer = nullptr;
+  }
+  ScheduleResetPriority("temporaryPriorityMS");
+}
+
 } // anonymous namespace
 
 void
 InitProcessPriorityManager()
 {
   if (sInitialized) {
     return;
   }
 
   // If IPC tabs aren't enabled at startup, don't bother with any of this.
   if (!Preferences::GetBool("dom.ipc.processPriorityManager.enabled") ||
       Preferences::GetBool("dom.ipc.tabs.disabled")) {
+    LOG("InitProcessPriorityManager bailing due to prefs.");
     return;
   }
 
   sInitialized = true;
 
   // If we're the master process, mark ourselves as such and don't create a
   // ProcessPriorityManager (we never want to mark the master process as
   // backgrounded).
@@ -427,11 +500,22 @@ CurrentProcessIsForeground()
   // so if the manager does not exist, then we must be in the foreground.
   if (!sManager) {
     return true;
   }
 
   return sManager->GetPriority() >= PROCESS_PRIORITY_FOREGROUND;
 }
 
+void
+TemporarilySetProcessPriorityToForeground()
+{
+  if (sManager) {
+    sManager->TemporarilySetIsForeground();
+  } else {
+    LOG("TemporarilySetProcessPriorityToForeground called before "
+        "InitProcessPriorityManager.  Bailing.");
+  }
+}
+
 } // namespace ipc
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ProcessPriorityManager.h
+++ b/dom/ipc/ProcessPriorityManager.h
@@ -28,13 +28,24 @@ void InitProcessPriorityManager();
 
 /**
  * True iff the current process has foreground or higher priority as
  * computed by DOM visibility.  The returned answer may not match the
  * actual OS process priority, for short intervals.
  */
 bool CurrentProcessIsForeground();
 
+/**
+ * If this process is in the background, temporarily boost its priority to the
+ * foreground.  This priority boost will expire after a few seconds
+ * (dom.ipc.processPriorityManager.temporaryPriorityMS).
+ *
+ * You might want to call this function when a process starts loading some
+ * things, but doesn't yet have a foreground window.  The hope would be that by
+ * once the timer here expires, the process will have a foreground window.
+ */
+void TemporarilySetProcessPriorityToForeground();
+
 } // namespace ipc
 } // namespace dom
 } // namespace mozilla
 
 #endif