commit bcf14f680dce1867432d22a716e7cc251ad8bd19
parent 0834332cfd4dd0ebe40d04ef0e7716d49ff6cbfc
Author: Andrew Osmond <aosmond@gmail.com>
Date: Tue, 4 Nov 2025 22:13:11 +0000
Bug 1992857 - Avoid disabling GPU process when backgrounded on Android. r=jnicol
This patch makes it so that we silently fail to setup the GPU process
and its associated IPDL actors on Android when we are in the background.
This is because Android limits our ability to keep a GPU process when
backgrounded, and the higher levels should have an ability to recover
from this situation for content processes when brought back into the
foreground. It also resets the unstable and launch attempt counters when
the fore/background state changes to be more forgiving of GPU process
crashes during the transitions.
When the GPU process is relaunched, it will also destroy the fallback
renderers created on Android, so that the next paint will properly
request remote compositing.
Differential Revision: https://phabricator.services.mozilla.com/D267679
Diffstat:
14 files changed, 190 insertions(+), 53 deletions(-)
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
@@ -2911,20 +2911,25 @@ bool ContentParent::InitInternal(ProcessPriority aInitialPriority) {
Endpoint<PRemoteMediaManagerChild> videoManager;
AutoTArray<uint32_t, 3> namespaces;
- if (NS_FAILED(gpuReadyRv) ||
- !gpm->CreateContentBridges(OtherEndpointProcInfo(), &compositor,
- &imageBridge, &vrBridge, &videoManager,
- mChildID, &namespaces)) {
+ if (NS_SUCCEEDED(gpuReadyRv) &&
+ gpm->CreateContentBridges(OtherEndpointProcInfo(), &compositor,
+ &imageBridge, &vrBridge, &videoManager,
+ mChildID, &namespaces)) {
+ (void)SendInitRendering(std::move(compositor), std::move(imageBridge),
+ std::move(vrBridge), std::move(videoManager),
+ namespaces);
+ } else {
// This can fail if we've already started shutting down the compositor
- // thread. See Bug 1562763 comment 8.
- MOZ_ASSERT(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown));
- return false;
+ // thread. See Bug 1562763 comment 8. On Android this can fail we are are
+ // put in the background, making it unlikely the content process will remain
+ // alive for much longer, but if it does, we will do the necessary
+ // operations for InitRendering in ReinitRendering when the GPU process is
+ // available.
+ if (gpuReadyRv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN) {
+ return false;
+ }
}
- (void)SendInitRendering(std::move(compositor), std::move(imageBridge),
- std::move(vrBridge), std::move(videoManager),
- namespaces);
-
gpm->AddListener(this);
if (StaticPrefs::media_rdd_process_enabled()) {
@@ -3101,7 +3106,6 @@ void ContentParent::OnCompositorUnexpectedShutdown() {
if (!gpm->CreateContentBridges(OtherEndpointProcInfo(), &compositor,
&imageBridge, &vrBridge, &videoManager,
mChildID, &namespaces)) {
- MOZ_ASSERT(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown));
return;
}
diff --git a/gfx/ipc/GPUProcessListener.h b/gfx/ipc/GPUProcessListener.h
@@ -6,15 +6,23 @@
#ifndef _include_mozilla_gfx_ipc_GPUProcessListener_h_
#define _include_mozilla_gfx_ipc_GPUProcessListener_h_
+#include "nsISupportsImpl.h"
+
namespace mozilla {
namespace gfx {
class GPUProcessListener {
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
public:
virtual ~GPUProcessListener() = default;
// Called when the compositor has died and the rendering stack must be
- // recreated.
+ // recreated. Before OnCompositorUnexpectedShutdown.
+ virtual void OnCompositorDestroyBackgrounded() {}
+
+ // Called when the compositor has died and the rendering stack must be
+ // recreated. After OnCompositorDestroyBackgrounded.
virtual void OnCompositorUnexpectedShutdown() {}
// Called when devices have been reset and tabs must throw away their
diff --git a/gfx/ipc/GPUProcessManager.cpp b/gfx/ipc/GPUProcessManager.cpp
@@ -172,12 +172,9 @@ void GPUProcessManager::NotifyObserve(const char* aTopic,
} else if (!strcmp(aTopic, "nsPref:changed")) {
OnPreferenceChange(aData);
} else if (!strcmp(aTopic, "application-foreground")) {
- mAppInForeground = true;
- if (!mProcess && gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
- (void)LaunchGPUProcess();
- }
+ SetAppInForeground(true);
} else if (!strcmp(aTopic, "application-background")) {
- mAppInForeground = false;
+ SetAppInForeground(false);
} else if (!strcmp(aTopic, "screen-information-changed")) {
ScreenInformationChanged();
}
@@ -420,7 +417,24 @@ nsresult GPUProcessManager::EnsureGPUReady() {
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
- do {
+ while (true) {
+ // We allow the GPU process to launch if we are:
+ // 1) in the foreground, as the application is being actively used.
+ // 2) if we have no launch failures, because even if we are backgrounded, we
+ // can try once to secure it. This is useful for geckoview-junit tests.
+ // 3) if our pref is set to allow background launches; this is false by
+ // default on Android due to its issues with keeping the GPU process
+ // alive in the background, and true on all other platforms.
+ //
+ // If we are not in a position to try launching and/or waiting for the GPU
+ // process, then we should just abort for now. The higher levels will fail
+ // to create the content process, but all of this should get recreated when
+ // the app comes back into the foreground.
+ if (!mAppInForeground && mLaunchProcessAttempts > 0 &&
+ !StaticPrefs::layers_gpu_process_launch_in_background()) {
+ return NS_ERROR_ABORT;
+ }
+
// Launch the GPU process if it is enabled but hasn't been (re-)launched
// yet, and wait for it to complete the handshake. As part of WaitForLaunch,
// we know that OnProcessLaunchComplete has been called. If it succeeds,
@@ -430,13 +444,18 @@ nsresult GPUProcessManager::EnsureGPUReady() {
nsresult rv = LaunchGPUProcess();
if (NS_SUCCEEDED(rv) && mProcess->WaitForLaunch() && mGPUChild) {
MOZ_DIAGNOSTIC_ASSERT(mGPUChild->IsGPUReady());
- return NS_OK;
+ break;
}
MOZ_RELEASE_ASSERT(rv != NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
MOZ_RELEASE_ASSERT(!mProcess);
MOZ_RELEASE_ASSERT(!mGPUChild);
- } while (gfxConfig::IsEnabled(Feature::GPU_PROCESS));
+ MOZ_DIAGNOSTIC_ASSERT(mLaunchProcessAttempts > 0);
+
+ if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ break;
+ }
+ }
return NS_OK;
}
@@ -570,7 +589,8 @@ void GPUProcessManager::OnProcessLaunchComplete(GPUProcessHost* aHost) {
// we did not get to the point where we are using the features, we should just
// follow the same fallback procedure.
auto* gpuChild = mProcess->GetActor();
- if (!mProcess->IsConnected() || !gpuChild || !gpuChild->EnsureGPUReady()) {
+ if (NS_WARN_IF(!mProcess->IsConnected()) || NS_WARN_IF(!gpuChild) ||
+ NS_WARN_IF(!gpuChild->EnsureGPUReady())) {
++mLaunchProcessAttempts;
if (mLaunchProcessAttempts >
uint32_t(StaticPrefs::layers_gpu_process_max_launch_attempts())) {
@@ -909,7 +929,9 @@ void GPUProcessManager::OnRemoteProcessDeviceReset(
}
void GPUProcessManager::NotifyListenersOnCompositeDeviceReset() {
- for (const auto& listener : mListeners) {
+ nsTArray<RefPtr<GPUProcessListener>> listeners;
+ listeners.AppendElements(mListeners);
+ for (const auto& listener : listeners) {
listener->OnCompositorDeviceReset();
}
}
@@ -1073,7 +1095,14 @@ void GPUProcessManager::ReinitializeRendering() {
// Notify content. This will ensure that each content process re-establishes
// a connection to the compositor thread (whether it's in-process or in a
// newly launched GPU process).
- for (const auto& listener : mListeners) {
+ nsTArray<RefPtr<GPUProcessListener>> listeners;
+ listeners.AppendElements(mListeners);
+ // Make sure any fallback renderers get destroyed first.
+ for (const auto& listener : listeners) {
+ listener->OnCompositorDestroyBackgrounded();
+ }
+ // Then do the recreations.
+ for (const auto& listener : listeners) {
listener->OnCompositorUnexpectedShutdown();
}
@@ -1784,6 +1813,13 @@ void GPUProcessManager::SetAppInForeground(bool aInForeground) {
#if defined(XP_WIN)
SetProcessIsForeground();
#endif
+
+ // If we moved into the foreground, then we need to make sure the GPU process
+ // completes its launch. Otherwise listeners may be left dangling from
+ // previous calls that returned NS_ERROR_ABORT due to being in the background.
+ if (aInForeground && gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
+ (void)LaunchGPUProcess();
+ }
}
#if defined(XP_WIN)
diff --git a/gfx/ipc/GPUProcessManager.h b/gfx/ipc/GPUProcessManager.h
@@ -107,6 +107,8 @@ class GPUProcessManager final : public GPUProcessHost::Listener {
// process, even if in shutdown.
// - NS_ERROR_ILLEGAL_DURING_SHUTDOWN if compositing is not ready, and we are
// in shutdown.
+ // - NS_ERROR_ABORT if on Android and we are in the background. This is a
+ // temporary error that we should recover from when in the foreground.
nsresult EnsureGPUReady();
bool IsGPUReady() const;
@@ -395,7 +397,7 @@ class GPUProcessManager final : public GPUProcessHost::Listener {
nsTArray<RefPtr<RemoteCompositorSession>> mRemoteSessions;
nsTArray<RefPtr<InProcessCompositorSession>> mInProcessSessions;
- nsTArray<GPUProcessListener*> mListeners;
+ nsTArray<RefPtr<GPUProcessListener>> mListeners;
uint32_t mDeviceResetCount;
TimeStamp mDeviceResetLastTime;
diff --git a/gfx/layers/wr/WebRenderLayerManager.h b/gfx/layers/wr/WebRenderLayerManager.h
@@ -57,6 +57,8 @@ class LayerUserData;
class WebRenderLayerManager final : public WindowRenderer {
typedef nsTHashSet<RefPtr<WebRenderUserData>> WebRenderUserDataRefTable;
+ NS_INLINE_DECL_REFCOUNTING(WebRenderLayerManager, final)
+
public:
explicit WebRenderLayerManager(nsIWidget* aWidget);
bool Initialize(PCompositorBridgeChild* aCBChild, wr::PipelineId aLayersId,
diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp
@@ -4102,12 +4102,6 @@ void gfxPlatform::BuildContentDeviceData(
mozilla::gfx::ContentDeviceData* aOut) {
MOZ_ASSERT(XRE_IsParentProcess());
-#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
- if (auto* gpm = GPUProcessManager::Get()) {
- MOZ_DIAGNOSTIC_ASSERT(gpm->IsGPUReady());
- }
-#endif
-
aOut->prefs().hwCompositing() = gfxConfig::GetValue(Feature::HW_COMPOSITING);
aOut->prefs().oglCompositing() =
gfxConfig::GetValue(Feature::OPENGL_COMPOSITING);
diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -78,6 +78,7 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/gfx/DisplayConfigWindows.h"
+#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/layers/DeviceAttachmentsD3D11.h"
#include "mozilla/WindowsProcessMitigations.h"
#include "D3D11Checks.h"
@@ -1798,6 +1799,13 @@ void gfxWindowsPlatform::ImportContentDeviceData(
}
void gfxWindowsPlatform::BuildContentDeviceData(ContentDeviceData* aOut) {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // D3D11_COMPOSITING may be updated by the GPU process in DeviceManagerDx.
+ if (auto* gpm = GPUProcessManager::Get()) {
+ MOZ_DIAGNOSTIC_ASSERT(gpm->IsGPUReady());
+ }
+#endif
+
// Check for device resets before giving back new graphics information.
UpdateRenderMode();
diff --git a/layout/painting/WindowRenderer.cpp b/layout/painting/WindowRenderer.cpp
@@ -10,6 +10,7 @@
#include "mozilla/EffectSet.h"
#include "mozilla/dom/Animation.h" // for Animation
#include "mozilla/dom/AnimationEffect.h"
+#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only)
#include "nsDisplayList.h"
@@ -225,4 +226,33 @@ void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder,
}
}
+BackgroundedFallbackRenderer::BackgroundedFallbackRenderer(nsIWidget* aWidget)
+ : mWidget(aWidget) {
+ MOZ_ASSERT(mWidget);
+ if (auto* gpm = gfx::GPUProcessManager::Get()) {
+ gpm->AddListener(this);
+ }
+}
+
+BackgroundedFallbackRenderer::~BackgroundedFallbackRenderer() { Destroy(); }
+
+void BackgroundedFallbackRenderer::Destroy() {
+ if (!mWidget) {
+ return;
+ }
+
+ if (auto* gpm = gfx::GPUProcessManager::Get()) {
+ gpm->RemoveListener(this);
+ }
+
+ mWidget = nullptr;
+}
+
+void BackgroundedFallbackRenderer::OnCompositorDestroyBackgrounded() {
+ // We may get freed after this but the caller has a strong reference.
+ if (RefPtr<nsIWidget> widget = mWidget) {
+ widget->NotifyCompositorSessionLost(/* aSession */ nullptr);
+ }
+}
+
} // namespace mozilla
diff --git a/layout/painting/WindowRenderer.h b/layout/painting/WindowRenderer.h
@@ -10,6 +10,7 @@
#include "gfxContext.h"
#include "mozilla/ScrollPositionUpdate.h" // for ScrollPositionUpdate
#include "mozilla/dom/Animation.h" // for Animation
+#include "mozilla/gfx/GPUProcessListener.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID
#include "mozilla/webrender/webrender_ffi.h"
@@ -97,7 +98,7 @@ class FrameRecorder {
* cleanup can be done once that happens.
*/
class WindowRenderer : public FrameRecorder {
- NS_INLINE_DECL_REFCOUNTING(WindowRenderer)
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
public:
// Cast to implementation types.
@@ -241,23 +242,21 @@ class WindowRenderer : public FrameRecorder {
*/
class FallbackRenderer : public WindowRenderer {
public:
- FallbackRenderer* AsFallback() override { return this; }
+ FallbackRenderer* AsFallback() final { return this; }
void SetTarget(gfxContext* aContext);
- bool BeginTransaction(const nsCString& aURL = nsCString()) override;
+ bool BeginTransaction(const nsCString& aURL = nsCString()) final;
- bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override {
+ bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) final {
return false;
}
- layers::LayersBackend GetBackendType() override {
+ layers::LayersBackend GetBackendType() final {
return layers::LayersBackend::LAYERS_NONE;
}
- void GetBackendName(nsAString& name) override {
- name.AssignLiteral("Fallback");
- }
+ void GetBackendName(nsAString& name) final { name.AssignLiteral("Fallback"); }
void EndTransactionWithColor(const nsIntRect& aRect,
const gfx::DeviceColor& aColor);
@@ -267,6 +266,45 @@ class FallbackRenderer : public WindowRenderer {
EndTransactionFlags aFlags);
gfxContext* mTarget = nullptr;
+
+ protected:
+ FallbackRenderer() = default;
+};
+
+/**
+ * DefaultFallbackRenderer is intended to be used when the caller does not want
+ * it to be destroyed/recreated based on the compositing state.
+ */
+class DefaultFallbackRenderer final : public FallbackRenderer {
+ NS_INLINE_DECL_REFCOUNTING(DefaultFallbackRenderer, final)
+
+ public:
+ DefaultFallbackRenderer() = default;
+
+ private:
+ ~DefaultFallbackRenderer() final = default;
+};
+
+/**
+ * BackgroundedFallbackRenderer is intended to be used as a placeholder while we
+ * wait for compositing to be reinstantiated.
+ */
+class BackgroundedFallbackRenderer final : public FallbackRenderer,
+ public gfx::GPUProcessListener {
+ NS_INLINE_DECL_REFCOUNTING(BackgroundedFallbackRenderer, final)
+
+ public:
+ explicit BackgroundedFallbackRenderer(nsIWidget* aWidget);
+
+ void Destroy() final;
+
+ void OnCompositorDestroyBackgrounded() final;
+
+ private:
+ ~BackgroundedFallbackRenderer() final;
+
+ // The widget to notify when compositing is reinstantiated.
+ nsIWidget* mWidget;
};
} // namespace mozilla
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
@@ -9533,6 +9533,14 @@
#endif
mirror: once
+# If true, when the process is in the background, we refuse to launch the GPU
+# process and instead fail with a temporary error that we can recover from when
+# we enter the foreground and the GPU process launches.
+- name: layers.gpu-process.launch-in-background
+ type: bool
+ value: @IS_NOT_ANDROID@
+ mirror: always
+
# On Mac, enables using the `<Brand> GPU Helper` executable as the
# GPU child process instead of the plugin-container executable. This does
# not control if a GPU process is used. It controls which executable the GPU
@@ -9563,11 +9571,7 @@
# during process launch, before any configuration is attempted.
- name: layers.gpu-process.max_launch_attempts
type: RelaxedAtomicInt32
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX)
value: 2
-#else
- value: 0
-#endif
mirror: always
# How many unstable GPU process restarts we allow for a given configuration.
diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp
@@ -531,7 +531,7 @@ WindowRenderer* PuppetWidget::GetWindowRenderer() {
if (XRE_IsParentProcess()) {
// On the parent process there is no CompositorBridgeChild which confuses
// some layers code, so we use basic layers instead. Note that we create
- mWindowRenderer = new FallbackRenderer;
+ mWindowRenderer = CreateFallbackRenderer();
return mWindowRenderer;
}
diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp
@@ -131,8 +131,6 @@ static mozilla::LazyLogModule sGVSupportLog("GeckoViewSupport");
// one.
MOZ_RUNINIT static nsTArray<nsWindow*> gTopLevelWindows;
-static bool sFailedToCreateGLContext = false;
-
static const double kTouchResampleVsyncAdjustMs = 5.0;
static const int32_t INPUT_RESULT_UNHANDLED =
@@ -2696,15 +2694,13 @@ void nsWindow::CreateLayerManager() {
}
});
}
-
return;
}
-
- // If we get here, then off main thread compositing failed to initialize.
- sFailedToCreateGLContext = true;
}
- if (!ComputeShouldAccelerate() || sFailedToCreateGLContext) {
+ if (ComputeShouldAccelerate()) {
+ mWindowRenderer = CreateBackgroundedFallbackRenderer();
+ } else {
printf_stderr(" -- creating basic, not accelerated\n");
mWindowRenderer = CreateFallbackRenderer();
}
diff --git a/widget/nsIWidget.cpp b/widget/nsIWidget.cpp
@@ -1483,7 +1483,7 @@ already_AddRefed<WebRenderLayerManager> nsIWidget::CreateCompositorSession(
// If it failed to connect to GPU process, GPU process usage is disabled in
// EnsureGPUReady(). It could update gfxVars and gfxConfigs.
gfx::GPUProcessManager* gpm = gfx::GPUProcessManager::Get();
- if (NS_WARN_IF(!gpm || NS_FAILED(gpm->EnsureGPUReady()))) {
+ if (NS_WARN_IF(!gpm) || NS_WARN_IF(NS_FAILED(gpm->EnsureGPUReady()))) {
return nullptr;
}
@@ -1671,7 +1671,15 @@ WindowRenderer* nsIWidget::GetWindowRenderer() {
}
WindowRenderer* nsIWidget::CreateFallbackRenderer() {
- return new FallbackRenderer;
+ // We don't provide a reference to ourself because we want to stay with the
+ // fallback renderer regardless of changes in compositing.
+ return new DefaultFallbackRenderer();
+}
+
+WindowRenderer* nsIWidget::CreateBackgroundedFallbackRenderer() {
+ // Provide a reference back to ourself so that when the GPU process and
+ // hardware compositing is once again available, we can return to it.
+ return new BackgroundedFallbackRenderer(this);
}
CompositorBridgeChild* nsIWidget::GetRemoteRenderer() {
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
@@ -1911,6 +1911,13 @@ class nsIWidget : public nsSupportsWeakReference {
WindowRenderer* CreateFallbackRenderer();
/**
+ * Returns a FallbackRenderer which is intended to be temporary while
+ * backgrounded without a GPU process. It listens to GPUProcessManager events
+ * in order to destroy itself when the GPU process becomes available.
+ */
+ WindowRenderer* CreateBackgroundedFallbackRenderer();
+
+ /**
* Setter/Getter of the system font setting for testing.
*/
virtual nsresult SetSystemFont(const nsCString& aFontName) {