Bug 1772839 - Work around compositor being resumed with abandoned Surfaces. r=geckoview-reviewers,owlish

On some Android devices (predominantly Huawei devices running Android
9 or below) we are seeing large numbers of crashes due to the
compositor being unable to create an EGL surface to render in to. From
local testing, this appears to be due to the Surface we are being
provided from the SurfaceView being in an "abandoned"
state. Presumably this is due to an operating system bug - the Surface
is valid at the time of the surfaceChanged() callback, but becomes
abandoned moments later despite surfaceDestroyed() not being called.

We are able to detect when the Surface is in such a state from C++
code by calling ANativeWindow_getWidth(), as that will return a
negative value to indicate an error.

This patch uses this method to check whether the Surface is in such a
state prior to resuming the compositor. If so, rather than immediately
resuming the compositor it instead toggles the SurfaceView's
visibility. This tricks the SurfaceView in to providing a new (and
hopefully valid) Surface, which will in turn resume the compositor via
the surfaceChanged callback.

Differential Revision: https://phabricator.services.mozilla.com/D151479
This commit is contained in:
Jamie Nicol 2022-07-13 15:38:51 +00:00
parent b8446a48d8
commit d40ea70ccc
5 changed files with 74 additions and 7 deletions

View File

@ -16,6 +16,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import org.mozilla.gecko.annotation.WrapForJNI;
/** Provides transparent access to either a SurfaceView or TextureView */
public class SurfaceViewWrapper {
@ -112,6 +113,17 @@ public class SurfaceViewWrapper {
return mView;
}
/**
* Returns whether the Surface's underlying BufferQueue has been abandoned.
*
* <p>On some devices a Surface obtained during the surfaceChanged callback can become abandoned
* by the time we attempt to use it, despite surfaceDestroyed not being called. Attempting to
* render in to such a Surface will fail. This function checks whether a Surface is in such state,
* allowing us to request a new Surface if so.
*/
@WrapForJNI(calledFrom = "ui", dispatchTo = "current")
public static native boolean isSurfaceAbandoned(final Surface surface);
/**
* Translates SurfaceTextureListener and SurfaceHolder.Callback into a common interface
* SurfaceViewWrapper.Listener

View File

@ -105,13 +105,26 @@ public class GeckoView extends FrameLayout {
onGlobalLayout();
if (GeckoView.this.mSurfaceWrapper != null) {
final SurfaceViewWrapper wrapper = GeckoView.this.mSurfaceWrapper;
mDisplay.surfaceChanged(
new GeckoDisplay.SurfaceInfo.Builder(wrapper.getSurface())
.surfaceControl(wrapper.getSurfaceControl())
.size(wrapper.getWidth(), wrapper.getHeight())
.build());
mDisplay.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
GeckoView.this.setActive(true);
// On some devices, we have seen that the Surface can become abandoned sometime in between
// the surfaceChanged callback and attempting to use the Surface here. In such cases,
// rendering in to the Surface will always fail, resulting in the user being presented a
// blank, unresponsive screen or the application crashing. To work around this, check
// whether the Surface is in such a state, and if so toggle the SurfaceView's visibility
// in order to request a new Surface. See bug 1772839.
final boolean isAbandoned = SurfaceViewWrapper.isSurfaceAbandoned(wrapper.getSurface());
if (isAbandoned && wrapper.getView().getVisibility() == View.VISIBLE) {
wrapper.getView().setVisibility(View.INVISIBLE);
wrapper.getView().setVisibility(View.VISIBLE);
} else {
mDisplay.surfaceChanged(
new GeckoDisplay.SurfaceInfo.Builder(wrapper.getSurface())
.surfaceControl(wrapper.getSurfaceControl())
.size(wrapper.getWidth(), wrapper.getHeight())
.build());
mDisplay.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
GeckoView.this.setActive(true);
}
}
}

View File

@ -0,0 +1,39 @@
/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
* 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 SurfaceViewWrapperSupport_h__
#define SurfaceViewWrapperSupport_h__
#include "mozilla/java/SurfaceViewWrapperNatives.h"
#include <android/native_window.h>
#include <android/native_window_jni.h>
namespace mozilla {
namespace widget {
class SurfaceViewWrapperSupport final
: public java::SurfaceViewWrapper::Natives<SurfaceViewWrapperSupport> {
public:
static bool IsSurfaceAbandoned(jni::Object::Param aSurface) {
ANativeWindow* win =
ANativeWindow_fromSurface(jni::GetEnvForThread(), aSurface.Get());
if (!win) {
return true;
}
// If the Surface's underlying BufferQueue has been abandoned, then
// ANativeWindow_getWidth (or height) will return a negative value
// to indicate an error.
int32_t width = ANativeWindow_getWidth(win);
ANativeWindow_release(win);
return width < 0;
}
};
} // namespace widget
} // namespace mozilla
#endif // SurfaceViewWrapperSupport_h__

View File

@ -78,6 +78,7 @@ classes_with_WrapForJNI = [
"SurfaceAllocator",
"SurfaceControlManager",
"SurfaceTextureListener",
"SurfaceViewWrapper",
"TelemetryUtils",
"WebAuthnTokenManager",
"WebMessage",

View File

@ -75,6 +75,7 @@
#include "Telemetry.h"
#include "WebExecutorSupport.h"
#include "Base64UtilsSupport.h"
#include "SurfaceViewWrapperSupport.h"
#ifdef DEBUG_ANDROID_EVENTS
# define EVLOG(args...) ALOG(args)
@ -439,6 +440,7 @@ nsAppShell::nsAppShell()
mozilla::widget::ImageDecoderSupport::Init();
mozilla::widget::WebExecutorSupport::Init();
mozilla::widget::Base64UtilsSupport::Init();
mozilla::widget::SurfaceViewWrapperSupport::Init();
nsWindow::InitNatives();
mozilla::gl::AndroidSurfaceTexture::Init();
mozilla::widget::GeckoTelemetryDelegate::Init();