Bug 1334716 - Make process selection a service and implementable in JS. r=krizsa
MozReview-Commit-ID: CViRvZB8nKe
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -515,16 +515,19 @@
@RESPATH@/components/NotificationStorage.manifest
@RESPATH@/components/Push.js
@RESPATH@/components/Push.manifest
@RESPATH@/components/PushComponents.js
@RESPATH@/components/remotebrowserutils.manifest
@RESPATH@/components/RemoteWebNavigation.js
+@RESPATH@/components/ProcessSelector.js
+@RESPATH@/components/ProcessSelector.manifest
+
@RESPATH@/components/SlowScriptDebug.manifest
@RESPATH@/components/SlowScriptDebug.js
#ifdef MOZ_WEBRTC
@RESPATH@/components/PeerConnection.js
@RESPATH@/components/PeerConnection.manifest
#endif
new file mode 100644
--- /dev/null
+++ b/dom/base/ProcessSelector.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import('resource://gre/modules/Services.jsm');
+
+const BASE_PREF = "dom.ipc.processCount"
+const PREF_BRANCH = BASE_PREF + ".";
+
+// Utilities:
+function getMaxContentParents(processType) {
+ let maxContentParents = -1;
+ try {
+ maxContentParents = Services.prefs.getIntPref(PREF_BRANCH + aType);
+ } catch (e) {
+ // Pref probably didn't exist, get the default number of processes.
+ try {
+ maxContentParents = Services.prefs.getIntPref(BASE_PREF);
+ } catch (e) {
+ // No prefs? That's odd, use only one process.
+ maxContentParents = 1;
+ }
+ }
+
+ return maxContentParents;
+}
+
+// Fills up aProcesses until max and then selects randomly from the available
+// ones.
+function RandomSelector() {
+}
+
+RandomSelector.prototype = {
+ classID: Components.ID("{c616fcfd-9737-41f1-aa74-cee72a38f91b}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentProcessProvider]),
+
+ provideProcess(aType, aOpener, aProcesses, aCount) {
+ let maxContentParents = getMaxContentParents();
+ if (aCount < maxContentParents) {
+ return Ci.nsIContentProcessProvider.NEW_PROCESS;
+ }
+
+ let startIdx = Math.floor(Math.random() * maxContentParents);
+ let curIdx = startIdx;
+
+ do {
+ if (aProcesses[curIdx].opener === aOpener) {
+ return curIdx;
+ }
+
+ curIdx = (curIdx + 1) % maxContentParents;
+ } while (curIdx !== startIdx);
+
+ return Ci.nsIContentProcessProvider.NEW_PROCESS;
+ },
+};
+
+var components = [RandomSelector];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
new file mode 100644
--- /dev/null
+++ b/dom/base/ProcessSelector.manifest
@@ -0,0 +1,2 @@
+component {c616fcfd-9737-41f1-aa74-cee72a38f91b} ProcessSelector.js
+contract @mozilla.org/ipc/processselector;1 {c616fcfd-9737-41f1-aa74-cee72a38f91b}
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -386,16 +386,18 @@ if CONFIG['INTEL_ARCHITECTURE']:
SOURCES += ['nsTextFragmentSSE2.cpp']
SOURCES['nsTextFragmentSSE2.cpp'].flags += CONFIG['SSE2_FLAGS']
EXTRA_COMPONENTS += [
'contentAreaDropListener.js',
'contentAreaDropListener.manifest',
'messageWakeupService.js',
'messageWakeupService.manifest',
+ 'ProcessSelector.js',
+ 'ProcessSelector.manifest',
'SlowScriptDebug.js',
'SlowScriptDebug.manifest',
]
# Firefox for Android provides an alternate version of this component
if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
EXTRA_COMPONENTS += [
'SiteSpecificUserAgent.js',
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -6,16 +6,17 @@
XPIDL_SOURCES += [
'domstubs.idl',
'nsIBrowser.idl',
'nsIBrowserDOMWindow.idl',
'nsIContentPermissionPrompt.idl',
'nsIContentPrefService.idl',
'nsIContentPrefService2.idl',
+ 'nsIContentProcess.idl',
'nsIContentURIGrouper.idl',
'nsIDOMChromeWindow.idl',
'nsIDOMClientRect.idl',
'nsIDOMClientRectList.idl',
'nsIDOMConstructor.idl',
'nsIDOMCrypto.idl',
'nsIDOMGlobalPropertyInitializer.idl',
'nsIDOMHistory.idl',
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsIContentProcess.idl
@@ -0,0 +1,53 @@
+/* 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 "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIMessageSender;
+interface nsIURI;
+
+[scriptable, builtinclass, uuid(456f58be-29dd-4973-885b-95aece1c9a8a)]
+interface nsIContentProcessInfo : nsISupports
+{
+ /**
+ * Is this content process alive?
+ */
+ readonly attribute boolean isAlive;
+
+ /**
+ * The content process's PID.
+ * Throws if the process is not alive.
+ */
+ readonly attribute int32_t processId;
+
+ /**
+ * This content process's opener.
+ */
+ readonly attribute nsIContentProcessInfo opener;
+
+ /**
+ * The process manager for this ContentParent (so a process message manager
+ * as opposed to a frame message manager.
+ */
+ readonly attribute nsIMessageSender messageManager;
+};
+
+[scriptable, uuid(83ffb063-5f65-4c45-ae07-3f553e0809bb)]
+interface nsIContentProcessProvider : nsISupports
+{
+ /**
+ * Return this from provideProcess to create a new process.
+ */
+ const int32_t NEW_PROCESS = -1;
+
+ /**
+ * Given aAliveProcesses (with an opener aOpener), choose which process of
+ * aType to use. Return nsIContentProcessProvider.NEW_PROCESS to ask the
+ * caller to create a new content process.
+ */
+ int32_t provideProcess(in AString aType, in nsIContentProcessInfo aOpener,
+ [array, size_is(aCount)] in nsIContentProcessInfo aAliveProcesses,
+ in uint32_t aCount);
+};
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -108,16 +108,17 @@
#include "nsContentUtils.h"
#include "nsDebugImpl.h"
#include "nsFrameLoader.h"
#include "nsFrameMessageManager.h"
#include "nsHashPropertyBag.h"
#include "nsIAlertsService.h"
#include "nsIClipboard.h"
#include "nsContentPermissionHelper.h"
+#include "nsIContentProcess.h"
#include "nsICycleCollectorListener.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDocument.h"
#include "nsIDOMGeoGeolocation.h"
#include "nsIDOMGeoPositionError.h"
#include "nsIDragService.h"
#include "mozilla/dom/WakeLock.h"
#include "nsIDOMWindow.h"
@@ -434,16 +435,100 @@ ContentParentsMemoryReporter::CollectRep
KIND_OTHER, UNITS_COUNT,
numQueuedMessages, desc, aData);
}
return NS_OK;
}
nsClassHashtable<nsStringHashKey, nsTArray<ContentParent*>>* ContentParent::sBrowserContentParents;
+
+namespace {
+
+class ScriptableCPInfo final : public nsIContentProcessInfo
+{
+public:
+ explicit ScriptableCPInfo(ContentParent* aParent)
+ : mContentParent(aParent)
+ {
+ MOZ_ASSERT(mContentParent);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPROCESSINFO
+
+ void ProcessDied()
+ {
+ mContentParent = nullptr;
+ }
+
+private:
+ ~ScriptableCPInfo()
+ {
+ MOZ_ASSERT(!mContentParent, "must call ProcessDied");
+ }
+
+ ContentParent* mContentParent;
+};
+
+NS_IMPL_ISUPPORTS(ScriptableCPInfo, nsIContentProcessInfo)
+
+NS_IMETHODIMP
+ScriptableCPInfo::GetIsAlive(bool* aIsAlive)
+{
+ *aIsAlive = mContentParent != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableCPInfo::GetProcessId(int32_t* aPID)
+{
+ if (!mContentParent) {
+ *aPID = -1;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ *aPID = mContentParent->Pid();
+ if (*aPID == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableCPInfo::GetOpener(nsIContentProcessInfo** aInfo)
+{
+ *aInfo = nullptr;
+ if (!mContentParent) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (ContentParent* opener = mContentParent->Opener()) {
+ nsCOMPtr<nsIContentProcessInfo> info = opener->ScriptableHelper();
+ info.forget(aInfo);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptableCPInfo::GetMessageManager(nsIMessageSender** aMessenger)
+{
+ *aMessenger = nullptr;
+ if (!mContentParent) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsCOMPtr<nsIMessageSender> manager = mContentParent->GetMessageManager();
+ manager.forget(aMessenger);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
nsTArray<ContentParent*>* ContentParent::sPrivateContent;
StaticAutoPtr<LinkedList<ContentParent> > ContentParent::sContentParents;
#if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
UniquePtr<SandboxBrokerPolicyFactory> ContentParent::sSandboxBrokerPolicyFactory;
#endif
// This is true when subprocess launching is enabled. This is the
// case between StartUp() and ShutDown() or JoinAllSubprocesses().
@@ -670,47 +755,74 @@ ContentParent::RandomSelect(const nsTArr
}
/*static*/ already_AddRefed<ContentParent>
ContentParent::GetNewOrUsedBrowserProcess(const nsAString& aRemoteType,
ProcessPriority aPriority,
ContentParent* aOpener)
{
nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType);
-
uint32_t maxContentParents = GetMaxProcessCount(aRemoteType);
- RefPtr<ContentParent> p;
- if (contentParents.Length() >= maxContentParents) {
+ if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
// We never want to re-use Large-Allocation processes.
- if (aRemoteType.EqualsLiteral(LARGE_ALLOCATION_REMOTE_TYPE)) {
+ if (contentParents.Length() >= maxContentParents) {
return GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
aPriority,
aOpener);
}
-
- if ((p = RandomSelect(contentParents, aOpener, maxContentParents))) {
+ } else {
+ nsTArray<nsIContentProcessInfo*> infos(contentParents.Length());
+ for (auto* cp : contentParents) {
+ infos.AppendElement(cp->mScriptableHelper);
+ }
+
+ nsCOMPtr<nsIContentProcessProvider> cpp =
+ do_GetService("@mozilla.org/ipc/processselector;1");
+ nsIContentProcessInfo* openerInfo = aOpener ? aOpener->mScriptableHelper.get() : nullptr;
+ int32_t index;
+ if (cpp &&
+ NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, openerInfo,
+ infos.Elements(), infos.Length(),
+ &index))) {
+ // If the provider returned an existing ContentParent, use that one.
+ if (0 <= index && static_cast<uint32_t>(index) <= maxContentParents) {
+ RefPtr<ContentParent> retval = contentParents[index];
+ return retval.forget();
+ }
+ } else {
+ // If there was a problem with the JS chooser, fall back to a random
+ // selection.
+ NS_WARNING("nsIContentProcessProvider failed to return a process");
+ RefPtr<ContentParent> random;
+ if (contentParents.Length() >= maxContentParents &&
+ (random = RandomSelect(contentParents, aOpener, maxContentParents))) {
+ return random.forget();
+ }
+ }
+
+ // Try to take the preallocated process only for the default process type.
+ RefPtr<ContentParent> p;
+ if (aRemoteType.EqualsLiteral(DEFAULT_REMOTE_TYPE) &&
+ (p = PreallocatedProcessManager::Take())) {
+ // For pre-allocated process we have not set the opener yet.
+ p->mOpener = aOpener;
+ contentParents.AppendElement(p);
return p.forget();
}
}
- // Try to take the preallocated process only for the default process type.
- if (aRemoteType.Equals(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)) &&
- (p = PreallocatedProcessManager::Take())) {
- // For pre-allocated process we have not set the opener yet.
- p->mOpener = aOpener;
- } else {
- p = new ContentParent(aOpener, aRemoteType);
-
- if (!p->LaunchSubprocess(aPriority)) {
- return nullptr;
- }
-
- p->Init();
- }
+ // Create a new process from scratch.
+ RefPtr<ContentParent> p = new ContentParent(aOpener, aRemoteType);
+
+ if (!p->LaunchSubprocess(aPriority)) {
+ return nullptr;
+ }
+
+ p->Init();
contentParents.AppendElement(p);
return p.forget();
}
/*static*/ ProcessPriority
ContentParent::GetInitialProcessPriority(Element* aFrameElement)
{
@@ -1180,16 +1292,18 @@ ContentParent::Init()
mGatherer = static_cast<ProfileGatherer*>(gatherer.get());
StartProfiler(currentProfilerParams);
}
#endif
RefPtr<GeckoMediaPluginServiceParent> gmps(GeckoMediaPluginServiceParent::GetSingleton());
gmps->UpdateContentProcessGMPCapabilities();
+
+ mScriptableHelper = new ScriptableCPInfo(this);
}
namespace {
class RemoteWindowContext final : public nsIRemoteWindowContext
, public nsIInterfaceRequestor
{
public:
@@ -1250,16 +1364,21 @@ ContentParent::SetPriorityAndCheckIsAliv
#endif
return true;
}
void
ContentParent::ShutDownProcess(ShutDownMethod aMethod)
{
+ if (mScriptableHelper) {
+ static_cast<ScriptableCPInfo*>(mScriptableHelper.get())->ProcessDied();
+ mScriptableHelper = nullptr;
+ }
+
// Shutting down by sending a shutdown message works differently than the
// other methods. We first call Shutdown() in the child. After the child is
// ready, it calls FinishShutdown() on us. Then we close the channel.
if (aMethod == SEND_SHUTDOWN_MESSAGE) {
if (mIPCOpen && !mShutdownPending && SendShutdown()) {
mShutdownPending = true;
// Start the force-kill timer if we haven't already.
StartForceKillTimer();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -40,16 +40,17 @@
#define DEFAULT_REMOTE_TYPE "web"
#define FILE_REMOTE_TYPE "file"
#define EXTENSION_REMOTE_TYPE "extension"
// This must start with the DEFAULT_REMOTE_TYPE above.
#define LARGE_ALLOCATION_REMOTE_TYPE "webLargeAllocation"
class nsConsoleService;
+class nsIContentProcessInfo;
class nsICycleCollectorLogSink;
class nsIDumpGCAndCCLogsCallback;
class nsITabParent;
class nsITimer;
class ParentIdleListener;
class nsIWidget;
namespace mozilla {
@@ -367,16 +368,20 @@ public:
{
return mSubprocess;
}
ContentParent* Opener() const
{
return mOpener;
}
+ nsIContentProcessInfo* ScriptableHelper() const
+ {
+ return mScriptableHelper;
+ }
bool NeedsPermissionsUpdate() const
{
return mSendPermissionUpdates;
}
/**
* Kill our subprocess and make sure it dies. Should only be used
@@ -1161,16 +1166,17 @@ private:
bool mCalledClose;
bool mCalledKillHard;
bool mCreatedPairedMinidumps;
bool mShutdownPending;
bool mIPCOpen;
RefPtr<nsConsoleService> mConsoleService;
nsConsoleService* GetConsoleService();
+ nsCOMPtr<nsIContentProcessInfo> mScriptableHelper;
nsTArray<nsCOMPtr<nsIObserver>> mIdleListeners;
#ifdef MOZ_X11
// Dup of child's X socket, used to scope its resources to this
// object instead of the child process's lifetime.
ScopedClose mChildXSocketFdDup;
#endif