diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp index 80dd959fba75..c498747d02e0 100644 --- a/docshell/base/CanonicalBrowsingContext.cpp +++ b/docshell/base/CanonicalBrowsingContext.cpp @@ -1758,7 +1758,7 @@ void CanonicalBrowsingContext::PendingRemotenessChange::Clear() { // When this PendingRemotenessChange was created, it was given a // `mContentParent`. if (mContentParent) { - mContentParent->RemoveKeepAlive(mTarget->BrowserId()); + mContentParent->RemoveKeepAlive(); mContentParent = nullptr; } @@ -1914,7 +1914,7 @@ CanonicalBrowsingContext::ChangeRemoteness( // Switching to local, so we don't need to create a new process, and will // instead use our embedder process. change->mContentParent = embedderBrowser->Manager(); - change->mContentParent->AddKeepAlive(BrowserId()); + change->mContentParent->AddKeepAlive(); change->ProcessLaunched(); return promise.forget(); } @@ -1933,7 +1933,7 @@ CanonicalBrowsingContext::ChangeRemoteness( aOptions.mReplaceBrowsingContext && aOptions.mRemoteType == existingProcess->GetRemoteType()) { change->mContentParent = existingProcess; - change->mContentParent->AddKeepAlive(BrowserId()); + change->mContentParent->AddKeepAlive(); change->ProcessLaunched(); return promise.forget(); } @@ -1955,7 +1955,6 @@ CanonicalBrowsingContext::ChangeRemoteness( change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess( /* aRemoteType = */ aOptions.mRemoteType, /* aGroup = */ finalGroup, - /* aBrowserId */ BrowserId(), /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND, /* aPreferUsed = */ preferUsed); if (!change->mContentParent) { @@ -1966,7 +1965,7 @@ CanonicalBrowsingContext::ChangeRemoteness( // Add a KeepAlive used by this ContentParent, which will be cleared when // the change is complete. This should prevent the process dying before // we're ready to use it. - change->mContentParent->AddKeepAlive(BrowserId()); + change->mContentParent->AddKeepAlive(); if (change->mContentParent->IsLaunching()) { change->mContentParent->WaitForLaunchAsync()->Then( GetMainThreadSerialEventTarget(), __func__, diff --git a/dom/base/ProcessSelector.jsm b/dom/base/ProcessSelector.jsm new file mode 100644 index 000000000000..e582d485b413 --- /dev/null +++ b/dom/base/ProcessSelector.jsm @@ -0,0 +1,60 @@ +/* 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/. */ + +// 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: ChromeUtils.generateQI(["nsIContentProcessProvider"]), + + provideProcess(aType, aProcesses, aMaxCount) { + if (aProcesses.length < aMaxCount) { + return Ci.nsIContentProcessProvider.NEW_PROCESS; + } + + return Math.floor(Math.random() * aMaxCount); + }, +}; + +// Fills up aProcesses until max and then selects one from the available +// ones that host the least number of tabs. +function MinTabSelector() {} + +MinTabSelector.prototype = { + classID: Components.ID("{2dc08eaf-6eef-4394-b1df-a3a927c1290b}"), + QueryInterface: ChromeUtils.generateQI(["nsIContentProcessProvider"]), + + provideProcess(aType, aProcesses, aMaxCount) { + let min = Number.MAX_VALUE; + let candidate = Ci.nsIContentProcessProvider.NEW_PROCESS; + + // The reason for not directly using aProcesses.length here is because if + // we keep processes alive for testing but want a test to use only single + // content process we can just keep relying on dom.ipc.processCount = 1 + // this way. + let numIters = Math.min(aProcesses.length, aMaxCount); + + for (let i = 0; i < numIters; i++) { + let process = aProcesses[i]; + let tabCount = process.tabCount; + if (tabCount < min) { + min = tabCount; + candidate = i; + } + } + + // If all current processes have at least one tab and we have not yet + // reached the maximum, spawn a new process. + if (min > 0 && aProcesses.length < aMaxCount) { + return Ci.nsIContentProcessProvider.NEW_PROCESS; + } + + // Otherwise we use candidate. + return candidate; + }, +}; + +var EXPORTED_SYMBOLS = ["RandomSelector", "MinTabSelector"]; diff --git a/dom/base/components.conf b/dom/base/components.conf index ebb89c3a85f7..33fbb01dcb0c 100644 --- a/dom/base/components.conf +++ b/dom/base/components.conf @@ -13,6 +13,17 @@ Classes = [ 'jsm': 'resource://gre/modules/ContentAreaDropListener.jsm', 'constructor': 'ContentAreaDropListener', }, + { + 'cid': '{c616fcfd-9737-41f1-aa74-cee72a38f91b}', + 'jsm': 'resource://gre/modules/ProcessSelector.jsm', + 'constructor': 'RandomSelector', + }, + { + 'cid': '{2dc08eaf-6eef-4394-b1df-a3a927c1290b}', + 'contract_ids': ['@mozilla.org/ipc/processselector;1'], + 'jsm': 'resource://gre/modules/ProcessSelector.jsm', + 'constructor': 'MinTabSelector', + }, { 'cid': '{e740ddb4-18b4-4aac-8ae1-9b0f4320769d}', 'contract_ids': ['@mozilla.org/dom/slow-script-debug;1'], diff --git a/dom/base/moz.build b/dom/base/moz.build index c8bd3337a849..229e06a54520 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -521,6 +521,7 @@ EXTRA_JS_MODULES += [ "DOMRequestHelper.jsm", "IndexedDBHelper.jsm", "LocationHelper.jsm", + "ProcessSelector.jsm", "SlowScriptDebug.jsm", ] diff --git a/dom/interfaces/base/moz.build b/dom/interfaces/base/moz.build index ec3e81510c08..3749363ea1eb 100644 --- a/dom/interfaces/base/moz.build +++ b/dom/interfaces/base/moz.build @@ -15,6 +15,7 @@ XPIDL_SOURCES += [ "nsIBrowserUsage.idl", "nsIContentPermissionPrompt.idl", "nsIContentPrefService2.idl", + "nsIContentProcess.idl", "nsIDOMChromeWindow.idl", "nsIDOMGlobalPropertyInitializer.idl", "nsIDOMWindow.idl", diff --git a/dom/interfaces/base/nsIContentProcess.idl b/dom/interfaces/base/nsIContentProcess.idl new file mode 100644 index 000000000000..198bb093a6ec --- /dev/null +++ b/dom/interfaces/base/nsIContentProcess.idl @@ -0,0 +1,51 @@ +/* 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 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; + + /** + * Number of opened tabs living in this content process. + */ + readonly attribute int32_t tabCount; + + /** + * The process manager for this ContentParent (so a process message manager + * as opposed to a frame message manager. + */ + readonly attribute nsISupports 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, 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 AUTF8String aType, + in Array aAliveProcesses, + in uint32_t aMaxCount); +}; diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 00ae45d91a2d..ff7a7c1d6968 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -249,8 +249,6 @@ BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId, if (aBrowsingContext->Top()->IsPriorityActive()) { ProcessPriorityManager::ActivityChanged(this, true); } - - mManager->AddKeepAlive(aBrowsingContext->BrowserId()); } BrowserParent::~BrowserParent() = default; @@ -640,17 +638,13 @@ void BrowserParent::Destroy() { mIsDestroyed = true; - if (CanSend()) { - Manager()->RemoveKeepAlive(mBrowsingContext->BrowserId()); - } - Manager()->NotifyTabDestroying(); // This `AddKeepAlive` will be cleared if `mMarkedDestroying` is set in // `ActorDestroy`. Out of caution, we don't add the `KeepAlive` if our IPC // actor has somehow already been destroyed, as that would mean `ActorDestroy` // won't be called. - if (CanSend()) { + if (CanRecv()) { mBrowsingContext->Group()->AddKeepAlive(); } @@ -678,10 +672,6 @@ mozilla::ipc::IPCResult BrowserParent::RecvEnsureLayersConnected( } void BrowserParent::ActorDestroy(ActorDestroyReason why) { - if (!mIsDestroyed) { - Manager()->RemoveKeepAlive(mBrowsingContext->BrowserId()); - } - Manager()->NotifyTabDestroyed(mTabId, mMarkedDestroying); ContentProcessManager::GetSingleton()->UnregisterRemoteFrame(mTabId); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 5dca1b9e062c..2e660738e00b 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -198,6 +198,7 @@ #include "nsICaptivePortalService.h" #include "nsICertOverrideService.h" #include "nsIClipboard.h" +#include "nsIContentProcess.h" #include "nsIContentSecurityPolicy.h" #include "nsICookie.h" #include "nsICrashService.h" @@ -525,6 +526,70 @@ uint64_t ComputeLoadedOriginHash(nsIPrincipal* aPrincipal) { return ((uint64_t)originNoSuffix) << 32 | originSuffix; } +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::GetTabCount(int32_t* aTabCount) { + if (!mContentParent) { + return NS_ERROR_NOT_INITIALIZED; + } + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + *aTabCount = cpm->GetBrowserParentCountByProcessId(mContentParent->ChildID()); + + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetMessageManager(nsISupports** aMessenger) { + *aMessenger = nullptr; + if (!mContentParent) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr manager = mContentParent->GetMessageManager(); + manager.forget(aMessenger); + return NS_OK; +} + ProcessID GetTelemetryProcessID(const nsACString& remoteType) { // OOP WebExtensions run in a content process. // For Telemetry though we want to break out collected data from the @@ -769,6 +834,39 @@ void ContentParent::ReleaseCachedProcesses() { } } +/*static*/ +already_AddRefed ContentParent::MinTabSelect( + const nsTArray& aContentParents, + int32_t aMaxContentParents) { + uint32_t maxSelectable = + std::min(static_cast(aContentParents.Length()), + static_cast(aMaxContentParents)); + uint32_t min = INT_MAX; + RefPtr candidate; + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + + for (uint32_t i = 0; i < maxSelectable; i++) { + ContentParent* p = aContentParents[i]; + MOZ_DIAGNOSTIC_ASSERT(!p->IsDead()); + + uint32_t tabCount = cpm->GetBrowserParentCountByProcessId(p->ChildID()); + if (tabCount < min) { + candidate = p; + min = tabCount; + } + } + + // If all current processes have at least one tab and we have not yet reached + // the maximum, use a new process. + if (min > 0 && + aContentParents.Length() < static_cast(aMaxContentParents)) { + return nullptr; + } + + // Otherwise we return candidate. + return candidate.forget(); +} + /* static */ already_AddRefed ContentParent::CreateRemoteTypeIsolationPrincipal( @@ -789,81 +887,62 @@ ContentParent::CreateRemoteTypeIsolationPrincipal( return principal.forget(); } -// Determine the effective number of tabs which are loaded in a given content -// process, based on the keepalive table. We don't count `0` (as it doesn't -// correspond to a tab), and `aIgnoreBrowserId` (as it corresponds to the -// navigating tab, or is `0`) when doing the counts. -uint32_t ContentParent::EffectiveTabCount(uint64_t aIgnoreBrowserId) { - nsTHashSet browserIds; - // First, collect the BrowserIds associated with each KeepAlive. - for (const auto& id : mKeepAlivesByBrowserId.Keys()) { - if (id != 0 && id != aIgnoreBrowserId) { - browserIds.Insert(aIgnoreBrowserId); - } - } - // Then, collect the BrowserIds associated with each existing BrowserParent, - // even if they're still actively being destroyed. (FIXME: Why should we be - // counting tabs as they're being unloaded?) - for (auto* bp : ManagedPBrowserParent()) { - uint64_t browserId = - BrowserParent::GetFrom(bp)->GetBrowsingContext()->BrowserId(); - if (browserId != 0 && browserId != aIgnoreBrowserId) { - browserIds.Insert(browserId); - } - } - return browserIds.Count(); -} - /*static*/ already_AddRefed ContentParent::GetUsedBrowserProcess( const nsACString& aRemoteType, nsTArray& aContentParents, - uint32_t aMaxContentParents, uint64_t aBrowserId, bool aPreferUsed, - ProcessPriority aPriority) { + uint32_t aMaxContentParents, bool aPreferUsed, ProcessPriority aPriority) { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED AutoRestore ar(sInProcessSelector); sInProcessSelector = true; #endif - if (!StaticPrefs::dom_ipc_disableContentProcessReuse() && - !aContentParents.IsEmpty()) { - // Collect a list of eligible content processes which are tied for the - // lowest effective tab count. We only consider at most `aMaxContentParents` - // processes even if more are available, in case the process count was - // dropped by a test. - RefPtr selected; - uint32_t minTabCount = UINT32_MAX; - uint32_t processCount = - std::min(aMaxContentParents, uint32_t(aContentParents.Length())); - for (uint32_t i = 0; i < processCount; ++i) { - uint32_t effectiveTabCount = - aContentParents[i]->EffectiveTabCount(aBrowserId); - if (effectiveTabCount < minTabCount) { - minTabCount = effectiveTabCount; - selected = aContentParents[i]; - } - } + uint32_t numberOfParents = aContentParents.Length(); + nsTArray> infos(numberOfParents); + for (auto* cp : aContentParents) { + infos.AppendElement(cp->mScriptableHelper); + } - // If every tab has at least one tab (other than our tab, which was excluded - // from the count) already loaded in it, prefer creating a new process. - if (minTabCount > 0 && !aPreferUsed && - aMaxContentParents > aContentParents.Length()) { - selected = nullptr; - } + if (aPreferUsed && numberOfParents) { + // If we prefer re-using existing content processes, we don't want to create + // a new process, and instead re-use an existing one, so pretend the process + // limit is at the current number of processes. + aMaxContentParents = numberOfParents; + } - // If we successfully selected a content process, return it. - if (selected) { + nsCOMPtr cpp = + do_GetService("@mozilla.org/ipc/processselector;1"); + int32_t index; + if (cpp && NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, infos, + aMaxContentParents, &index))) { + // If the provider returned an existing ContentParent, use that one. + if (0 <= index && static_cast(index) <= aMaxContentParents) { + RefPtr retval = aContentParents[index]; if (profiler_thread_is_being_profiled_for_markers()) { - nsPrintfCString marker("Reused process %" PRIu64, - (uint64_t)selected->ChildID()); + nsPrintfCString marker("Reused process %u", + (unsigned int)retval->ChildID()); PROFILER_MARKER_TEXT("Process", DOM, {}, marker); } MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, - ("GetUsedProcess: Reused process %p (%" PRIu64 ") for %s", - selected.get(), (uint64_t)selected->ChildID(), + ("GetUsedProcess: Reused process %p (%u) for %s", retval.get(), + (unsigned int)retval->ChildID(), PromiseFlatCString(aRemoteType).get())); - selected->AssertAlive(); - selected->StopRecycling(); - return selected.forget(); + retval->AssertAlive(); + retval->StopRecycling(); + 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 random; + if ((random = MinTabSelect(aContentParents, aMaxContentParents))) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetUsedProcess: Reused random process %p (%d) for %s", + random.get(), (unsigned int)random->ChildID(), + PromiseFlatCString(aRemoteType).get())); + random->AssertAlive(); + random->StopRecycling(); + return random.forget(); } } @@ -943,7 +1022,7 @@ already_AddRefed ContentParent::GetUsedBrowserProcess( already_AddRefed ContentParent::GetNewOrUsedLaunchingBrowserProcess( const nsACString& aRemoteType, BrowsingContextGroup* aGroup, - uint64_t aBrowserId, ProcessPriority aPriority, bool aPreferUsed) { + ProcessPriority aPriority, bool aPreferUsed) { MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, ("GetNewOrUsedProcess for type %s", PromiseFlatCString(aRemoteType).get())); @@ -968,9 +1047,8 @@ ContentParent::GetNewOrUsedLaunchingBrowserProcess( uint32_t maxContentParents = GetMaxProcessCount(aRemoteType); // Let's try and reuse an existing process. - contentParent = - GetUsedBrowserProcess(aRemoteType, contentParents, maxContentParents, - aBrowserId, aPreferUsed, aPriority); + contentParent = GetUsedBrowserProcess( + aRemoteType, contentParents, maxContentParents, aPreferUsed, aPriority); if (!contentParent) { // No reusable process. Let's create and launch one. @@ -1013,12 +1091,11 @@ ContentParent::GetNewOrUsedLaunchingBrowserProcess( RefPtr ContentParent::GetNewOrUsedBrowserProcessAsync(const nsACString& aRemoteType, BrowsingContextGroup* aGroup, - uint64_t aBrowserId, ProcessPriority aPriority, bool aPreferUsed) { // Obtain a `ContentParent` launched asynchronously. RefPtr contentParent = GetNewOrUsedLaunchingBrowserProcess( - aRemoteType, aGroup, aBrowserId, aPriority, aPreferUsed); + aRemoteType, aGroup, aPriority, aPreferUsed); if (!contentParent) { // In case of launch error, stop here. return LaunchPromise::CreateAndReject(LaunchError(), __func__); @@ -1029,9 +1106,9 @@ ContentParent::GetNewOrUsedBrowserProcessAsync(const nsACString& aRemoteType, /*static*/ already_AddRefed ContentParent::GetNewOrUsedBrowserProcess( const nsACString& aRemoteType, BrowsingContextGroup* aGroup, - uint64_t aBrowserId, ProcessPriority aPriority, bool aPreferUsed) { + ProcessPriority aPriority, bool aPreferUsed) { RefPtr contentParent = GetNewOrUsedLaunchingBrowserProcess( - aRemoteType, aGroup, aBrowserId, aPriority, aPreferUsed); + aRemoteType, aGroup, aPriority, aPreferUsed); if (!contentParent || !contentParent->WaitForLaunchSync(aPriority)) { // In case of launch error, stop here. return nullptr; @@ -1397,8 +1474,7 @@ already_AddRefed ContentParent::CreateBrowser( aContext.JSPluginId(), PROCESS_PRIORITY_FOREGROUND); } else { constructorSender = GetNewOrUsedBrowserProcess( - remoteType, aBrowsingContext->Group(), aBrowsingContext->BrowserId(), - PROCESS_PRIORITY_FOREGROUND); + remoteType, aBrowsingContext->Group(), PROCESS_PRIORITY_FOREGROUND); } if (!constructorSender) { return nullptr; @@ -1856,6 +1932,11 @@ void ContentParent::MarkAsDead() { } #endif + if (mScriptableHelper) { + static_cast(mScriptableHelper.get())->ProcessDied(); + mScriptableHelper = nullptr; + } + mLifecycleState = LifecycleState::DEAD; } @@ -2133,7 +2214,7 @@ bool ContentParent::ShouldKeepProcessAlive() { return true; } - if (!mKeepAlivesByBrowserId.IsEmpty()) { + if (mNumKeepaliveCalls > 0) { return true; } @@ -2204,21 +2285,14 @@ void ContentParent::NotifyTabDestroying() { #endif // !defined(MOZ_WIDGET_ANDROID) } -void ContentParent::AddKeepAlive(uint64_t aBrowserId) { +void ContentParent::AddKeepAlive() { // Something wants to keep this content process alive. - auto& numKeepaliveCalls = mKeepAlivesByBrowserId.LookupOrInsert(aBrowserId); - ++numKeepaliveCalls; + ++mNumKeepaliveCalls; } -void ContentParent::RemoveKeepAlive(uint64_t aBrowserId) { - MOZ_DIAGNOSTIC_ASSERT(!mKeepAlivesByBrowserId.IsEmpty()); - - auto entry = mKeepAlivesByBrowserId.Lookup(aBrowserId); - MOZ_DIAGNOSTIC_ASSERT(entry && entry.Data() > 0); - --entry.Data(); - if (entry.Data() == 0) { - entry.Remove(); - } +void ContentParent::RemoveKeepAlive() { + MOZ_DIAGNOSTIC_ASSERT(mNumKeepaliveCalls > 0); + --mNumKeepaliveCalls; MaybeBeginShutDown(); } @@ -2664,6 +2738,7 @@ ContentParent::ContentParent(const nsACString& aRemoteType, int32_t aJSPluginID) mJSPluginID(aJSPluginID), mRemoteWorkerActorData("ContentParent::mRemoteWorkerActorData"), mNumDestroyingTabs(0), + mNumKeepaliveCalls(0), mLifecycleState(LifecycleState::LAUNCHING), mIsForBrowser(!mRemoteType.IsEmpty()), mCalledClose(false), @@ -2705,6 +2780,10 @@ ContentParent::ContentParent(const nsACString& aRemoteType, int32_t aJSPluginID) ("CreateSubprocess: ContentParent %p mSubprocess %p handle %" PRIuPTR, this, mSubprocess, mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); + + // This is safe to do in the constructor, as it doesn't take a strong + // reference. + mScriptableHelper = new ScriptableCPInfo(this); } ContentParent::~ContentParent() { @@ -2734,6 +2813,13 @@ ContentParent::~ContentParent() { mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); mSubprocess->Destroy(); } + + // Make sure to clear the connection from `mScriptableHelper` if it hasn't + // been cleared yet. + if (mScriptableHelper) { + static_cast(mScriptableHelper.get())->ProcessDied(); + mScriptableHelper = nullptr; + } } bool ContentParent::InitInternal(ProcessPriority aInitialPriority) { diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index a901230fc4c0..58709df2327b 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -53,6 +53,7 @@ #define CHILD_PROCESS_SHUTDOWN_MESSAGE u"child-process-shutdown"_ns class nsConsoleService; +class nsIContentProcessInfo; class nsICycleCollectorLogSink; class nsIDumpGCAndCCLogsCallback; class nsIRemoteTab; @@ -175,6 +176,15 @@ class ContentParent final static void LogAndAssertFailedPrincipalValidationInfo( nsIPrincipal* aPrincipal, const char* aMethod); + /** + * Picks a random content parent from |aContentParents| respecting the index + * limit set by |aMaxContentParents|. + * Returns null if non available. + */ + static already_AddRefed MinTabSelect( + const nsTArray& aContentParents, + int32_t maxContentParents); + /** * Get or create a content process for: * 1. browser iframe @@ -183,13 +193,11 @@ class ContentParent final */ static RefPtr GetNewOrUsedBrowserProcessAsync( const nsACString& aRemoteType, BrowsingContextGroup* aGroup = nullptr, - uint64_t aBrowserId = 0, hal::ProcessPriority aPriority = hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, bool aPreferUsed = false); static already_AddRefed GetNewOrUsedBrowserProcess( const nsACString& aRemoteType, BrowsingContextGroup* aGroup = nullptr, - uint64_t aBrowserId = 0, hal::ProcessPriority aPriority = hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, bool aPreferUsed = false); @@ -206,7 +214,6 @@ class ContentParent final */ static already_AddRefed GetNewOrUsedLaunchingBrowserProcess( const nsACString& aRemoteType, BrowsingContextGroup* aGroup = nullptr, - uint64_t aBrowserId = 0, hal::ProcessPriority aPriority = hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, bool aPreferUsed = false); @@ -366,10 +373,9 @@ class ContentParent final void NotifyTabDestroyed(const TabId& aTabId, bool aNotifiedDestroying); // Manage the set of `KeepAlive`s on this ContentParent which are preventing - // it from being destroyed. This is keyed by BrowserId to allow it to be used - // to assist in process selection. - void AddKeepAlive(uint64_t aBrowserId = 0); - void RemoveKeepAlive(uint64_t aBrowserId = 0); + // it from being destroyed. + void AddKeepAlive(); + void RemoveKeepAlive(); TestShellParent* CreateTestShell(); @@ -404,6 +410,8 @@ class ContentParent final GeckoChildProcessHost* Process() const { return mSubprocess; } + nsIContentProcessInfo* ScriptableHelper() const { return mScriptableHelper; } + mozilla::dom::ProcessMessageManager* GetMessageManager() const { return mMessageManager; } @@ -1466,12 +1474,7 @@ class ContentParent final // Return an existing ContentParent if possible. Otherwise, `nullptr`. static already_AddRefed GetUsedBrowserProcess( const nsACString& aRemoteType, nsTArray& aContentParents, - uint32_t aMaxContentParents, uint64_t aBrowserId, bool aPreferUsed, - ProcessPriority aPriority); - - // Count the number of tabs loaded in this ContentParent. Tabs associated with - // the given BrowserId will not be counted, if passed. - uint32_t EffectiveTabCount(uint64_t aIgnoreBrowserId = 0); + uint32_t aMaxContentParents, bool aPreferUsed, ProcessPriority aPriority); void AddToPool(nsTArray&); void RemoveFromPool(nsTArray&); @@ -1547,10 +1550,7 @@ class ContentParent final // NotifyTabDestroying() but not called NotifyTabDestroyed(). int32_t mNumDestroyingTabs; - // The number of KeepAlive calls for this ContentParent, keyed by BrowserId. - // This is used to track the number of tabs which are actively being hosted by - // each ContentParent. - nsTHashMap mKeepAlivesByBrowserId; + uint32_t mNumKeepaliveCalls; // The process starts in the LAUNCHING state, and transitions to // ALIVE once it can accept IPC messages. It remains ALIVE only @@ -1590,6 +1590,8 @@ class ContentParent final uint8_t mIsInPool : 1; + nsCOMPtr mScriptableHelper; + nsTArray> mIdleListeners; #ifdef MOZ_X11 diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.cpp b/dom/workers/remoteworkers/RemoteWorkerManager.cpp index 988bf9acc023..d9c266f68313 100644 --- a/dom/workers/remoteworkers/RemoteWorkerManager.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp @@ -716,7 +716,6 @@ void RemoteWorkerManager::LaunchNewContentProcess( ContentParent::GetNewOrUsedBrowserProcessAsync( /* aRemoteType = */ remoteType, /* aGroup */ nullptr, - /* aBrowserId */ 0, hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, /* aPreferUsed */ true) ->Then(GetCurrentSerialEventTarget(), __func__, diff --git a/image/ImageCacheKey.cpp b/image/ImageCacheKey.cpp index 11c4192f367c..67819071bd36 100644 --- a/image/ImageCacheKey.cpp +++ b/image/ImageCacheKey.cpp @@ -35,8 +35,7 @@ ImageCacheKey::ImageCacheKey(nsIURI* aURI, const OriginAttributes& aAttrs, mOriginAttributes(aAttrs), mControlledDocument(GetSpecialCaseDocumentToken(aDocument)), mIsolationKey(GetIsolationKey(aDocument, aURI)), - mIsChrome(false), - mAppType(GetAppType(aDocument)) { + mIsChrome(false) { if (mURI->SchemeIs("chrome")) { mIsChrome = true; } @@ -48,8 +47,7 @@ ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther) mControlledDocument(aOther.mControlledDocument), mIsolationKey(aOther.mIsolationKey), mHash(aOther.mHash), - mIsChrome(aOther.mIsChrome), - mAppType(aOther.mAppType) {} + mIsChrome(aOther.mIsChrome) {} ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther) : mURI(std::move(aOther.mURI)), @@ -57,8 +55,7 @@ ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther) mControlledDocument(aOther.mControlledDocument), mIsolationKey(aOther.mIsolationKey), mHash(aOther.mHash), - mIsChrome(aOther.mIsChrome), - mAppType(aOther.mAppType) {} + mIsChrome(aOther.mIsChrome) {} bool ImageCacheKey::operator==(const ImageCacheKey& aOther) const { // Don't share the image cache between a controlled document and anything @@ -76,10 +73,6 @@ bool ImageCacheKey::operator==(const ImageCacheKey& aOther) const { if (mOriginAttributes != aOther.mOriginAttributes) { return false; } - // Don't share the image cache between two different appTypes - if (mAppType != aOther.mAppType) { - return false; - } // For non-blob URIs, compare the URIs. bool equals = false; @@ -103,7 +96,7 @@ void ImageCacheKey::EnsureHash() const { hash = HashString(spec); hash = AddToHash(hash, HashString(suffix), HashString(mIsolationKey), - HashString(ptr), mAppType); + HashString(ptr)); mHash.emplace(hash); } @@ -172,24 +165,5 @@ nsCString ImageCacheKey::GetIsolationKey(Document* aDocument, nsIURI* aURI) { return ""_ns; } -/* static */ -nsIDocShell::AppType ImageCacheKey::GetAppType(Document* aDocument) { - if (!aDocument) { - return nsIDocShell::APP_TYPE_UNKNOWN; - } - - nsCOMPtr dsti = aDocument->GetDocShell(); - if (!dsti) { - return nsIDocShell::APP_TYPE_UNKNOWN; - } - - nsCOMPtr root; - dsti->GetInProcessRootTreeItem(getter_AddRefs(root)); - if (nsCOMPtr docShell = do_QueryInterface(root)) { - return docShell->GetAppType(); - } - return nsIDocShell::APP_TYPE_UNKNOWN; -} - } // namespace image } // namespace mozilla diff --git a/image/ImageCacheKey.h b/image/ImageCacheKey.h index 7c4fa286ea51..7b728a1d1741 100644 --- a/image/ImageCacheKey.h +++ b/image/ImageCacheKey.h @@ -14,7 +14,6 @@ #include "mozilla/Maybe.h" #include "mozilla/RefPtr.h" #include "PLDHashTable.h" -#include "nsIDocShell.h" class nsIURI; @@ -71,10 +70,6 @@ class ImageCacheKey final { // document's base domain. This is handled by this method. static nsCString GetIsolationKey(dom::Document* aDocument, nsIURI* aURI); - // The AppType of the docshell an image is loaded in can influence whether the - // image is allowed to load. The specific AppType is fetched by this method. - static nsIDocShell::AppType GetAppType(dom::Document* aDocument); - void EnsureHash() const; nsCOMPtr mURI; @@ -83,7 +78,6 @@ class ImageCacheKey final { nsCString mIsolationKey; mutable Maybe mHash; bool mIsChrome; - nsIDocShell::AppType mAppType; }; } // namespace image diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index fe346c315e1e..b7dedb5aceb5 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -2560,15 +2560,6 @@ #endif mirror: always -# If true, disables non-required re-use of content processes. This can be used -# in tests to force a new process to be used whenever a process selection -# decision is made. Setting this pref can cause dom.ipc.processCount limits to -# be exceeded. -- name: dom.ipc.disableContentProcessReuse - type: bool - value: false - mirror: always - - name: dom.ipc.tabs.disabled type: bool value: false diff --git a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm index eb436babd46f..d84c5cff0ac3 100644 --- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm +++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm @@ -19,6 +19,9 @@ var EXPORTED_SYMBOLS = ["BrowserTestUtils"]; const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); +const { ComponentUtils } = ChromeUtils.import( + "resource://gre/modules/ComponentUtils.jsm" +); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); @@ -40,9 +43,32 @@ XPCOMUtils.defineLazyServiceGetters(this, { ], }); +const PROCESSSELECTOR_CONTRACTID = "@mozilla.org/ipc/processselector;1"; +const OUR_PROCESSSELECTOR_CID = Components.ID( + "{f9746211-3d53-4465-9aeb-ca0d96de0253}" +); +const EXISTING_JSID = Cc[PROCESSSELECTOR_CONTRACTID]; +const DEFAULT_PROCESSSELECTOR_CID = EXISTING_JSID + ? Components.ID(EXISTING_JSID.number) + : null; + let gListenerId = 0; -const DISABLE_CONTENT_PROCESS_REUSE_PREF = "dom.ipc.disableContentProcessReuse"; +// A process selector that always asks for a new process. +function NewProcessSelector() {} + +NewProcessSelector.prototype = { + classID: OUR_PROCESSSELECTOR_CID, + QueryInterface: ChromeUtils.generateQI(["nsIContentProcessProvider"]), + + provideProcess() { + return Ci.nsIContentProcessProvider.NEW_PROCESS; + }, +}; + +let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); +let selectorFactory = ComponentUtils._getFactory(NewProcessSelector); +registrar.registerFactory(OUR_PROCESSSELECTOR_CID, "", null, selectorFactory); const kAboutPageRegistrationContentScript = "chrome://mochikit/content/tests/BrowserTestUtils/content-about-page-utils.js"; @@ -200,17 +226,19 @@ var BrowserTestUtils = { } = options; let promises, tab; - let disableReusePrefValue = Services.prefs.getBoolPref( - DISABLE_CONTENT_PROCESS_REUSE_PREF - ); try { // If we're asked to force a new process, replace the normal process // selector with one that always asks for a new process. // If DEFAULT_PROCESSSELECTOR_CID is null, we're in non-e10s mode and we // should skip this. - if (options.forceNewProcess) { + if (options.forceNewProcess && DEFAULT_PROCESSSELECTOR_CID) { Services.ppmm.releaseCachedProcesses(); - Services.prefs.setBoolPref(DISABLE_CONTENT_PROCESS_REUSE_PREF, true); + registrar.registerFactory( + OUR_PROCESSSELECTOR_CID, + "", + PROCESSSELECTOR_CONTRACTID, + null + ); } promises = [ @@ -235,10 +263,12 @@ var BrowserTestUtils = { } } finally { // Restore the original process selector, if needed. - if (options.forceNewProcess) { - Services.prefs.setBoolPref( - DISABLE_CONTENT_PROCESS_REUSE_PREF, - disableReusePrefValue + if (options.forceNewProcess && DEFAULT_PROCESSSELECTOR_CID) { + registrar.registerFactory( + DEFAULT_PROCESSSELECTOR_CID, + "", + PROCESSSELECTOR_CONTRACTID, + null ); } }