Bug 844323 - Part 2 (The Main Event): Move ProcesPriorityManager to the main process. r=bent,khuey
authorJustin Lebar <justin.lebar@gmail.com>
Thu, 25 Apr 2013 20:53:26 -0400
changeset 140873 a5628782c872b636ce2feaf8939e5b4a98c46382
parent 140872 38fee265dbe51dcacf9fe0b693727ea3e4faac4d
child 140874 e8e47474ef41bb470dd4183918cccef5100b2224
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent, khuey
bugs844323
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 844323 - Part 2 (The Main Event): Move ProcesPriorityManager to the main process. r=bent,khuey
content/base/public/nsIFrameLoader.idl
content/base/src/nsFrameLoader.cpp
content/base/src/nsFrameLoader.h
content/canvas/src/WebGLContext.cpp
dom/audiochannel/AudioChannelService.cpp
dom/audiochannel/AudioChannelService.h
dom/browser-element/BrowserElementChildPreload.js
dom/browser-element/BrowserElementParent.jsm
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PBrowser.ipdl
dom/ipc/PContent.ipdl
dom/ipc/ProcessPriorityManager.cpp
dom/ipc/ProcessPriorityManager.h
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
layout/build/nsLayoutStatics.cpp
--- a/content/base/public/nsIFrameLoader.idl
+++ b/content/base/public/nsIFrameLoader.idl
@@ -8,16 +8,17 @@
 interface nsFrameLoader;
 interface nsIDocShell;
 interface nsIURI;
 interface nsIFrame;
 interface nsSubDocumentFrame;
 interface nsIMessageSender;
 interface nsIVariant;
 interface nsIDOMElement;
+interface nsITabParent;
 
 typedef unsigned long long nsContentViewId;
 
 /**
  * These interfaces do *not* scroll or scale the content document;
  * instead they set a "goal" scroll/scale wrt the current content
  * view.  When the content document is painted, the scroll*
  * attributes are used to set a compensating transform.  If the
@@ -105,25 +106,31 @@ interface nsIContentViewManager : nsISup
                          [retval, array, size_is(aLength)] out nsIContentView aResult);
 
   /**
    * The root content view.
    */
   readonly attribute nsIContentView rootContentView;
 };
 
-[scriptable, uuid(a4db652e-e3b0-4345-8107-cf6a30486759)]
+[scriptable, builtinclass, uuid(e4333e51-f2fa-4fdd-becd-75d000703355)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
 
   /**
+   * Get this frame loader's TabParent, if it has a remote frame.  Otherwise,
+   * returns null.
+   */
+  readonly attribute nsITabParent tabParent;
+
+  /**
    * Start loading the frame. This method figures out what to load
    * from the owner content in the frame loader.
    */
   void loadFrame();
 
   /**
    * Loads the specified URI in this frame. Behaves identically to loadFrame,
    * except that this method allows specifying the URI to load.
@@ -244,16 +251,25 @@ interface nsIFrameLoader : nsISupports
 
   /**
    * The element which owns this frame loader.
    *
    * For example, if this is a frame loader for an <iframe>, this attribute
    * returns the iframe element.
    */
   readonly attribute nsIDOMElement ownerElement;
+
+  /**
+   * Get or set this frame loader's visibility.
+   *
+   * The notion of "visibility" here is separate from the notion of a
+   * window/docshell's visibility.  This field is mostly here so that we can
+   * have a notion of visibility in the parent process when frames are OOP.
+   */
+  [infallible] attribute boolean visible;
 };
 
 %{C++
 class nsFrameLoader;
 %}
 
 native alreadyAddRefed_nsFrameLoader(already_AddRefed<nsFrameLoader>);
 
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -280,16 +280,17 @@ nsFrameLoader::nsFrameLoader(Element* aO
   , mNetworkCreated(aNetworkCreated)
   , mDelayRemoteDialogs(false)
   , mRemoteBrowserShown(false)
   , mRemoteFrame(false)
   , mClipSubdocument(true)
   , mClampScrollPosition(true)
   , mRemoteBrowserInitialized(false)
   , mObservingOwnerContent(false)
+  , mVisible(true)
   , mCurrentRemoteFrame(nullptr)
   , mRemoteBrowser(nullptr)
   , mRenderMode(RENDER_MODE_DEFAULT)
   , mEventMode(EVENT_MODE_NORMAL_DISPATCH)
 {
   ResetPermissionManagerStatus();
 }
 
@@ -2546,8 +2547,34 @@ nsFrameLoader::ResetPermissionManagerSta
 
   // Register the new AppId.
   if (appId != nsIScriptSecurityManager::NO_APP_ID) {
     mAppIdSentToPermissionManager = appId;
     permMgr->AddrefAppId(mAppIdSentToPermissionManager);
   }
 }
 
+/* [infallible] */ NS_IMETHODIMP
+nsFrameLoader::SetVisible(bool aVisible)
+{
+  mVisible = aVisible;
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (os) {
+    os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+                        "frameloader-visible-changed", nullptr);
+  }
+  return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsFrameLoader::GetVisible(bool* aVisible)
+{
+  *aVisible = mVisible;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::GetTabParent(nsITabParent** aTabParent)
+{
+  nsCOMPtr<nsITabParent> tp = mRemoteBrowser;
+  tp.forget(aTabParent);
+  return NS_OK;
+}
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -423,16 +423,21 @@ private:
   bool mDelayRemoteDialogs : 1;
   bool mRemoteBrowserShown : 1;
   bool mRemoteFrame : 1;
   bool mClipSubdocument : 1;
   bool mClampScrollPosition : 1;
   bool mRemoteBrowserInitialized : 1;
   bool mObservingOwnerContent : 1;
 
+  // Backs nsIFrameLoader::{Get,Set}Visible.  Visibility state here relates to
+  // whether this frameloader's <iframe mozbrowser> is setVisible(true)'ed, and
+  // doesn't necessarily correlate with docshell/document visibility.
+  bool mVisible : 1;
+
   // XXX leaking
   nsCOMPtr<nsIObserver> mChildHost;
   RenderFrameParent* mCurrentRemoteFrame;
   TabParent* mRemoteBrowser;
 
   // See nsIFrameLoader.idl.  Short story, if !(mRenderMode &
   // RENDER_MODE_ASYNC_SCROLL), all the fields below are ignored in
   // favor of what content tells.
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -46,32 +46,32 @@
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/ProcessPriorityManager.h"
 
 #include "Layers.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
-using namespace mozilla::dom::ipc;
 using namespace mozilla::gfx;
 using namespace mozilla::gl;
 using namespace mozilla::layers;
 
 NS_IMETHODIMP
 WebGLMemoryPressureObserver::Observe(nsISupports* aSubject,
                                      const char* aTopic,
                                      const PRUnichar* aSomeData)
 {
     if (strcmp(aTopic, "memory-pressure"))
         return NS_OK;
 
     bool wantToLoseContext = true;
 
-    if (!mContext->mCanLoseContextInForeground && CurrentProcessIsForeground())
+    if (!mContext->mCanLoseContextInForeground &&
+        ProcessPriorityManager::CurrentProcessIsForeground())
         wantToLoseContext = false;
     else if (!nsCRT::strcmp(aSomeData,
                             NS_LITERAL_STRING("heap-minimize").get()))
         wantToLoseContext = mContext->mLoseContextOnHeapMinimize;
 
     if (wantToLoseContext)
         mContext->ForceLoseContext();
 
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
 #include "mozilla/Util.h"
 
 #include "mozilla/dom/ContentParent.h"
 
 #include "nsThreadUtils.h"
+#include "nsHashPropertyBag.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsIAudioManager.h"
 #endif
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
@@ -99,17 +100,17 @@ void
 AudioChannelService::RegisterType(AudioChannelType aType, uint64_t aChildID)
 {
   AudioChannelInternalType type = GetInternalType(aType, true);
   mChannelCounters[type].AppendElement(aChildID);
 
   // In order to avoid race conditions, it's safer to notify any existing
   // agent any time a new one is registered.
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
-    SendAudioChannelChangedNotification();
+    SendAudioChannelChangedNotification(aChildID);
     Notify();
   }
 }
 
 void
 AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
 {
   nsAutoPtr<AudioChannelAgentData> data;
@@ -137,17 +138,17 @@ AudioChannelService::UnregisterType(Audi
     // We only remove ChildID when it is in the foreground.
     // If in the background, we kept ChildID for allowing it to play next song.
     if (aType == AUDIO_CHANNEL_CONTENT &&
         mActiveContentChildIDs.Contains(aChildID) &&
         !aElementHidden &&
         !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID)) {
       mActiveContentChildIDs.RemoveElement(aChildID);
     }
-    SendAudioChannelChangedNotification();
+    SendAudioChannelChangedNotification(aChildID);
     Notify();
   }
 }
 
 void
 AudioChannelService::UpdateChannelType(AudioChannelType aType,
                                        uint64_t aChildID,
                                        bool aElementHidden,
@@ -175,17 +176,16 @@ AudioChannelService::GetMuted(AudioChann
   bool oldElementHidden = data->mElementHidden;
   // Update visibility.
   data->mElementHidden = aElementHidden;
 
   bool muted = GetMutedInternal(data->mType, CONTENT_PROCESS_ID_MAIN,
                                 aElementHidden, oldElementHidden);
   data->mMuted = muted;
 
-  SendAudioChannelChangedNotification();
   return muted;
 }
 
 bool
 AudioChannelService::GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
                                       bool aElementHidden, bool aElementWasHidden)
 {
   UpdateChannelType(aType, aChildID, aElementHidden, aElementWasHidden);
@@ -202,17 +202,16 @@ AudioChannelService::GetMutedInternal(Au
       mActiveContentChildIDsFrozen = false;
       mActiveContentChildIDs.Clear();
     }
 
     if (!mActiveContentChildIDs.Contains(aChildID)) {
       mActiveContentChildIDs.AppendElement(aChildID);
     }
   }
-
   else if (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN &&
            oldType == AUDIO_CHANNEL_INT_CONTENT &&
            !mActiveContentChildIDsFrozen) {
     // If nothing is visible, the list has to been frozen.
     // Or if there is still any one with other ChildID in foreground then
     // it should be removed from list and left other ChildIDs in the foreground
     // to keep playing. Finally only last one childID which go to background
     // will be in list.
@@ -223,16 +222,18 @@ AudioChannelService::GetMutedInternal(Au
       mActiveContentChildIDs.RemoveElement(aChildID);
     }
   }
 
   if (newType != oldType && aType == AUDIO_CHANNEL_CONTENT) {
     Notify();
   }
 
+  SendAudioChannelChangedNotification(aChildID);
+
   // Let play any visible audio channel.
   if (!aElementHidden) {
     return false;
   }
 
   bool muted = false;
 
   // We are not visible, maybe we have to mute.
@@ -253,23 +254,39 @@ AudioChannelService::GetMutedInternal(Au
 bool
 AudioChannelService::ContentOrNormalChannelIsActive()
 {
   return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
          !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() ||
          !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty();
 }
 
+bool
+AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID)
+{
+  return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) ||
+         mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) ||
+         mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID);
+}
+
 void
-AudioChannelService::SendAudioChannelChangedNotification()
+AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID)
 {
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     return;
   }
 
+  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+  props->Init();
+  props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID);
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  obs->NotifyObservers(static_cast<nsIWritablePropertyBag*>(props),
+                       "audio-channel-process-changed", nullptr);
+
   // Calculating the most important active channel.
   AudioChannelType higher = AUDIO_CHANNEL_LAST;
 
   // Top-Down in the hierarchy for visible elements
   if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) {
     higher = AUDIO_CHANNEL_PUBLICNOTIFICATION;
   }
 
@@ -336,31 +353,29 @@ AudioChannelService::SendAudioChannelCha
 
     nsString channelName;
     if (mCurrentHigherChannel != AUDIO_CHANNEL_LAST) {
       channelName.AssignASCII(ChannelName(mCurrentHigherChannel));
     } else {
       channelName.AssignLiteral("none");
     }
 
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
   }
 
   if (visibleHigher != mCurrentVisibleHigherChannel) {
     mCurrentVisibleHigherChannel = visibleHigher;
 
     nsString channelName;
     if (mCurrentVisibleHigherChannel != AUDIO_CHANNEL_LAST) {
       channelName.AssignASCII(ChannelName(mCurrentVisibleHigherChannel));
     } else {
       channelName.AssignLiteral("none");
     }
 
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get());
   }
 }
 
 PLDHashOperator
 AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
                                       AudioChannelAgentData* aData, void* aUnused)
 {
@@ -456,17 +471,17 @@ AudioChannelService::Observe(nsISupports
       if ((index = mActiveContentChildIDs.IndexOf(childID)) != -1) {
         mActiveContentChildIDs.RemoveElementAt(index);
       }
     }
 
     // We don't have to remove the agents from the mAgents hashtable because if
     // that table contains only agents running on the same process.
 
-    SendAudioChannelChangedNotification();
+    SendAudioChannelChangedNotification(childID);
     Notify();
   } else {
     NS_WARNING("ipc:content-shutdown message without childID property");
   }
 
   return NS_OK;
 }
 
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -55,23 +55,30 @@ public:
   virtual bool GetMuted(AudioChannelAgent* aAgent, bool aElementHidden);
 
   /**
    * Return true if there is a content channel active in this process
    * or one of its subprocesses.
    */
   virtual bool ContentOrNormalChannelIsActive();
 
+  /**
+   * Return true iff a normal or content channel is active for the given process
+   * ID.
+   */
+  virtual bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID);
+
 protected:
   void Notify();
 
   /**
-   * Send the audio-channel-changed notification if needed.
+   * Send the audio-channel-changed notification for the given process ID if
+   * needed.
    */
-  void SendAudioChannelChangedNotification();
+  void SendAudioChannelChangedNotification(uint64_t aChildID);
 
   /* Register/Unregister IPC types: */
   void RegisterType(AudioChannelType aType, uint64_t aChildID);
   void UnregisterType(AudioChannelType aType, bool aElementHidden,
                       uint64_t aChildID);
 
   bool GetMutedInternal(AudioChannelType aType, uint64_t aChildID,
                         bool aElementHidden, bool aElementWasHidden);
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -641,46 +641,41 @@ BrowserElementChild.prototype = {
 
   _recvSetVisible: function(data) {
     debug("Received setVisible message: (" + data.json.visible + ")");
     if (this._forcedVisible == data.json.visible) {
       return;
     }
 
     this._forcedVisible = data.json.visible;
-    this._updateDocShellVisibility();
-
-    // Fire a notification to the ProcessPriorityManager to reset this
-    // process's priority now (as opposed to after a brief delay).
-    var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-    os.notifyObservers(/* subject */ null, 'process-priority:reset-now',
-                       /* data */ null);
+    this._updateVisibility();
   },
 
   _recvVisible: function(data) {
     sendAsyncMsg('got-visible', {
       id: data.json.id,
       successRv: docShell.isActive
     });
   },
 
   /**
    * Called when the window which contains this iframe becomes hidden or
    * visible.
    */
   _recvOwnerVisibilityChange: function(data) {
     debug("Received ownerVisibilityChange: (" + data.json.visible + ")");
     this._ownerVisible = data.json.visible;
-    this._updateDocShellVisibility();
+    this._updateVisibility();
   },
 
-  _updateDocShellVisibility: function() {
+  _updateVisibility: function() {
     var visible = this._forcedVisible && this._ownerVisible;
     if (docShell.isActive !== visible) {
       docShell.isActive = visible;
+      sendAsyncMsg('visibility-change', {visibility: visible});
     }
   },
 
   _recvSendMouseEvent: function(data) {
     let json = data.json;
     let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                        .getInterface(Ci.nsIDOMWindowUtils);
     utils.sendMouseEvent(json.type, json.x, json.y, json.button,
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -113,17 +113,18 @@ function BrowserElementParent(frameLoade
     "showmodalprompt": this._handleShowModalPrompt,
     "got-purge-history": this._gotDOMRequestResult,
     "got-screenshot": this._gotDOMRequestResult,
     "got-can-go-back": this._gotDOMRequestResult,
     "got-can-go-forward": this._gotDOMRequestResult,
     "fullscreen-origin-change": this._remoteFullscreenOriginChange,
     "rollback-fullscreen": this._remoteFrameFullscreenReverted,
     "exit-fullscreen": this._exitFullscreen,
-    "got-visible": this._gotDOMRequestResult
+    "got-visible": this._gotDOMRequestResult,
+    "visibility-change": this._childVisibilityChange,
   }
 
   this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
     if (self._isAlive() && (aMsg.data.msg_name in mmCalls)) {
       return mmCalls[aMsg.data.msg_name].apply(self, arguments);
     }
   });
 
@@ -442,16 +443,17 @@ BrowserElementParent.prototype = {
     else {
       debug("Got error in gotDOMRequestResult.");
       Services.DOMRequest.fireErrorAsync(req, data.json.errorMsg);
     }
   },
 
   _setVisible: function(visible) {
     this._sendAsyncMsg('set-visible', {visible: visible});
+    this._frameLoader.visible = visible;
   },
 
   _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
     this._sendAsyncMsg("send-mouse-event", {
       "type": type,
       "x": x,
       "y": y,
       "button": button,
@@ -557,16 +559,29 @@ BrowserElementParent.prototype = {
   /**
    * Called when the visibility of the window which owns this iframe changes.
    */
   _ownerVisibilityChange: function() {
     this._sendAsyncMsg('owner-visibility-change',
                        {visible: !this._window.document.hidden});
   },
 
+  /*
+   * Called when the child notices that its visibility has changed.
+   *
+   * This is sometimes redundant; for example, the child's visibility may
+   * change in response to a setVisible request that we made here!  But it's
+   * not always redundant; for example, the child's visibility may change in
+   * response to its parent docshell being hidden.
+   */
+  _childVisibilityChange: function(data) {
+    debug("_childVisibilityChange(" + data.json.visible + ")");
+    this._frameLoader.visible = data.json.visible;
+  },
+
   _exitFullscreen: function() {
     this._windowUtils.exitFullscreen();
   },
 
   _remoteFullscreenOriginChange: function(data) {
     let origin = data.json._payload_;
     this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
   },
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -43,16 +43,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsXULAppAPI.h"
 #include "nsWeakReference.h"
 #include "nsIScriptError.h"
 #include "nsIConsoleService.h"
 #include "nsJSEnvironment.h"
 #include "SandboxHal.h"
 #include "nsDebugImpl.h"
+#include "nsHashPropertyBag.h"
 #include "nsLayoutStylesheetCache.h"
 
 #include "IHistory.h"
 #include "nsDocShellCID.h"
 #include "nsNetUtil.h"
 
 #include "base/message_loop.h"
 #include "base/process_util.h"
@@ -566,44 +567,31 @@ ContentChild::AllocPBrowser(const IPCTab
 
 bool
 ContentChild::RecvPBrowserConstructor(PBrowserChild* actor,
                                       const IPCTabContext& context,
                                       const uint32_t& chromeFlags)
 {
     // This runs after AllocPBrowser() returns and the IPC machinery for this
     // PBrowserChild has been set up.
-    //
-    // We have to NotifyObservers("tab-child-created") before we
-    // TemporarilyLockProcessPriority because the NotifyObservers call may cause
-    // us to initialize the ProcessPriorityManager, and
-    // TemporarilyLockProcessPriority only works after the
-    // ProcessPriorityManager has been initialized.
 
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os) {
         nsITabChild* tc =
             static_cast<nsITabChild*>(static_cast<TabChild*>(actor));
         os->NotifyObservers(tc, "tab-child-created", nullptr);
     }
 
     static bool hasRunOnce = false;
     if (!hasRunOnce) {
         hasRunOnce = true;
 
         MOZ_ASSERT(!sFirstIdleTask);
         sFirstIdleTask = NewRunnableFunction(FirstIdle);
         MessageLoop::current()->PostIdleTask(FROM_HERE, sFirstIdleTask);
-
-        // We are either a brand-new process loading its first PBrowser, or we
-        // are the preallocated process transforming into a particular
-        // app/browser.  Either way, our parent has already set our process
-        // priority, and we want to leave it there for a few seconds while we
-        // start up.
-        TemporarilyLockProcessPriority();
     }
 
     return true;
 }
 
 
 bool
 ContentChild::DeallocPBrowser(PBrowserChild* iframe)
@@ -1203,10 +1191,62 @@ ContentChild::RecvFileSystemUpdate(const
     unused << aFsName;
     unused << aName;
     unused << aState;
     unused << aMountGeneration;
 #endif
     return true;
 }
 
+bool
+ContentChild::RecvNotifyProcessPriorityChanged(
+    const hal::ProcessPriority& aPriority)
+{
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    NS_ENSURE_TRUE(os, true);
+
+    nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+    props->Init();
+    props->SetPropertyAsInt32(NS_LITERAL_STRING("priority"),
+                              static_cast<int32_t>(aPriority));
+
+    os->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
+                        "ipc:process-priority-changed",  nullptr);
+    return true;
+}
+
+bool
+ContentChild::RecvMinimizeMemoryUsage()
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr =
+        do_GetService("@mozilla.org/memory-reporter-manager;1");
+    NS_ENSURE_TRUE(mgr, true);
+
+    nsCOMPtr<nsICancelableRunnable> runnable =
+        do_QueryReferent(mMemoryMinimizerRunnable);
+
+    // Cancel the previous task if it's still pending.
+    if (runnable) {
+        runnable->Cancel();
+        runnable = nullptr;
+    }
+
+    mgr->MinimizeMemoryUsage(/* callback = */ nullptr,
+                             getter_AddRefs(runnable));
+    mMemoryMinimizerRunnable = do_GetWeakReference(runnable);
+    return true;
+}
+
+bool
+ContentChild::RecvCancelMinimizeMemoryUsage()
+{
+    nsCOMPtr<nsICancelableRunnable> runnable =
+        do_QueryReferent(mMemoryMinimizerRunnable);
+    if (runnable) {
+        runnable->Cancel();
+        mMemoryMinimizerRunnable = nullptr;
+    }
+
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -187,16 +187,20 @@ public:
     virtual bool RecvLastPrivateDocShellDestroyed();
 
     virtual bool RecvFilePathUpdate(const nsString& type, const nsString& path, const nsCString& reason);
     virtual bool RecvFileSystemUpdate(const nsString& aFsName,
                                       const nsString& aName,
                                       const int32_t& aState,
                                       const int32_t& aMountGeneration);
 
+    virtual bool RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority);
+    virtual bool RecvMinimizeMemoryUsage();
+    virtual bool RecvCancelMinimizeMemoryUsage();
+
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
 
@@ -239,16 +243,17 @@ private:
 
 #ifdef ANDROID
     gfxIntSize mScreenSize;
 #endif
 
     bool mIsForApp;
     bool mIsForBrowser;
     nsString mProcessName;
+    nsWeakPtr mMemoryMinimizerRunnable;
 
     static ContentChild* sSingleton;
 
     DISALLOW_EVIL_CONSTRUCTORS(ContentChild);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -54,16 +54,17 @@
 #include "nsConsoleService.h"
 #include "nsDebugImpl.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDOMFile.h"
 #include "nsExternalHelperAppService.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashPropertyBag.h"
 #include "nsIAlertsService.h"
+#include "nsIAppsService.h"
 #include "nsIClipboard.h"
 #include "nsIDOMApplicationRegistry.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMWakeLock.h"
 #include "nsIDOMWindow.h"
 #include "nsIFilePicker.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
@@ -216,18 +217,19 @@ static uint64_t gContentChildID = 1;
 // PreallocateAppProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within
 // MaybeTakePreallocatedAppProcess.
 
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateAppProcess()
 {
     nsRefPtr<ContentParent> process =
-        new ContentParent(MAGIC_PREALLOCATED_APP_MANIFEST_URL,
-                          /*isBrowserElement=*/false,
+        new ContentParent(/* app = */ nullptr,
+                          /* isForBrowserElement = */ false,
+                          /* isForPreallocated = */ true,
                           // Final privileges are set when we
                           // transform into our app.
                           base::PRIVILEGES_INHERIT,
                           PROCESS_PRIORITY_BACKGROUND);
     process->Init();
     return process.forget();
 }
 
@@ -334,18 +336,19 @@ ContentParent::GetNewOrUsed(bool aForBro
     if (sNonAppContentParents->Length() >= uint32_t(maxContentProcesses)) {
         uint32_t idx = rand() % sNonAppContentParents->Length();
         nsRefPtr<ContentParent> p = (*sNonAppContentParents)[idx];
         NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sNonAppContentParents?");
         return p.forget();
     }
 
     nsRefPtr<ContentParent> p =
-        new ContentParent(/* appManifestURL = */ EmptyString(),
+        new ContentParent(/* app = */ nullptr,
                           aForBrowserElement,
+                          /* isForPreallocated = */ false,
                           base::PRIVILEGES_DEFAULT,
                           PROCESS_PRIORITY_FOREGROUND);
     p->Init();
     sNonAppContentParents->AppendElement(p);
     return p.forget();
 }
 
 namespace {
@@ -456,42 +459,33 @@ ContentParent::CreateBrowserOrApp(const 
     }
 
     if (!p) {
         ChildPrivileges privs = PrivilegesForApp(ownApp);
         p = MaybeTakePreallocatedAppProcess(manifestURL, privs,
                                             initialPriority);
         if (!p) {
             NS_WARNING("Unable to use pre-allocated app process");
-            p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
-                                  privs, initialPriority);
+            p = new ContentParent(ownApp,
+                                  /* isForBrowserElement = */ false,
+                                  /* isForPreallocated = */ false,
+                                  privs,
+                                  initialPriority);
             p->Init();
         }
         sAppContentParents->Put(manifestURL, p);
     }
 
     nsRefPtr<TabParent> tp = new TabParent(aContext);
     tp->SetOwnerElement(aFrameElement);
     PBrowserParent* browser = p->SendPBrowserConstructor(
-        tp.forget().get(), // DeallocPBrowserParent() releases this ref.
+        nsRefPtr<TabParent>(tp).forget().get(), // DeallocPBrowserParent() releases this ref.
         aContext.AsIPCTabContext(),
         /* chromeFlags */ 0);
 
-    // Send the frame element's mozapptype down to the child process.  This ends
-    // up in TabChild::GetAppType().  We have to do this /before/ we acquire the
-    // CPU wake lock for this process, because if the child sees that it has a
-    // CPU wake lock but its TabChild doesn't have the right mozapptype, it
-    // might downgrade its process priority.
-    nsCOMPtr<Element> frameElement = do_QueryInterface(aFrameElement);
-    if (frameElement) {
-      nsAutoString appType;
-      frameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::mozapptype, appType);
-      unused << browser->SendSetAppType(appType);
-    }
-
     p->MaybeTakeCPUWakeLock(aFrameElement);
 
     return static_cast<TabParent*>(browser);
 }
 
 void
 ContentParent::GetAll(nsTArray<ContentParent*>& aArray)
 {
@@ -625,27 +619,16 @@ StaticAutoPtr<LinkedList<SystemMessageHa
     SystemMessageHandledListener::sListeners;
 
 NS_IMPL_ISUPPORTS1(SystemMessageHandledListener,
                    nsITimerCallback)
 
 } // anonymous namespace
 
 void
-ContentParent::SetProcessPriority(ProcessPriority aPriority)
-{
-    if (!Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
-        return;
-    }
-
-    hal::SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
-                            aPriority);
-}
-
-void
 ContentParent::MaybeTakeCPUWakeLock(nsIDOMElement* aFrameElement)
 {
     // Take the CPU wake lock on behalf of this processs if it's expecting a
     // system message.  We'll release the CPU lock once the message is
     // delivered, or after some period of time, which ever comes first.
 
     nsCOMPtr<nsIMozBrowserFrame> browserFrame =
         do_QueryInterface(aFrameElement);
@@ -662,17 +645,17 @@ ContentParent::MaybeTakeCPUWakeLock(nsID
     nsRefPtr<SystemMessageHandledListener> listener =
         new SystemMessageHandledListener();
     listener->Init(lock);
 }
 
 bool
 ContentParent::SetPriorityAndCheckIsAlive(ProcessPriority aPriority)
 {
-    SetProcessPriority(aPriority);
+    ProcessPriorityManager::SetProcessPriority(this, aPriority);
 
     // Now that we've set this process's priority, check whether the process is
     // still alive.  Hopefully we've set the priority to FOREGROUND*, so the
     // process won't unexpectedly crash after this point!
     //
     // It's not legal to call DidProcessCrash on Windows if the process has not
     // terminated yet, so we have to skip this check here.
 #ifndef XP_WIN
@@ -681,24 +664,48 @@ ContentParent::SetPriorityAndCheckIsAliv
     if (exited) {
         return false;
     }
 #endif
 
     return true;
 }
 
+// Helper for ContentParent::TransformPreallocatedIntoApp.
+static void
+TryGetNameFromManifestURL(const nsAString& aManifestURL,
+                          nsAString& aName)
+{
+    aName.Truncate();
+    if (aManifestURL.IsEmpty() ||
+        aManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL) {
+        return;
+    }
+
+    nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+    NS_ENSURE_TRUE_VOID(appsService);
+
+    nsCOMPtr<mozIDOMApplication> domApp;
+    appsService->GetAppByManifestURL(aManifestURL, getter_AddRefs(domApp));
+
+    nsCOMPtr<mozIApplication> app = do_QueryInterface(domApp);
+    if (!app) {
+        return;
+    }
+
+    app->GetName(aName);
+}
+
 bool
 ContentParent::TransformPreallocatedIntoApp(const nsAString& aAppManifestURL,
                                             ChildPrivileges aPrivs)
 {
-    MOZ_ASSERT(mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL);
-    // Clients should think of mAppManifestURL as const ... we're
-    // bending the rules here just for the preallocation hack.
-    const_cast<nsString&>(mAppManifestURL) = aAppManifestURL;
+    MOZ_ASSERT(IsPreallocated());
+    mAppManifestURL = aAppManifestURL;
+    TryGetNameFromManifestURL(aAppManifestURL, mAppName);
 
     return SendSetProcessPrivileges(aPrivs);
 }
 
 void
 ContentParent::ShutDownProcess()
 {
   if (!mIsDestroyed) {
@@ -996,53 +1003,67 @@ ContentParent::DestroyTestShell(TestShel
 TestShellParent*
 ContentParent::GetTestShellSingleton()
 {
     if (!ManagedPTestShellParent().Length())
         return nullptr;
     return static_cast<TestShellParent*>(ManagedPTestShellParent()[0]);
 }
 
-ContentParent::ContentParent(const nsAString& aAppManifestURL,
+ContentParent::ContentParent(mozIApplication* aApp,
                              bool aIsForBrowser,
+                             bool aIsForPreallocated,
                              ChildPrivileges aOSPrivileges,
                              ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
     : mSubprocess(nullptr)
     , mOSPrivileges(aOSPrivileges)
     , mChildID(gContentChildID++)
     , mGeolocationWatchID(-1)
     , mRunToCompletionDepth(0)
     , mShouldCallUnblockChild(false)
-    , mAppManifestURL(aAppManifestURL)
     , mForceKillTask(nullptr)
     , mNumDestroyingTabs(0)
     , mIsAlive(true)
     , mIsDestroyed(false)
     , mSendPermissionUpdates(false)
     , mIsForBrowser(aIsForBrowser)
 {
+    // No more than one of !!aApp, aIsForBrowser, and aIsForPreallocated should
+    // be true.
+    MOZ_ASSERT(!!aApp + aIsForBrowser + aIsForPreallocated <= 1);
+
     // Insert ourselves into the global linked list of ContentParent objects.
     sContentParents.insertBack(this);
 
+    if (aApp) {
+        aApp->GetManifestURL(mAppManifestURL);
+        aApp->GetName(mAppName);
+    } else if (aIsForPreallocated) {
+        mAppManifestURL = MAGIC_PREALLOCATED_APP_MANIFEST_URL;
+    }
+
     // 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);
 
     mSubprocess->LaunchAndWaitForProcessHandle();
 
-    // Set the subprocess's priority.  We do this first because we're likely
-    // /lowering/ its CPU and memory priority, which it has inherited from this
-    // process.
-    SetProcessPriority(aInitialPriority);
+    Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
 
-    Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
+    // Set the subprocess's priority.  We do this early on because we're likely
+    // /lowering/ the process's CPU and memory priority, which it has inherited
+    // from this process.
+    //
+    // This call can cause us to send IPC messages to the child process, so it
+    // must come after the Open() call above.
+    ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
 
     // 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
@@ -1364,17 +1385,17 @@ ContentParent::RecvAudioChannelUnregiste
 }
 
 bool
 ContentParent::RecvAudioChannelChangedNotification()
 {
     nsRefPtr<AudioChannelService> service =
         AudioChannelService::GetAudioChannelService();
     if (service) {
-       service->SendAudioChannelChangedNotification();
+       service->SendAudioChannelChangedNotification(ChildID());
     }
     return true;
 }
 
 bool
 ContentParent::RecvBroadcastVolume(const nsString& aVolumeName)
 {
 #ifdef MOZ_WIDGET_GONK
@@ -1709,16 +1730,40 @@ ContentParent::KillHard()
         NS_WARNING("failed to kill subprocess!");
     }
     XRE_GetIOMessageLoop()->PostTask(
         FROM_HERE,
         NewRunnableFunction(&ProcessWatcher::EnsureProcessTerminated,
                             OtherProcess(), /*force=*/true));
 }
 
+bool
+ContentParent::IsPreallocated()
+{
+    return mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL;
+}
+
+void
+ContentParent::FriendlyName(nsAString& aName)
+{
+    aName.Truncate();
+    if (IsPreallocated()) {
+        aName.AssignLiteral("(Preallocated)");
+    } else if (mIsForBrowser) {
+        aName.AssignLiteral("Browser");
+    } else if (!mAppName.IsEmpty()) {
+        aName = mAppName;
+    } else if (!mAppManifestURL.IsEmpty()) {
+        aName.AssignLiteral("Unknown app: ");
+        aName.Append(mAppManifestURL);
+    } else {
+        aName.AssignLiteral("???");
+    }
+}
+
 PCrashReporterParent*
 ContentParent::AllocPCrashReporter(const NativeThreadId& tid,
                                    const uint32_t& processType)
 {
 #ifdef MOZ_CRASHREPORTER
   return new CrashReporterParent();
 #else
   return nullptr;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -11,16 +11,17 @@
 
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/HalTypes.h"
+#include "mozilla/LinkedList.h"
 
 #include "nsFrameMessageManager.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsNetUtil.h"
 #include "nsIPermissionManager.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIMemoryReporter.h"
@@ -132,30 +133,43 @@ public:
     bool IsForApp();
 
     void SetChildMemoryReporters(const InfallibleTArray<MemoryReport>& report);
 
     GeckoChildProcessHost* Process() {
         return mSubprocess;
     }
 
+    int32_t Pid() {
+        return base::GetProcId(mSubprocess->GetChildProcessHandle());
+    }
+
     bool NeedsPermissionsUpdate() {
         return mSendPermissionUpdates;
     }
 
     BlobParent* GetOrCreateActorForBlob(nsIDOMBlob* aBlob);
 
     /**
      * Kill our subprocess and make sure it dies.  Should only be used
      * in emergency situations since it bypasses the normal shutdown
      * process.
      */
     void KillHard();
 
     uint64_t ChildID() { return mChildID; }
+    bool IsPreallocated();
+
+    /**
+     * Get a user-friendly name for this ContentParent.  We make no guarantees
+     * about this name: It might not be unique, apps can spoof special names,
+     * etc.  So please don't use this name to make any decisions about the
+     * ContentParent based on the value returned here.
+     */
+    void FriendlyName(nsAString& aName);
 
 protected:
     void OnChannelConnected(int32_t pid);
     virtual void ActorDestroy(ActorDestroyReason why);
 
 private:
     static nsDataHashtable<nsStringHashKey, ContentParent*> *sAppContentParents;
     static nsTArray<ContentParent*>* sNonAppContentParents;
@@ -175,27 +189,28 @@ private:
 
     static hal::ProcessPriority GetInitialProcessPriority(nsIDOMElement* aFrameElement);
 
     // Hide the raw constructor methods since we don't want client code
     // using them.
     using PContentParent::SendPBrowserConstructor;
     using PContentParent::SendPTestShellConstructor;
 
-    ContentParent(const nsAString& aAppManifestURL, bool aIsForBrowser,
+    // No more than one of !!aApp, aIsForBrowser, and aIsForPreallocated may be
+    // true.
+    ContentParent(mozIApplication* aApp,
+                  bool aIsForBrowser,
+                  bool aIsForPreallocated,
                   ChildPrivileges aOSPrivileges = base::PRIVILEGES_DEFAULT,
                   hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
+
     virtual ~ContentParent();
 
     void Init();
 
-    // Set the child process's priority.  Once the child starts up, it will
-    // manage its own priority via the ProcessPriorityManager.
-    void SetProcessPriority(hal::ProcessPriority aInitialPriority);
-
     // If the frame element indicates that the child process is "critical" and
     // has a pending system message, this function acquires the CPU wake lock on
     // behalf of the child.  We'll release the lock when the system message is
     // handled or after a timeout, whichever comes first.
     void MaybeTakeCPUWakeLock(nsIDOMElement* aFrameElement);
 
     // Set the child process's priority and then check whether the child is
     // still alive.  Returns true if the process is still alive, and false
@@ -401,17 +416,25 @@ private:
     bool mShouldCallUnblockChild;
 
     // This is a cache of all of the memory reporters
     // registered in the child process.  To update this, one
     // can broadcast the topic "child-memory-reporter-request" using
     // the nsIObserverService.
     nsCOMArray<nsIMemoryReporter> mMemoryReporters;
 
-    const nsString mAppManifestURL;
+    nsString mAppManifestURL;
+
+    /**
+     * We cache mAppName instead of looking it up using mAppManifestURL when we
+     * need it because it turns out that getting an app from the apps service is
+     * expensive.
+     */
+    nsString mAppName;
+
     nsRefPtr<nsFrameMessageManager> mMessageManager;
 
     // After we initiate shutdown, we also start a timer to ensure
     // that even content processes that are 100% blocked (say from
     // SIGSTOP), are still killed eventually.  This task enforces that
     // timer.
     CancelableTask* mForceKillTask;
     // How many tabs we're waiting to finish their destruction
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -380,30 +380,16 @@ child:
      * the document.  The rendered image will be of size |renderSize|.
      */
     PDocumentRenderer(nsRect documentRect, gfxMatrix transform,
                       nsString bgcolor,
                       uint32_t renderFlags, bool flushLayout,
                       nsIntSize renderSize);
 
     /**
-     * Send the child its app type.  The app type identifies the kind of app
-     * shown in this PBrowser.  Currently, the only recognized app type is
-     * "homescreen".
-     *
-     * The value here corresponds to the "mozapptype" attribute on iframes.  For
-     * example, <iframe mozbrowser mozapp="..." mozapptype="homescreen">.
-     *
-     * Only app frames (i.e., frames with an app-id) should have a non-empty app
-     * type.  If you try to SetAppType() with a non-empty app type on a non-app
-     * PBrowserChild, we may assert.
-     */
-    SetAppType(nsString appType);
-
-    /**
      * Sent by the chrome process when it no longer wants this remote
      * <browser>.  The child side cleans up in response, then
      * finalizing its death by sending back __delete__() to the
      * parent.
      */
     Destroy();
 
 /*
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -22,16 +22,17 @@ include protocol PStorage;
 include protocol PTestShell;
 include DOMTypes;
 include InputStreamParams;
 include URIParams;
 
 include "mozilla/chrome/RegistryMessageUtils.h";
 include "mozilla/dom/PermissionMessageUtils.h";
 include "mozilla/dom/TabMessageUtils.h";
+include "mozilla/HalTypes.h";
 include "mozilla/layout/RenderFrameUtils.h";
 include "mozilla/net/NeckoMessageUtils.h";
 include "nsGeoPositionIPCSerialiser.h";
 
 using GeoPosition;
 using PrefTuple;
 
 using ChromePackage;
@@ -39,16 +40,17 @@ using ResourceMapping;
 using OverrideMapping;
 using base::ChildPrivileges;
 using IPC::Permission;
 using IPC::Principal;
 using mozilla::null_t;
 using mozilla::void_t;
 using mozilla::dom::AudioChannelType;
 using mozilla::dom::NativeThreadId;
+using mozilla::hal::ProcessPriority;
 using mozilla::layout::ScrollingBehavior;
 using gfxIntSize;
 
 namespace mozilla {
 namespace dom {
 
 struct FontListEntry {
     nsString  familyName;
@@ -344,16 +346,20 @@ child:
     // Notify child that last-pb-context-exited notification was observed
     LastPrivateDocShellDestroyed();
 
     FilePathUpdate(nsString type, nsString filepath, nsCString reasons);
 
     FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
                      int32_t mountGeneration);
 
+    NotifyProcessPriorityChanged(ProcessPriority priority);
+    MinimizeMemoryUsage();
+    CancelMinimizeMemoryUsage();
+
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -1,658 +1,948 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "ProcessPriorityManager.h"
 #include "mozilla/ClearOnShutdown.h"
-#include "mozilla/ProcessPriorityManager.h"
-#include "mozilla/dom/ContentChild.h"
-#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
 #include "mozilla/Hal.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
-#include "mozilla/HalTypes.h"
-#include "mozilla/TimeStamp.h"
+#include "mozilla/unused.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"
 #include "nsIDOMWindow.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMDocument.h"
 #include "nsPIDOMWindow.h"
 #include "StaticPtr.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsPrintfCString.h"
+#include "prlog.h"
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
 #else
 #include <unistd.h>
 #endif
 
-using namespace mozilla::hal;
-
-namespace mozilla {
-namespace dom {
-namespace ipc {
-
-namespace {
-static bool sInitialized = false;
-class ProcessPriorityManager;
-static StaticRefPtr<ProcessPriorityManager> sManager;
-
-// Some header defines a LOG macro, but we don't want it here.
 #ifdef LOG
 #undef LOG
 #endif
 
-// Enable logging by setting
-//
-//   NSPR_LOG_MODULES=ProcessPriorityManager:5
+// Use LOGP inside a ParticularProcessPriorityManager method; use LOG
+// everywhere else.  LOGP prints out information about the particular process
+// priority manager.
 //
-// in your environment.  Or just comment out the "&& 0" below, if you're on
-// Android/B2G.
+// (Wow, our logging story is a huge mess.)
+
+// #define ENABLE_LOGGING 1
 
-#if defined(ANDROID) && 0
-#include <android/log.h>
-#define LOG(fmt, ...) \
-  __android_log_print(ANDROID_LOG_INFO, \
+#if defined(ANDROID) && defined(ENABLE_LOGGING)
+#  include <android/log.h>
+#  define LOG(fmt, ...) \
+     __android_log_print(ANDROID_LOG_INFO, \
+       "Gecko:ProcessPriorityManager", \
+       fmt, ## __VA_ARGS__)
+#  define LOGP(fmt, ...) \
+    __android_log_print(ANDROID_LOG_INFO, \
       "Gecko:ProcessPriorityManager", \
-      fmt, ## __VA_ARGS__)
+      "[%schild-id=%llu, pid=%d] " fmt, \
+      NameWithComma().get(), \
+      (long long unsigned) ChildID(), Pid(), ## __VA_ARGS__)
+
+#elif defined(ENABLE_LOGGING)
+#  define LOG(fmt, ...) \
+     printf("ProcessPriorityManager - " fmt "\n", ##__VA_ARGS__)
+#  define LOGP(fmt, ...) \
+     printf("ProcessPriorityManager[%schild-id=%llu, pid=%d] - " fmt "\n", \
+       NameWithComma().get(), \
+       (unsigned long long) ChildID(), Pid(), ##__VA_ARGS__)
+
 #elif defined(PR_LOGGING)
-static PRLogModuleInfo*
-GetPPMLog()
-{
-  static PRLogModuleInfo *sLog;
-  if (!sLog)
-    sLog = PR_NewLogModule("ProcessPriorityManager");
-  return sLog;
-}
-#define LOG(fmt, ...) \
-  PR_LOG(GetPPMLog(), PR_LOG_DEBUG,                                     \
-         ("[%d] ProcessPriorityManager - " fmt, getpid(), ##__VA_ARGS__))
+  static PRLogModuleInfo*
+  GetPPMLog()
+  {
+    static PRLogModuleInfo *sLog;
+    if (!sLog)
+      sLog = PR_NewLogModule("ProcessPriorityManager");
+    return sLog;
+  }
+#  define LOG(fmt, ...) \
+     PR_LOG(GetPPMLog(), PR_LOG_DEBUG, \
+            ("ProcessPriorityManager - " fmt, ##__VA_ARGS__))
+#  define LOGP(fmt, ...) \
+     PR_LOG(GetPPMLog(), PR_LOG_DEBUG, \
+            ("ProcessPriorityManager[%schild-id=%llu, pid=%d] - " fmt, \
+            NameWithComma().get(), \
+            (unsigned long long) ChildID(), Pid(), ##__VA_ARGS__))
 #else
 #define LOG(fmt, ...)
+#define LOGP(fmt, ...)
 #endif
 
-uint64_t
-GetContentChildID()
-{
-  ContentChild* contentChild = ContentChild::GetSingleton();
-  if (!contentChild) {
-    return 0;
-  }
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::hal;
 
-  return contentChild->GetID();
-}
+namespace {
+
+class ParticularProcessPriorityManager;
 
 /**
- * This class listens to various Gecko events and asks the hal back-end to
- * change this process's priority when it transitions between various states of
- * "importance".
- *
- * The process's priority determines its CPU priority and also how likely it is
- * to be killed when the system is running out of memory.
- *
- * The most basic dichotomy in the ProcessPriorityManager is between
- * "foreground" processes, which usually have at least one active docshell, and
- * "background" processes.
- *
- * An important heuristic here is that we don't always mark a process as having
- * "background" priority until it's met the requisite criteria for some amount
- * of time.
+ * This singleton class does the work to implement the process priority manager
+ * in the main process.  This class may not be used in child processes.  (You
+ * can call StaticInit, but it won't do anything, and GetSingleton() will
+ * return null.)
  *
- * We do this because otherwise there are cases where we'd thrash a process
- * between foreground and background priorities; for example, Gaia sometimes
- * releases and re-acquires CPU wake locks in quick succession.
- *
- * On the other hand, when the embedder of an <iframe mozbrowser> calls
- * setVisible(false) on an iframe, we immediately send the relevant process to
- * the background, if it has no other foreground docshells.  This is necessary
- * to ensure that when we load an app, the embedder first has a chance to send
- * the previous app into the background.  This ensures that there's only one
- * foreground app at a time, thus ensuring that we kill the right process if we
- * come under memory pressure.
+ * ProcessPriorityManager::CurrentProcessIsForeground(), which can be called in
+ * any process, is handled separately, by the ProcessPriorityManagerChild
+ * class.
  */
-class ProcessPriorityManager MOZ_FINAL
+class ProcessPriorityManagerImpl MOZ_FINAL
   : public nsIObserver
-  , public nsIDOMEventListener
-  , public nsITimerCallback
-  , public WakeLockObserver
 {
 public:
-  ProcessPriorityManager();
-  void Init();
+  /**
+   * If we're in the main process, get the ProcessPriorityManagerImpl
+   * singleton.  If we're in a child process, return null.
+   */
+  static ProcessPriorityManagerImpl* GetSingleton();
+
+  static void StaticInit();
+  static bool PrefsEnabled();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSITIMERCALLBACK
   NS_DECL_NSIOBSERVER
-  NS_DECL_NSIDOMEVENTLISTENER
-  void Notify(const WakeLockInformation& aWakeLockInfo);
 
-  ProcessPriority GetPriority() const { return mProcessPriority; }
+  /**
+   * This function implements ProcessPriorityManager::SetProcessPriority.
+   */
+  void SetProcessPriority(ContentParent* aContentParent,
+                          ProcessPriority aPriority);
 
   /**
-   * This function doesn't do exactly what you might think; see the comment on
-   * ProcessPriorityManager.h::TemporarilyLockProcessPriority().
+   * If a magic testing-only pref is set, notify the observer service on the
+   * given topic with the given data.  This is used for testing
    */
-  void TemporarilyLockProcessPriority();
+  void FireTestOnlyObserverNotification(const char* aTopic,
+                                        const nsACString& aData = EmptyCString());
+
+private:
+  static bool sPrefListenersRegistered;
+  static bool sInitialized;
+  static StaticRefPtr<ProcessPriorityManagerImpl> sSingleton;
+
+  static int PrefChangedCallback(const char* aPref, void* aClosure);
+
+  ProcessPriorityManagerImpl();
+  ~ProcessPriorityManagerImpl() {}
+  DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManagerImpl);
+
+  void Init();
+
+  already_AddRefed<ParticularProcessPriorityManager>
+  GetParticularProcessPriorityManager(ContentParent* aContentParent);
+
+  void ObserveContentParentCreated(nsISupports* aContentParent);
+  void ObserveContentParentDestroyed(nsISupports* aSubject);
+
+  nsDataHashtable<nsUint64HashKey, nsRefPtr<ParticularProcessPriorityManager> >
+    mParticularManagers;
+};
+
+/**
+ * This singleton class implements the parts of the process priority manager
+ * that are available from all processes.
+ */
+class ProcessPriorityManagerChild MOZ_FINAL
+  : public nsIObserver
+{
+public:
+  static void StaticInit();
+  static ProcessPriorityManagerChild* Singleton();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  bool CurrentProcessIsForeground();
+
+private:
+  static StaticRefPtr<ProcessPriorityManagerChild> sSingleton;
+
+  ProcessPriorityManagerChild();
+  ~ProcessPriorityManagerChild() {}
+  DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManagerChild);
+
+  void Init();
+
+  hal::ProcessPriority mCachedPriority;
+};
+
+/**
+ * This class manages the priority of one particular process.  It is
+ * main-process only.
+ */
+class ParticularProcessPriorityManager MOZ_FINAL
+  : public WakeLockObserver
+  , public nsIObserver
+  , public nsITimerCallback
+{
+public:
+  ParticularProcessPriorityManager(ContentParent* aContentParent);
+  ~ParticularProcessPriorityManager();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSITIMERCALLBACK
+
+  virtual void Notify(const WakeLockInformation& aInfo) MOZ_OVERRIDE;
+  void Init();
+
+  int32_t Pid() const;
+  uint64_t ChildID() const;
 
   /**
-   * Recompute this process's priority and apply it, potentially after a brief
-   * delay.
-   *
-   * If we are transitioning to a priority that is lower than the current
-   * priority, that transition happens after a grace period.  Otherwise the
-   * transition happens immediately.
+   * Used in logging, this method returns the ContentParent's name followed by
+   * ", ".  If we can't get the ContentParent's name for some reason, it
+   * returns an empty string.
    *
-   * Note that PROCESS_PRIORITY_UNKNOWN is considered the highest priority.
-   * Going from UNKNOWN to any other priority requires a grace period.
+   * The reference returned here is guaranteed to be live until the next call
+   * to NameWithComma() or until the ParticularProcessPriorityManager is
+   * destroyed, whichever comes first.
    */
+  const nsAutoCString& NameWithComma();
+
+  bool HasAppType(const char* aAppType);
+
+  void OnAudioChannelProcessChanged(nsISupports* aSubject);
+  void OnRemoteBrowserFrameShown(nsISupports* aSubject);
+  void OnTabParentDestroyed(nsISupports* aSubject);
+  void OnFrameloaderVisibleChanged(nsISupports* aSubject);
+  void OnChannelConnected(nsISupports* aSubject);
+
+  ProcessPriority ComputePriority();
+  void ScheduleResetPriority(const char* aTimeoutPref);
   void ResetPriority();
-
-  /**
-   * Recompute this process's priority and apply it immediately.
-   */
   void ResetPriorityNow();
 
-private:
-  void OnContentDocumentGlobalCreated(nsISupports* aOuterWindow);
-
-  /**
-   * Is this process a "critical" process that's holding the "CPU" or
-   * "high-priority" wake lock?
-   */
-  bool IsCriticalProcessWithWakeLock();
-
-  /**
-   * Compute whether this process is in the foreground and return the result.
-   */
-  bool ComputeIsInForeground();
-
-  /**
-   * Compute the priority this process ought to have, based on what we know at
-   * the moment.
-   */
-  ProcessPriority ComputePriority();
-
-  /**
-   * Immediately set this process's priority to the given priority.
-   */
   void SetPriorityNow(ProcessPriority aPriority);
 
-  /**
-   * 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);
+  void ShutDown();
+
+private:
+  void FireTestOnlyObserverNotification(
+    const char* aTopic,
+    const nsACString& aData = EmptyCString());
 
-  // Tracks whether this process holds the "cpu" lock.
+  void FireTestOnlyObserverNotification(
+    const char* aTopic,
+    const char* aData = nullptr);
+
+  ContentParent* mContentParent;
+  uint64_t mChildID;
+  ProcessPriority mPriority;
   bool mHoldsCPUWakeLock;
-
-  // Tracks whether this process holds the "high-priority" lock.
   bool mHoldsHighPriorityWakeLock;
 
-  // mProcessPriority tracks the priority we've given this process in hal.
-  ProcessPriority mProcessPriority;
-
-  // Have we seen at least one tab-child-created event yet?  Until this is
-  // true, ResetPriority() and ResetPriorityNow() do nothing.
-  bool mObservedTabChildCreated;
+  /**
+   * Used to implement NameWithComma().
+   */
+  nsAutoCString mNameWithComma;
 
-  nsTArray<nsWeakPtr> mWindows;
-
-  // When this timer expires, we set mResetPriorityTimer to null and run
-  // ResetPriorityNow().
   nsCOMPtr<nsITimer> mResetPriorityTimer;
-
-  nsWeakPtr mMemoryMinimizerRunnable;
 };
 
-NS_IMPL_ISUPPORTS3(ProcessPriorityManager,
-                   nsIObserver,
-                   nsIDOMEventListener,
-                   nsITimerCallback)
+/* static */ bool ProcessPriorityManagerImpl::sInitialized = false;
+/* static */ bool ProcessPriorityManagerImpl::sPrefListenersRegistered = false;
+/* static */ StaticRefPtr<ProcessPriorityManagerImpl>
+  ProcessPriorityManagerImpl::sSingleton;
+
+NS_IMPL_ISUPPORTS1(ProcessPriorityManagerImpl,
+                   nsIObserver);
+
+/* static */ int
+ProcessPriorityManagerImpl::PrefChangedCallback(const char* aPref,
+                                                void* aClosure)
+{
+  StaticInit();
+  return 0;
+}
+
+/* static */ bool
+ProcessPriorityManagerImpl::PrefsEnabled()
+{
+  return Preferences::GetBool("dom.ipc.processPriorityManager.enabled") &&
+         !Preferences::GetBool("dom.ipc.tabs.disabled");
+}
+
+/* static */ void
+ProcessPriorityManagerImpl::StaticInit()
+{
+  if (sInitialized) {
+    return;
+  }
+
+  // The process priority manager is main-process only.
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    sInitialized = true;
+    return;
+  }
 
-ProcessPriorityManager::ProcessPriorityManager()
-  : mHoldsCPUWakeLock(false)
-  , mHoldsHighPriorityWakeLock(false)
-  , mProcessPriority(ProcessPriority(-1))
-  , mObservedTabChildCreated(false)
+  // If IPC tabs aren't enabled at startup, don't bother with any of this.
+  if (!PrefsEnabled()) {
+    LOG("InitProcessPriorityManager bailing due to prefs.");
+
+    // Run StaticInit() again if the prefs change.  We don't expect this to
+    // happen in normal operation, but it happens during testing.
+    if (!sPrefListenersRegistered) {
+      sPrefListenersRegistered = true;
+      Preferences::RegisterCallback(PrefChangedCallback,
+                                    "dom.ipc.processPriorityManager.enabled");
+      Preferences::RegisterCallback(PrefChangedCallback,
+                                    "dom.ipc.tabs.disabled");
+    }
+    return;
+  }
+
+  sInitialized = true;
+
+  sSingleton = new ProcessPriorityManagerImpl();
+  sSingleton->Init();
+  ClearOnShutdown(&sSingleton);
+}
+
+/* static */ ProcessPriorityManagerImpl*
+ProcessPriorityManagerImpl::GetSingleton()
 {
-  // When our parent process forked us, it may have set our process's priority
-  // to one of a few of the process priorities, depending on exactly why this
-  // process was created.
-  //
-  // We don't know which priority we were given, so we set mProcessPriority to
-  // -1 so that the next time ResetPriorityNow is run, we'll definitely call
-  // into hal and set our priority.
+  if (!sSingleton) {
+    StaticInit();
+  }
+
+  return sSingleton;
+}
+
+ProcessPriorityManagerImpl::ProcessPriorityManagerImpl()
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+  mParticularManagers.Init();
 }
 
 void
-ProcessPriorityManager::Init()
+ProcessPriorityManagerImpl::Init()
+{
+  LOG("Starting up.  This is the master process.");
+
+  // The master process's priority never changes; set it here and then forget
+  // about it.  We'll manage only subprocesses' priorities using the process
+  // priority manager.
+  hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER);
+
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (os) {
+    os->AddObserver(this, "ipc:content-created", /* ownsWeak */ false);
+    os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ false);
+  }
+}
+
+NS_IMETHODIMP
+ProcessPriorityManagerImpl::Observe(
+  nsISupports* aSubject,
+  const char* aTopic,
+  const PRUnichar* aData)
 {
-  LOG("Starting up.");
+  nsDependentCString topic(aTopic);
+  if (topic.EqualsLiteral("ipc:content-created")) {
+    ObserveContentParentCreated(aSubject);
+  } else if (topic.EqualsLiteral("ipc:content-shutdown")) {
+    ObserveContentParentDestroyed(aSubject);
+  } else {
+    MOZ_ASSERT(false);
+  }
+
+  return NS_OK;
+}
+
+already_AddRefed<ParticularProcessPriorityManager>
+ProcessPriorityManagerImpl::GetParticularProcessPriorityManager(
+  ContentParent* aContentParent)
+{
+  nsRefPtr<ParticularProcessPriorityManager> pppm;
+  mParticularManagers.Get(aContentParent->ChildID(), &pppm);
+  if (!pppm) {
+    pppm = new ParticularProcessPriorityManager(aContentParent);
+    pppm->Init();
+    mParticularManagers.Put(aContentParent->ChildID(), pppm);
+
+    FireTestOnlyObserverNotification("process-created",
+      nsPrintfCString("%lld", aContentParent->ChildID()));
+  }
+
+  return pppm.forget();
+}
 
-  // We can't do this in the constructor because we need to hold a strong ref
-  // to |this| before calling these methods.
-  //
-  // Notice that we track /window/ creation and destruction even though our
-  // notion of "is-foreground" is tied to /docshell/ activity.  We do this
-  // because docshells don't fire an event when their visibility changes, but
-  // windows do.
+void
+ProcessPriorityManagerImpl::SetProcessPriority(ContentParent* aContentParent,
+                                               ProcessPriority aPriority)
+{
+  MOZ_ASSERT(aContentParent);
+  nsRefPtr<ParticularProcessPriorityManager> pppm =
+    GetParticularProcessPriorityManager(aContentParent);
+  pppm->SetPriorityNow(aPriority);
+}
+
+void
+ProcessPriorityManagerImpl::ObserveContentParentCreated(
+  nsISupports* aContentParent)
+{
+  // Do nothing; it's sufficient to get the PPPM.  But assign to nsRefPtr so we
+  // don't leak the already_AddRefed object.
+  nsCOMPtr<nsIObserver> cp = do_QueryInterface(aContentParent);
+  nsRefPtr<ParticularProcessPriorityManager> pppm =
+    GetParticularProcessPriorityManager(static_cast<ContentParent*>(cp.get()));
+}
+
+void
+ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject)
+{
+  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE_VOID(props);
+
+  uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN;
+  props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
+  NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN);
+
+  nsRefPtr<ParticularProcessPriorityManager> pppm;
+  mParticularManagers.Get(childID, &pppm);
+  MOZ_ASSERT(pppm);
+  if (pppm) {
+    pppm->ShutDown();
+  }
+
+  mParticularManagers.Remove(childID);
+}
+
+NS_IMPL_ISUPPORTS2(ParticularProcessPriorityManager,
+                   nsIObserver,
+                   nsITimerCallback);
+
+ParticularProcessPriorityManager::ParticularProcessPriorityManager(
+  ContentParent* aContentParent)
+  : mContentParent(aContentParent)
+  , mChildID(aContentParent->ChildID())
+  , mPriority(PROCESS_PRIORITY_UNKNOWN)
+  , mHoldsCPUWakeLock(false)
+  , mHoldsHighPriorityWakeLock(false)
+{
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+  LOGP("Creating ParticularProcessPriorityManager.");
+}
+
+void
+ParticularProcessPriorityManager::Init()
+{
+  RegisterWakeLockObserver(this);
+
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-  os->AddObserver(this, "tab-child-created", /* ownsWeak = */ false);
-  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);
-  os->AddObserver(this, "process-priority:reset-now", /* ownsWeak = */ false);
-
-  RegisterWakeLockObserver(this);
+  if (os) {
+    os->AddObserver(this, "audio-channel-process-changed", /* ownsWeak */ false);
+    os->AddObserver(this, "remote-browser-frame-shown", /* ownsWeak */ false);
+    os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ false);
+    os->AddObserver(this, "frameloader-visible-changed", /* ownsWeak */ false);
+  }
 
   // This process may already hold the CPU lock; for example, our parent may
   // have acquired it on our behalf.
   WakeLockInformation info1, info2;
   GetWakeLockInfo(NS_LITERAL_STRING("cpu"), &info1);
-  mHoldsCPUWakeLock = info1.lockingProcesses().Contains(GetContentChildID());
+  mHoldsCPUWakeLock = info1.lockingProcesses().Contains(ChildID());
 
   GetWakeLockInfo(NS_LITERAL_STRING("high-priority"), &info2);
-  mHoldsHighPriorityWakeLock = info2.lockingProcesses().Contains(GetContentChildID());
-
-  LOG("Done starting up.  mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d",
-      mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock);
+  mHoldsHighPriorityWakeLock = info2.lockingProcesses().Contains(ChildID());
+  LOGP("Done starting up.  mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d",
+       mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock);
 }
 
-NS_IMETHODIMP
-ProcessPriorityManager::Observe(
-  nsISupports* aSubject,
-  const char* aTopic,
-  const PRUnichar* aData)
+ParticularProcessPriorityManager::~ParticularProcessPriorityManager()
 {
-  if (!strcmp(aTopic, "tab-child-created")) {
-    mObservedTabChildCreated = true;
-    ResetPriority();
-  } else if (!strcmp(aTopic, "content-document-global-created")) {
-    OnContentDocumentGlobalCreated(aSubject);
-  } else if (!strcmp(aTopic, "inner-window-destroyed") ||
-             !strcmp(aTopic, "audio-channel-agent-changed")) {
-    ResetPriority();
-  } else if (!strcmp(aTopic, "process-priority:reset-now")) {
-    LOG("Got process-priority:reset-now notification.");
-    ResetPriorityNow();
-  } else {
-    MOZ_ASSERT(false);
-  }
-  return NS_OK;
+  LOGP("Destroying ParticularProcessPriorityManager.");
+  UnregisterWakeLockObserver(this);
 }
 
-void
-ProcessPriorityManager::Notify(const WakeLockInformation& aInfo)
+/* virtual */ void
+ParticularProcessPriorityManager::Notify(const WakeLockInformation& aInfo)
 {
   bool* dest = nullptr;
-  if (aInfo.topic() == NS_LITERAL_STRING("cpu")) {
+  if (aInfo.topic().EqualsLiteral("cpu")) {
     dest = &mHoldsCPUWakeLock;
-  } else if (aInfo.topic() == NS_LITERAL_STRING("high-priority")) {
+  } else if (aInfo.topic().EqualsLiteral("high-priority")) {
     dest = &mHoldsHighPriorityWakeLock;
   }
 
   if (dest) {
-    bool thisProcessLocks =
-      aInfo.lockingProcesses().Contains(GetContentChildID());
-
+    bool thisProcessLocks = aInfo.lockingProcesses().Contains(ChildID());
     if (thisProcessLocks != *dest) {
       *dest = thisProcessLocks;
-      LOG("Got wake lock changed event. "
-          "Now mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d",
-          mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock);
+      LOGP("Got wake lock changed event. "
+           "Now mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d",
+           mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock);
       ResetPriority();
     }
   }
 }
 
 NS_IMETHODIMP
-ProcessPriorityManager::HandleEvent(
-  nsIDOMEvent* aEvent)
+ParticularProcessPriorityManager::Observe(nsISupports* aSubject,
+                                          const char* aTopic,
+                                          const PRUnichar* aData)
+{
+  if (!mContentParent) {
+    // We've been shut down.
+    return NS_OK;
+  }
+
+  nsDependentCString topic(aTopic);
+
+  if (topic.EqualsLiteral("audio-channel-process-changed")) {
+    OnAudioChannelProcessChanged(aSubject);
+  } else if (topic.EqualsLiteral("remote-browser-frame-shown")) {
+    OnRemoteBrowserFrameShown(aSubject);
+  } else if (topic.EqualsLiteral("ipc:browser-destroyed")) {
+    OnTabParentDestroyed(aSubject);
+  } else if (topic.EqualsLiteral("frameloader-visible-changed")) {
+    OnFrameloaderVisibleChanged(aSubject);
+  } else {
+    MOZ_ASSERT(false);
+  }
+
+  return NS_OK;
+}
+
+uint64_t
+ParticularProcessPriorityManager::ChildID() const
 {
-  LOG("Got visibilitychange.");
-  ResetPriority();
-  return NS_OK;
+  // We have to cache mContentParent->ChildID() instead of getting it from the
+  // ContentParent each time because after ShutDown() is called, mContentParent
+  // is null.  If we didn't cache ChildID(), then we wouldn't be able to run
+  // LOGP() after ShutDown().
+  return mChildID;
+}
+
+int32_t
+ParticularProcessPriorityManager::Pid() const
+{
+  return mContentParent ? mContentParent->Pid() : -1;
+}
+
+const nsAutoCString&
+ParticularProcessPriorityManager::NameWithComma()
+{
+  mNameWithComma.Truncate();
+  if (!mContentParent) {
+    return mNameWithComma; // empty string
+  }
+
+  nsAutoString name;
+  mContentParent->FriendlyName(name);
+  if (name.IsEmpty()) {
+    return mNameWithComma; // empty string
+  }
+
+  mNameWithComma = NS_ConvertUTF16toUTF8(name);
+  mNameWithComma.AppendLiteral(", ");
+  return mNameWithComma;
 }
 
 void
-ProcessPriorityManager::OnContentDocumentGlobalCreated(
-  nsISupports* aOuterWindow)
+ParticularProcessPriorityManager::OnAudioChannelProcessChanged(nsISupports* aSubject)
 {
-  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);
+  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE_VOID(props);
+
+  uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN;
+  props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), &childID);
+  if (childID == ChildID()) {
+    ResetPriority();
+  }
+}
 
-  // We're only interested in top-level windows.
-  nsCOMPtr<nsIDOMWindow> parentOuterWindow;
-  innerWindow->GetScriptableParent(getter_AddRefs(parentOuterWindow));
-  NS_ENSURE_TRUE_VOID(parentOuterWindow);
-  if (parentOuterWindow != outerWindow) {
+void
+ParticularProcessPriorityManager::OnRemoteBrowserFrameShown(nsISupports* aSubject)
+{
+  nsCOMPtr<nsIFrameLoader> fl = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE_VOID(fl);
+
+  nsCOMPtr<nsITabParent> tp;
+  fl->GetTabParent(getter_AddRefs(tp));
+  NS_ENSURE_TRUE_VOID(tp);
+
+  if (static_cast<TabParent*>(tp.get())->Manager() != mContentParent) {
     return;
   }
 
-  nsCOMPtr<EventTarget> target = do_QueryInterface(innerWindow);
-  NS_ENSURE_TRUE_VOID(target);
-
-  nsWeakPtr weakWin = do_GetWeakReference(innerWindow);
-  NS_ENSURE_TRUE_VOID(weakWin);
-
-  if (mWindows.Contains(weakWin)) {
-    return;
-  }
-
-  target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"),
-                                 this,
-                                 /* useCapture = */ false,
-                                 /* wantsUntrusted = */ false);
-
-  mWindows.AppendElement(weakWin);
-
   ResetPriority();
 }
 
-bool
-ProcessPriorityManager::IsCriticalProcessWithWakeLock()
+void
+ParticularProcessPriorityManager::OnTabParentDestroyed(nsISupports* aSubject)
 {
-  if (!(mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock)) {
-    return false;
-  }
+  nsCOMPtr<nsITabParent> tp = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE_VOID(tp);
 
-  ContentChild* contentChild = ContentChild::GetSingleton();
-  if (!contentChild) {
-    return false;
+  if (static_cast<TabParent*>(tp.get())->Manager() != mContentParent) {
+    return;
   }
 
-  const InfallibleTArray<PBrowserChild*>& browsers =
-    contentChild->ManagedPBrowserChild();
-  for (uint32_t i = 0; i < browsers.Length(); i++) {
-    nsAutoString appType;
-    static_cast<TabChild*>(browsers[i])->GetAppType(appType);
-    if (appType.EqualsLiteral("critical")) {
-      return true;
-    }
-  }
-
-  return false;
+  ResetPriority();
 }
 
 void
-ProcessPriorityManager::ResetPriority()
+ParticularProcessPriorityManager::OnFrameloaderVisibleChanged(nsISupports* aSubject)
 {
-  if (!mObservedTabChildCreated) {
-    LOG("ResetPriority bailing because we haven't observed "
-        "a tab-child-created event.");
+  nsCOMPtr<nsIFrameLoader> fl = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE_VOID(fl);
+
+  nsCOMPtr<nsITabParent> tp;
+  fl->GetTabParent(getter_AddRefs(tp));
+  if (!tp) {
+    return;
+  }
+
+  if (static_cast<TabParent*>(tp.get())->Manager() != mContentParent) {
     return;
   }
 
+  // Most of the time when something changes in a process we call
+  // ResetPriority(), giving a grace period before downgrading its priority.
+  // But notice that here don't give a grace period: We call ResetPriorityNow()
+  // instead.
+  //
+  // We do this because we're reacting here to a setVisibility() call, which is
+  // an explicit signal from the process embedder that we should re-prioritize
+  // a process.  If we gave a grace period in response to setVisibility()
+  // calls, it would be impossible for the embedder to explicitly prioritize
+  // processes and prevent e.g. the case where we switch which process is in
+  // the foreground and, during the old fg processs's grace period, it OOMs the
+  // new fg process.
+
+  ResetPriorityNow();
+}
+
+void
+ParticularProcessPriorityManager::ResetPriority()
+{
   ProcessPriority processPriority = ComputePriority();
-  if (mProcessPriority == PROCESS_PRIORITY_UNKNOWN ||
-      mProcessPriority > processPriority) {
+  if (mPriority == PROCESS_PRIORITY_UNKNOWN ||
+      mPriority > processPriority) {
     ScheduleResetPriority("backgroundGracePeriodMS");
     return;
   }
 
   SetPriorityNow(processPriority);
 }
 
 void
-ProcessPriorityManager::ResetPriorityNow()
+ParticularProcessPriorityManager::ResetPriorityNow()
 {
-  if (!mObservedTabChildCreated) {
-    LOG("ResetPriorityNow bailing because we haven't observed "
-        "a tab-child-created event.");
+  SetPriorityNow(ComputePriority());
+}
+
+void
+ParticularProcessPriorityManager::ScheduleResetPriority(const char* aTimeoutPref)
+{
+  if (mResetPriorityTimer) {
+    LOGP("ScheduleResetPriority bailing; the timer is already running.");
     return;
   }
 
-  SetPriorityNow(ComputePriority());
+  uint32_t timeout = Preferences::GetUint(
+    nsPrintfCString("dom.ipc.processPriorityManager.%s", aTimeoutPref).get());
+  LOGP("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
+ParticularProcessPriorityManager::Notify(nsITimer* aTimer)
+{
+  LOGP("Reset priority timer callback; about to ResetPriorityNow.");
+  ResetPriorityNow();
+  mResetPriorityTimer = nullptr;
+  return NS_OK;
 }
 
 bool
-ProcessPriorityManager::ComputeIsInForeground()
+ParticularProcessPriorityManager::HasAppType(const char* aAppType)
 {
-  // Critical processes holding the CPU/high-priority wake lock are always
-  // considered to be in the foreground.
-  if (IsCriticalProcessWithWakeLock()) {
-    return true;
+  const InfallibleTArray<PBrowserParent*>& browsers =
+    mContentParent->ManagedPBrowserParent();
+  for (uint32_t i = 0; i < browsers.Length(); i++) {
+    nsAutoString appType;
+    static_cast<TabParent*>(browsers[i])->GetAppType(appType);
+    if (appType.EqualsASCII(aAppType)) {
+      return true;
+    }
   }
 
-  // We could try to be clever and keep a running count of the number of active
-  // docshells, 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.
-
-  bool allHidden = true;
-  for (uint32_t i = 0; i < mWindows.Length(); i++) {
-    nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindows[i]);
-    if (!window) {
-      mWindows.RemoveElementAt(i);
-      i--;
-      continue;
-    }
-
-    nsCOMPtr<nsIDocShell> docshell = do_GetInterface(window);
-    if (!docshell) {
-      continue;
-    }
-
-    bool isActive = false;
-    docshell->GetIsActive(&isActive);
-
-
-#ifdef DEBUG
-    nsAutoCString spec;
-    nsCOMPtr<nsIURI> uri = window->GetDocumentURI();
-    if (uri) {
-      uri->GetSpec(spec);
-    }
-    LOG("Docshell at %s has visibility %d.", spec.get(), isActive);
-#endif
-
-    allHidden = allHidden && !isActive;
-
-    // We could break out early from this loop if
-    //   isActive && mProcessPriority == BACKGROUND,
-    // but then we might not clean up all the weak refs.
-  }
-
-  return !allHidden;
+  return false;
 }
 
 ProcessPriority
-ProcessPriorityManager::ComputePriority()
+ParticularProcessPriorityManager::ComputePriority()
 {
-  if (ComputeIsInForeground()) {
-    if (IsCriticalProcessWithWakeLock()) {
-      return PROCESS_PRIORITY_FOREGROUND_HIGH;
+  if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) &&
+      HasAppType("critical")) {
+    return PROCESS_PRIORITY_FOREGROUND_HIGH;
+  }
+
+  bool isVisible = false;
+  const InfallibleTArray<PBrowserParent*>& browsers =
+    mContentParent->ManagedPBrowserParent();
+  for (uint32_t i = 0; i < browsers.Length(); i++) {
+    if (static_cast<TabParent*>(browsers[i])->IsVisible()) {
+      isVisible = true;
+      break;
     }
+  }
+
+  if (isVisible) {
     return PROCESS_PRIORITY_FOREGROUND;
   }
 
   AudioChannelService* service = AudioChannelService::GetAudioChannelService();
-  if (service->ContentOrNormalChannelIsActive()) {
+  if (service->ProcessContentOrNormalChannelIsActive(ChildID())) {
     return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE;
   }
 
-  bool isHomescreen = false;
-
-  ContentChild* contentChild = ContentChild::GetSingleton();
-  if (contentChild) {
-    const InfallibleTArray<PBrowserChild*>& browsers =
-      contentChild->ManagedPBrowserChild();
-    for (uint32_t i = 0; i < browsers.Length(); i++) {
-      nsAutoString appType;
-      static_cast<TabChild*>(browsers[i])->GetAppType(appType);
-      if (appType.EqualsLiteral("homescreen")) {
-        isHomescreen = true;
-        break;
-      }
-    }
-  }
-
-  return isHomescreen ?
+  return HasAppType("homescreen") ?
          PROCESS_PRIORITY_BACKGROUND_HOMESCREEN :
          PROCESS_PRIORITY_BACKGROUND;
 }
 
 void
-ProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority)
+ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority)
 {
   if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
     MOZ_ASSERT(false);
     return;
   }
 
-  if (mProcessPriority == aPriority) {
+  if (mPriority == aPriority) {
+    return;
+  }
+
+  // If the prefs were disabled after this ParticularProcessPriorityManager was
+  // created, we can at least avoid any further calls to
+  // hal::SetProcessPriority.  Supporting dynamic enabling/disabling of the
+  // ProcessPriorityManager is mostly for testing.
+  if (!ProcessPriorityManagerImpl::PrefsEnabled()) {
+    return;
+  }
+
+  LOGP("Changing priority from %s to %s.",
+       ProcessPriorityToString(mPriority),
+       ProcessPriorityToString(aPriority));
+  mPriority = aPriority;
+  hal::SetProcessPriority(Pid(), mPriority);
+
+  unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
+
+  if (aPriority >= PROCESS_PRIORITY_FOREGROUND) {
+    unused << mContentParent->SendCancelMinimizeMemoryUsage();
+  } else {
+    unused << mContentParent->SendMinimizeMemoryUsage();
+  }
+
+  FireTestOnlyObserverNotification("process-priority-set",
+                                   ProcessPriorityToString(mPriority));
+}
+
+void
+ParticularProcessPriorityManager::ShutDown()
+{
+  MOZ_ASSERT(mContentParent);
+
+  if (mResetPriorityTimer) {
+    mResetPriorityTimer->Cancel();
+    mResetPriorityTimer = nullptr;
+  }
+
+  mContentParent = nullptr;
+}
+
+void
+ProcessPriorityManagerImpl::FireTestOnlyObserverNotification(
+  const char* aTopic,
+  const nsACString& aData /* = EmptyCString() */)
+{
+  if (!Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) {
     return;
   }
 
-  LOG("Changing priority from %s to %s.",
-      ProcessPriorityToString(mProcessPriority),
-      ProcessPriorityToString(aPriority));
-  mProcessPriority = aPriority;
-  hal::SetProcessPriority(getpid(), mProcessPriority);
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(os);
+
+  nsPrintfCString topic("process-priority-manager:TEST-ONLY:%s", aTopic);
+
+  LOG("Notifying observer %s, data %s",
+      topic.get(), PromiseFlatCString(aData).get());
+  os->NotifyObservers(nullptr, topic.get(), NS_ConvertUTF8toUTF16(aData).get());
+}
+
+void
+ParticularProcessPriorityManager::FireTestOnlyObserverNotification(
+  const char* aTopic,
+  const char* aData /* = nullptr */ )
+{
+  if (!Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) {
+    return;
+  }
+
+  nsAutoCString data;
+  if (aData) {
+    data.AppendASCII(aData);
+  }
+
+  FireTestOnlyObserverNotification(aTopic, data);
+}
+
+void
+ParticularProcessPriorityManager::FireTestOnlyObserverNotification(
+  const char* aTopic,
+  const nsACString& aData /* = EmptyCString() */)
+{
+  if (!Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) {
+    return;
+  }
 
-  if (aPriority >= PROCESS_PRIORITY_FOREGROUND) {
-    // Cancel the memory minimization procedure we might have started.
-    nsCOMPtr<nsICancelableRunnable> runnable =
-      do_QueryReferent(mMemoryMinimizerRunnable);
-    if (runnable) {
-      runnable->Cancel();
-    }
+  nsAutoCString data(nsPrintfCString("%lld", ChildID()));
+  if (!aData.IsEmpty()) {
+    data.AppendLiteral(":");
+    data.Append(aData);
+  }
+
+  // ProcessPriorityManagerImpl::GetSingleton() is guaranteed not to return
+  // null, since ProcessPriorityManagerImpl is the only class which creates
+  // ParticularProcessPriorityManagers.
+
+  ProcessPriorityManagerImpl::GetSingleton()->
+    FireTestOnlyObserverNotification(aTopic, data);
+}
+
+StaticRefPtr<ProcessPriorityManagerChild>
+ProcessPriorityManagerChild::sSingleton;
+
+/* static */ void
+ProcessPriorityManagerChild::StaticInit()
+{
+  if (!sSingleton) {
+    sSingleton = new ProcessPriorityManagerChild();
+    ClearOnShutdown(&sSingleton);
+  }
+}
+
+/* static */ ProcessPriorityManagerChild*
+ProcessPriorityManagerChild::Singleton()
+{
+  StaticInit();
+  return sSingleton;
+}
+
+NS_IMPL_ISUPPORTS1(ProcessPriorityManagerChild,
+                   nsIObserver)
+
+ProcessPriorityManagerChild::ProcessPriorityManagerChild()
+{
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    mCachedPriority = PROCESS_PRIORITY_MASTER;
   } else {
-    // 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);
-
-      // Cancel the previous task if it's still pending
-      if (runnable) {
-        runnable->Cancel();
-      }
-
-      mgr->MinimizeMemoryUsage(/* callback = */ nullptr,
-                               getter_AddRefs(runnable));
-      mMemoryMinimizerRunnable = do_GetWeakReference(runnable);
-    }
+    mCachedPriority = PROCESS_PRIORITY_UNKNOWN;
   }
 }
 
 void
-ProcessPriorityManager::ScheduleResetPriority(const char* aTimeoutPref)
+ProcessPriorityManagerChild::Init()
 {
-  if (mResetPriorityTimer) {
-    LOG("ScheduleResetPriority bailing; the timer is already running.");
-    return;
+  // The process priority should only be changed in child processes; don't even
+  // bother listening for changes if we're in the main process.
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+    NS_ENSURE_TRUE_VOID(os);
+    os->AddObserver(this, "ipc:process-priority-changed", /* weak = */ false);
   }
-
-  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)
+ProcessPriorityManagerChild::Observe(
+  nsISupports* aSubject,
+  const char* aTopic,
+  const PRUnichar* aData)
 {
-  LOG("Reset priority timer callback; about to ResetPriorityNow.");
-  ResetPriorityNow();
-  mResetPriorityTimer = nullptr;
+  MOZ_ASSERT(!strcmp(aTopic, "ipc:process-priority-changed"));
+
+  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
+  NS_ENSURE_TRUE(props, NS_OK);
+
+  int32_t priority = static_cast<int32_t>(PROCESS_PRIORITY_UNKNOWN);
+  props->GetPropertyAsInt32(NS_LITERAL_STRING("priority"), &priority);
+  NS_ENSURE_TRUE(ProcessPriority(priority) != PROCESS_PRIORITY_UNKNOWN, NS_OK);
+
+  mCachedPriority = static_cast<ProcessPriority>(priority);
+
   return NS_OK;
 }
 
-void
-ProcessPriorityManager::TemporarilyLockProcessPriority()
+bool
+ProcessPriorityManagerChild::CurrentProcessIsForeground()
 {
-  LOG("TemporarilyLockProcessPriority");
-
-  // Each call to TemporarilyLockProcessPriority gives us an additional
-  // temporaryPriorityMS at our current priority (unless we receive a
-  // process-priority:reset-now notification).  So cancel our timer if it's
-  // running (which is due to a previous call to either
-  // TemporarilyLockProcessPriority() or ResetPriority()).
-  if (mResetPriorityTimer) {
-    mResetPriorityTimer->Cancel();
-    mResetPriorityTimer = nullptr;
-  }
-  ScheduleResetPriority("temporaryPriorityLockMS");
+  return mCachedPriority == PROCESS_PRIORITY_UNKNOWN ||
+         mCachedPriority >= PROCESS_PRIORITY_FOREGROUND;
 }
 
 } // 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;
-  }
+namespace mozilla {
 
-  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).
-  if (XRE_GetProcessType() == GeckoProcessType_Default) {
-    LOG("This is the master process.");
-    hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER);
-    return;
-  }
-
-  sManager = new ProcessPriorityManager();
-  sManager->Init();
-  ClearOnShutdown(&sManager);
+/* static */ void
+ProcessPriorityManager::Init()
+{
+  ProcessPriorityManagerImpl::StaticInit();
+  ProcessPriorityManagerChild::StaticInit();
 }
 
-bool
-CurrentProcessIsForeground()
+/* static */ void
+ProcessPriorityManager::SetProcessPriority(ContentParent* aContentParent,
+                                           ProcessPriority aPriority)
 {
-  // The process priority manager is the only thing which changes our priority,
-  // so if the manager does not exist, then we must be in the foreground.
-  if (!sManager) {
-    return true;
-  }
+  MOZ_ASSERT(aContentParent);
 
-  return sManager->GetPriority() >= PROCESS_PRIORITY_FOREGROUND;
-}
-
-void
-TemporarilyLockProcessPriority()
-{
-  if (sManager) {
-    sManager->TemporarilyLockProcessPriority();
-  } else {
-    LOG("TemporarilyLockProcessPriority called before "
-        "InitProcessPriorityManager.  Bailing.");
+  ProcessPriorityManagerImpl* singleton =
+    ProcessPriorityManagerImpl::GetSingleton();
+  if (singleton) {
+    singleton->SetProcessPriority(aContentParent, aPriority);
   }
 }
 
-} // namespace ipc
-} // namespace dom
+/* static */ bool
+ProcessPriorityManager::CurrentProcessIsForeground()
+{
+  return ProcessPriorityManagerChild::Singleton()->
+    CurrentProcessIsForeground();
+}
+
 } // namespace mozilla
--- a/dom/ipc/ProcessPriorityManager.h
+++ b/dom/ipc/ProcessPriorityManager.h
@@ -2,51 +2,80 @@
 /* vim: set sw=2 ts=8 et ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ProcessPriorityManager_h_
 #define mozilla_ProcessPriorityManager_h_
 
+#include "mozilla/HalTypes.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserver.h"
+#include "nsDataHashtable.h"
+
 namespace mozilla {
 namespace dom {
-namespace ipc {
-
-/**
- * Initialize the ProcessPriorityManager.
- *
- * The ProcessPriorityManager informs the hal back-end whether this is the root
- * Gecko process, and, if we're not the root, informs hal when this process
- * transitions between having no visible top-level windows, and having at least
- * one visible top-level window.
- *
- * Hal may adjust this process's operating system priority (e.g. niceness, on
- * *nix) according to these notificaitons.
- *
- * This function call does nothing if the pref for OOP tabs is not set.
- */
-void InitProcessPriorityManager();
+class ContentParent;
+}
 
 /**
- * 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.
+ * This class sets the priority of subprocesses in response to explicit
+ * requests and events in the system.
+ *
+ * A process's priority changes e.g. when it goes into the background via
+ * mozbrowser's setVisible(false).  Process priority affects CPU scheduling and
+ * also which processes get killed when we run out of memory.
+ *
+ * After you call Initialize(), the only thing you probably have to do is call
+ * SetProcessPriority on processes immediately after creating them in order to
+ * set their initial priority.  The ProcessPriorityManager takes care of the
+ * rest.
  */
-bool CurrentProcessIsForeground();
+class ProcessPriorityManager MOZ_FINAL
+{
+public:
+  /**
+   * Initialize the ProcessPriorityManager machinery, causing the
+   * ProcessPriorityManager to actively manage the priorities of all
+   * subprocesses.  You should call this before creating any subprocesses.
+   *
+   * You should also call this function even if you're in a child process,
+   * since it will initialize ProcessPriorityManagerChild.
+   */
+  static void Init();
 
-/**
- * Calling this function prevents us from changing this process's priority
- * for a few seconds, if that change in priority would not have taken effect
- * immediately to begin with.
- *
- * In practice, this prevents foreground --> background transitions, but not
- * background --> foreground transitions.  It also does not prevent
- * transitions from an unknown priority (as happens immediately after we're
- * constructed) to a foreground priority.
- */
-void TemporarilyLockProcessPriority();
+  /**
+   * Set the process priority of a given ContentParent's process.
+   *
+   * Note that because this method takes a ContentParent*, you can only set the
+   * priority of your subprocesses.  In fact, because we don't support nested
+   * content processes (bug 761935), you can only call this method from the
+   * main process.
+   *
+   * It probably only makes sense to call this function immediately after a
+   * process is created.  At this point, the process priority manager doesn't
+   * have enough context about the processs to know what its priority should
+   * be.
+   *
+   * Eventually whatever priority you set here can and probably will be
+   * overwritten by the process priority manager.
+   */
+  static void SetProcessPriority(dom::ContentParent* aContentParent,
+                                 hal::ProcessPriority aPriority);
 
-} // namespace ipc
-} // namespace dom
+  /**
+   * Returns true iff this process's priority is FOREGROUND*.
+   *
+   * Note that because process priorities are set in the main process, it's
+   * possible for this method to return a stale value.  So be careful about
+   * what you use this for.
+   */
+  static bool CurrentProcessIsForeground();
+
+private:
+  ProcessPriorityManager();
+  DISALLOW_EVIL_CONSTRUCTORS(ProcessPriorityManager);
+};
+
 } // namespace mozilla
 
 #endif
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2013,24 +2013,16 @@ TabChild::RecvDestroy()
   }
 
   // XXX what other code in ~TabChild() should we be running here?
   DestroyWindow();
 
   return Send__delete__(this);
 }
 
-/* virtual */ bool
-TabChild::RecvSetAppType(const nsString& aAppType)
-{
-  MOZ_ASSERT_IF(!aAppType.IsEmpty(), HasOwnApp());
-  mAppType = aAppType;
-  return true;
-}
-
 PRenderFrameChild*
 TabChild::AllocPRenderFrame(ScrollingBehavior* aScrolling,
                             TextureFactoryIdentifier* aTextureFactoryIdentifier,
                             uint64_t* aLayersId)
 {
     return new RenderFrameChild();
 }
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -310,26 +310,16 @@ public:
     /**
      * Signal to this TabChild that it should be made visible:
      * activated widget, retained layer tree, etc.  (Respectively,
      * made not visible.)
      */
     void MakeVisible();
     void MakeHidden();
 
-    virtual bool RecvSetAppType(const nsString& aAppType);
-
-    /**
-     * Get this object's app type.
-     *
-     * A TabChild's app type corresponds to the value of its frame element's
-     * "mozapptype" attribute.
-     */
-    void GetAppType(nsAString& aAppType) const { aAppType = mAppType; }
-
     // Returns true if the file descriptor was found in the cache, false
     // otherwise.
     bool GetCachedFileDescriptor(const nsAString& aPath,
                                  nsICachedFileDescriptorListener* aCallback);
 
     void CancelCachedFileDescriptorCallback(
                                     const nsAString& aPath,
                                     nsICachedFileDescriptorListener* aCallback);
@@ -454,17 +444,16 @@ private:
         mCachedFileDescriptorInfos;
     float mOldViewportWidth;
     nscolor mLastBackgroundColor;
     ScrollingBehavior mScrolling;
     bool mDidFakeShow;
     bool mNotified;
     bool mContentDocumentIsDisplayed;
     bool mTriedBrowserInit;
-    nsString mAppType;
     ScreenOrientation mOrientation;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 inline TabChild*
 GetTabChildFrom(nsIDocShell* aDocShell)
 {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -215,16 +215,41 @@ TabParent::~TabParent()
 void
 TabParent::SetOwnerElement(nsIDOMElement* aElement)
 {
   mFrameElement = aElement;
   TryCacheDPI();
 }
 
 void
+TabParent::GetAppType(nsAString& aOut)
+{
+  aOut.Truncate();
+  nsCOMPtr<Element> elem = do_QueryInterface(mFrameElement);
+  if (!elem) {
+    return;
+  }
+
+  elem->GetAttr(kNameSpaceID_None, nsGkAtoms::mozapptype, aOut);
+}
+
+bool
+TabParent::IsVisible()
+{
+  nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  if (!frameLoader) {
+    return false;
+  }
+
+  bool visible = false;
+  frameLoader->GetVisible(&visible);
+  return visible;
+}
+
+void
 TabParent::Destroy()
 {
   if (mIsDestroyed) {
     return;
   }
 
   // If this fails, it's most likely due to a content-process crash,
   // and auto-cleanup will kick in.  Otherwise, the child side will
@@ -260,28 +285,30 @@ TabParent::ActorDestroy(ActorDestroyReas
 {
   if (sEventCapturer == this) {
     sEventCapturer = nullptr;
   }
   if (mIMETabParent == this) {
     mIMETabParent = nullptr;
   }
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (frameLoader) {
     ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr, nullptr);
     frameLoader->DestroyChild();
 
-    if (why == AbnormalShutdown) {
-      nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-      if (os) {
-        os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
-                            "oop-frameloader-crashed", nullptr);
-      }
+    if (why == AbnormalShutdown && os) {
+      os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, frameLoader),
+                          "oop-frameloader-crashed", nullptr);
     }
   }
+
+  if (os) {
+    os->NotifyObservers(NS_ISUPPORTS_CAST(nsITabParent*, this), "ipc:browser-destroyed", nullptr);
+  }
 }
 
 bool
 TabParent::RecvMoveFocus(const bool& aForward)
 {
   nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
   if (fm) {
     nsCOMPtr<nsIDOMElement> dummy;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -57,16 +57,30 @@ class TabParent : public PBrowserParent
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
     typedef mozilla::layout::ScrollingBehavior ScrollingBehavior;
 
 public:
     TabParent(const TabContext& aContext);
     virtual ~TabParent();
     nsIDOMElement* GetOwnerElement() { return mFrameElement; }
     void SetOwnerElement(nsIDOMElement* aElement);
+
+    /**
+     * Get the mozapptype attribute from this TabParent's owner DOM element.
+     */
+    void GetAppType(nsAString& aOut);
+
+    /**
+     * Returns true iff this TabParent's nsIFrameLoader is visible.
+     *
+     * The frameloader's visibility can be independent of e.g. its docshell's
+     * visibility.
+     */
+    bool IsVisible();
+
     nsIBrowserDOMWindow *GetBrowserDOMWindow() { return mBrowserDOMWindow; }
     void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
 
     /**
      * Return the TabParent that has decided it wants to capture an
      * event series for fast-path dispatch to its subprocess, if one
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -260,17 +260,17 @@ nsLayoutStatics::Initialize()
 
   NS_SealStaticAtomTable();
 
   nsWindowMemoryReporter::Init();
 
   SVGElementFactory::Init();
   nsSVGUtils::Init();
 
-  InitProcessPriorityManager();
+  ProcessPriorityManager::Init();
 
   nsPermissionManager::AppClearDataObserverInit();
   nsCookieService::AppClearDataObserverInit();
   nsApplicationCacheService::AppClearDataObserverInit();
 
   InitializeDateCacheCleaner();
 
   return NS_OK;