Bug 836654 - Part 7: Hold a CPU wake lock while a "critical" process that's expecting a system message loads. r=cjones,fabrice

This commit is contained in:
Justin Lebar 2013-02-15 12:07:34 -05:00
Родитель f44d60c8cc
Коммит fb22e05f5c
7 изменённых файлов: 272 добавлений и 53 удалений

Просмотреть файл

@ -584,6 +584,11 @@ pref("dom.ipc.processPrelaunch.enabled", true);
pref("dom.ipc.processPrelaunch.delayMs", 5000);
#endif
// When a process receives a system message, we hold a CPU wake lock on its
// behalf for this many seconds, or until it handles the system message,
// whichever comes first.
pref("dom.ipc.systemMessageCPULockTimeoutSec", 30);
// Ignore the "dialog=1" feature in window.open.
pref("dom.disable_window_open_dialog_feature", true);

Просмотреть файл

@ -98,6 +98,7 @@
#endif
using namespace mozilla;
using namespace mozilla::hal;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using namespace mozilla::layers;
@ -2044,11 +2045,9 @@ nsFrameLoader::TryRemoteBrowser()
context.SetTabContextForBrowserFrame(containingApp, scrollingBehavior);
}
mRemoteBrowser = ContentParent::CreateBrowserOrApp(context);
nsCOMPtr<nsIDOMElement> ownerElement = do_QueryInterface(mOwnerContent);
mRemoteBrowser = ContentParent::CreateBrowserOrApp(context, ownerElement);
if (mRemoteBrowser) {
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mOwnerContent);
mRemoteBrowser->SetOwnerElement(element);
// If we're an app, send the frame element's mozapptype down to the child
// process. This ends up in TabChild::GetAppType().
if (ownApp) {

Просмотреть файл

@ -235,6 +235,39 @@ ConsoleListener::Observe(nsIConsoleMessage* aMessage)
return NS_OK;
}
class SystemMessageHandledObserver MOZ_FINAL : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
void Init();
};
void SystemMessageHandledObserver::Init()
{
nsCOMPtr<nsIObserverService> os =
mozilla::services::GetObserverService();
if (os) {
os->AddObserver(this, "SystemMessageManager:HandleMessageDone",
/* ownsWeak */ false);
}
}
NS_IMETHODIMP
SystemMessageHandledObserver::Observe(nsISupports* aSubject,
const char* aTopic,
const PRUnichar* aData)
{
if (ContentChild::GetSingleton()) {
ContentChild::GetSingleton()->SendSystemMessageHandled();
}
return NS_OK;
}
NS_IMPL_ISUPPORTS1(SystemMessageHandledObserver, nsIObserver)
ContentChild* ContentChild::sSingleton;
ContentChild::ContentChild()
@ -340,6 +373,11 @@ ContentChild::InitXPCOM()
DebugOnly<FileUpdateDispatcher*> observer = FileUpdateDispatcher::GetSingleton();
NS_ASSERTION(observer, "FileUpdateDispatcher is null");
// This object is held alive by the observer service.
nsRefPtr<SystemMessageHandledObserver> sysMsgObserver =
new SystemMessageHandledObserver();
sysMsgObserver->Init();
}
PMemoryReportRequestChild*
@ -524,11 +562,12 @@ ContentChild::AllocPBrowser(const IPCTabContext& aContext,
sFirstIdleTask = NewRunnableFunction(FirstIdle);
MessageLoop::current()->PostIdleTask(FROM_HERE, sFirstIdleTask);
// If we are the preallocated process transforming into an app process,
// we'll have background priority at this point. Give ourselves a
// priority boost for a few seconds, so we don't get killed while we're
// loading our first TabChild.
TemporarilySetProcessPriorityToForeground();
// We 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();
}
// We'll happily accept any kind of IPCTabContext here; we don't need to

Просмотреть файл

@ -26,6 +26,7 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/ExternalHelperAppParent.h"
#include "mozilla/dom/PMemoryReportRequestParent.h"
#include "mozilla/dom/power/PowerManagerService.h"
#include "mozilla/dom/StorageParent.h"
#include "mozilla/dom/bluetooth/PBluetoothParent.h"
#include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
@ -61,9 +62,11 @@
#include "nsIConsoleService.h"
#include "nsIDOMApplicationRegistry.h"
#include "nsIDOMGeoGeolocation.h"
#include "nsIDOMWakeLock.h"
#include "nsIDOMWindow.h"
#include "nsIFilePicker.h"
#include "nsIMemoryReporter.h"
#include "nsIMozBrowserFrame.h"
#include "nsIMutable.h"
#include "nsIObserverService.h"
#include "nsIPresShell.h"
@ -123,8 +126,9 @@ using base::ChildPrivileges;
using base::KillProcess;
using namespace mozilla::dom::bluetooth;
using namespace mozilla::dom::devicestorage;
using namespace mozilla::dom::sms;
using namespace mozilla::dom::indexedDB;
using namespace mozilla::dom::power;
using namespace mozilla::dom::sms;
using namespace mozilla::hal;
using namespace mozilla::ipc;
using namespace mozilla::layers;
@ -208,7 +212,8 @@ ContentParent::PreallocateAppProcess()
/*isBrowserElement=*/false,
// Final privileges are set when we
// transform into our app.
base::PRIVILEGES_INHERIT);
base::PRIVILEGES_INHERIT,
PROCESS_PRIORITY_BACKGROUND);
sPreallocatedAppProcess->Init();
}
@ -235,7 +240,8 @@ ContentParent::ScheduleDelayedPreallocateAppProcess()
/*static*/ already_AddRefed<ContentParent>
ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs)
ChildPrivileges aPrivs,
ProcessPriority aInitialPriority)
{
nsRefPtr<ContentParent> process = sPreallocatedAppProcess.get();
sPreallocatedAppProcess = nullptr;
@ -244,10 +250,8 @@ ContentParent::MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL,
return nullptr;
}
if (!process->TransformPreallocatedIntoApp(aAppManifestURL, aPrivs)) {
NS_WARNING("Can't TransformPrealocatedIntoApp. Maybe "
"the preallocated process died?");
if (!process->TransformPreallocatedIntoApp(aAppManifestURL, aPrivs,
aInitialPriority)) {
// Kill the process just in case it's not actually dead; we don't want
// to "leak" this process!
process->KillHard();
@ -365,7 +369,9 @@ ContentParent::GetNewOrUsed(bool aForBrowserElement)
nsRefPtr<ContentParent> p =
new ContentParent(/* appManifestURL = */ EmptyString(),
aForBrowserElement);
aForBrowserElement,
base::PRIVILEGES_DEFAULT,
PROCESS_PRIORITY_FOREGROUND);
p->Init();
gNonAppContentParents->AppendElement(p);
return p;
@ -402,8 +408,37 @@ PrivilegesForApp(mozIApplication* aApp)
return base::PRIVILEGES_DEFAULT;
}
/*static*/ ProcessPriority
ContentParent::GetInitialProcessPriority(nsIDOMElement* aFrameElement)
{
// Frames with mozapptype == critical which are expecting a system message
// get FOREGROUND_HIGH priority. All other frames get FOREGROUND priority.
if (!aFrameElement) {
return PROCESS_PRIORITY_FOREGROUND;
}
nsAutoString appType;
nsCOMPtr<Element> frameElement = do_QueryInterface(aFrameElement);
frameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::mozapptype, appType);
if (appType != NS_LITERAL_STRING("critical")) {
return PROCESS_PRIORITY_FOREGROUND;
}
nsCOMPtr<nsIMozBrowserFrame> browserFrame =
do_QueryInterface(aFrameElement);
if (!browserFrame) {
return PROCESS_PRIORITY_FOREGROUND;
}
return browserFrame->GetIsExpectingSystemMessage() ?
PROCESS_PRIORITY_FOREGROUND_HIGH :
PROCESS_PRIORITY_FOREGROUND;
}
/*static*/ TabParent*
ContentParent::CreateBrowserOrApp(const TabContext& aContext)
ContentParent::CreateBrowserOrApp(const TabContext& aContext,
nsIDOMElement* aFrameElement)
{
if (!sCanLaunchSubprocesses) {
return nullptr;
@ -412,6 +447,7 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext)
if (aContext.IsBrowserElement() || !aContext.HasOwnApp()) {
if (ContentParent* cp = GetNewOrUsed(aContext.IsBrowserElement())) {
nsRefPtr<TabParent> tp(new TabParent(aContext));
tp->SetOwnerElement(aFrameElement);
PBrowserParent* browser = cp->SendPBrowserConstructor(
tp.forget().get(), // DeallocPBrowserParent() releases this ref.
aContext.AsIPCTabContext(),
@ -441,18 +477,24 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext)
nsRefPtr<ContentParent> p = gAppContentParents->Get(manifestURL);
if (!p) {
ProcessPriority initialPriority = GetInitialProcessPriority(aFrameElement);
ChildPrivileges privs = PrivilegesForApp(ownApp);
p = MaybeTakePreallocatedAppProcess(manifestURL, privs);
p = MaybeTakePreallocatedAppProcess(manifestURL, privs,
initialPriority);
if (!p) {
NS_WARNING("Unable to use pre-allocated app process");
p = new ContentParent(manifestURL, /* isBrowserElement = */ false,
privs);
privs, initialPriority);
p->Init();
}
gAppContentParents->Put(manifestURL, p);
}
p->MaybeTakeCPUWakeLock(aFrameElement);
nsRefPtr<TabParent> tp = new TabParent(aContext);
tp->SetOwnerElement(aFrameElement);
PBrowserParent* browser = p->SendPBrowserConstructor(
tp.forget().get(), // DeallocPBrowserParent() releases this ref.
aContext.AsIPCTabContext(),
@ -529,23 +571,136 @@ ContentParent::Init()
NS_ASSERTION(observer, "FileUpdateDispatcher is null");
}
namespace {
class SystemMessageHandledListener MOZ_FINAL
: public nsITimerCallback
, public LinkedListElement<SystemMessageHandledListener>
{
public:
NS_DECL_ISUPPORTS
SystemMessageHandledListener() {}
static void OnSystemMessageHandled()
{
if (!sListeners) {
return;
}
SystemMessageHandledListener* listener = sListeners->popFirst();
if (!listener) {
return;
}
// Careful: ShutDown() may delete |this|.
listener->ShutDown();
}
void Init(nsIDOMMozWakeLock* aWakeLock)
{
MOZ_ASSERT(!mWakeLock);
MOZ_ASSERT(!mTimer);
// mTimer keeps a strong reference to |this|. When this object's
// destructor runs, it will remove itself from the LinkedList.
if (!sListeners) {
sListeners = new LinkedList<SystemMessageHandledListener>();
ClearOnShutdown(&sListeners);
}
sListeners->insertBack(this);
mWakeLock = aWakeLock;
mTimer = do_CreateInstance("@mozilla.org/timer;1");
uint32_t timeoutSec =
Preferences::GetInt("dom.ipc.systemMessageCPULockTimeoutSec", 30);
mTimer->InitWithCallback(this, timeoutSec * 1000,
nsITimer::TYPE_ONE_SHOT);
}
NS_IMETHOD Notify(nsITimer* aTimer)
{
// Careful: ShutDown() may delete |this|.
ShutDown();
return NS_OK;
}
private:
static StaticAutoPtr<LinkedList<SystemMessageHandledListener> > sListeners;
void ShutDown()
{
nsRefPtr<SystemMessageHandledListener> kungFuDeathGrip = this;
mWakeLock->Unlock();
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
}
nsCOMPtr<nsIDOMMozWakeLock> mWakeLock;
nsCOMPtr<nsITimer> mTimer;
};
StaticAutoPtr<LinkedList<SystemMessageHandledListener> >
SystemMessageHandledListener::sListeners;
NS_IMPL_ISUPPORTS1(SystemMessageHandledListener,
nsITimerCallback)
} // anonymous namespace
void
ContentParent::SetProcessInitialPriority(ProcessPriority aInitialPriority)
{
if (!Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
return;
}
SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
aInitialPriority);
}
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);
if (!browserFrame ||
!browserFrame->GetIsExpectingSystemMessage()) {
return;
}
nsRefPtr<PowerManagerService> pms = PowerManagerService::GetInstance();
nsCOMPtr<nsIDOMMozWakeLock> lock =
pms->NewWakeLockOnBehalfOfProcess(NS_LITERAL_STRING("cpu"), this);
// This object's Init() function keeps it alive.
nsRefPtr<SystemMessageHandledListener> listener =
new SystemMessageHandledListener();
listener->Init(lock);
}
bool
ContentParent::TransformPreallocatedIntoApp(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs)
ChildPrivileges aPrivs,
ProcessPriority aInitialPriority)
{
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;
// Boost this process's priority. The subprocess will call
// TemporarilySetProcessPriorityToForeground() from within
// ContentChild::AllocPBrowser, but this happens earlier, thus reducing the
// window in which the child might be killed due to low memory.
if (Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
PROCESS_PRIORITY_FOREGROUND);
}
SetProcessInitialPriority(aInitialPriority);
// Now that we've increased the process's priority from BACKGROUND (where
// the preallocated app sits) to something higher, check whether the process
@ -870,7 +1025,8 @@ ContentParent::GetTestShellSingleton()
ContentParent::ContentParent(const nsAString& aAppManifestURL,
bool aIsForBrowser,
ChildOSPrivileges aOSPrivileges)
ChildOSPrivileges aOSPrivileges,
ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
: mSubprocess(nullptr)
, mOSPrivileges(aOSPrivileges)
, mChildID(CONTENT_PROCESS_ID_UNKNOWN)
@ -895,20 +1051,10 @@ ContentParent::ContentParent(const nsAString& aAppManifestURL,
mSubprocess->LaunchAndWaitForProcessHandle();
// Set the subprocess's priority (bg if we're a preallocated process, fg
// otherwise). We do this first because we're likely /lowering/ its CPU and
// memory priority, which it has inherited from this process.
if (Preferences::GetBool("dom.ipc.processPriorityManager.enabled")) {
ProcessPriority priority;
if (aAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL) {
priority = PROCESS_PRIORITY_BACKGROUND;
} else {
priority = PROCESS_PRIORITY_FOREGROUND;
}
SetProcessPriority(base::GetProcId(mSubprocess->GetChildProcessHandle()),
priority);
}
// 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.
SetProcessInitialPriority(aInitialPriority);
Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle());
@ -2428,5 +2574,12 @@ ContentParent::CheckAppHasPermission(const nsAString& aPermission)
return AssertAppHasPermission(this, NS_ConvertUTF16toUTF8(aPermission).get());
}
bool
ContentParent::RecvSystemMessageHandled()
{
SystemMessageHandledListener::OnSystemMessageHandled();
return true;
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -85,9 +85,13 @@ public:
static ContentParent* GetNewOrUsed(bool aForBrowserElement = false);
/**
* Get or create a content process for the given TabContext.
* Get or create a content process for the given TabContext. aFrameElement
* should be the frame/iframe element with which this process will
* associated.
*/
static TabParent* CreateBrowserOrApp(const TabContext& aContext);
static TabParent*
CreateBrowserOrApp(const TabContext& aContext,
nsIDOMElement* aFrameElement);
static void GetAll(nsTArray<ContentParent*>& aArray);
@ -166,7 +170,10 @@ private:
// if it's dead), this returns false.
static already_AddRefed<ContentParent>
MaybeTakePreallocatedAppProcess(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs);
ChildPrivileges aPrivs,
hal::ProcessPriority aInitialPriority);
static hal::ProcessPriority GetInitialProcessPriority(nsIDOMElement* aFrameElement);
static void FirstIdle();
@ -176,15 +183,27 @@ private:
using PContentParent::SendPTestShellConstructor;
ContentParent(const nsAString& aAppManifestURL, bool aIsForBrowser,
ChildOSPrivileges aOSPrivileges = base::PRIVILEGES_DEFAULT);
ChildOSPrivileges 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 SetProcessInitialPriority(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);
// Transform a pre-allocated app process into a "real" app
// process, for the specified manifest URL.
bool TransformPreallocatedIntoApp(const nsAString& aAppManifestURL,
ChildPrivileges aPrivs);
ChildPrivileges aPrivs,
hal::ProcessPriority aInitialPriority);
/**
* Mark this ContentParent as dead for the purposes of Get*().
@ -355,6 +374,8 @@ private:
virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus);
virtual bool RecvSystemMessageHandled() MOZ_OVERRIDE;
virtual void ProcessingError(Result what) MOZ_OVERRIDE;
GeckoChildProcessHost* mSubprocess;

Просмотреть файл

@ -455,6 +455,9 @@ parent:
async RecordingDeviceEvents(nsString recordingStatus);
// Notify the parent that the child has finished handling a system message.
async SystemMessageHandled();
both:
AsyncMessage(nsString aMessage, ClonedMessageData aData);
};

Просмотреть файл

@ -75,10 +75,9 @@ SystemMessageManager.prototype = {
aHandler.handleMessage(wrapped ? aMessage
: ObjectWrapper.wrap(aMessage, this._window));
// Notify the parent process the message is handled.
cpmm.sendAsyncMessage("SystemMessageManager:HandleMessageDone",
{ type: aType,
message: aMessage });
Services.obs.notifyObservers(/* aSubject */ null,
"SystemMessageManager:HandleMessageDone",
/* aData */ null);
},
mozSetMessageHandler: function sysMessMgr_setMessageHandler(aType, aHandler) {