From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- widget/android/AndroidAlerts.cpp | 146 ++ widget/android/AndroidAlerts.h | 47 + widget/android/AndroidBridge.cpp | 741 ++++++ widget/android/AndroidBridge.h | 362 +++ widget/android/AndroidBridgeUtilities.h | 19 + widget/android/AndroidColors.h | 28 + widget/android/AndroidCompositorWidget.cpp | 30 + widget/android/AndroidCompositorWidget.h | 40 + widget/android/AndroidContentController.cpp | 68 + widget/android/AndroidContentController.h | 51 + widget/android/AndroidUiThread.cpp | 342 +++ widget/android/AndroidUiThread.h | 25 + widget/android/AndroidView.h | 35 + widget/android/AndroidVsync.cpp | 140 ++ widget/android/AndroidVsync.h | 75 + widget/android/Base64UtilsSupport.h | 52 + widget/android/EventDispatcher.cpp | 1026 ++++++++ widget/android/EventDispatcher.h | 104 + widget/android/GeckoBatteryManager.h | 28 + widget/android/GeckoEditableSupport.cpp | 1630 ++++++++++++ widget/android/GeckoEditableSupport.h | 287 +++ widget/android/GeckoNetworkManager.h | 45 + widget/android/GeckoProcessManager.cpp | 77 + widget/android/GeckoProcessManager.h | 84 + widget/android/GeckoScreenOrientation.h | 52 + widget/android/GeckoSystemStateListener.h | 31 + widget/android/GeckoTelemetryDelegate.h | 101 + widget/android/GeckoVRManager.h | 24 + widget/android/GeckoViewSupport.h | 109 + widget/android/GfxInfo.cpp | 800 ++++++ widget/android/GfxInfo.h | 117 + widget/android/ImageDecoderSupport.cpp | 176 ++ widget/android/ImageDecoderSupport.h | 30 + widget/android/MediaKeysEventSourceFactory.cpp | 17 + widget/android/PrefsHelper.h | 306 +++ widget/android/ScreenHelperAndroid.cpp | 129 + widget/android/ScreenHelperAndroid.h | 40 + widget/android/Telemetry.h | 86 + widget/android/WebAuthnTokenManager.h | 79 + widget/android/WebExecutorSupport.cpp | 460 ++++ widget/android/WebExecutorSupport.h | 32 + widget/android/WindowEvent.h | 57 + .../bindings/AccessibilityEvent-classes.txt | 3 + widget/android/bindings/AndroidBuild-classes.txt | 5 + .../android/bindings/AndroidGraphics-classes.txt | 10 + .../android/bindings/AndroidInputType-classes.txt | 3 + widget/android/bindings/AndroidRect-classes.txt | 2 + widget/android/bindings/InetAddress-classes.txt | 6 + widget/android/bindings/JavaBuiltins-classes.txt | 25 + widget/android/bindings/JavaExceptions-classes.txt | 5 + widget/android/bindings/KeyEvent-classes.txt | 3 + widget/android/bindings/MediaCodec-classes.txt | 9 + widget/android/bindings/MotionEvent-classes.txt | 3 + widget/android/bindings/SurfaceTexture-classes.txt | 2 + .../android/bindings/ViewConfiguration-classes.txt | 1 + widget/android/bindings/moz.build | 53 + widget/android/components.conf | 114 + widget/android/jni/Accessors.h | 251 ++ widget/android/jni/Conversions.cpp | 107 + widget/android/jni/Conversions.h | 23 + widget/android/jni/GeckoBundleUtils.h | 41 + widget/android/jni/GeckoResultUtils.h | 54 + widget/android/jni/Natives.h | 1615 ++++++++++++ widget/android/jni/Refs.h | 1016 ++++++++ widget/android/jni/Types.h | 160 ++ widget/android/jni/Utils.cpp | 341 +++ widget/android/jni/Utils.h | 148 ++ widget/android/jni/moz.build | 33 + widget/android/moz.build | 198 ++ widget/android/nsAndroidProtocolHandler.cpp | 140 ++ widget/android/nsAndroidProtocolHandler.h | 33 + widget/android/nsAppShell.cpp | 781 ++++++ widget/android/nsAppShell.h | 250 ++ widget/android/nsClipboard.cpp | 163 ++ widget/android/nsClipboard.h | 22 + widget/android/nsDeviceContextAndroid.cpp | 79 + widget/android/nsDeviceContextAndroid.h | 33 + widget/android/nsIAndroidBridge.idl | 87 + widget/android/nsLookAndFeel.cpp | 531 ++++ widget/android/nsLookAndFeel.h | 49 + widget/android/nsNativeBasicThemeAndroid.cpp | 15 + widget/android/nsNativeBasicThemeAndroid.h | 20 + widget/android/nsNativeThemeAndroid.cpp | 926 +++++++ widget/android/nsNativeThemeAndroid.h | 66 + widget/android/nsPrintSettingsServiceAndroid.cpp | 33 + widget/android/nsPrintSettingsServiceAndroid.h | 18 + widget/android/nsUserIdleServiceAndroid.cpp | 14 + widget/android/nsUserIdleServiceAndroid.h | 35 + widget/android/nsWidgetFactory.cpp | 21 + widget/android/nsWidgetFactory.h | 22 + widget/android/nsWindow.cpp | 2619 ++++++++++++++++++++ widget/android/nsWindow.h | 279 +++ 92 files changed, 18595 insertions(+) create mode 100644 widget/android/AndroidAlerts.cpp create mode 100644 widget/android/AndroidAlerts.h create mode 100644 widget/android/AndroidBridge.cpp create mode 100644 widget/android/AndroidBridge.h create mode 100644 widget/android/AndroidBridgeUtilities.h create mode 100644 widget/android/AndroidColors.h create mode 100644 widget/android/AndroidCompositorWidget.cpp create mode 100644 widget/android/AndroidCompositorWidget.h create mode 100644 widget/android/AndroidContentController.cpp create mode 100644 widget/android/AndroidContentController.h create mode 100644 widget/android/AndroidUiThread.cpp create mode 100644 widget/android/AndroidUiThread.h create mode 100644 widget/android/AndroidView.h create mode 100644 widget/android/AndroidVsync.cpp create mode 100644 widget/android/AndroidVsync.h create mode 100644 widget/android/Base64UtilsSupport.h create mode 100644 widget/android/EventDispatcher.cpp create mode 100644 widget/android/EventDispatcher.h create mode 100644 widget/android/GeckoBatteryManager.h create mode 100644 widget/android/GeckoEditableSupport.cpp create mode 100644 widget/android/GeckoEditableSupport.h create mode 100644 widget/android/GeckoNetworkManager.h create mode 100644 widget/android/GeckoProcessManager.cpp create mode 100644 widget/android/GeckoProcessManager.h create mode 100644 widget/android/GeckoScreenOrientation.h create mode 100644 widget/android/GeckoSystemStateListener.h create mode 100644 widget/android/GeckoTelemetryDelegate.h create mode 100644 widget/android/GeckoVRManager.h create mode 100644 widget/android/GeckoViewSupport.h create mode 100644 widget/android/GfxInfo.cpp create mode 100644 widget/android/GfxInfo.h create mode 100644 widget/android/ImageDecoderSupport.cpp create mode 100644 widget/android/ImageDecoderSupport.h create mode 100644 widget/android/MediaKeysEventSourceFactory.cpp create mode 100644 widget/android/PrefsHelper.h create mode 100644 widget/android/ScreenHelperAndroid.cpp create mode 100644 widget/android/ScreenHelperAndroid.h create mode 100644 widget/android/Telemetry.h create mode 100644 widget/android/WebAuthnTokenManager.h create mode 100644 widget/android/WebExecutorSupport.cpp create mode 100644 widget/android/WebExecutorSupport.h create mode 100644 widget/android/WindowEvent.h create mode 100644 widget/android/bindings/AccessibilityEvent-classes.txt create mode 100644 widget/android/bindings/AndroidBuild-classes.txt create mode 100644 widget/android/bindings/AndroidGraphics-classes.txt create mode 100644 widget/android/bindings/AndroidInputType-classes.txt create mode 100644 widget/android/bindings/AndroidRect-classes.txt create mode 100644 widget/android/bindings/InetAddress-classes.txt create mode 100644 widget/android/bindings/JavaBuiltins-classes.txt create mode 100644 widget/android/bindings/JavaExceptions-classes.txt create mode 100644 widget/android/bindings/KeyEvent-classes.txt create mode 100644 widget/android/bindings/MediaCodec-classes.txt create mode 100644 widget/android/bindings/MotionEvent-classes.txt create mode 100644 widget/android/bindings/SurfaceTexture-classes.txt create mode 100644 widget/android/bindings/ViewConfiguration-classes.txt create mode 100644 widget/android/bindings/moz.build create mode 100644 widget/android/components.conf create mode 100644 widget/android/jni/Accessors.h create mode 100644 widget/android/jni/Conversions.cpp create mode 100644 widget/android/jni/Conversions.h create mode 100644 widget/android/jni/GeckoBundleUtils.h create mode 100644 widget/android/jni/GeckoResultUtils.h create mode 100644 widget/android/jni/Natives.h create mode 100644 widget/android/jni/Refs.h create mode 100644 widget/android/jni/Types.h create mode 100644 widget/android/jni/Utils.cpp create mode 100644 widget/android/jni/Utils.h create mode 100644 widget/android/jni/moz.build create mode 100644 widget/android/moz.build create mode 100644 widget/android/nsAndroidProtocolHandler.cpp create mode 100644 widget/android/nsAndroidProtocolHandler.h create mode 100644 widget/android/nsAppShell.cpp create mode 100644 widget/android/nsAppShell.h create mode 100644 widget/android/nsClipboard.cpp create mode 100644 widget/android/nsClipboard.h create mode 100644 widget/android/nsDeviceContextAndroid.cpp create mode 100644 widget/android/nsDeviceContextAndroid.h create mode 100644 widget/android/nsIAndroidBridge.idl create mode 100644 widget/android/nsLookAndFeel.cpp create mode 100644 widget/android/nsLookAndFeel.h create mode 100644 widget/android/nsNativeBasicThemeAndroid.cpp create mode 100644 widget/android/nsNativeBasicThemeAndroid.h create mode 100644 widget/android/nsNativeThemeAndroid.cpp create mode 100644 widget/android/nsNativeThemeAndroid.h create mode 100644 widget/android/nsPrintSettingsServiceAndroid.cpp create mode 100644 widget/android/nsPrintSettingsServiceAndroid.h create mode 100644 widget/android/nsUserIdleServiceAndroid.cpp create mode 100644 widget/android/nsUserIdleServiceAndroid.h create mode 100644 widget/android/nsWidgetFactory.cpp create mode 100644 widget/android/nsWidgetFactory.h create mode 100644 widget/android/nsWindow.cpp create mode 100644 widget/android/nsWindow.h (limited to 'widget/android') diff --git a/widget/android/AndroidAlerts.cpp b/widget/android/AndroidAlerts.cpp new file mode 100644 index 0000000000..33c779f23d --- /dev/null +++ b/widget/android/AndroidAlerts.cpp @@ -0,0 +1,146 @@ +/* -*- Mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* 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 "AndroidAlerts.h" +#include "mozilla/java/GeckoRuntimeWrappers.h" +#include "mozilla/java/WebNotificationWrappers.h" +#include "nsIPrincipal.h" +#include "nsIURI.h" + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(AndroidAlerts, nsIAlertsService) + +StaticAutoPtr AndroidAlerts::sListenerMap; +nsDataHashtable + AndroidAlerts::mNotificationsMap; + +NS_IMETHODIMP +AndroidAlerts::ShowAlertNotification( + const nsAString& aImageUrl, const nsAString& aAlertTitle, + const nsAString& aAlertText, bool aAlertTextClickable, + const nsAString& aAlertCookie, nsIObserver* aAlertListener, + const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang, + const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing, + bool aRequireInteraction) { + MOZ_ASSERT_UNREACHABLE("Should be implemented by nsAlertsService."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +AndroidAlerts::ShowAlert(nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + return ShowPersistentNotification(u""_ns, aAlert, aAlertListener); +} + +NS_IMETHODIMP +AndroidAlerts::ShowPersistentNotification(const nsAString& aPersistentData, + nsIAlertNotification* aAlert, + nsIObserver* aAlertListener) { + // nsAlertsService disables our alerts backend if we ever return failure + // here. To keep the backend enabled, we always return NS_OK even if we + // encounter an error here. + nsresult rv; + + nsAutoString imageUrl; + rv = aAlert->GetImageURL(imageUrl); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoString title; + rv = aAlert->GetTitle(title); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoString text; + rv = aAlert->GetText(text); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoString cookie; + rv = aAlert->GetCookie(cookie); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoString name; + rv = aAlert->GetName(name); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoString lang; + rv = aAlert->GetLang(lang); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsAutoString dir; + rv = aAlert->GetDir(dir); + NS_ENSURE_SUCCESS(rv, NS_OK); + + bool requireInteraction; + rv = aAlert->GetRequireInteraction(&requireInteraction); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsCOMPtr uri; + rv = aAlert->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsCString spec; + if (uri) { + rv = uri->GetDisplaySpec(spec); + NS_ENSURE_SUCCESS(rv, NS_OK); + } + + if (aPersistentData.IsEmpty() && aAlertListener) { + if (!sListenerMap) { + sListenerMap = new ListenerMap(); + } + // This will remove any observers already registered for this name. + sListenerMap->Put(name, aAlertListener); + } + + java::WebNotification::LocalRef notification = notification->New( + title, name, cookie, text, imageUrl, dir, lang, requireInteraction, spec); + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + if (runtime != NULL) { + runtime->NotifyOnShow(notification); + } + mNotificationsMap.Put(name, notification); + + return NS_OK; +} + +NS_IMETHODIMP +AndroidAlerts::CloseAlert(const nsAString& aAlertName) { + java::WebNotification::LocalRef notification = + mNotificationsMap.Get(aAlertName); + if (!notification) { + return NS_OK; + } + + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + if (runtime != NULL) { + runtime->NotifyOnClose(notification); + } + mNotificationsMap.Remove(aAlertName); + + return NS_OK; +} + +void AndroidAlerts::NotifyListener(const nsAString& aName, const char* aTopic, + const char16_t* aCookie) { + if (!sListenerMap) { + return; + } + + nsCOMPtr listener = sListenerMap->Get(aName); + if (!listener) { + return; + } + + listener->Observe(nullptr, aTopic, aCookie); + + if ("alertfinished"_ns.Equals(aTopic)) { + sListenerMap->Remove(aName); + mNotificationsMap.Remove(aName); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/AndroidAlerts.h b/widget/android/AndroidAlerts.h new file mode 100644 index 0000000000..bab955232a --- /dev/null +++ b/widget/android/AndroidAlerts.h @@ -0,0 +1,47 @@ +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* 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 mozilla_widget_AndroidAlerts_h__ +#define mozilla_widget_AndroidAlerts_h__ + +#include "nsDataHashtable.h" +#include "nsInterfaceHashtable.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIAlertsService.h" +#include "nsIObserver.h" + +#include "mozilla/java/WebNotificationWrappers.h" + +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace widget { + +class AndroidAlerts : public nsIAlertsService { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIALERTSSERVICE + + AndroidAlerts() {} + + static void NotifyListener(const nsAString& aName, const char* aTopic, + const char16_t* aCookie); + + static nsDataHashtable + mNotificationsMap; + + protected: + virtual ~AndroidAlerts() { sListenerMap = nullptr; } + + using ListenerMap = nsInterfaceHashtable; + static StaticAutoPtr sListenerMap; +}; + +} // namespace widget +} // namespace mozilla + +#endif // nsAndroidAlerts_h__ diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp new file mode 100644 index 0000000000..71215e41b0 --- /dev/null +++ b/widget/android/AndroidBridge.cpp @@ -0,0 +1,741 @@ +/* -*- 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/. */ + +#include +#include +#include +#include + +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" + +#include "mozilla/Hal.h" +#include "nsXULAppAPI.h" +#include +#include "AndroidBridge.h" +#include "AndroidBridgeUtilities.h" +#include "AndroidRect.h" +#include "nsAlertsUtils.h" +#include "nsAppShell.h" +#include "nsOSHelperAppService.h" +#include "nsWindow.h" +#include "mozilla/Preferences.h" +#include "nsThreadUtils.h" +#include "gfxPlatform.h" +#include "gfxContext.h" +#include "mozilla/gfx/2D.h" +#include "gfxUtils.h" +#include "nsPresContext.h" +#include "nsPIDOMWindow.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsPrintfCString.h" +#include "nsContentUtils.h" + +#include "EventDispatcher.h" +#include "MediaCodec.h" +#include "SurfaceTexture.h" +#include "GLContextProvider.h" + +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "WidgetUtils.h" + +#include "mozilla/java/EventDispatcherWrappers.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/GeckoThreadWrappers.h" +#include "mozilla/java/HardwareCodecCapabilityUtilsWrappers.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +AndroidBridge* AndroidBridge::sBridge = nullptr; +static jobject sGlobalContext = nullptr; +nsDataHashtable AndroidBridge::sStoragePaths; + +jmethodID AndroidBridge::GetMethodID(JNIEnv* env, jclass jClass, + const char* methodName, + const char* methodType) { + jmethodID methodID = env->GetMethodID(jClass, methodName, methodType); + if (!methodID) { + ALOG( + ">>> FATAL JNI ERROR! GetMethodID(methodName=\"%s\", " + "methodType=\"%s\") failed. Did ProGuard optimize away something it " + "shouldn't have?", + methodName, methodType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return methodID; +} + +jmethodID AndroidBridge::GetStaticMethodID(JNIEnv* env, jclass jClass, + const char* methodName, + const char* methodType) { + jmethodID methodID = env->GetStaticMethodID(jClass, methodName, methodType); + if (!methodID) { + ALOG( + ">>> FATAL JNI ERROR! GetStaticMethodID(methodName=\"%s\", " + "methodType=\"%s\") failed. Did ProGuard optimize away something it " + "shouldn't have?", + methodName, methodType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return methodID; +} + +jfieldID AndroidBridge::GetFieldID(JNIEnv* env, jclass jClass, + const char* fieldName, + const char* fieldType) { + jfieldID fieldID = env->GetFieldID(jClass, fieldName, fieldType); + if (!fieldID) { + ALOG( + ">>> FATAL JNI ERROR! GetFieldID(fieldName=\"%s\", " + "fieldType=\"%s\") failed. Did ProGuard optimize away something it " + "shouldn't have?", + fieldName, fieldType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return fieldID; +} + +jfieldID AndroidBridge::GetStaticFieldID(JNIEnv* env, jclass jClass, + const char* fieldName, + const char* fieldType) { + jfieldID fieldID = env->GetStaticFieldID(jClass, fieldName, fieldType); + if (!fieldID) { + ALOG( + ">>> FATAL JNI ERROR! GetStaticFieldID(fieldName=\"%s\", " + "fieldType=\"%s\") failed. Did ProGuard optimize away something it " + "shouldn't have?", + fieldName, fieldType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return fieldID; +} + +void AndroidBridge::ConstructBridge() { + /* NSS hack -- bionic doesn't handle recursive unloads correctly, + * because library finalizer functions are called with the dynamic + * linker lock still held. This results in a deadlock when trying + * to call dlclose() while we're already inside dlclose(). + * Conveniently, NSS has an env var that can prevent it from unloading. + */ + putenv(const_cast("NSS_DISABLE_UNLOAD=1")); + + MOZ_ASSERT(!sBridge); + sBridge = new AndroidBridge(); +} + +void AndroidBridge::DeconstructBridge() { + if (sBridge) { + delete sBridge; + // AndroidBridge destruction requires sBridge to still be valid, + // so we set sBridge to nullptr after deleting it. + sBridge = nullptr; + } +} + +AndroidBridge::~AndroidBridge() {} + +AndroidBridge::AndroidBridge() { + ALOG_BRIDGE("AndroidBridge::Init"); + + JNIEnv* const jEnv = jni::GetGeckoThreadEnv(); + AutoLocalJNIFrame jniFrame(jEnv); + + mMessageQueue = java::GeckoThread::MsgQueue(); + auto msgQueueClass = jni::Class::LocalRef::Adopt( + jEnv, jEnv->GetObjectClass(mMessageQueue.Get())); + // mMessageQueueNext must not be null + mMessageQueueNext = + GetMethodID(jEnv, msgQueueClass.Get(), "next", "()Landroid/os/Message;"); + // mMessageQueueMessages may be null (e.g. due to proguard optimization) + mMessageQueueMessages = jEnv->GetFieldID(msgQueueClass.Get(), "mMessages", + "Landroid/os/Message;"); + + AutoJNIClass string(jEnv, "java/lang/String"); + jStringClass = string.getGlobalRef(); + + mAPIVersion = jni::GetAPIVersion(); + + AutoJNIClass channels(jEnv, "java/nio/channels/Channels"); + jChannels = channels.getGlobalRef(); + jChannelCreate = channels.getStaticMethod( + "newChannel", + "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;"); + + AutoJNIClass readableByteChannel(jEnv, + "java/nio/channels/ReadableByteChannel"); + jReadableByteChannel = readableByteChannel.getGlobalRef(); + jByteBufferRead = + readableByteChannel.getMethod("read", "(Ljava/nio/ByteBuffer;)I"); + + AutoJNIClass inputStream(jEnv, "java/io/InputStream"); + jInputStream = inputStream.getGlobalRef(); + jClose = inputStream.getMethod("close", "()V"); + jAvailable = inputStream.getMethod("available", "()I"); +} + +static void getHandlersFromStringArray( + JNIEnv* aJNIEnv, jni::ObjectArray::Param aArr, size_t aLen, + nsIMutableArray* aHandlersArray, nsIHandlerApp** aDefaultApp, + const nsAString& aAction = u""_ns, const nsACString& aMimeType = ""_ns) { + auto getNormalizedString = [](jni::Object::Param obj) -> nsString { + nsString out; + if (!obj) { + out.SetIsVoid(true); + } else { + out.Assign(jni::String::Ref::From(obj)->ToString()); + } + return out; + }; + + for (size_t i = 0; i < aLen; i += 4) { + nsString name(getNormalizedString(aArr->GetElement(i))); + nsString isDefault(getNormalizedString(aArr->GetElement(i + 1))); + nsString packageName(getNormalizedString(aArr->GetElement(i + 2))); + nsString className(getNormalizedString(aArr->GetElement(i + 3))); + + nsIHandlerApp* app = nsOSHelperAppService::CreateAndroidHandlerApp( + name, className, packageName, className, aMimeType, aAction); + + aHandlersArray->AppendElement(app); + if (aDefaultApp && isDefault.Length() > 0) *aDefaultApp = app; + } +} + +bool AndroidBridge::GetHandlersForMimeType(const nsAString& aMimeType, + nsIMutableArray* aHandlersArray, + nsIHandlerApp** aDefaultApp, + const nsAString& aAction) { + ALOG_BRIDGE("AndroidBridge::GetHandlersForMimeType"); + + auto arr = java::GeckoAppShell::GetHandlersForMimeType(aMimeType, aAction); + if (!arr) return false; + + JNIEnv* const env = arr.Env(); + size_t len = arr->Length(); + + if (!aHandlersArray) return len > 0; + + getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp, + aAction, NS_ConvertUTF16toUTF8(aMimeType)); + return true; +} + +bool AndroidBridge::HasHWVP8Encoder() { + ALOG_BRIDGE("AndroidBridge::HasHWVP8Encoder"); + + bool value = java::GeckoAppShell::HasHWVP8Encoder(); + + return value; +} + +bool AndroidBridge::HasHWVP8Decoder() { + ALOG_BRIDGE("AndroidBridge::HasHWVP8Decoder"); + + bool value = java::GeckoAppShell::HasHWVP8Decoder(); + + return value; +} + +bool AndroidBridge::HasHWH264() { + ALOG_BRIDGE("AndroidBridge::HasHWH264"); + + return java::HardwareCodecCapabilityUtils::HasHWH264(); +} + +bool AndroidBridge::GetHandlersForURL(const nsAString& aURL, + nsIMutableArray* aHandlersArray, + nsIHandlerApp** aDefaultApp, + const nsAString& aAction) { + ALOG_BRIDGE("AndroidBridge::GetHandlersForURL"); + + auto arr = java::GeckoAppShell::GetHandlersForURL(aURL, aAction); + if (!arr) return false; + + JNIEnv* const env = arr.Env(); + size_t len = arr->Length(); + + if (!aHandlersArray) return len > 0; + + getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp, + aAction); + return true; +} + +void AndroidBridge::GetMimeTypeFromExtensions(const nsACString& aFileExt, + nsCString& aMimeType) { + ALOG_BRIDGE("AndroidBridge::GetMimeTypeFromExtensions"); + + auto jstrType = java::GeckoAppShell::GetMimeTypeFromExtensions(aFileExt); + + if (jstrType) { + aMimeType = jstrType->ToCString(); + } +} + +void AndroidBridge::GetExtensionFromMimeType(const nsACString& aMimeType, + nsACString& aFileExt) { + ALOG_BRIDGE("AndroidBridge::GetExtensionFromMimeType"); + + auto jstrExt = java::GeckoAppShell::GetExtensionFromMimeType(aMimeType); + + if (jstrExt) { + aFileExt = jstrExt->ToCString(); + } +} + +gfx::Rect AndroidBridge::getScreenSize() { + ALOG_BRIDGE("AndroidBridge::getScreenSize"); + + java::sdk::Rect::LocalRef screenrect = java::GeckoAppShell::GetScreenSize(); + gfx::Rect screensize(screenrect->Left(), screenrect->Top(), + screenrect->Width(), screenrect->Height()); + + return screensize; +} + +int AndroidBridge::GetScreenDepth() { + ALOG_BRIDGE("%s", __PRETTY_FUNCTION__); + + static int sDepth = 0; + if (sDepth) return sDepth; + + const int DEFAULT_DEPTH = 16; + + if (jni::IsAvailable()) { + sDepth = java::GeckoAppShell::GetScreenDepth(); + } + if (!sDepth) return DEFAULT_DEPTH; + + return sDepth; +} + +void AndroidBridge::Vibrate(const nsTArray& aPattern) { + ALOG_BRIDGE("%s", __PRETTY_FUNCTION__); + + uint32_t len = aPattern.Length(); + if (!len) { + ALOG_BRIDGE(" invalid 0-length array"); + return; + } + + // It's clear if this worth special-casing, but it creates less + // java junk, so dodges the GC. + if (len == 1) { + jlong d = aPattern[0]; + if (d < 0) { + ALOG_BRIDGE(" invalid vibration duration < 0"); + return; + } + java::GeckoAppShell::Vibrate(d); + return; + } + + // First element of the array vibrate() expects is how long to wait + // *before* vibrating. For us, this is always 0. + + JNIEnv* const env = jni::GetGeckoThreadEnv(); + AutoLocalJNIFrame jniFrame(env, 1); + + jlongArray array = env->NewLongArray(len + 1); + if (!array) { + ALOG_BRIDGE(" failed to allocate array"); + return; + } + + jlong* elts = env->GetLongArrayElements(array, nullptr); + elts[0] = 0; + for (uint32_t i = 0; i < aPattern.Length(); ++i) { + jlong d = aPattern[i]; + if (d < 0) { + ALOG_BRIDGE(" invalid vibration duration < 0"); + env->ReleaseLongArrayElements(array, elts, JNI_ABORT); + return; + } + elts[i + 1] = d; + } + env->ReleaseLongArrayElements(array, elts, 0); + + java::GeckoAppShell::Vibrate(jni::LongArray::Ref::From(array), + -1 /* don't repeat */); +} + +void AndroidBridge::GetIconForExtension(const nsACString& aFileExt, + uint32_t aIconSize, + uint8_t* const aBuf) { + ALOG_BRIDGE("AndroidBridge::GetIconForExtension"); + NS_ASSERTION(aBuf != nullptr, + "AndroidBridge::GetIconForExtension: aBuf is null!"); + if (!aBuf) return; + + auto arr = java::GeckoAppShell::GetIconForExtension( + NS_ConvertUTF8toUTF16(aFileExt), aIconSize); + + NS_ASSERTION( + arr != nullptr, + "AndroidBridge::GetIconForExtension: Returned pixels array is null!"); + if (!arr) return; + + JNIEnv* const env = arr.Env(); + uint32_t len = static_cast(env->GetArrayLength(arr.Get())); + jbyte* elements = env->GetByteArrayElements(arr.Get(), 0); + + uint32_t bufSize = aIconSize * aIconSize * 4; + NS_ASSERTION( + len == bufSize, + "AndroidBridge::GetIconForExtension: Pixels array is incomplete!"); + if (len == bufSize) memcpy(aBuf, elements, bufSize); + + env->ReleaseByteArrayElements(arr.Get(), elements, 0); +} + +bool AndroidBridge::GetStaticIntField(const char* className, + const char* fieldName, int32_t* aInt, + JNIEnv* jEnv /* = nullptr */) { + ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName); + + if (!jEnv) { + if (!jni::IsAvailable()) { + return false; + } + jEnv = jni::GetGeckoThreadEnv(); + } + + AutoJNIClass cls(jEnv, className); + jfieldID field = cls.getStaticField(fieldName, "I"); + + if (!field) { + return false; + } + + *aInt = static_cast(jEnv->GetStaticIntField(cls.getRawRef(), field)); + return true; +} + +bool AndroidBridge::GetStaticStringField(const char* className, + const char* fieldName, + nsAString& result, + JNIEnv* jEnv /* = nullptr */) { + ALOG_BRIDGE("AndroidBridge::GetStaticStringField %s", fieldName); + + if (!jEnv) { + if (!jni::IsAvailable()) { + return false; + } + jEnv = jni::GetGeckoThreadEnv(); + } + + AutoLocalJNIFrame jniFrame(jEnv, 1); + AutoJNIClass cls(jEnv, className); + jfieldID field = cls.getStaticField(fieldName, "Ljava/lang/String;"); + + if (!field) { + return false; + } + + jstring jstr = (jstring)jEnv->GetStaticObjectField(cls.getRawRef(), field); + if (!jstr) return false; + + result.Assign(jni::String::Ref::From(jstr)->ToString()); + return true; +} + +namespace mozilla { +class TracerRunnable : public Runnable { + public: + TracerRunnable() : Runnable("TracerRunnable") { + mTracerLock = new Mutex("TracerRunnable"); + mTracerCondVar = new CondVar(*mTracerLock, "TracerRunnable"); + mMainThread = do_GetMainThread(); + } + ~TracerRunnable() { + delete mTracerCondVar; + delete mTracerLock; + mTracerLock = nullptr; + mTracerCondVar = nullptr; + } + + virtual nsresult Run() { + MutexAutoLock lock(*mTracerLock); + if (!AndroidBridge::Bridge()) return NS_OK; + + mHasRun = true; + mTracerCondVar->Notify(); + return NS_OK; + } + + bool Fire() { + if (!mTracerLock || !mTracerCondVar) return false; + MutexAutoLock lock(*mTracerLock); + mHasRun = false; + mMainThread->Dispatch(this, NS_DISPATCH_NORMAL); + while (!mHasRun) mTracerCondVar->Wait(); + return true; + } + + void Signal() { + MutexAutoLock lock(*mTracerLock); + mHasRun = true; + mTracerCondVar->Notify(); + } + + private: + Mutex* mTracerLock; + CondVar* mTracerCondVar; + bool mHasRun; + nsCOMPtr mMainThread; +}; +StaticRefPtr sTracerRunnable; + +bool InitWidgetTracing() { + if (!sTracerRunnable) sTracerRunnable = new TracerRunnable(); + return true; +} + +void CleanUpWidgetTracing() { sTracerRunnable = nullptr; } + +bool FireAndWaitForTracerEvent() { + if (sTracerRunnable) return sTracerRunnable->Fire(); + return false; +} + +void SignalTracerThread() { + if (sTracerRunnable) return sTracerRunnable->Signal(); +} + +} // namespace mozilla + +void AndroidBridge::GetCurrentBatteryInformation( + hal::BatteryInformation* aBatteryInfo) { + ALOG_BRIDGE("AndroidBridge::GetCurrentBatteryInformation"); + + // To prevent calling too many methods through JNI, the Java method returns + // an array of double even if we actually want a double and a boolean. + auto arr = java::GeckoAppShell::GetCurrentBatteryInformation(); + + JNIEnv* const env = arr.Env(); + if (!arr || env->GetArrayLength(arr.Get()) != 3) { + return; + } + + jdouble* info = env->GetDoubleArrayElements(arr.Get(), 0); + + aBatteryInfo->level() = info[0]; + aBatteryInfo->charging() = info[1] == 1.0f; + aBatteryInfo->remainingTime() = info[2]; + + env->ReleaseDoubleArrayElements(arr.Get(), info, 0); +} + +void AndroidBridge::GetCurrentNetworkInformation( + hal::NetworkInformation* aNetworkInfo) { + ALOG_BRIDGE("AndroidBridge::GetCurrentNetworkInformation"); + + // To prevent calling too many methods through JNI, the Java method returns + // an array of double even if we actually want an integer, a boolean, and an + // integer. + + auto arr = java::GeckoAppShell::GetCurrentNetworkInformation(); + + JNIEnv* const env = arr.Env(); + if (!arr || env->GetArrayLength(arr.Get()) != 3) { + return; + } + + jdouble* info = env->GetDoubleArrayElements(arr.Get(), 0); + + aNetworkInfo->type() = info[0]; + aNetworkInfo->isWifi() = info[1] == 1.0f; + aNetworkInfo->dhcpGateway() = info[2]; + + env->ReleaseDoubleArrayElements(arr.Get(), info, 0); +} + +jobject AndroidBridge::GetGlobalContextRef() { + // The context object can change, so get a fresh copy every time. + auto context = java::GeckoAppShell::GetApplicationContext(); + sGlobalContext = jni::Object::GlobalRef(context).Forget(); + MOZ_ASSERT(sGlobalContext); + return sGlobalContext; +} + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsAndroidBridge, nsIAndroidEventDispatcher, nsIAndroidBridge) + +nsAndroidBridge::nsAndroidBridge() { + if (jni::IsAvailable()) { + RefPtr dispatcher = new widget::EventDispatcher(); + dispatcher->Attach(java::EventDispatcher::GetInstance(), + /* window */ nullptr); + mEventDispatcher = dispatcher; + } +} + +NS_IMETHODIMP +nsAndroidBridge::GetDispatcherByName(const char* aName, + nsIAndroidEventDispatcher** aResult) { + if (!jni::IsAvailable()) { + return NS_ERROR_FAILURE; + } + + RefPtr dispatcher = new widget::EventDispatcher(); + dispatcher->Attach(java::EventDispatcher::ByName(aName), + /* window */ nullptr); + dispatcher.forget(aResult); + return NS_OK; +} + +nsAndroidBridge::~nsAndroidBridge() {} + +NS_IMETHODIMP nsAndroidBridge::ContentDocumentChanged( + mozIDOMWindowProxy* aWindow) { + AndroidBridge::Bridge()->ContentDocumentChanged(aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsAndroidBridge::IsContentDocumentDisplayed( + mozIDOMWindowProxy* aWindow, bool* aRet) { + *aRet = AndroidBridge::Bridge()->IsContentDocumentDisplayed(aWindow); + return NS_OK; +} + +uint32_t AndroidBridge::GetScreenOrientation() { + ALOG_BRIDGE("AndroidBridge::GetScreenOrientation"); + + int16_t orientation = java::GeckoAppShell::GetScreenOrientation(); + + return static_cast(orientation); +} + +uint16_t AndroidBridge::GetScreenAngle() { + return java::GeckoAppShell::GetScreenAngle(); +} + +nsresult AndroidBridge::GetProxyForURI(const nsACString& aSpec, + const nsACString& aScheme, + const nsACString& aHost, + const int32_t aPort, + nsACString& aResult) { + if (!jni::IsAvailable()) { + return NS_ERROR_FAILURE; + } + + auto jstrRet = + java::GeckoAppShell::GetProxyForURI(aSpec, aScheme, aHost, aPort); + + if (!jstrRet) return NS_ERROR_FAILURE; + + aResult = jstrRet->ToCString(); + return NS_OK; +} + +bool AndroidBridge::PumpMessageLoop() { + JNIEnv* const env = jni::GetGeckoThreadEnv(); + + if (mMessageQueueMessages) { + auto msg = jni::Object::LocalRef::Adopt( + env, env->GetObjectField(mMessageQueue.Get(), mMessageQueueMessages)); + // if queue.mMessages is null, queue.next() will block, which we don't + // want. It turns out to be an order of magnitude more performant to do + // this extra check here and block less vs. one fewer checks here and + // more blocking. + if (!msg) { + return false; + } + } + + auto msg = jni::Object::LocalRef::Adopt( + env, env->CallObjectMethod(mMessageQueue.Get(), mMessageQueueNext)); + if (!msg) { + return false; + } + + return java::GeckoThread::PumpMessageLoop(msg); +} + +NS_IMETHODIMP nsAndroidBridge::GetIsFennec(bool* aIsFennec) { + *aIsFennec = false; + return NS_OK; +} + +NS_IMETHODIMP nsAndroidBridge::GetBrowserApp( + nsIAndroidBrowserApp** aBrowserApp) { + nsAppShell* const appShell = nsAppShell::Get(); + if (appShell) NS_IF_ADDREF(*aBrowserApp = appShell->GetBrowserApp()); + return NS_OK; +} + +NS_IMETHODIMP nsAndroidBridge::SetBrowserApp( + nsIAndroidBrowserApp* aBrowserApp) { + nsAppShell* const appShell = nsAppShell::Get(); + if (appShell) appShell->SetBrowserApp(aBrowserApp); + return NS_OK; +} + +extern "C" __attribute__((visibility("default"))) jobject JNICALL +Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv* env, jclass, + jlong size); + +void AndroidBridge::ContentDocumentChanged(mozIDOMWindowProxy* aWindow) { + if (RefPtr widget = + nsWindow::From(nsPIDOMWindowOuter::From(aWindow))) { + widget->SetContentDocumentDisplayed(false); + } +} + +bool AndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow) { + if (RefPtr widget = + nsWindow::From(nsPIDOMWindowOuter::From(aWindow))) { + return widget->IsContentDocumentDisplayed(); + } + return false; +} + +jni::Object::LocalRef AndroidBridge::ChannelCreate(jni::Object::Param stream) { + JNIEnv* const env = jni::GetEnvForThread(); + auto rv = jni::Object::LocalRef::Adopt( + env, env->CallStaticObjectMethod(sBridge->jChannels, + sBridge->jChannelCreate, stream.Get())); + MOZ_CATCH_JNI_EXCEPTION(env); + return rv; +} + +void AndroidBridge::InputStreamClose(jni::Object::Param obj) { + JNIEnv* const env = jni::GetEnvForThread(); + env->CallVoidMethod(obj.Get(), sBridge->jClose); + MOZ_CATCH_JNI_EXCEPTION(env); +} + +uint32_t AndroidBridge::InputStreamAvailable(jni::Object::Param obj) { + JNIEnv* const env = jni::GetEnvForThread(); + auto rv = env->CallIntMethod(obj.Get(), sBridge->jAvailable); + MOZ_CATCH_JNI_EXCEPTION(env); + return rv; +} + +nsresult AndroidBridge::InputStreamRead(jni::Object::Param obj, char* aBuf, + uint32_t aCount, uint32_t* aRead) { + JNIEnv* const env = jni::GetEnvForThread(); + auto arr = jni::ByteBuffer::New(aBuf, aCount); + jint read = + env->CallIntMethod(obj.Get(), sBridge->jByteBufferRead, arr.Get()); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + + if (read <= 0) { + *aRead = 0; + return NS_OK; + } + *aRead = read; + return NS_OK; +} diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h new file mode 100644 index 0000000000..3183de5ae6 --- /dev/null +++ b/widget/android/AndroidBridge.h @@ -0,0 +1,362 @@ +/* -*- 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 AndroidBridge_h__ +#define AndroidBridge_h__ + +#include +#include +#include +#include + +#include "APKOpen.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" + +#include "js/RootingAPI.h" +#include "js/Value.h" +#include "mozilla/jni/Refs.h" + +#include "nsIMutableArray.h" +#include "nsIMIMEInfo.h" +#include "nsColor.h" +#include "gfxRect.h" + +#include "nsIAndroidBridge.h" + +#include "mozilla/Likely.h" +#include "mozilla/Mutex.h" +#include "mozilla/Types.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/jni/Utils.h" +#include "nsDataHashtable.h" + +#include "Units.h" + +// Some debug #defines +// #define DEBUG_ANDROID_EVENTS +// #define DEBUG_ANDROID_WIDGET + +class nsPIDOMWindowOuter; + +typedef void* EGLSurface; +class nsIRunnable; + +namespace mozilla { + +class AutoLocalJNIFrame; + +namespace hal { +class BatteryInformation; +class NetworkInformation; +} // namespace hal + +// The order and number of the members in this structure must correspond +// to the attrsAppearance array in GeckoAppShell.getSystemColors() +typedef struct AndroidSystemColors { + nscolor textColorPrimary; + nscolor textColorPrimaryInverse; + nscolor textColorSecondary; + nscolor textColorSecondaryInverse; + nscolor textColorTertiary; + nscolor textColorTertiaryInverse; + nscolor textColorHighlight; + nscolor colorForeground; + nscolor colorBackground; + nscolor panelColorForeground; + nscolor panelColorBackground; +} AndroidSystemColors; + +class AndroidBridge final { + public: + enum { + // Values for NotifyIME, in addition to values from the Gecko + // IMEMessage enum; use negative values here to prevent conflict + NOTIFY_IME_OPEN_VKB = -2, + NOTIFY_IME_REPLY_EVENT = -1, + }; + + enum { + LAYER_CLIENT_TYPE_NONE = 0, + LAYER_CLIENT_TYPE_GL = 2 // AndroidGeckoGLLayerClient + }; + + static bool IsJavaUiThread() { + return mozilla::jni::GetUIThreadId() == gettid(); + } + + static void ConstructBridge(); + static void DeconstructBridge(); + + static AndroidBridge* Bridge() { return sBridge; } + + void ContentDocumentChanged(mozIDOMWindowProxy* aDOMWindow); + bool IsContentDocumentDisplayed(mozIDOMWindowProxy* aDOMWindow); + + bool GetHandlersForURL(const nsAString& aURL, + nsIMutableArray* handlersArray = nullptr, + nsIHandlerApp** aDefaultApp = nullptr, + const nsAString& aAction = u""_ns); + + bool GetHandlersForMimeType(const nsAString& aMimeType, + nsIMutableArray* handlersArray = nullptr, + nsIHandlerApp** aDefaultApp = nullptr, + const nsAString& aAction = u""_ns); + + bool HasHWVP8Encoder(); + bool HasHWVP8Decoder(); + bool HasHWH264(); + + void GetMimeTypeFromExtensions(const nsACString& aFileExt, + nsCString& aMimeType); + void GetExtensionFromMimeType(const nsACString& aMimeType, + nsACString& aFileExt); + + gfx::Rect getScreenSize(); + int GetScreenDepth(); + + void Vibrate(const nsTArray& aPattern); + + void GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, + uint8_t* const aBuf); + + bool GetStaticStringField(const char* classID, const char* field, + nsAString& result, JNIEnv* env = nullptr); + + bool GetStaticIntField(const char* className, const char* fieldName, + int32_t* aInt, JNIEnv* env = nullptr); + + // Returns a global reference to the Context for Fennec's Activity. The + // caller is responsible for ensuring this doesn't leak by calling + // DeleteGlobalRef() when the context is no longer needed. + jobject GetGlobalContextRef(void); + + void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo); + + void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo); + + // These methods don't use a ScreenOrientation because it's an + // enum and that would require including the header which requires + // include IPC headers which requires including basictypes.h which + // requires a lot of changes... + uint32_t GetScreenOrientation(); + uint16_t GetScreenAngle(); + + int GetAPIVersion() { return mAPIVersion; } + + nsresult GetProxyForURI(const nsACString& aSpec, const nsACString& aScheme, + const nsACString& aHost, const int32_t aPort, + nsACString& aResult); + + bool PumpMessageLoop(); + + // Utility methods. + static jfieldID GetFieldID(JNIEnv* env, jclass jClass, const char* fieldName, + const char* fieldType); + static jfieldID GetStaticFieldID(JNIEnv* env, jclass jClass, + const char* fieldName, + const char* fieldType); + static jmethodID GetMethodID(JNIEnv* env, jclass jClass, + const char* methodName, const char* methodType); + static jmethodID GetStaticMethodID(JNIEnv* env, jclass jClass, + const char* methodName, + const char* methodType); + + static jni::Object::LocalRef ChannelCreate(jni::Object::Param); + + static void InputStreamClose(jni::Object::Param obj); + static uint32_t InputStreamAvailable(jni::Object::Param obj); + static nsresult InputStreamRead(jni::Object::Param obj, char* aBuf, + uint32_t aCount, uint32_t* aRead); + + protected: + static nsDataHashtable sStoragePaths; + + static AndroidBridge* sBridge; + + AndroidBridge(); + ~AndroidBridge(); + + int mAPIVersion; + + // intput stream + jclass jReadableByteChannel; + jclass jChannels; + jmethodID jChannelCreate; + jmethodID jByteBufferRead; + + jclass jInputStream; + jmethodID jClose; + jmethodID jAvailable; + + jmethodID jCalculateLength; + + // some convinient types to have around + jclass jStringClass; + + jni::Object::GlobalRef mMessageQueue; + jfieldID mMessageQueueMessages; + jmethodID mMessageQueueNext; +}; + +class AutoJNIClass { + private: + JNIEnv* const mEnv; + const jclass mClass; + + public: + AutoJNIClass(JNIEnv* jEnv, const char* name) + : mEnv(jEnv), mClass(jni::GetClassRef(jEnv, name)) {} + + ~AutoJNIClass() { mEnv->DeleteLocalRef(mClass); } + + jclass getRawRef() const { return mClass; } + + jclass getGlobalRef() const { + return static_cast(mEnv->NewGlobalRef(mClass)); + } + + jfieldID getField(const char* name, const char* type) const { + return AndroidBridge::GetFieldID(mEnv, mClass, name, type); + } + + jfieldID getStaticField(const char* name, const char* type) const { + return AndroidBridge::GetStaticFieldID(mEnv, mClass, name, type); + } + + jmethodID getMethod(const char* name, const char* type) const { + return AndroidBridge::GetMethodID(mEnv, mClass, name, type); + } + + jmethodID getStaticMethod(const char* name, const char* type) const { + return AndroidBridge::GetStaticMethodID(mEnv, mClass, name, type); + } +}; + +class AutoJObject { + public: + explicit AutoJObject(JNIEnv* aJNIEnv = nullptr) : mObject(nullptr) { + mJNIEnv = aJNIEnv ? aJNIEnv : jni::GetGeckoThreadEnv(); + } + + AutoJObject(JNIEnv* aJNIEnv, jobject aObject) { + mJNIEnv = aJNIEnv ? aJNIEnv : jni::GetGeckoThreadEnv(); + mObject = aObject; + } + + ~AutoJObject() { + if (mObject) mJNIEnv->DeleteLocalRef(mObject); + } + + jobject operator=(jobject aObject) { + if (mObject) { + mJNIEnv->DeleteLocalRef(mObject); + } + return mObject = aObject; + } + + operator jobject() { return mObject; } + + private: + JNIEnv* mJNIEnv; + jobject mObject; +}; + +class AutoLocalJNIFrame { + public: + explicit AutoLocalJNIFrame(int nEntries = 15) + : mEntries(nEntries), + mJNIEnv(jni::GetGeckoThreadEnv()), + mHasFrameBeenPushed(false) { + MOZ_ASSERT(mJNIEnv); + Push(); + } + + explicit AutoLocalJNIFrame(JNIEnv* aJNIEnv, int nEntries = 15) + : mEntries(nEntries), + mJNIEnv(aJNIEnv ? aJNIEnv : jni::GetGeckoThreadEnv()), + mHasFrameBeenPushed(false) { + MOZ_ASSERT(mJNIEnv); + Push(); + } + + ~AutoLocalJNIFrame() { + if (mHasFrameBeenPushed) { + Pop(); + } + } + + JNIEnv* GetEnv() { return mJNIEnv; } + + bool CheckForException() { + if (mJNIEnv->ExceptionCheck()) { + MOZ_CATCH_JNI_EXCEPTION(mJNIEnv); + return true; + } + return false; + } + + // Note! Calling Purge makes all previous local refs created in + // the AutoLocalJNIFrame's scope INVALID; be sure that you locked down + // any local refs that you need to keep around in global refs! + void Purge() { + Pop(); + Push(); + } + + template + ReturnType Pop(ReturnType aResult = nullptr) { + MOZ_ASSERT(mHasFrameBeenPushed); + mHasFrameBeenPushed = false; + return static_cast( + mJNIEnv->PopLocalFrame(static_cast(aResult))); + } + + private: + void Push() { + MOZ_ASSERT(!mHasFrameBeenPushed); + // Make sure there is enough space to store a local ref to the + // exception. I am not completely sure this is needed, but does + // not hurt. + if (mJNIEnv->PushLocalFrame(mEntries + 1) != 0) { + CheckForException(); + return; + } + mHasFrameBeenPushed = true; + } + + const int mEntries; + JNIEnv* const mJNIEnv; + bool mHasFrameBeenPushed; +}; + +} // namespace mozilla + +#define NS_ANDROIDBRIDGE_CID \ + { \ + 0x0FE2321D, 0xEBD9, 0x467D, { \ + 0xA7, 0x43, 0x03, 0xA6, 0x8D, 0x40, 0x59, 0x9E \ + } \ + } + +class nsAndroidBridge final : public nsIAndroidBridge { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIANDROIDBRIDGE + + NS_FORWARD_SAFE_NSIANDROIDEVENTDISPATCHER(mEventDispatcher) + + nsAndroidBridge(); + + private: + ~nsAndroidBridge(); + + nsCOMPtr mEventDispatcher; + + protected: +}; + +#endif /* AndroidBridge_h__ */ diff --git a/widget/android/AndroidBridgeUtilities.h b/widget/android/AndroidBridgeUtilities.h new file mode 100644 index 0000000000..2d67c7ff46 --- /dev/null +++ b/widget/android/AndroidBridgeUtilities.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 ALOG +# if defined(DEBUG) || defined(FORCE_ALOG) +# define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko", ##args) +# else +# define ALOG(args...) ((void)0) +# endif +#endif + +#ifdef DEBUG +# define ALOG_BRIDGE(args...) ALOG(args) +#else +# define ALOG_BRIDGE(args...) ((void)0) +#endif diff --git a/widget/android/AndroidColors.h b/widget/android/AndroidColors.h new file mode 100644 index 0000000000..1b594f6c3d --- /dev/null +++ b/widget/android/AndroidColors.h @@ -0,0 +1,28 @@ +/* -*- Mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* 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 mozilla_widget_AndroidColors_h +#define mozilla_widget_AndroidColors_h + +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace widget { + +static const gfx::sRGBColor sAndroidBackgroundColor(gfx::sRGBColor(1.0f, 1.0f, + 1.0f)); +static const gfx::sRGBColor sAndroidBorderColor(gfx::sRGBColor(0.73f, 0.73f, + 0.73f)); +static const gfx::sRGBColor sAndroidCheckColor(gfx::sRGBColor(0.19f, 0.21f, + 0.23f)); +static const gfx::sRGBColor sAndroidDisabledColor(gfx::sRGBColor(0.88f, 0.88f, + 0.88f)); +static const gfx::sRGBColor sAndroidActiveColor(gfx::sRGBColor(0.94f, 0.94f, + 0.94f)); + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_AndroidColors_h diff --git a/widget/android/AndroidCompositorWidget.cpp b/widget/android/AndroidCompositorWidget.cpp new file mode 100644 index 0000000000..ec45ace6d4 --- /dev/null +++ b/widget/android/AndroidCompositorWidget.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 et tw=80 : */ +/* 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 "AndroidCompositorWidget.h" +#include "nsWindow.h" + +namespace mozilla { +namespace widget { + +EGLNativeWindowType AndroidCompositorWidget::GetEGLNativeWindow() { + return (EGLNativeWindowType)mWidget->GetNativeData(NS_JAVA_SURFACE); +} + +EGLNativeWindowType AndroidCompositorWidget::GetPresentationEGLSurface() { + return (EGLNativeWindowType)mWidget->GetNativeData(NS_PRESENTATION_SURFACE); +} + +void AndroidCompositorWidget::SetPresentationEGLSurface(EGLSurface aVal) { + mWidget->SetNativeData(NS_PRESENTATION_SURFACE, (uintptr_t)aVal); +} + +ANativeWindow* AndroidCompositorWidget::GetPresentationANativeWindow() { + return (ANativeWindow*)mWidget->GetNativeData(NS_PRESENTATION_WINDOW); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/AndroidCompositorWidget.h b/widget/android/AndroidCompositorWidget.h new file mode 100644 index 0000000000..09cbaad593 --- /dev/null +++ b/widget/android/AndroidCompositorWidget.h @@ -0,0 +1,40 @@ +/* -*- 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 mozilla_widget_AndroidCompositorWidget_h +#define mozilla_widget_AndroidCompositorWidget_h + +#include "GLDefs.h" +#include "mozilla/widget/InProcessCompositorWidget.h" + +struct ANativeWindow; + +namespace mozilla { +namespace widget { + +/** + * AndroidCompositorWidget inherits from InProcessCompositorWidget because + * Android does not support OOP compositing yet. Once it does, + * AndroidCompositorWidget will be made to inherit from CompositorWidget + * instead. + */ +class AndroidCompositorWidget final : public InProcessCompositorWidget { + public: + using InProcessCompositorWidget::InProcessCompositorWidget; + + AndroidCompositorWidget* AsAndroid() override { return this; } + + EGLNativeWindowType GetEGLNativeWindow(); + + EGLSurface GetPresentationEGLSurface(); + void SetPresentationEGLSurface(EGLSurface aVal); + + ANativeWindow* GetPresentationANativeWindow(); +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_AndroidCompositorWidget_h diff --git a/widget/android/AndroidContentController.cpp b/widget/android/AndroidContentController.cpp new file mode 100644 index 0000000000..c42370e26c --- /dev/null +++ b/widget/android/AndroidContentController.cpp @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#include "AndroidContentController.h" + +#include "AndroidBridge.h" +#include "base/message_loop.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "nsIObserverService.h" +#include "nsLayoutUtils.h" +#include "nsWindow.h" + +using mozilla::layers::IAPZCTreeManager; + +namespace mozilla { +namespace widget { + +void AndroidContentController::Destroy() { + mAndroidWindow = nullptr; + ChromeProcessController::Destroy(); +} + +void AndroidContentController::UpdateOverscrollVelocity( + const ScrollableLayerGuid& aGuid, const float aX, const float aY, + const bool aIsRootContent) { + if (aIsRootContent && mAndroidWindow) { + mAndroidWindow->UpdateOverscrollVelocity(aX, aY); + } +} + +void AndroidContentController::UpdateOverscrollOffset( + const ScrollableLayerGuid& aGuid, const float aX, const float aY, + const bool aIsRootContent) { + if (aIsRootContent && mAndroidWindow) { + mAndroidWindow->UpdateOverscrollOffset(aX, aY); + } +} + +void AndroidContentController::NotifyAPZStateChange( + const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) { + // This function may get invoked twice, if the first invocation is not on + // the main thread then the ChromeProcessController version of this function + // will redispatch to the main thread. We want to make sure that our handling + // only happens on the main thread. + ChromeProcessController::NotifyAPZStateChange(aGuid, aChange, aArg); + if (NS_IsMainThread()) { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (aChange == + layers::GeckoContentController::APZStateChange::eTransformEnd) { + // This is used by tests to determine when the APZ is done doing whatever + // it's doing. XXX generify this as needed when writing additional tests. + observerService->NotifyObservers(nullptr, "APZ:TransformEnd", nullptr); + observerService->NotifyObservers(nullptr, "PanZoom:StateChange", + u"NOTHING"); + } else if (aChange == layers::GeckoContentController::APZStateChange:: + eTransformBegin) { + observerService->NotifyObservers(nullptr, "PanZoom:StateChange", + u"PANNING"); + } + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/AndroidContentController.h b/widget/android/AndroidContentController.h new file mode 100644 index 0000000000..d3535c78cf --- /dev/null +++ b/widget/android/AndroidContentController.h @@ -0,0 +1,51 @@ +/* -*- 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 AndroidContentController_h__ +#define AndroidContentController_h__ + +#include "mozilla/layers/ChromeProcessController.h" +#include "mozilla/EventForwards.h" // for Modifiers +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsTArray.h" +#include "nsWindow.h" + +namespace mozilla { +namespace layers { +class APZEventState; +class IAPZCTreeManager; +} // namespace layers +namespace widget { + +class AndroidContentController final + : public mozilla::layers::ChromeProcessController { + public: + AndroidContentController(nsWindow* aWindow, + mozilla::layers::APZEventState* aAPZEventState, + mozilla::layers::IAPZCTreeManager* aAPZCTreeManager) + : mozilla::layers::ChromeProcessController(aWindow, aAPZEventState, + aAPZCTreeManager), + mAndroidWindow(aWindow) {} + + // ChromeProcessController methods + virtual void Destroy() override; + void UpdateOverscrollVelocity(const ScrollableLayerGuid& aGuid, + const float aX, const float aY, + const bool aIsRootContent) override; + void UpdateOverscrollOffset(const ScrollableLayerGuid& aGuid, const float aX, + const float aY, + const bool aIsRootContent) override; + void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, int aArg) override; + + private: + nsWindow* mAndroidWindow; +}; + +} // namespace widget +} // namespace mozilla + +#endif diff --git a/widget/android/AndroidUiThread.cpp b/widget/android/AndroidUiThread.cpp new file mode 100644 index 0000000000..a06ad4df17 --- /dev/null +++ b/widget/android/AndroidUiThread.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "base/message_loop.h" +#include "mozilla/Atomics.h" +#include "mozilla/EventQueue.h" +#include "mozilla/java/GeckoThreadWrappers.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadEventQueue.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "GeckoProfiler.h" +#include "nsThread.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +namespace { + +class AndroidUiThread; +class AndroidUiTask; + +StaticAutoPtr > sTaskQueue; +StaticAutoPtr sTaskQueueLock; +StaticRefPtr sThread; +static bool sThreadDestroyed; +static MessageLoop* sMessageLoop; +static Atomic sMessageLoopAccessMonitor; + +void EnqueueTask(already_AddRefed aTask, int aDelayMs); + +/* + * The AndroidUiThread is derived from nsThread so that nsIRunnable objects that + * get dispatched may be intercepted. Only nsIRunnable objects that need to be + * synchronously executed are passed into the nsThread to be queued. All other + * nsIRunnable object are immediately dispatched to the Android UI thread. + * AndroidUiThread is derived from nsThread instead of being an nsIEventTarget + * wrapper that contains an nsThread object because if nsIRunnable objects with + * a delay were dispatch directly to an nsThread object, such as obtained from + * nsThreadManager::GetCurrentThread(), the nsIRunnable could get stuck in the + * nsThread nsIRunnable queue. This is due to the fact that Android controls the + * event loop in the Android UI thread and has no knowledge of when the nsThread + * needs to be drained. + */ + +class AndroidUiThread : public nsThread { + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(AndroidUiThread, nsThread) + AndroidUiThread() + : nsThread( + MakeNotNull(MakeUnique()), + nsThread::NOT_MAIN_THREAD, 0) {} + + nsresult Dispatch(already_AddRefed aEvent, + uint32_t aFlags) override; + nsresult DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) override; + + private: + ~AndroidUiThread() {} +}; + +NS_IMETHODIMP +AndroidUiThread::Dispatch(already_AddRefed aEvent, + uint32_t aFlags) { + if (aFlags & NS_DISPATCH_SYNC) { + return nsThread::Dispatch(std::move(aEvent), aFlags); + } else { + EnqueueTask(std::move(aEvent), 0); + return NS_OK; + } +} + +NS_IMETHODIMP +AndroidUiThread::DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) { + EnqueueTask(std::move(aEvent), aDelayMs); + return NS_OK; +} + +static void PumpEvents() { NS_ProcessPendingEvents(sThread.get()); } + +class ThreadObserver : public nsIThreadObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADOBSERVER + + ThreadObserver() {} + + private: + virtual ~ThreadObserver() {} +}; + +NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver) + +NS_IMETHODIMP +ThreadObserver::OnDispatchedEvent() { + EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0); + return NS_OK; +} + +NS_IMETHODIMP +ThreadObserver::OnProcessNextEvent(nsIThreadInternal* thread, bool mayWait) { + return NS_OK; +} + +NS_IMETHODIMP +ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) { + return NS_OK; +} + +class AndroidUiTask : public LinkedListElement { + using TimeStamp = mozilla::TimeStamp; + using TimeDuration = mozilla::TimeDuration; + + public: + explicit AndroidUiTask(already_AddRefed aTask) + : mTask(aTask), + mRunTime() // Null timestamp representing no delay. + {} + + AndroidUiTask(already_AddRefed aTask, int aDelayMs) + : mTask(aTask), + mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs)) {} + + bool IsEarlierThan(const AndroidUiTask& aOther) const { + if (mRunTime) { + return aOther.mRunTime ? mRunTime < aOther.mRunTime : false; + } + // In the case of no delay, we're earlier if aOther has a delay. + // Otherwise, we're not earlier, to maintain task order. + return !!aOther.mRunTime; + } + + int64_t MillisecondsToRunTime() const { + if (mRunTime) { + return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds()); + } + return 0; + } + + already_AddRefed TakeTask() { return mTask.forget(); } + + private: + nsCOMPtr mTask; + const TimeStamp mRunTime; +}; + +class CreateOnUiThread : public Runnable { + public: + CreateOnUiThread() : Runnable("CreateOnUiThread") {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(!sThreadDestroyed); + MOZ_ASSERT(sMessageLoopAccessMonitor); + MonitorAutoLock lock(*sMessageLoopAccessMonitor); + sThread = new AndroidUiThread(); + sThread->InitCurrentThread(); + sThread->SetObserver(new ThreadObserver()); + PROFILER_REGISTER_THREAD("AndroidUI"); + sMessageLoop = + new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get()); + lock.NotifyAll(); + return NS_OK; + } +}; + +class DestroyOnUiThread : public Runnable { + public: + DestroyOnUiThread() : Runnable("DestroyOnUiThread"), mDestroyed(false) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(!sThreadDestroyed); + MOZ_ASSERT(sMessageLoopAccessMonitor); + MOZ_ASSERT(sTaskQueue); + MonitorAutoLock lock(*sMessageLoopAccessMonitor); + sThreadDestroyed = true; + + { + // Flush the queue + MutexAutoLock lock(*sTaskQueueLock); + while (AndroidUiTask* task = sTaskQueue->getFirst()) { + delete task; + } + } + + delete sMessageLoop; + sMessageLoop = nullptr; + MOZ_ASSERT(sThread); + PROFILER_UNREGISTER_THREAD(); + nsThreadManager::get().UnregisterCurrentThread(*sThread); + sThread = nullptr; + mDestroyed = true; + lock.NotifyAll(); + return NS_OK; + } + + void WaitForDestruction() { + MOZ_ASSERT(sMessageLoopAccessMonitor); + MonitorAutoLock lock(*sMessageLoopAccessMonitor); + while (!mDestroyed) { + lock.Wait(); + } + } + + private: + bool mDestroyed; +}; + +void EnqueueTask(already_AddRefed aTask, int aDelayMs) { + if (sThreadDestroyed) { + return; + } + + // add the new task into the sTaskQueue, sorted with + // the earliest task first in the queue + AndroidUiTask* newTask = + (aDelayMs ? new AndroidUiTask(std::move(aTask), aDelayMs) + : new AndroidUiTask(std::move(aTask))); + + bool headOfList = false; + { + MOZ_ASSERT(sTaskQueue); + MOZ_ASSERT(sTaskQueueLock); + MutexAutoLock lock(*sTaskQueueLock); + + AndroidUiTask* task = sTaskQueue->getFirst(); + + while (task) { + if (newTask->IsEarlierThan(*task)) { + task->setPrevious(newTask); + break; + } + task = task->getNext(); + } + + if (!newTask->isInList()) { + sTaskQueue->insertBack(newTask); + } + headOfList = !newTask->getPrevious(); + } + + if (headOfList) { + // if we're inserting it at the head of the queue, notify Java because + // we need to get a callback at an earlier time than the last scheduled + // callback + java::GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs)); + } +} + +} // namespace + +namespace mozilla { + +void CreateAndroidUiThread() { + MOZ_ASSERT(!sThread); + MOZ_ASSERT(!sMessageLoopAccessMonitor); + sTaskQueue = new LinkedList(); + sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock"); + sMessageLoopAccessMonitor = + new Monitor("AndroidUiThreadMessageLoopAccessMonitor"); + sThreadDestroyed = false; + RefPtr runnable = new CreateOnUiThread; + EnqueueTask(do_AddRef(runnable), 0); +} + +void DestroyAndroidUiThread() { + MOZ_ASSERT(sThread); + RefPtr runnable = new DestroyOnUiThread; + EnqueueTask(do_AddRef(runnable), 0); + runnable->WaitForDestruction(); + delete sMessageLoopAccessMonitor; + sMessageLoopAccessMonitor = nullptr; +} + +MessageLoop* GetAndroidUiThreadMessageLoop() { + if (!sMessageLoopAccessMonitor) { + return nullptr; + } + + MonitorAutoLock lock(*sMessageLoopAccessMonitor); + while (!sMessageLoop) { + lock.Wait(); + } + + return sMessageLoop; +} + +RefPtr GetAndroidUiThread() { + if (!sMessageLoopAccessMonitor) { + return nullptr; + } + + MonitorAutoLock lock(*sMessageLoopAccessMonitor); + while (!sThread) { + lock.Wait(); + } + + return sThread; +} + +int64_t RunAndroidUiTasks() { + MutexAutoLock lock(*sTaskQueueLock); + + if (sThreadDestroyed) { + return -1; + } + + while (!sTaskQueue->isEmpty()) { + AndroidUiTask* task = sTaskQueue->getFirst(); + const int64_t timeLeft = task->MillisecondsToRunTime(); + if (timeLeft > 0) { + // this task (and therefore all remaining tasks) + // have not yet reached their runtime. return the + // time left until we should be called again + return timeLeft; + } + + // Retrieve task before unlocking/running. + nsCOMPtr runnable(task->TakeTask()); + // LinkedListElements auto remove from list upon destruction + delete task; + + // Unlock to allow posting new tasks reentrantly. + MutexAutoUnlock unlock(*sTaskQueueLock); + runnable->Run(); + if (sThreadDestroyed) { + return -1; + } + } + return -1; +} + +} // namespace mozilla diff --git a/widget/android/AndroidUiThread.h b/widget/android/AndroidUiThread.h new file mode 100644 index 0000000000..af722fb048 --- /dev/null +++ b/widget/android/AndroidUiThread.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 AndroidUiThread_h__ +#define AndroidUiThread_h__ + +#include +#include + +class MessageLoop; + +namespace mozilla { + +void CreateAndroidUiThread(); +void DestroyAndroidUiThread(); +int64_t RunAndroidUiTasks(); + +MessageLoop* GetAndroidUiThreadMessageLoop(); +RefPtr GetAndroidUiThread(); + +} // namespace mozilla + +#endif // AndroidUiThread_h__ diff --git a/widget/android/AndroidView.h b/widget/android/AndroidView.h new file mode 100644 index 0000000000..173db8b5c1 --- /dev/null +++ b/widget/android/AndroidView.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_widget_AndroidView_h +#define mozilla_widget_AndroidView_h + +#include "mozilla/widget/EventDispatcher.h" + +namespace mozilla { +namespace widget { + +class AndroidView final : public nsIAndroidView { + virtual ~AndroidView() {} + + public: + const RefPtr mEventDispatcher{ + new mozilla::widget::EventDispatcher()}; + + AndroidView() {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIANDROIDVIEW + + NS_FORWARD_NSIANDROIDEVENTDISPATCHER(mEventDispatcher->) + + mozilla::java::GeckoBundle::GlobalRef mInitData; +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_AndroidView_h diff --git a/widget/android/AndroidVsync.cpp b/widget/android/AndroidVsync.cpp new file mode 100644 index 0000000000..a25644ce80 --- /dev/null +++ b/widget/android/AndroidVsync.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "AndroidVsync.h" + +#include "nsTArray.h" + +/** + * Implementation for the AndroidVsync class. + */ + +namespace mozilla { +namespace widget { + +StaticDataMutex> AndroidVsync::sInstance( + "AndroidVsync::sInstance"); + +/* static */ RefPtr AndroidVsync::GetInstance() { + auto weakInstance = sInstance.Lock(); + RefPtr instance(*weakInstance); + if (!instance) { + instance = new AndroidVsync(); + *weakInstance = instance; + } + return instance; +} + +/** + * Owned by the Java AndroidVsync instance. + */ +class AndroidVsyncSupport final + : public java::AndroidVsync::Natives { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidVsyncSupport) + + using Base = java::AndroidVsync::Natives; + using Base::AttachNative; + using Base::DisposeNative; + + explicit AndroidVsyncSupport(AndroidVsync* aAndroidVsync) + : mAndroidVsync(std::move(aAndroidVsync), + "AndroidVsyncSupport::mAndroidVsync") {} + + // Called by Java + void NotifyVsync(const java::AndroidVsync::LocalRef& aInstance, + int64_t aFrameTimeNanos) { + auto androidVsync = mAndroidVsync.Lock(); + if (*androidVsync) { + (*androidVsync)->NotifyVsync(aFrameTimeNanos); + } + } + + // Called by the AndroidVsync destructor + void Unlink() { + auto androidVsync = mAndroidVsync.Lock(); + *androidVsync = nullptr; + } + + protected: + ~AndroidVsyncSupport() = default; + + DataMutex mAndroidVsync; +}; + +AndroidVsync::AndroidVsync() : mImpl("AndroidVsync.mImpl") { + AndroidVsyncSupport::Init(); + + auto impl = mImpl.Lock(); + impl->mSupport = new AndroidVsyncSupport(this); + impl->mSupportJava = java::AndroidVsync::New(); + AndroidVsyncSupport::AttachNative(impl->mSupportJava, impl->mSupport); + float fps = impl->mSupportJava->GetRefreshRate(); + impl->mVsyncDuration = TimeDuration::FromMilliseconds(1000.0 / fps); +} + +AndroidVsync::~AndroidVsync() { + auto impl = mImpl.Lock(); + impl->mInputObservers.Clear(); + impl->mRenderObservers.Clear(); + impl->UpdateObservingVsync(); + impl->mSupport->Unlink(); +} + +TimeDuration AndroidVsync::GetVsyncRate() { + auto impl = mImpl.Lock(); + return impl->mVsyncDuration; +} + +void AndroidVsync::RegisterObserver(Observer* aObserver, ObserverType aType) { + auto impl = mImpl.Lock(); + if (aType == AndroidVsync::INPUT) { + impl->mInputObservers.AppendElement(aObserver); + } else { + impl->mRenderObservers.AppendElement(aObserver); + } + impl->UpdateObservingVsync(); +} + +void AndroidVsync::UnregisterObserver(Observer* aObserver, ObserverType aType) { + auto impl = mImpl.Lock(); + if (aType == AndroidVsync::INPUT) { + impl->mInputObservers.RemoveElement(aObserver); + } else { + impl->mRenderObservers.RemoveElement(aObserver); + } + impl->UpdateObservingVsync(); +} + +void AndroidVsync::Impl::UpdateObservingVsync() { + bool shouldObserve = + !mInputObservers.IsEmpty() || !mRenderObservers.IsEmpty(); + if (shouldObserve != mObservingVsync) { + mObservingVsync = mSupportJava->ObserveVsync(shouldObserve); + } +} + +// Always called on the Java UI thread. +void AndroidVsync::NotifyVsync(int64_t aFrameTimeNanos) { + // Convert aFrameTimeNanos to a TimeStamp. The value converts trivially to + // the internal ticks representation of TimeStamp_posix; both use the + // monotonic clock and are in nanoseconds. + TimeStamp timeStamp = TimeStamp::FromSystemTime(aFrameTimeNanos); + + // Do not keep the lock held while calling OnVsync. + nsTArray observers; + { + auto impl = mImpl.Lock(); + observers.AppendElements(impl->mInputObservers); + observers.AppendElements(impl->mRenderObservers); + } + for (Observer* observer : observers) { + observer->OnVsync(timeStamp); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/AndroidVsync.h b/widget/android/AndroidVsync.h new file mode 100644 index 0000000000..617beba017 --- /dev/null +++ b/widget/android/AndroidVsync.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_widget_AndroidVsync_h +#define mozilla_widget_AndroidVsync_h + +#include "mozilla/DataMutex.h" +#include "mozilla/java/AndroidVsyncNatives.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace widget { + +class AndroidVsyncSupport; + +/** + * A thread-safe way to listen to vsync notifications on Android. All methods + * can be called on any thread. + * Observers must keep a strong reference to the AndroidVsync instance until + * they unregister themselves. + */ +class AndroidVsync final : public SupportsThreadSafeWeakPtr { + public: + MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(AndroidVsync) + MOZ_DECLARE_REFCOUNTED_TYPENAME(AndroidVsync) + + static RefPtr GetInstance(); + + ~AndroidVsync(); + + class Observer { + public: + // Will be called on the Java UI thread. + virtual void OnVsync(const TimeStamp& aTimeStamp) = 0; + }; + + // INPUT observers are called before RENDER observers. + enum ObserverType { INPUT, RENDER }; + void RegisterObserver(Observer* aObserver, ObserverType aType); + void UnregisterObserver(Observer* aObserver, ObserverType aType); + + TimeDuration GetVsyncRate(); + + private: + friend class AndroidVsyncSupport; + + AndroidVsync(); + + // Called by Java, via AndroidVsyncSupport + void NotifyVsync(int64_t aFrameTimeNanos); + + struct Impl { + void UpdateObservingVsync(); + + TimeDuration mVsyncDuration; + nsTArray mInputObservers; + nsTArray mRenderObservers; + RefPtr mSupport; + java::AndroidVsync::GlobalRef mSupportJava; + bool mObservingVsync = false; + }; + + DataMutex mImpl; + + static StaticDataMutex> sInstance; +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_AndroidVsync_h diff --git a/widget/android/Base64UtilsSupport.h b/widget/android/Base64UtilsSupport.h new file mode 100644 index 0000000000..8ac4347aaa --- /dev/null +++ b/widget/android/Base64UtilsSupport.h @@ -0,0 +1,52 @@ +/* -*- 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 Base64UtilsSupport_h__ +#define Base64UtilsSupport_h__ + +#include "mozilla/Base64.h" +#include "mozilla/java/Base64UtilsNatives.h" + +namespace mozilla { +namespace widget { + +class Base64UtilsSupport final + : public java::Base64Utils::Natives { + public: + static jni::ByteArray::LocalRef Decode(jni::String::Param data) { + if (!data) { + return nullptr; + } + + FallibleTArray bytes; + if (NS_FAILED(Base64URLDecode( + data->ToCString(), Base64URLDecodePaddingPolicy::Ignore, bytes))) { + return nullptr; + } + + return jni::ByteArray::New((const signed char*)bytes.Elements(), + bytes.Length()); + } + + static jni::String::LocalRef Encode(jni::ByteArray::Param data) { + if (!data) { + return nullptr; + } + + nsTArray bytes = data->GetElements(); + nsCString result; + if (NS_FAILED( + Base64URLEncode(data->Length(), (const uint8_t*)bytes.Elements(), + Base64URLEncodePaddingPolicy::Omit, result))) { + return nullptr; + } + return jni::StringParam(result); + } +}; + +} // namespace widget +} // namespace mozilla + +#endif // Base64UtilsSupport_h__ diff --git a/widget/android/EventDispatcher.cpp b/widget/android/EventDispatcher.cpp new file mode 100644 index 0000000000..061bd64556 --- /dev/null +++ b/widget/android/EventDispatcher.cpp @@ -0,0 +1,1026 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- + * vim: set sw=2 ts=4 expandtab: + * 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 "EventDispatcher.h" + +#include "JavaBuiltins.h" +#include "nsAppShell.h" +#include "nsJSUtils.h" +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject +#include "js/String.h" // JS::StringHasLatin1Chars +#include "js/Warnings.h" // JS::WarnUTF8 +#include "xpcpublic.h" + +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/java/EventCallbackWrappers.h" + +// Disable the C++ 2a warning. See bug #1509926 +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wc++2a-compat" +#endif + +namespace mozilla { +namespace widget { + +namespace detail { + +bool CheckJS(JSContext* aCx, bool aResult) { + if (!aResult) { + JS_ClearPendingException(aCx); + } + return aResult; +} + +nsresult BoxString(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + return NS_OK; + } + + MOZ_ASSERT(aData.isString()); + + JS::RootedString str(aCx, aData.toString()); + + if (JS::StringHasLatin1Chars(str)) { + nsAutoJSString autoStr; + NS_ENSURE_TRUE(CheckJS(aCx, autoStr.init(aCx, str)), NS_ERROR_FAILURE); + + // StringParam can automatically convert a nsString to jstring. + aOut = jni::StringParam(autoStr, aOut.Env()); + return NS_OK; + } + + // Two-byte string + JNIEnv* const env = aOut.Env(); + const char16_t* chars; + { + JS::AutoCheckCannotGC nogc; + size_t len = 0; + chars = JS_GetTwoByteStringCharsAndLength(aCx, nogc, str, &len); + if (chars) { + aOut = jni::String::LocalRef::Adopt( + env, env->NewString(reinterpret_cast(chars), len)); + } + } + if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult BoxObject(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut); + +template +nsresult BoxArrayPrimitive(JSContext* aCx, JS::HandleObject aData, + jni::Object::LocalRef& aOut, size_t aLength, + JS::HandleValue aElement) { + JS::RootedValue element(aCx); + auto data = MakeUnique(aLength); + data[0] = (aElement.get().*ToType)(); + + for (size_t i = 1; i < aLength; i++) { + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE((element.get().*IsType)(), NS_ERROR_INVALID_ARG); + + data[i] = (element.get().*ToType)(); + } + aOut = (*NewArray)(data.get(), aLength); + return NS_OK; +} + +template +nsresult BoxArrayObject(JSContext* aCx, JS::HandleObject aData, + jni::Object::LocalRef& aOut, size_t aLength, + JS::HandleValue aElement, IsType&& aIsType) { + auto out = jni::ObjectArray::New(aLength); + JS::RootedValue element(aCx); + jni::Object::LocalRef jniElement(aOut.Env()); + + nsresult rv = (*Box)(aCx, aElement, jniElement); + NS_ENSURE_SUCCESS(rv, rv); + out->SetElement(0, jniElement); + + for (size_t i = 1; i < aLength; i++) { + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, i, &element)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(element.isNullOrUndefined() || aIsType(element), + NS_ERROR_INVALID_ARG); + + rv = (*Box)(aCx, element, jniElement); + NS_ENSURE_SUCCESS(rv, rv); + out->SetElement(i, jniElement); + } + aOut = out; + return NS_OK; +} + +nsresult BoxArray(JSContext* aCx, JS::HandleObject aData, + jni::Object::LocalRef& aOut) { + uint32_t length = 0; + NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, aData, &length)), + NS_ERROR_FAILURE); + + if (!length) { + // Always represent empty arrays as an empty boolean array. + aOut = java::GeckoBundle::EMPTY_BOOLEAN_ARRAY(); + return NS_OK; + } + + // We only check the first element's type. If the array has mixed types, + // we'll throw an error during actual conversion. + JS::RootedValue element(aCx); + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)), + NS_ERROR_FAILURE); + + if (element.isBoolean()) { + return BoxArrayPrimitive( + aCx, aData, aOut, length, element); + } + + if (element.isInt32()) { + nsresult rv = + BoxArrayPrimitive(aCx, aData, aOut, + length, element); + if (rv != NS_ERROR_INVALID_ARG) { + return rv; + } + // Not int32, but we can still try a double array. + } + + if (element.isNumber()) { + return BoxArrayPrimitive( + aCx, aData, aOut, length, element); + } + + if (element.isNullOrUndefined() || element.isString()) { + const auto isString = [](JS::HandleValue val) -> bool { + return val.isString(); + }; + nsresult rv = BoxArrayObject( + aCx, aData, aOut, length, element, isString); + if (element.isString() || rv != NS_ERROR_INVALID_ARG) { + return rv; + } + // First element was null/undefined, so it may still be an object array. + } + + const auto isObject = [aCx](JS::HandleValue val) -> bool { + if (!val.isObject()) { + return false; + } + bool array = false; + JS::RootedObject obj(aCx, &val.toObject()); + // We don't support array of arrays. + return CheckJS(aCx, JS::IsArrayObject(aCx, obj, &array)) && !array; + }; + + if (element.isNullOrUndefined() || isObject(element)) { + return BoxArrayObject( + aCx, aData, aOut, length, element, isObject); + } + + NS_WARNING("Unknown type"); + return NS_ERROR_INVALID_ARG; +} + +nsresult BoxValue(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut); + +nsresult BoxObject(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + return NS_OK; + } + + MOZ_ASSERT(aData.isObject()); + + JS::Rooted ids(aCx, JS::IdVector(aCx)); + JS::RootedObject obj(aCx, &aData.toObject()); + + bool isArray = false; + if (CheckJS(aCx, JS::IsArrayObject(aCx, obj, &isArray)) && isArray) { + return BoxArray(aCx, obj, aOut); + } + + NS_ENSURE_TRUE(CheckJS(aCx, JS_Enumerate(aCx, obj, &ids)), NS_ERROR_FAILURE); + + const size_t length = ids.length(); + auto keys = jni::ObjectArray::New(length); + auto values = jni::ObjectArray::New(length); + + // Iterate through each property of the JS object. + for (size_t i = 0; i < ids.length(); i++) { + const JS::RootedId id(aCx, ids[i]); + JS::RootedValue idVal(aCx); + JS::RootedValue val(aCx); + jni::Object::LocalRef key(aOut.Env()); + jni::Object::LocalRef value(aOut.Env()); + + NS_ENSURE_TRUE(CheckJS(aCx, JS_IdToValue(aCx, id, &idVal)), + NS_ERROR_FAILURE); + + JS::RootedString idStr(aCx, JS::ToString(aCx, idVal)); + NS_ENSURE_TRUE(CheckJS(aCx, !!idStr), NS_ERROR_FAILURE); + + idVal.setString(idStr); + NS_ENSURE_SUCCESS(BoxString(aCx, idVal, key), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetPropertyById(aCx, obj, id, &val)), + NS_ERROR_FAILURE); + + nsresult rv = BoxValue(aCx, val, value); + if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) { + nsAutoJSString autoStr; + if (CheckJS(aCx, autoStr.init(aCx, idVal.toString()))) { + JS_ReportErrorUTF8(aCx, u8"Invalid event data property %s", + NS_ConvertUTF16toUTF8(autoStr).get()); + } + } + NS_ENSURE_SUCCESS(rv, rv); + + keys->SetElement(i, key); + values->SetElement(i, value); + } + + aOut = java::GeckoBundle::New(keys, values); + return NS_OK; +} + +nsresult BoxValue(JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + } else if (aData.isBoolean()) { + aOut = aData.toBoolean() ? java::sdk::Boolean::TRUE() + : java::sdk::Boolean::FALSE(); + } else if (aData.isInt32()) { + aOut = java::sdk::Integer::ValueOf(aData.toInt32()); + } else if (aData.isNumber()) { + aOut = java::sdk::Double::New(aData.toNumber()); + } else if (aData.isString()) { + return BoxString(aCx, aData, aOut); + } else if (aData.isObject()) { + return BoxObject(aCx, aData, aOut); + } else { + NS_WARNING("Unknown type"); + return NS_ERROR_INVALID_ARG; + } + return NS_OK; +} + +nsresult BoxData(const nsAString& aEvent, JSContext* aCx, JS::HandleValue aData, + jni::Object::LocalRef& aOut, bool aObjectOnly) { + nsresult rv = NS_ERROR_INVALID_ARG; + + if (!aObjectOnly) { + rv = BoxValue(aCx, aData, aOut); + } else if (aData.isObject() || aData.isNullOrUndefined()) { + rv = BoxObject(aCx, aData, aOut); + } + if (rv != NS_ERROR_INVALID_ARG) { + return rv; + } + + NS_ConvertUTF16toUTF8 event(aEvent); + if (JS_IsExceptionPending(aCx)) { + JS::WarnUTF8(aCx, "Error dispatching %s", event.get()); + } else { + JS_ReportErrorUTF8(aCx, "Invalid event data for %s", event.get()); + } + return NS_ERROR_INVALID_ARG; +} + +nsresult UnboxString(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandleValue aOut) { + if (!aData) { + aOut.setNull(); + return NS_OK; + } + + MOZ_ASSERT(aData.IsInstanceOf()); + + JNIEnv* const env = aData.Env(); + const jstring jstr = jstring(aData.Get()); + const size_t len = env->GetStringLength(jstr); + const jchar* const jchars = env->GetStringChars(jstr, nullptr); + + if (NS_WARN_IF(!jchars)) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + + auto releaseStr = MakeScopeExit([env, jstr, jchars] { + env->ReleaseStringChars(jstr, jchars); + env->ExceptionClear(); + }); + + JS::RootedString str( + aCx, + JS_NewUCStringCopyN(aCx, reinterpret_cast(jchars), len)); + NS_ENSURE_TRUE(CheckJS(aCx, !!str), NS_ERROR_FAILURE); + + aOut.setString(str); + return NS_OK; +} + +nsresult UnboxValue(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandleValue aOut); + +nsresult UnboxBundle(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandleValue aOut) { + if (!aData) { + aOut.setNull(); + return NS_OK; + } + + MOZ_ASSERT(aData.IsInstanceOf()); + + JNIEnv* const env = aData.Env(); + const auto& bundle = java::GeckoBundle::Ref::From(aData); + jni::ObjectArray::LocalRef keys = bundle->Keys(); + jni::ObjectArray::LocalRef values = bundle->Values(); + const size_t len = keys->Length(); + JS::RootedObject obj(aCx, JS_NewPlainObject(aCx)); + + NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE); + NS_ENSURE_TRUE(values->Length() == len, NS_ERROR_FAILURE); + + for (size_t i = 0; i < len; i++) { + jni::String::LocalRef key = keys->GetElement(i); + const size_t keyLen = env->GetStringLength(key.Get()); + const jchar* const keyChars = env->GetStringChars(key.Get(), nullptr); + if (NS_WARN_IF(!keyChars)) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + + auto releaseKeyChars = MakeScopeExit([env, &key, keyChars] { + env->ReleaseStringChars(key.Get(), keyChars); + env->ExceptionClear(); + }); + + JS::RootedValue value(aCx); + nsresult rv = UnboxValue(aCx, values->GetElement(i), &value); + if (rv == NS_ERROR_INVALID_ARG && !JS_IsExceptionPending(aCx)) { + JS_ReportErrorUTF8( + aCx, u8"Invalid event data property %s", + NS_ConvertUTF16toUTF8( + nsString(reinterpret_cast(keyChars), keyLen)) + .get()); + } + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE( + CheckJS(aCx, JS_SetUCProperty( + aCx, obj, reinterpret_cast(keyChars), + keyLen, value)), + NS_ERROR_FAILURE); + } + + aOut.setObject(*obj); + return NS_OK; +} + +template +nsresult UnboxArrayPrimitive(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandleValue aOut) { + JNIEnv* const env = aData.Env(); + const ArrayType jarray = ArrayType(aData.Get()); + JNIType* const array = (env->*GetElements)(jarray, nullptr); + JS::RootedVector elements(aCx); + + if (NS_WARN_IF(!array)) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + + auto releaseArray = MakeScopeExit([env, jarray, array] { + (env->*ReleaseElements)(jarray, array, JNI_ABORT); + env->ExceptionClear(); + }); + + const size_t len = env->GetArrayLength(jarray); + NS_ENSURE_TRUE(elements.initCapacity(len), NS_ERROR_FAILURE); + + for (size_t i = 0; i < len; i++) { + NS_ENSURE_TRUE(elements.append((*ToValue)(Type(array[i]))), + NS_ERROR_FAILURE); + } + + JS::RootedObject obj(aCx, + JS::NewArrayObject(aCx, JS::HandleValueArray(elements))); + NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE); + + aOut.setObject(*obj); + return NS_OK; +} + +struct StringArray : jni::ObjectBase { + static const char name[]; +}; + +struct GeckoBundleArray : jni::ObjectBase { + static const char name[]; +}; + +const char StringArray::name[] = "[Ljava/lang/String;"; +const char GeckoBundleArray::name[] = "[Lorg/mozilla/gecko/util/GeckoBundle;"; + +template +nsresult UnboxArrayObject(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandleValue aOut) { + jni::ObjectArray::LocalRef array(aData.Env(), + jni::ObjectArray::Ref::From(aData)); + const size_t len = array->Length(); + JS::RootedObject obj(aCx, JS::NewArrayObject(aCx, len)); + NS_ENSURE_TRUE(CheckJS(aCx, !!obj), NS_ERROR_FAILURE); + + for (size_t i = 0; i < len; i++) { + jni::Object::LocalRef element = array->GetElement(i); + JS::RootedValue value(aCx); + nsresult rv = (*Unbox)(aCx, element, &value); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(CheckJS(aCx, JS_SetElement(aCx, obj, i, value)), + NS_ERROR_FAILURE); + } + + aOut.setObject(*obj); + return NS_OK; +} + +nsresult UnboxValue(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandleValue aOut) { + using jni::Java2Native; + + if (!aData) { + aOut.setNull(); + } else if (aData.IsInstanceOf()) { + aOut.setBoolean(Java2Native(aData, aData.Env())); + } else if (aData.IsInstanceOf()) { + aOut.setInt32(Java2Native(aData, aData.Env())); + } else if (aData.IsInstanceOf() || + aData.IsInstanceOf()) { + aOut.setInt32(java::sdk::Number::Ref::From(aData)->IntValue()); + } else if (aData.IsInstanceOf()) { + aOut.setNumber(Java2Native(aData, aData.Env())); + } else if (aData.IsInstanceOf() || + aData.IsInstanceOf()) { + aOut.setNumber(java::sdk::Number::Ref::From(aData)->DoubleValue()); + } else if (aData.IsInstanceOf()) { + return UnboxString(aCx, aData, aOut); + } else if (aData.IsInstanceOf()) { + return UnboxString(aCx, java::sdk::String::ValueOf(aData), aOut); + } else if (aData.IsInstanceOf()) { + return UnboxBundle(aCx, aData, aOut); + + } else if (aData.IsInstanceOf()) { + return UnboxArrayPrimitive< + bool, jboolean, jbooleanArray, &JNIEnv::GetBooleanArrayElements, + &JNIEnv::ReleaseBooleanArrayElements, &JS::BooleanValue>(aCx, aData, + aOut); + + } else if (aData.IsInstanceOf()) { + return UnboxArrayPrimitive< + int32_t, jint, jintArray, &JNIEnv::GetIntArrayElements, + &JNIEnv::ReleaseIntArrayElements, &JS::Int32Value>(aCx, aData, aOut); + + } else if (aData.IsInstanceOf()) { + return UnboxArrayPrimitive< + double, jdouble, jdoubleArray, &JNIEnv::GetDoubleArrayElements, + &JNIEnv::ReleaseDoubleArrayElements, &JS::DoubleValue>(aCx, aData, + aOut); + + } else if (aData.IsInstanceOf()) { + return UnboxArrayObject<&UnboxString>(aCx, aData, aOut); + } else if (aData.IsInstanceOf()) { + return UnboxArrayObject<&UnboxBundle>(aCx, aData, aOut); + } else { + NS_WARNING("Invalid type"); + return NS_ERROR_INVALID_ARG; + } + return NS_OK; +} + +nsresult UnboxData(jni::String::Param aEvent, JSContext* aCx, + jni::Object::Param aData, JS::MutableHandleValue aOut, + bool aBundleOnly) { + MOZ_ASSERT(NS_IsMainThread()); + + jni::Object::LocalRef jniData(jni::GetGeckoThreadEnv(), aData); + nsresult rv = NS_ERROR_INVALID_ARG; + + if (!aBundleOnly) { + rv = UnboxValue(aCx, jniData, aOut); + } else if (!jniData || jniData.IsInstanceOf()) { + rv = UnboxBundle(aCx, jniData, aOut); + } + if (rv != NS_ERROR_INVALID_ARG || !aEvent) { + return rv; + } + + nsCString event = aEvent->ToCString(); + if (JS_IsExceptionPending(aCx)) { + JS::WarnUTF8(aCx, "Error dispatching %s", event.get()); + } else { + JS_ReportErrorUTF8(aCx, "Invalid event data for %s", event.get()); + } + return NS_ERROR_INVALID_ARG; +} + +class JavaCallbackDelegate final : public nsIAndroidEventCallback { + const java::EventCallback::GlobalRef mCallback; + + virtual ~JavaCallbackDelegate() {} + + NS_IMETHOD Call(JSContext* aCx, JS::HandleValue aData, + void (java::EventCallback::*aCall)(jni::Object::Param) + const) { + MOZ_ASSERT(NS_IsMainThread()); + + jni::Object::LocalRef data(jni::GetGeckoThreadEnv()); + nsresult rv = BoxData(u"callback"_ns, aCx, aData, data, + /* ObjectOnly */ false); + NS_ENSURE_SUCCESS(rv, rv); + + dom::AutoNoJSAPI nojsapi; + + (java::EventCallback(*mCallback).*aCall)(data); + return NS_OK; + } + + public: + explicit JavaCallbackDelegate(java::EventCallback::Param aCallback) + : mCallback(jni::GetGeckoThreadEnv(), aCallback) {} + + NS_DECL_ISUPPORTS + + NS_IMETHOD OnSuccess(JS::HandleValue aData, JSContext* aCx) override { + return Call(aCx, aData, &java::EventCallback::SendSuccess); + } + + NS_IMETHOD OnError(JS::HandleValue aData, JSContext* aCx) override { + return Call(aCx, aData, &java::EventCallback::SendError); + } +}; + +NS_IMPL_ISUPPORTS(JavaCallbackDelegate, nsIAndroidEventCallback) + +class NativeCallbackDelegateSupport final + : public java::EventDispatcher::NativeCallbackDelegate ::Natives< + NativeCallbackDelegateSupport> { + using CallbackDelegate = java::EventDispatcher::NativeCallbackDelegate; + using Base = CallbackDelegate::Natives; + + const nsCOMPtr mCallback; + const nsCOMPtr mFinalizer; + const nsCOMPtr mGlobalObject; + + void Call(jni::Object::Param aData, + nsresult (nsIAndroidEventCallback::*aCall)(JS::HandleValue, + JSContext*)) { + MOZ_ASSERT(NS_IsMainThread()); + + // Use either the attached window's realm or a default realm. + + dom::AutoJSAPI jsapi; + NS_ENSURE_TRUE_VOID(jsapi.Init(mGlobalObject)); + + JS::RootedValue data(jsapi.cx()); + nsresult rv = UnboxData(u"callback"_ns, jsapi.cx(), aData, &data, + /* BundleOnly */ false); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = (mCallback->*aCall)(data, jsapi.cx()); + NS_ENSURE_SUCCESS_VOID(rv); + } + + public: + using Base::AttachNative; + + template + static void OnNativeCall(Functor&& aCall) { + if (NS_IsMainThread()) { + // Invoke callbacks synchronously if we're already on Gecko thread. + return aCall(); + } + NS_DispatchToMainThread( + NS_NewRunnableFunction("OnNativeCall", std::move(aCall))); + } + + static void Finalize(const CallbackDelegate::LocalRef& aInstance) { + DisposeNative(aInstance); + } + + NativeCallbackDelegateSupport(nsIAndroidEventCallback* callback, + nsIAndroidEventFinalizer* finalizer, + nsIGlobalObject* globalObject) + : mCallback(callback), + mFinalizer(finalizer), + mGlobalObject(globalObject) {} + + ~NativeCallbackDelegateSupport() { + if (mFinalizer) { + mFinalizer->OnFinalize(); + } + } + + void SendSuccess(jni::Object::Param aData) { + Call(aData, &nsIAndroidEventCallback::OnSuccess); + } + + void SendError(jni::Object::Param aData) { + Call(aData, &nsIAndroidEventCallback::OnError); + } +}; + +class FinalizingCallbackDelegate final : public nsIAndroidEventCallback { + const nsCOMPtr mCallback; + const nsCOMPtr mFinalizer; + + virtual ~FinalizingCallbackDelegate() { + if (mFinalizer) { + mFinalizer->OnFinalize(); + } + } + + public: + FinalizingCallbackDelegate(nsIAndroidEventCallback* aCallback, + nsIAndroidEventFinalizer* aFinalizer) + : mCallback(aCallback), mFinalizer(aFinalizer) {} + + NS_DECL_ISUPPORTS + NS_FORWARD_NSIANDROIDEVENTCALLBACK(mCallback->); +}; + +NS_IMPL_ISUPPORTS(FinalizingCallbackDelegate, nsIAndroidEventCallback) + +} // namespace detail + +using namespace detail; + +NS_IMPL_ISUPPORTS(EventDispatcher, nsIAndroidEventDispatcher) + +nsIGlobalObject* EventDispatcher::GetGlobalObject() { + if (mDOMWindow) { + return nsGlobalWindowInner::Cast(mDOMWindow->GetCurrentInnerWindow()); + } + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +nsresult EventDispatcher::DispatchOnGecko(ListenersList* list, + const nsAString& aEvent, + JS::HandleValue aData, + nsIAndroidEventCallback* aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + dom::AutoNoJSAPI nojsapi; + + list->lockCount++; + + auto iteratingScope = MakeScopeExit([list] { + list->lockCount--; + if (list->lockCount || !list->unregistering) { + return; + } + + list->unregistering = false; + for (ssize_t i = list->listeners.Count() - 1; i >= 0; i--) { + if (list->listeners[i]) { + continue; + } + list->listeners.RemoveObjectAt(i); + } + }); + + const size_t count = list->listeners.Count(); + for (size_t i = 0; i < count; i++) { + if (!list->listeners[i]) { + // Unregistered. + continue; + } + const nsresult rv = list->listeners[i]->OnEvent(aEvent, aData, aCallback); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + return NS_OK; +} + +java::EventDispatcher::NativeCallbackDelegate::LocalRef +EventDispatcher::WrapCallback(nsIAndroidEventCallback* aCallback, + nsIAndroidEventFinalizer* aFinalizer) { + if (!aCallback) { + return java::EventDispatcher::NativeCallbackDelegate::LocalRef( + jni::GetGeckoThreadEnv()); + } + + java::EventDispatcher::NativeCallbackDelegate::LocalRef callback = + java::EventDispatcher::NativeCallbackDelegate::New(); + NativeCallbackDelegateSupport::AttachNative( + callback, MakeUnique(aCallback, aFinalizer, + GetGlobalObject())); + return callback; +} + +bool EventDispatcher::HasListener(const char16_t* aEvent) { + java::EventDispatcher::LocalRef dispatcher(mDispatcher); + if (!dispatcher) { + return false; + } + + nsDependentString event(aEvent); + return dispatcher->HasListener(event); +} + +NS_IMETHODIMP +EventDispatcher::Dispatch(JS::HandleValue aEvent, JS::HandleValue aData, + nsIAndroidEventCallback* aCallback, + nsIAndroidEventFinalizer* aFinalizer, + JSContext* aCx) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aEvent.isString()) { + NS_WARNING("Invalid event name"); + return NS_ERROR_INVALID_ARG; + } + + nsAutoJSString event; + NS_ENSURE_TRUE(CheckJS(aCx, event.init(aCx, aEvent.toString())), + NS_ERROR_OUT_OF_MEMORY); + + // Don't need to lock here because we're on the main thread, and we can't + // race against Register/UnregisterListener. + + ListenersList* list = mListenersMap.Get(event); + if (list) { + if (!aCallback || !aFinalizer) { + return DispatchOnGecko(list, event, aData, aCallback); + } + nsCOMPtr callback( + new FinalizingCallbackDelegate(aCallback, aFinalizer)); + return DispatchOnGecko(list, event, aData, callback); + } + + java::EventDispatcher::LocalRef dispatcher(mDispatcher); + if (!dispatcher) { + return NS_OK; + } + + jni::Object::LocalRef data(jni::GetGeckoThreadEnv()); + nsresult rv = BoxData(event, aCx, aData, data, /* ObjectOnly */ true); + // Keep XPConnect from overriding the JSContext exception with one + // based on the nsresult. + // + // XXXbz Does xpconnect still do that? Needs to be checked/tested. + NS_ENSURE_SUCCESS(rv, JS_IsExceptionPending(aCx) ? NS_OK : rv); + + dom::AutoNoJSAPI nojsapi; + dispatcher->DispatchToThreads(event, data, + WrapCallback(aCallback, aFinalizer)); + return NS_OK; +} + +nsresult EventDispatcher::Dispatch(const char16_t* aEvent, + java::GeckoBundle::Param aData, + nsIAndroidEventCallback* aCallback) { + nsDependentString event(aEvent); + + ListenersList* list = mListenersMap.Get(event); + if (list) { + dom::AutoJSAPI jsapi; + NS_ENSURE_TRUE(jsapi.Init(GetGlobalObject()), NS_ERROR_FAILURE); + JS::RootedValue data(jsapi.cx()); + nsresult rv = UnboxData(/* Event */ nullptr, jsapi.cx(), aData, &data, + /* BundleOnly */ true); + NS_ENSURE_SUCCESS(rv, rv); + return DispatchOnGecko(list, event, data, aCallback); + } + + java::EventDispatcher::LocalRef dispatcher(mDispatcher); + if (!dispatcher) { + return NS_OK; + } + + dispatcher->DispatchToThreads(event, aData, WrapCallback(aCallback)); + return NS_OK; +} + +nsresult EventDispatcher::IterateEvents(JSContext* aCx, JS::HandleValue aEvents, + IterateEventsCallback aCallback, + nsIAndroidEventListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + auto processEvent = [this, aCx, aCallback, + aListener](JS::HandleValue event) -> nsresult { + nsAutoJSString str; + NS_ENSURE_TRUE(CheckJS(aCx, str.init(aCx, event.toString())), + NS_ERROR_OUT_OF_MEMORY); + return (this->*aCallback)(str, aListener); + }; + + if (aEvents.isString()) { + return processEvent(aEvents); + } + + bool isArray = false; + NS_ENSURE_TRUE(aEvents.isObject(), NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(CheckJS(aCx, JS::IsArrayObject(aCx, aEvents, &isArray)), + NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(isArray, NS_ERROR_INVALID_ARG); + + JS::RootedObject events(aCx, &aEvents.toObject()); + uint32_t length = 0; + NS_ENSURE_TRUE(CheckJS(aCx, JS::GetArrayLength(aCx, events, &length)), + NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(length, NS_ERROR_INVALID_ARG); + + for (size_t i = 0; i < length; i++) { + JS::RootedValue event(aCx); + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, events, i, &event)), + NS_ERROR_INVALID_ARG); + NS_ENSURE_TRUE(event.isString(), NS_ERROR_INVALID_ARG); + + const nsresult rv = processEvent(event); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +nsresult EventDispatcher::RegisterEventLocked( + const nsAString& aEvent, nsIAndroidEventListener* aListener) { + ListenersList* list = mListenersMap.Get(aEvent); + if (!list) { + list = new ListenersList(); + mListenersMap.Put(aEvent, list); + } + +#ifdef DEBUG + for (ssize_t i = 0; i < list->listeners.Count(); i++) { + NS_ENSURE_TRUE(list->listeners[i] != aListener, + NS_ERROR_ALREADY_INITIALIZED); + } +#endif + + list->listeners.AppendObject(aListener); + return NS_OK; +} + +NS_IMETHODIMP +EventDispatcher::RegisterListener(nsIAndroidEventListener* aListener, + JS::HandleValue aEvents, JSContext* aCx) { + return IterateEvents(aCx, aEvents, &EventDispatcher::RegisterEventLocked, + aListener); +} + +nsresult EventDispatcher::UnregisterEventLocked( + const nsAString& aEvent, nsIAndroidEventListener* aListener) { + ListenersList* list = mListenersMap.Get(aEvent); +#ifdef DEBUG + NS_ENSURE_TRUE(list, NS_ERROR_NOT_INITIALIZED); +#else + NS_ENSURE_TRUE(list, NS_OK); +#endif + + DebugOnly found = false; + for (ssize_t i = list->listeners.Count() - 1; i >= 0; i--) { + if (list->listeners[i] != aListener) { + continue; + } + if (list->lockCount) { + // Only mark for removal when list is locked. + list->listeners.ReplaceObjectAt(nullptr, i); + list->unregistering = true; + } else { + list->listeners.RemoveObjectAt(i); + } + found = true; + } +#ifdef DEBUG + return found ? NS_OK : NS_ERROR_NOT_INITIALIZED; +#else + return NS_OK; +#endif +} + +NS_IMETHODIMP +EventDispatcher::UnregisterListener(nsIAndroidEventListener* aListener, + JS::HandleValue aEvents, JSContext* aCx) { + return IterateEvents(aCx, aEvents, &EventDispatcher::UnregisterEventLocked, + aListener); +} + +void EventDispatcher::Attach(java::EventDispatcher::Param aDispatcher, + nsPIDOMWindowOuter* aDOMWindow) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aDispatcher); + + java::EventDispatcher::LocalRef dispatcher(mDispatcher); + + if (dispatcher) { + if (dispatcher == aDispatcher) { + // Only need to update the window. + mDOMWindow = aDOMWindow; + return; + } + dispatcher->SetAttachedToGecko(java::EventDispatcher::REATTACHING); + } + + dispatcher = java::EventDispatcher::LocalRef(aDispatcher); + NativesBase::AttachNative(dispatcher, this); + mDispatcher = dispatcher; + mDOMWindow = aDOMWindow; + + dispatcher->SetAttachedToGecko(java::EventDispatcher::ATTACHED); +} + +void EventDispatcher::Shutdown() { + mDispatcher = nullptr; + mDOMWindow = nullptr; +} + +void EventDispatcher::Detach() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mDispatcher); + + java::EventDispatcher::GlobalRef dispatcher(mDispatcher); + + // SetAttachedToGecko will call disposeNative for us later on the Gecko + // thread to make sure all pending dispatchToGecko calls have completed. + if (dispatcher) { + dispatcher->SetAttachedToGecko(java::EventDispatcher::DETACHED); + } + + Shutdown(); +} + +bool EventDispatcher::HasGeckoListener(jni::String::Param aEvent) { + // Can be called from any thread. + MutexAutoLock lock(mLock); + return !!mListenersMap.Get(aEvent->ToString()); +} + +void EventDispatcher::DispatchToGecko(jni::String::Param aEvent, + jni::Object::Param aData, + jni::Object::Param aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + // Don't need to lock here because we're on the main thread, and we can't + // race against Register/UnregisterListener. + + nsString event = aEvent->ToString(); + ListenersList* list = mListenersMap.Get(event); + if (!list || list->listeners.IsEmpty()) { + return; + } + + // Use the same compartment as the attached window if possible, otherwise + // use a default compartment. + dom::AutoJSAPI jsapi; + NS_ENSURE_TRUE_VOID(jsapi.Init(GetGlobalObject())); + + JS::RootedValue data(jsapi.cx()); + nsresult rv = UnboxData(aEvent, jsapi.cx(), aData, &data, + /* BundleOnly */ true); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr callback; + if (aCallback) { + callback = + new JavaCallbackDelegate(java::EventCallback::Ref::From(aCallback)); + } + + DispatchOnGecko(list, event, data, callback); +} + +/* static */ +nsresult EventDispatcher::UnboxBundle(JSContext* aCx, jni::Object::Param aData, + JS::MutableHandleValue aOut) { + return detail::UnboxBundle(aCx, aData, aOut); +} + +} // namespace widget +} // namespace mozilla + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif diff --git a/widget/android/EventDispatcher.h b/widget/android/EventDispatcher.h new file mode 100644 index 0000000000..a17822e248 --- /dev/null +++ b/widget/android/EventDispatcher.h @@ -0,0 +1,104 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- + * vim: set sw=2 ts=4 expandtab: + * 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 mozilla_widget_EventDispatcher_h +#define mozilla_widget_EventDispatcher_h + +#include "jsapi.h" +#include "nsClassHashtable.h" +#include "nsCOMArray.h" +#include "nsIAndroidBridge.h" +#include "nsHashKeys.h" +#include "nsPIDOMWindow.h" + +#include "mozilla/java/EventDispatcherNatives.h" +#include "mozilla/java/GeckoBundleWrappers.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace widget { + +/** + * EventDispatcher is the Gecko counterpart to the Java EventDispatcher class. + * Together, they make up a unified event bus. Events dispatched from the Java + * side may notify event listeners on the Gecko side, and vice versa. + */ +class EventDispatcher final + : public nsIAndroidEventDispatcher, + public java::EventDispatcher::Natives { + using NativesBase = java::EventDispatcher::Natives; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIANDROIDEVENTDISPATCHER + + EventDispatcher() {} + + void Attach(java::EventDispatcher::Param aDispatcher, + nsPIDOMWindowOuter* aDOMWindow); + void Detach(); + + nsresult Dispatch(const char16_t* aEvent, + java::GeckoBundle::Param aData = nullptr, + nsIAndroidEventCallback* aCallback = nullptr); + + bool HasListener(const char16_t* aEvent); + bool HasGeckoListener(jni::String::Param aEvent); + void DispatchToGecko(jni::String::Param aEvent, jni::Object::Param aData, + jni::Object::Param aCallback); + + static nsresult UnboxBundle(JSContext* aCx, jni::Object::Param aData, + JS::MutableHandleValue aOut); + + nsIGlobalObject* GetGlobalObject(); + + using NativesBase::DisposeNative; + + private: + friend class java::EventDispatcher::Natives; + + java::EventDispatcher::WeakRef mDispatcher; + nsCOMPtr mDOMWindow; + + virtual ~EventDispatcher() {} + + void Shutdown(); + + struct ListenersList { + nsCOMArray listeners{/* count */ 1}; + // 0 if the list can be modified + uint32_t lockCount{0}; + // true if this list has a listener that is being unregistered + bool unregistering{false}; + }; + + using ListenersMap = nsClassHashtable; + + Mutex mLock{"mozilla::widget::EventDispatcher"}; + ListenersMap mListenersMap; + + using IterateEventsCallback = + nsresult (EventDispatcher::*)(const nsAString&, nsIAndroidEventListener*); + + nsresult IterateEvents(JSContext* aCx, JS::HandleValue aEvents, + IterateEventsCallback aCallback, + nsIAndroidEventListener* aListener); + nsresult RegisterEventLocked(const nsAString&, nsIAndroidEventListener*); + nsresult UnregisterEventLocked(const nsAString&, nsIAndroidEventListener*); + + nsresult DispatchOnGecko(ListenersList* list, const nsAString& aEvent, + JS::HandleValue aData, + nsIAndroidEventCallback* aCallback); + + java::EventDispatcher::NativeCallbackDelegate::LocalRef WrapCallback( + nsIAndroidEventCallback* aCallback, + nsIAndroidEventFinalizer* aFinalizer = nullptr); +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_EventDispatcher_h diff --git a/widget/android/GeckoBatteryManager.h b/widget/android/GeckoBatteryManager.h new file mode 100644 index 0000000000..d9a171d0d1 --- /dev/null +++ b/widget/android/GeckoBatteryManager.h @@ -0,0 +1,28 @@ +/* -*- 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 GeckoBatteryManager_h +#define GeckoBatteryManager_h + +#include "nsAppShell.h" + +#include "mozilla/Hal.h" +#include "mozilla/java/GeckoBatteryManagerNatives.h" + +namespace mozilla { + +class GeckoBatteryManager final + : public java::GeckoBatteryManager::Natives { + public: + static void OnBatteryChange(double aLevel, bool aCharging, + double aRemainingTime) { + hal::NotifyBatteryChange( + hal::BatteryInformation(aLevel, aCharging, aRemainingTime)); + } +}; + +} // namespace mozilla + +#endif // GeckoBatteryManager_h diff --git a/widget/android/GeckoEditableSupport.cpp b/widget/android/GeckoEditableSupport.cpp new file mode 100644 index 0000000000..4579fc6198 --- /dev/null +++ b/widget/android/GeckoEditableSupport.cpp @@ -0,0 +1,1630 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*- + * vim: set sw=2 ts=4 expandtab: + * 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 "GeckoEditableSupport.h" + +#include "AndroidRect.h" +#include "KeyEvent.h" +#include "PuppetWidget.h" +#include "nsIContent.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/java/GeckoEditableChildWrappers.h" +#include "mozilla/java/GeckoServiceChildProcessWrappers.h" +#include "mozilla/Logging.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_intl.h" +#include "mozilla/TextComposition.h" +#include "mozilla/TextEventDispatcherListener.h" +#include "mozilla/TextEvents.h" +#include "mozilla/ToString.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/widget/GeckoViewSupport.h" + +#include +#include +#include + +#ifdef NIGHTLY_BUILD +static mozilla::LazyLogModule sGeckoEditableSupportLog("GeckoEditableSupport"); +# define ALOGIME(...) \ + MOZ_LOG(sGeckoEditableSupportLog, LogLevel::Debug, (__VA_ARGS__)) +#else +# define ALOGIME(args...) \ + do { \ + } while (0) +#endif + +static uint32_t ConvertAndroidKeyCodeToDOMKeyCode(int32_t androidKeyCode) { + // Special-case alphanumeric keycodes because they are most common. + if (androidKeyCode >= AKEYCODE_A && androidKeyCode <= AKEYCODE_Z) { + return androidKeyCode - AKEYCODE_A + NS_VK_A; + } + + if (androidKeyCode >= AKEYCODE_0 && androidKeyCode <= AKEYCODE_9) { + return androidKeyCode - AKEYCODE_0 + NS_VK_0; + } + + switch (androidKeyCode) { + // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3) + case AKEYCODE_BACK: + return NS_VK_ESCAPE; + // KEYCODE_CALL (5) ... KEYCODE_POUND (18) + case AKEYCODE_DPAD_UP: + return NS_VK_UP; + case AKEYCODE_DPAD_DOWN: + return NS_VK_DOWN; + case AKEYCODE_DPAD_LEFT: + return NS_VK_LEFT; + case AKEYCODE_DPAD_RIGHT: + return NS_VK_RIGHT; + case AKEYCODE_DPAD_CENTER: + return NS_VK_RETURN; + case AKEYCODE_VOLUME_UP: + return NS_VK_VOLUME_UP; + case AKEYCODE_VOLUME_DOWN: + return NS_VK_VOLUME_DOWN; + // KEYCODE_VOLUME_POWER (26) ... KEYCODE_Z (54) + case AKEYCODE_COMMA: + return NS_VK_COMMA; + case AKEYCODE_PERIOD: + return NS_VK_PERIOD; + case AKEYCODE_ALT_LEFT: + return NS_VK_ALT; + case AKEYCODE_ALT_RIGHT: + return NS_VK_ALT; + case AKEYCODE_SHIFT_LEFT: + return NS_VK_SHIFT; + case AKEYCODE_SHIFT_RIGHT: + return NS_VK_SHIFT; + case AKEYCODE_TAB: + return NS_VK_TAB; + case AKEYCODE_SPACE: + return NS_VK_SPACE; + // KEYCODE_SYM (63) ... KEYCODE_ENVELOPE (65) + case AKEYCODE_ENTER: + return NS_VK_RETURN; + case AKEYCODE_DEL: + return NS_VK_BACK; // Backspace + case AKEYCODE_GRAVE: + return NS_VK_BACK_QUOTE; + // KEYCODE_MINUS (69) + case AKEYCODE_EQUALS: + return NS_VK_EQUALS; + case AKEYCODE_LEFT_BRACKET: + return NS_VK_OPEN_BRACKET; + case AKEYCODE_RIGHT_BRACKET: + return NS_VK_CLOSE_BRACKET; + case AKEYCODE_BACKSLASH: + return NS_VK_BACK_SLASH; + case AKEYCODE_SEMICOLON: + return NS_VK_SEMICOLON; + // KEYCODE_APOSTROPHE (75) + case AKEYCODE_SLASH: + return NS_VK_SLASH; + // KEYCODE_AT (77) ... KEYCODE_MEDIA_FAST_FORWARD (90) + case AKEYCODE_MUTE: + return NS_VK_VOLUME_MUTE; + case AKEYCODE_PAGE_UP: + return NS_VK_PAGE_UP; + case AKEYCODE_PAGE_DOWN: + return NS_VK_PAGE_DOWN; + // KEYCODE_PICTSYMBOLS (94) ... KEYCODE_BUTTON_MODE (110) + case AKEYCODE_ESCAPE: + return NS_VK_ESCAPE; + case AKEYCODE_FORWARD_DEL: + return NS_VK_DELETE; + case AKEYCODE_CTRL_LEFT: + return NS_VK_CONTROL; + case AKEYCODE_CTRL_RIGHT: + return NS_VK_CONTROL; + case AKEYCODE_CAPS_LOCK: + return NS_VK_CAPS_LOCK; + case AKEYCODE_SCROLL_LOCK: + return NS_VK_SCROLL_LOCK; + // KEYCODE_META_LEFT (117) ... KEYCODE_FUNCTION (119) + case AKEYCODE_SYSRQ: + return NS_VK_PRINTSCREEN; + case AKEYCODE_BREAK: + return NS_VK_PAUSE; + case AKEYCODE_MOVE_HOME: + return NS_VK_HOME; + case AKEYCODE_MOVE_END: + return NS_VK_END; + case AKEYCODE_INSERT: + return NS_VK_INSERT; + // KEYCODE_FORWARD (125) ... KEYCODE_MEDIA_RECORD (130) + case AKEYCODE_F1: + return NS_VK_F1; + case AKEYCODE_F2: + return NS_VK_F2; + case AKEYCODE_F3: + return NS_VK_F3; + case AKEYCODE_F4: + return NS_VK_F4; + case AKEYCODE_F5: + return NS_VK_F5; + case AKEYCODE_F6: + return NS_VK_F6; + case AKEYCODE_F7: + return NS_VK_F7; + case AKEYCODE_F8: + return NS_VK_F8; + case AKEYCODE_F9: + return NS_VK_F9; + case AKEYCODE_F10: + return NS_VK_F10; + case AKEYCODE_F11: + return NS_VK_F11; + case AKEYCODE_F12: + return NS_VK_F12; + case AKEYCODE_NUM_LOCK: + return NS_VK_NUM_LOCK; + case AKEYCODE_NUMPAD_0: + return NS_VK_NUMPAD0; + case AKEYCODE_NUMPAD_1: + return NS_VK_NUMPAD1; + case AKEYCODE_NUMPAD_2: + return NS_VK_NUMPAD2; + case AKEYCODE_NUMPAD_3: + return NS_VK_NUMPAD3; + case AKEYCODE_NUMPAD_4: + return NS_VK_NUMPAD4; + case AKEYCODE_NUMPAD_5: + return NS_VK_NUMPAD5; + case AKEYCODE_NUMPAD_6: + return NS_VK_NUMPAD6; + case AKEYCODE_NUMPAD_7: + return NS_VK_NUMPAD7; + case AKEYCODE_NUMPAD_8: + return NS_VK_NUMPAD8; + case AKEYCODE_NUMPAD_9: + return NS_VK_NUMPAD9; + case AKEYCODE_NUMPAD_DIVIDE: + return NS_VK_DIVIDE; + case AKEYCODE_NUMPAD_MULTIPLY: + return NS_VK_MULTIPLY; + case AKEYCODE_NUMPAD_SUBTRACT: + return NS_VK_SUBTRACT; + case AKEYCODE_NUMPAD_ADD: + return NS_VK_ADD; + case AKEYCODE_NUMPAD_DOT: + return NS_VK_DECIMAL; + case AKEYCODE_NUMPAD_COMMA: + return NS_VK_SEPARATOR; + case AKEYCODE_NUMPAD_ENTER: + return NS_VK_RETURN; + case AKEYCODE_NUMPAD_EQUALS: + return NS_VK_EQUALS; + // KEYCODE_NUMPAD_LEFT_PAREN (162) ... KEYCODE_CALCULATOR (210) + + // Needs to confirm the behavior. If the key switches the open state + // of Japanese IME (or switches input character between Hiragana and + // Roman numeric characters), then, it might be better to use + // NS_VK_KANJI which is used for Alt+Zenkaku/Hankaku key on Windows. + case AKEYCODE_ZENKAKU_HANKAKU: + return 0; + case AKEYCODE_EISU: + return NS_VK_EISU; + case AKEYCODE_MUHENKAN: + return NS_VK_NONCONVERT; + case AKEYCODE_HENKAN: + return NS_VK_CONVERT; + case AKEYCODE_KATAKANA_HIRAGANA: + return 0; + case AKEYCODE_YEN: + return NS_VK_BACK_SLASH; // Same as other platforms. + case AKEYCODE_RO: + return NS_VK_BACK_SLASH; // Same as other platforms. + case AKEYCODE_KANA: + return NS_VK_KANA; + case AKEYCODE_ASSIST: + return NS_VK_HELP; + + // the A key is the action key for gamepad devices. + case AKEYCODE_BUTTON_A: + return NS_VK_RETURN; + + default: + ALOG( + "ConvertAndroidKeyCodeToDOMKeyCode: " + "No DOM keycode for Android keycode %d", + int(androidKeyCode)); + return 0; + } +} + +static KeyNameIndex ConvertAndroidKeyCodeToKeyNameIndex( + int32_t keyCode, int32_t action, int32_t domPrintableKeyValue) { + // Special-case alphanumeric keycodes because they are most common. + if (keyCode >= AKEYCODE_A && keyCode <= AKEYCODE_Z) { + return KEY_NAME_INDEX_USE_STRING; + } + + if (keyCode >= AKEYCODE_0 && keyCode <= AKEYCODE_9) { + return KEY_NAME_INDEX_USE_STRING; + } + + switch (keyCode) { +#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \ + case aNativeKey: \ + return aKeyNameIndex; + +#include "NativeKeyToDOMKeyName.h" + +#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX + + // KEYCODE_0 (7) ... KEYCODE_9 (16) + case AKEYCODE_STAR: // '*' key + case AKEYCODE_POUND: // '#' key + + // KEYCODE_A (29) ... KEYCODE_Z (54) + + case AKEYCODE_COMMA: // ',' key + case AKEYCODE_PERIOD: // '.' key + case AKEYCODE_SPACE: + case AKEYCODE_GRAVE: // '`' key + case AKEYCODE_MINUS: // '-' key + case AKEYCODE_EQUALS: // '=' key + case AKEYCODE_LEFT_BRACKET: // '[' key + case AKEYCODE_RIGHT_BRACKET: // ']' key + case AKEYCODE_BACKSLASH: // '\' key + case AKEYCODE_SEMICOLON: // ';' key + case AKEYCODE_APOSTROPHE: // ''' key + case AKEYCODE_SLASH: // '/' key + case AKEYCODE_AT: // '@' key + case AKEYCODE_PLUS: // '+' key + + case AKEYCODE_NUMPAD_0: + case AKEYCODE_NUMPAD_1: + case AKEYCODE_NUMPAD_2: + case AKEYCODE_NUMPAD_3: + case AKEYCODE_NUMPAD_4: + case AKEYCODE_NUMPAD_5: + case AKEYCODE_NUMPAD_6: + case AKEYCODE_NUMPAD_7: + case AKEYCODE_NUMPAD_8: + case AKEYCODE_NUMPAD_9: + case AKEYCODE_NUMPAD_DIVIDE: + case AKEYCODE_NUMPAD_MULTIPLY: + case AKEYCODE_NUMPAD_SUBTRACT: + case AKEYCODE_NUMPAD_ADD: + case AKEYCODE_NUMPAD_DOT: + case AKEYCODE_NUMPAD_COMMA: + case AKEYCODE_NUMPAD_EQUALS: + case AKEYCODE_NUMPAD_LEFT_PAREN: + case AKEYCODE_NUMPAD_RIGHT_PAREN: + + case AKEYCODE_YEN: // yen sign key + case AKEYCODE_RO: // Japanese Ro key + return KEY_NAME_INDEX_USE_STRING; + + case AKEYCODE_NUM: // XXX Not sure + case AKEYCODE_PICTSYMBOLS: + + case AKEYCODE_BUTTON_A: + case AKEYCODE_BUTTON_B: + case AKEYCODE_BUTTON_C: + case AKEYCODE_BUTTON_X: + case AKEYCODE_BUTTON_Y: + case AKEYCODE_BUTTON_Z: + case AKEYCODE_BUTTON_L1: + case AKEYCODE_BUTTON_R1: + case AKEYCODE_BUTTON_L2: + case AKEYCODE_BUTTON_R2: + case AKEYCODE_BUTTON_THUMBL: + case AKEYCODE_BUTTON_THUMBR: + case AKEYCODE_BUTTON_START: + case AKEYCODE_BUTTON_SELECT: + case AKEYCODE_BUTTON_MODE: + + case AKEYCODE_MEDIA_CLOSE: + + case AKEYCODE_BUTTON_1: + case AKEYCODE_BUTTON_2: + case AKEYCODE_BUTTON_3: + case AKEYCODE_BUTTON_4: + case AKEYCODE_BUTTON_5: + case AKEYCODE_BUTTON_6: + case AKEYCODE_BUTTON_7: + case AKEYCODE_BUTTON_8: + case AKEYCODE_BUTTON_9: + case AKEYCODE_BUTTON_10: + case AKEYCODE_BUTTON_11: + case AKEYCODE_BUTTON_12: + case AKEYCODE_BUTTON_13: + case AKEYCODE_BUTTON_14: + case AKEYCODE_BUTTON_15: + case AKEYCODE_BUTTON_16: + return KEY_NAME_INDEX_Unidentified; + + case AKEYCODE_UNKNOWN: + MOZ_ASSERT(action != AKEY_EVENT_ACTION_MULTIPLE, + "Don't call this when action is AKEY_EVENT_ACTION_MULTIPLE!"); + // It's actually an unknown key if the action isn't ACTION_MULTIPLE. + // However, it might cause text input. So, let's check the value. + return domPrintableKeyValue ? KEY_NAME_INDEX_USE_STRING + : KEY_NAME_INDEX_Unidentified; + + default: + ALOG( + "ConvertAndroidKeyCodeToKeyNameIndex: " + "No DOM key name index for Android keycode %d", + keyCode); + return KEY_NAME_INDEX_Unidentified; + } +} + +static CodeNameIndex ConvertAndroidScanCodeToCodeNameIndex(int32_t scanCode) { + switch (scanCode) { +#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \ + case aNativeKey: \ + return aCodeNameIndex; + +#include "NativeKeyToDOMCodeName.h" + +#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX + + default: + return CODE_NAME_INDEX_UNKNOWN; + } +} + +static void InitKeyEvent(WidgetKeyboardEvent& aEvent, int32_t aAction, + int32_t aKeyCode, int32_t aScanCode, + int32_t aMetaState, int64_t aTime, + int32_t aDomPrintableKeyValue, int32_t aRepeatCount, + int32_t aFlags) { + const uint32_t domKeyCode = ConvertAndroidKeyCodeToDOMKeyCode(aKeyCode); + + aEvent.mModifiers = nsWindow::GetModifiers(aMetaState); + aEvent.mKeyCode = domKeyCode; + + aEvent.mIsRepeat = + (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress) && + ((aFlags & java::sdk::KeyEvent::FLAG_LONG_PRESS) || aRepeatCount); + + aEvent.mKeyNameIndex = ConvertAndroidKeyCodeToKeyNameIndex( + aKeyCode, aAction, aDomPrintableKeyValue); + aEvent.mCodeNameIndex = ConvertAndroidScanCodeToCodeNameIndex(aScanCode); + + if (aEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING && + aDomPrintableKeyValue) { + aEvent.mKeyValue = char16_t(aDomPrintableKeyValue); + } + + aEvent.mLocation = + WidgetKeyboardEvent::ComputeLocationFromCodeValue(aEvent.mCodeNameIndex); + aEvent.mTime = aTime; + aEvent.mTimeStamp = nsWindow::GetEventTimeStamp(aTime); +} + +static nscolor ConvertAndroidColor(uint32_t aArgb) { + return NS_RGBA((aArgb & 0x00ff0000) >> 16, (aArgb & 0x0000ff00) >> 8, + (aArgb & 0x000000ff), (aArgb & 0xff000000) >> 24); +} + +static jni::ObjectArray::LocalRef ConvertRectArrayToJavaRectFArray( + const nsTArray& aRects, + const CSSToLayoutDeviceScale aScale) { + const size_t length = aRects.Length(); + auto rects = jni::ObjectArray::New(length); + + for (size_t i = 0; i < length; i++) { + const LayoutDeviceIntRect& tmp = aRects[i]; + + // Character bounds in CSS units. + auto rect = + java::sdk::RectF::New(tmp.x / aScale.scale, tmp.y / aScale.scale, + (tmp.x + tmp.width) / aScale.scale, + (tmp.y + tmp.height) / aScale.scale); + rects->SetElement(i, rect); + } + return rects; +} + +namespace mozilla { +namespace widget { + +NS_IMPL_ISUPPORTS(GeckoEditableSupport, TextEventDispatcherListener, + nsISupportsWeakReference) + +// This is the blocker helper class whether disposing GeckoEditableChild now. +// During JNI call from GeckoEditableChild, we shouldn't dispose it. +class MOZ_RAII AutoGeckoEditableBlocker final { + public: + explicit AutoGeckoEditableBlocker(GeckoEditableSupport* aGeckoEditableSupport) + : mGeckoEditable(aGeckoEditableSupport) { + mGeckoEditable->AddBlocker(); + } + ~AutoGeckoEditableBlocker() { mGeckoEditable->ReleaseBlocker(); } + + private: + RefPtr mGeckoEditable; +}; + +RefPtr GeckoEditableSupport::GetComposition() const { + nsCOMPtr widget = GetWidget(); + return widget ? IMEStateManager::GetTextCompositionFor(widget) : nullptr; +} + +bool GeckoEditableSupport::RemoveComposition(RemoveCompositionFlag aFlag) { + if (!mDispatcher || !mDispatcher->IsComposing()) { + return false; + } + + nsEventStatus status = nsEventStatus_eIgnore; + + NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false); + mDispatcher->CommitComposition( + status, aFlag == CANCEL_IME_COMPOSITION ? &EmptyString() : nullptr); + return true; +} + +void GeckoEditableSupport::OnKeyEvent(int32_t aAction, int32_t aKeyCode, + int32_t aScanCode, int32_t aMetaState, + int32_t aKeyPressMetaState, int64_t aTime, + int32_t aDomPrintableKeyValue, + int32_t aRepeatCount, int32_t aFlags, + bool aIsSynthesizedImeKey, + jni::Object::Param aOriginalEvent) { + AutoGeckoEditableBlocker blocker(this); + + nsCOMPtr widget = GetWidget(); + RefPtr dispatcher = + mDispatcher ? mDispatcher.get() + : widget ? widget->GetTextEventDispatcher() + : nullptr; + NS_ENSURE_TRUE_VOID(dispatcher && widget); + + if (!aIsSynthesizedImeKey) { + if (nsWindow* window = GetNsWindow()) { + window->UserActivity(); + } + } else if (aIsSynthesizedImeKey && mIMEMaskEventsCount > 0) { + // Don't synthesize editor keys when not focused. + return; + } + + EventMessage msg; + if (aAction == java::sdk::KeyEvent::ACTION_DOWN) { + msg = eKeyDown; + } else if (aAction == java::sdk::KeyEvent::ACTION_UP) { + msg = eKeyUp; + } else if (aAction == java::sdk::KeyEvent::ACTION_MULTIPLE) { + // Keys with multiple action are handled in Java, + // and we should never see one here + MOZ_CRASH("Cannot handle key with multiple action"); + } else { + NS_WARNING("Unknown key action event"); + return; + } + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetKeyboardEvent event(true, msg, widget); + InitKeyEvent(event, aAction, aKeyCode, aScanCode, aMetaState, aTime, + aDomPrintableKeyValue, aRepeatCount, aFlags); + + if (nsIWidget::UsePuppetWidgets()) { + // Don't use native key bindings. + event.PreventNativeKeyBindings(); + } + + if (aIsSynthesizedImeKey) { + // Keys synthesized by Java IME code are saved in the mIMEKeyEvents + // array until the next IME_REPLACE_TEXT event, at which point + // these keys are dispatched in sequence. + mIMEKeyEvents.AppendElement(UniquePtr(event.Duplicate())); + } else { + NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(dispatcher)); + dispatcher->DispatchKeyboardEvent(msg, event, status); + if (widget->Destroyed() || status == nsEventStatus_eConsumeNoDefault) { + // Skip default processing. + return; + } + mEditable->OnDefaultKeyEvent(aOriginalEvent); + } + + // Only send keypress after keydown. + if (msg != eKeyDown) { + return; + } + + WidgetKeyboardEvent pressEvent(true, eKeyPress, widget); + InitKeyEvent(pressEvent, aAction, aKeyCode, aScanCode, aKeyPressMetaState, + aTime, aDomPrintableKeyValue, aRepeatCount, aFlags); + + if (nsIWidget::UsePuppetWidgets()) { + // Don't use native key bindings. + pressEvent.PreventNativeKeyBindings(); + } + + if (aIsSynthesizedImeKey) { + mIMEKeyEvents.AppendElement(UniquePtr(pressEvent.Duplicate())); + } else { + dispatcher->MaybeDispatchKeypressEvents(pressEvent, status); + } +} + +/* + * Send dummy key events for pages that are unaware of input events, + * to provide web compatibility for pages that depend on key events. + */ +void GeckoEditableSupport::SendIMEDummyKeyEvent(nsIWidget* aWidget, + EventMessage msg) { + AutoGeckoEditableBlocker blocker(this); + + nsEventStatus status = nsEventStatus_eIgnore; + MOZ_ASSERT(mDispatcher); + + WidgetKeyboardEvent event(true, msg, aWidget); + event.mTime = PR_Now() / 1000; + // TODO: If we can know scan code of the key event which caused replacing + // composition string, we should set mCodeNameIndex here. Then, + // we should rename this method because it becomes not a "dummy" + // keyboard event. + event.mKeyCode = NS_VK_PROCESSKEY; + event.mKeyNameIndex = KEY_NAME_INDEX_Process; + // KeyboardEvents marked as "processed by IME" shouldn't cause any edit + // actions. So, we should set their native key binding to none before + // dispatch to avoid crash on PuppetWidget and avoid running redundant + // path to look for native key bindings. + event.PreventNativeKeyBindings(); + NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(mDispatcher)); + mDispatcher->DispatchKeyboardEvent(msg, event, status); +} + +void GeckoEditableSupport::AddIMETextChange(const IMETextChange& aChange) { + mIMETextChanges.AppendElement(aChange); + + // We may not be in the middle of flushing, + // in which case this flag is meaningless. + mIMETextChangedDuringFlush = true; + + // Now that we added a new range we need to go back and + // update all the ranges before that. + // Ranges that have offsets which follow this new range + // need to be updated to reflect new offsets + const int32_t delta = aChange.mNewEnd - aChange.mOldEnd; + for (int32_t i = mIMETextChanges.Length() - 2; i >= 0; i--) { + IMETextChange& previousChange = mIMETextChanges[i]; + if (previousChange.mStart > aChange.mOldEnd) { + previousChange.mStart += delta; + previousChange.mOldEnd += delta; + previousChange.mNewEnd += delta; + } + } + + // Now go through all ranges to merge any ranges that are connected + // srcIndex is the index of the range to merge from + // dstIndex is the index of the range to potentially merge into + int32_t srcIndex = mIMETextChanges.Length() - 1; + int32_t dstIndex = srcIndex; + + while (--dstIndex >= 0) { + IMETextChange& src = mIMETextChanges[srcIndex]; + IMETextChange& dst = mIMETextChanges[dstIndex]; + // When merging a more recent change into an older + // change, we need to compare recent change's (start, oldEnd) + // range to the older change's (start, newEnd) + if (src.mOldEnd < dst.mStart || dst.mNewEnd < src.mStart) { + // No overlap between ranges + continue; + } + + if (src.mStart == dst.mStart && src.mNewEnd == dst.mNewEnd) { + // Same range. Adjust old end offset. + dst.mOldEnd = std::min(src.mOldEnd, dst.mOldEnd); + } else { + // When merging two ranges, there are generally four posibilities: + // [----(----]----), (----[----]----), + // [----(----)----], (----[----)----] + // where [----] is the first range and (----) is the second range + // As seen above, the start of the merged range is always the lesser + // of the two start offsets. OldEnd and NewEnd then need to be + // adjusted separately depending on the case. In any case, the change + // in text length of the merged range should be the sum of text length + // changes of the two original ranges, i.e., + // newNewEnd - newOldEnd == newEnd1 - oldEnd1 + newEnd2 - oldEnd2 + dst.mStart = std::min(dst.mStart, src.mStart); + if (src.mOldEnd < dst.mNewEnd) { + // New range overlaps or is within previous range; merge + dst.mNewEnd += src.mNewEnd - src.mOldEnd; + } else { // src.mOldEnd >= dst.mNewEnd + // New range overlaps previous range; merge + dst.mOldEnd += src.mOldEnd - dst.mNewEnd; + dst.mNewEnd = src.mNewEnd; + } + } + // src merged to dst; delete src. + mIMETextChanges.RemoveElementAt(srcIndex); + // Any ranges that we skip over between src and dst are not mergeable + // so we can safely continue the merge starting at dst + srcIndex = dstIndex; + } +} + +void GeckoEditableSupport::PostFlushIMEChanges() { + if (!mIMETextChanges.IsEmpty() || mIMESelectionChanged) { + // Already posted + return; + } + + RefPtr self(this); + + nsAppShell::PostEvent([this, self] { + nsCOMPtr widget = GetWidget(); + if (widget && !widget->Destroyed()) { + FlushIMEChanges(); + } + }); +} + +void GeckoEditableSupport::FlushIMEChanges(FlushChangesFlag aFlags) { + // Only send change notifications if we are *not* masking events, + // i.e. if we have a focused editor, + NS_ENSURE_TRUE_VOID(!mIMEMaskEventsCount); + + if (mIMEDelaySynchronizeReply && mIMEActiveCompositionCount > 0) { + // We are still expecting more composition events to be handled. Once + // that happens, FlushIMEChanges will be called again. + return; + } + + nsCOMPtr widget = GetWidget(); + NS_ENSURE_TRUE_VOID(widget); + + struct TextRecord { + nsString text; + int32_t start; + int32_t oldEnd; + int32_t newEnd; + }; + AutoTArray textTransaction; + textTransaction.SetCapacity(mIMETextChanges.Length()); + + nsEventStatus status = nsEventStatus_eIgnore; + mIMETextChangedDuringFlush = false; + + auto shouldAbort = [=](bool aForce) -> bool { + if (!aForce && !mIMETextChangedDuringFlush) { + return false; + } + // A query event could have triggered more text changes to come in, as + // indicated by our flag. If that happens, try flushing IME changes + // again. + if (aFlags == FLUSH_FLAG_NONE) { + FlushIMEChanges(FLUSH_FLAG_RETRY); + } else { + // Don't retry if already retrying, to avoid infinite loops. + __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport", + "Already retrying IME flush"); + } + return true; + }; + + for (const IMETextChange& change : mIMETextChanges) { + if (change.mStart == change.mOldEnd && change.mStart == change.mNewEnd) { + continue; + } + + nsString insertedString; + WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, + widget); + + if (change.mNewEnd != change.mStart) { + queryTextContentEvent.InitForQueryTextContent( + change.mStart, change.mNewEnd - change.mStart); + widget->DispatchEvent(&queryTextContentEvent, status); + + if (shouldAbort(NS_WARN_IF(queryTextContentEvent.Failed()))) { + return; + } + + insertedString = queryTextContentEvent.mReply->DataRef(); + } + + textTransaction.AppendElement(TextRecord{insertedString, change.mStart, + change.mOldEnd, change.mNewEnd}); + } + + int32_t selStart = -1; + int32_t selEnd = -1; + + if (mIMESelectionChanged) { + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + widget); + widget->DispatchEvent(&querySelectedTextEvent, status); + + if (shouldAbort(NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection()))) { + return; + } + + selStart = static_cast( + querySelectedTextEvent.mReply->SelectionStartOffset()); + selEnd = static_cast( + querySelectedTextEvent.mReply->SelectionEndOffset()); + + if (aFlags == FLUSH_FLAG_RECOVER) { + // Sometimes we get out-of-bounds selection during recovery. + // Limit the offsets so we don't crash. + for (const TextRecord& record : textTransaction) { + const int32_t end = record.start + record.text.Length(); + selStart = std::min(selStart, end); + selEnd = std::min(selEnd, end); + } + } + } + + JNIEnv* const env = jni::GetGeckoThreadEnv(); + auto flushOnException = [=]() -> bool { + if (!env->ExceptionCheck()) { + return false; + } + if (aFlags != FLUSH_FLAG_RECOVER) { + // First time seeing an exception; try flushing text. + env->ExceptionClear(); + __android_log_print(ANDROID_LOG_WARN, "GeckoEditableSupport", + "Recovering from IME exception"); + FlushIMEText(FLUSH_FLAG_RECOVER); + } else { + // Give up because we've already tried. +#ifdef RELEASE_OR_BETA + env->ExceptionClear(); +#else + MOZ_CATCH_JNI_EXCEPTION(env); +#endif + } + return true; + }; + + // Commit the text change and selection change transaction. + mIMETextChanges.Clear(); + + for (const TextRecord& record : textTransaction) { + mEditable->OnTextChange(record.text, record.start, record.oldEnd, + record.newEnd); + if (flushOnException()) { + return; + } + } + + while (mIMEDelaySynchronizeReply && mIMEActiveSynchronizeCount) { + mIMEActiveSynchronizeCount--; + mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT); + } + mIMEDelaySynchronizeReply = false; + mIMEActiveSynchronizeCount = 0; + mIMEActiveCompositionCount = 0; + + if (mIMESelectionChanged) { + mIMESelectionChanged = false; + mEditable->OnSelectionChange(selStart, selEnd); + flushOnException(); + } +} + +void GeckoEditableSupport::FlushIMEText(FlushChangesFlag aFlags) { + NS_WARNING_ASSERTION( + !mIMEDelaySynchronizeReply || !mIMEActiveCompositionCount, + "Cannot synchronize Java text with Gecko text"); + + // Notify Java of the newly focused content + mIMETextChanges.Clear(); + mIMESelectionChanged = true; + + // Use 'INT32_MAX / 2' here because subsequent text changes might combine + // with this text change, and overflow might occur if we just use + // INT32_MAX. + IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE); + notification.mTextChangeData.mStartOffset = 0; + notification.mTextChangeData.mRemovedEndOffset = INT32_MAX / 2; + notification.mTextChangeData.mAddedEndOffset = INT32_MAX / 2; + NotifyIME(mDispatcher, notification); + + FlushIMEChanges(aFlags); +} + +void GeckoEditableSupport::UpdateCompositionRects() { + nsCOMPtr widget = GetWidget(); + RefPtr composition(GetComposition()); + NS_ENSURE_TRUE_VOID(mDispatcher && widget); + + if (!composition) { + return; + } + + nsEventStatus status = nsEventStatus_eIgnore; + uint32_t offset = composition->NativeOffsetOfStartComposition(); + WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray, + widget); + queryTextRectsEvent.InitForQueryTextRectArray(offset, + composition->String().Length()); + widget->DispatchEvent(&queryTextRectsEvent, status); + + auto rects = ConvertRectArrayToJavaRectFArray( + queryTextRectsEvent.Succeeded() + ? queryTextRectsEvent.mReply->mRectArray + : CopyableTArray(), + widget->GetDefaultScale()); + + mEditable->UpdateCompositionRects(rects); +} + +void GeckoEditableSupport::OnImeSynchronize() { + AutoGeckoEditableBlocker blocker(this); + + if (mIMEDelaySynchronizeReply) { + // If we are waiting for other events to reply, + // queue this reply as well. + mIMEActiveSynchronizeCount++; + return; + } + if (!mIMEMaskEventsCount) { + FlushIMEChanges(); + } + mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT); +} + +void GeckoEditableSupport::OnImeReplaceText(int32_t aStart, int32_t aEnd, + jni::String::Param aText) { + AutoGeckoEditableBlocker blocker(this); + + if (DoReplaceText(aStart, aEnd, aText)) { + mIMEDelaySynchronizeReply = true; + } + + OnImeSynchronize(); +} + +bool GeckoEditableSupport::DoReplaceText(int32_t aStart, int32_t aEnd, + jni::String::Param aText) { + ALOGIME("IME: IME_REPLACE_TEXT: text=\"%s\"", + NS_ConvertUTF16toUTF8(aText->ToString()).get()); + + // Return true if processed and we should reply to the OnImeReplaceText + // event later. Return false if _not_ processed and we should reply to the + // OnImeReplaceText event now. + + if (mIMEMaskEventsCount > 0) { + // Not focused; still reply to events, but don't do anything else. + return false; + } + + if (nsWindow* window = GetNsWindow()) { + window->UserActivity(); + } + + /* + Replace text in Gecko thread from aStart to aEnd with the string text. + */ + nsCOMPtr widget = GetWidget(); + NS_ENSURE_TRUE(mDispatcher && widget, false); + NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false); + + RefPtr composition(GetComposition()); + MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent()); + + nsString string(aText->ToString()); + const bool composing = !mIMERanges->IsEmpty(); + nsEventStatus status = nsEventStatus_eIgnore; + bool textChanged = composing; + bool performDeletion = true; + + if (!mIMEKeyEvents.IsEmpty() || !composition || !mDispatcher->IsComposing() || + uint32_t(aStart) != composition->NativeOffsetOfStartComposition() || + uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() + + composition->String().Length()) { + // Only start a new composition if we have key events, + // if we don't have an existing composition, or + // the replaced text does not match our composition. + textChanged |= RemoveComposition(); + +#ifdef NIGHTLY_BUILD + { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + widget); + widget->DispatchEvent(&querySelectedTextEvent, status); + if (querySelectedTextEvent.Succeeded()) { + ALOGIME( + "IME: Current selection: %s", + ToString(querySelectedTextEvent.mReply->mOffsetAndData).c_str()); + } + } +#endif + + // If aStart or aEnd is negative value, we use current selection instead + // of updating the selection. + if (aStart >= 0 && aEnd >= 0) { + // Use text selection to set target position(s) for + // insert, or replace, of text. + WidgetSelectionEvent event(true, eSetSelection, widget); + event.mOffset = uint32_t(aStart); + event.mLength = uint32_t(aEnd - aStart); + event.mExpandToClusterBoundary = false; + event.mReason = nsISelectionListener::IME_REASON; + widget->DispatchEvent(&event, status); + } + + if (!mIMEKeyEvents.IsEmpty()) { + bool ignoreNextKeyPress = false; + for (uint32_t i = 0; i < mIMEKeyEvents.Length(); i++) { + const auto event = mIMEKeyEvents[i]->AsKeyboardEvent(); + // widget for duplicated events is initially nullptr. + event->mWidget = widget; + + status = nsEventStatus_eIgnore; + if (event->mMessage != eKeyPress) { + mDispatcher->DispatchKeyboardEvent(event->mMessage, *event, status); + // Skip default processing. It means that next key press shouldn't + // be dispatched. + ignoreNextKeyPress = event->mMessage == eKeyDown && + status == nsEventStatus_eConsumeNoDefault; + } else { + if (ignoreNextKeyPress) { + // Don't dispatch key press since previous key down is consumed. + ignoreNextKeyPress = false; + continue; + } + mDispatcher->MaybeDispatchKeypressEvents(*event, status); + if (status == nsEventStatus_eConsumeNoDefault) { + textChanged = true; + } + } + if (!mDispatcher || widget->Destroyed()) { + // Don't wait for any text change event. + textChanged = false; + break; + } + } + mIMEKeyEvents.Clear(); + return textChanged; + } + + if (aStart != aEnd) { + // Perform a deletion first. + performDeletion = true; + } + } else if (composition->String().Equals(string)) { + /* If the new text is the same as the existing composition text, + * the NS_COMPOSITION_CHANGE event does not generate a text + * change notification. However, the Java side still expects + * one, so we manually generate a notification. */ + IMETextChange dummyChange; + dummyChange.mStart = aStart; + dummyChange.mOldEnd = dummyChange.mNewEnd = aEnd; + PostFlushIMEChanges(); + mIMESelectionChanged = true; + AddIMETextChange(dummyChange); + textChanged = true; + } + + if (StaticPrefs:: + intl_ime_hack_on_any_apps_fire_key_events_for_composition() || + mInputContext.mMayBeIMEUnaware) { + SendIMEDummyKeyEvent(widget, eKeyDown); + if (!mDispatcher || widget->Destroyed()) { + return false; + } + } + + if (performDeletion) { + WidgetContentCommandEvent event(true, eContentCommandDelete, widget); + event.mTime = PR_Now() / 1000; + widget->DispatchEvent(&event, status); + if (!mDispatcher || widget->Destroyed()) { + return false; + } + textChanged = true; + } + + if (composing) { + mDispatcher->SetPendingComposition(string, mIMERanges); + mDispatcher->FlushPendingComposition(status); + mIMEActiveCompositionCount++; + // Ensure IME ranges are empty. + mIMERanges->Clear(); + } else if (!string.IsEmpty() || mDispatcher->IsComposing()) { + mDispatcher->CommitComposition(status, &string); + mIMEActiveCompositionCount++; + textChanged = true; + } + if (!mDispatcher || widget->Destroyed()) { + return false; + } + + if (StaticPrefs:: + intl_ime_hack_on_any_apps_fire_key_events_for_composition() || + mInputContext.mMayBeIMEUnaware) { + SendIMEDummyKeyEvent(widget, eKeyUp); + // Widget may be destroyed after dispatching the above event. + } + return textChanged; +} + +void GeckoEditableSupport::OnImeAddCompositionRange( + int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle, + int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor, + int32_t aRangeBackColor, int32_t aRangeLineColor) { + AutoGeckoEditableBlocker blocker(this); + + if (mIMEMaskEventsCount > 0) { + // Not focused. + return; + } + + TextRange range; + range.mStartOffset = aStart; + range.mEndOffset = aEnd; + range.mRangeType = ToTextRangeType(aRangeType); + range.mRangeStyle.mDefinedStyles = aRangeStyle; + range.mRangeStyle.mLineStyle = TextRangeStyle::ToLineStyle(aRangeLineStyle); + range.mRangeStyle.mIsBoldLine = aRangeBoldLine; + range.mRangeStyle.mForegroundColor = + ConvertAndroidColor(uint32_t(aRangeForeColor)); + range.mRangeStyle.mBackgroundColor = + ConvertAndroidColor(uint32_t(aRangeBackColor)); + range.mRangeStyle.mUnderlineColor = + ConvertAndroidColor(uint32_t(aRangeLineColor)); + mIMERanges->AppendElement(range); +} + +void GeckoEditableSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd, + int32_t aFlags) { + AutoGeckoEditableBlocker blocker(this); + + if (DoUpdateComposition(aStart, aEnd, aFlags)) { + mIMEDelaySynchronizeReply = true; + } +} + +bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart, int32_t aEnd, + int32_t aFlags) { + if (mIMEMaskEventsCount > 0) { + // Not focused. + return false; + } + + nsCOMPtr widget = GetWidget(); + nsEventStatus status = nsEventStatus_eIgnore; + NS_ENSURE_TRUE(mDispatcher && widget, false); + + const bool keepCurrent = + !!(aFlags & java::GeckoEditableChild::FLAG_KEEP_CURRENT_COMPOSITION); + + // A composition with no ranges means we want to set the selection. + if (mIMERanges->IsEmpty()) { + if (keepCurrent && mDispatcher->IsComposing()) { + // Don't set selection if we want to keep current composition. + return false; + } + + MOZ_ASSERT(aStart >= 0 && aEnd >= 0); + const bool compositionChanged = RemoveComposition(); + + WidgetSelectionEvent selEvent(true, eSetSelection, widget); + selEvent.mOffset = std::min(aStart, aEnd); + selEvent.mLength = std::max(aStart, aEnd) - selEvent.mOffset; + selEvent.mReversed = aStart > aEnd; + selEvent.mExpandToClusterBoundary = false; + widget->DispatchEvent(&selEvent, status); + return compositionChanged; + } + + /** + * Update the composition from aStart to aEnd using information from added + * ranges. This is only used for visual indication and does not affect the + * text content. Only the offsets are specified and not the text content + * to eliminate the possibility of this event altering the text content + * unintentionally. + */ + nsString string; + RefPtr composition(GetComposition()); + MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent()); + + if (!composition || !mDispatcher->IsComposing() || + uint32_t(aStart) != composition->NativeOffsetOfStartComposition() || + uint32_t(aEnd) != composition->NativeOffsetOfStartComposition() + + composition->String().Length()) { + if (keepCurrent) { + // Don't start a new composition if we want to keep the current one. + mIMERanges->Clear(); + return false; + } + + // Only start new composition if we don't have an existing one, + // or if the existing composition doesn't match the new one. + RemoveComposition(); + + { + WidgetSelectionEvent event(true, eSetSelection, widget); + event.mOffset = uint32_t(aStart); + event.mLength = uint32_t(aEnd - aStart); + event.mExpandToClusterBoundary = false; + event.mReason = nsISelectionListener::IME_REASON; + widget->DispatchEvent(&event, status); + } + + { + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + widget); + widget->DispatchEvent(&querySelectedTextEvent, status); + MOZ_ASSERT(querySelectedTextEvent.Succeeded()); + if (querySelectedTextEvent.FoundSelection()) { + string = querySelectedTextEvent.mReply->DataRef(); + } + } + } else { + // If the new composition matches the existing composition, + // reuse the old composition. + string = composition->String(); + } + + ALOGIME("IME: IME_SET_TEXT: text=\"%s\", length=%u, range=%zu", + NS_ConvertUTF16toUTF8(string).get(), string.Length(), + mIMERanges->Length()); + + if (NS_WARN_IF(NS_FAILED(BeginInputTransaction(mDispatcher)))) { + mIMERanges->Clear(); + return false; + } + mDispatcher->SetPendingComposition(string, mIMERanges); + mDispatcher->FlushPendingComposition(status); + mIMEActiveCompositionCount++; + mIMERanges->Clear(); + return true; +} + +void GeckoEditableSupport::OnImeRequestCursorUpdates(int aRequestMode) { + AutoGeckoEditableBlocker blocker(this); + + if (aRequestMode == EditableClient::ONE_SHOT) { + UpdateCompositionRects(); + return; + } + + mIMEMonitorCursor = (aRequestMode == EditableClient::START_MONITOR); +} + +class MOZ_STACK_CLASS AutoSelectionRestore final { + public: + explicit AutoSelectionRestore(nsIWidget* widget, + TextEventDispatcher* dispatcher) + : mWidget(widget), mDispatcher(dispatcher) { + MOZ_ASSERT(widget); + if (!dispatcher || !dispatcher->IsComposing()) { + mOffset = UINT32_MAX; + mLength = UINT32_MAX; + return; + } + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + widget); + nsEventStatus status = nsEventStatus_eIgnore; + widget->DispatchEvent(&querySelectedTextEvent, status); + if (querySelectedTextEvent.DidNotFindSelection()) { + mOffset = UINT32_MAX; + mLength = UINT32_MAX; + return; + } + + mOffset = querySelectedTextEvent.mReply->StartOffset(); + mLength = querySelectedTextEvent.mReply->DataLength(); + } + + ~AutoSelectionRestore() { + if (mWidget->Destroyed() || mOffset == UINT32_MAX) { + return; + } + + WidgetSelectionEvent selection(true, eSetSelection, mWidget); + selection.mOffset = mOffset; + selection.mLength = mLength; + selection.mExpandToClusterBoundary = false; + selection.mReason = nsISelectionListener::IME_REASON; + nsEventStatus status = nsEventStatus_eIgnore; + mWidget->DispatchEvent(&selection, status); + } + + private: + nsCOMPtr mWidget; + RefPtr mDispatcher; + uint32_t mOffset; + uint32_t mLength; +}; + +void GeckoEditableSupport::OnImeRequestCommit() { + AutoGeckoEditableBlocker blocker(this); + + if (mIMEMaskEventsCount > 0) { + // Not focused. + return; + } + + nsCOMPtr widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + return; + } + + AutoSelectionRestore restore(widget, mDispatcher); + + RemoveComposition(COMMIT_IME_COMPOSITION); +} + +void GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification) { + RefPtr self(this); + + nsAppShell::PostEvent([this, self, aNotification] { + if (!mIMEMaskEventsCount) { + mEditable->NotifyIME(aNotification); + } + }); +} + +nsresult GeckoEditableSupport::NotifyIME( + TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) { + MOZ_ASSERT(mEditable); + + switch (aNotification.mMessage) { + case REQUEST_TO_COMMIT_COMPOSITION: { + ALOGIME("IME: REQUEST_TO_COMMIT_COMPOSITION"); + + RemoveComposition(COMMIT_IME_COMPOSITION); + AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_COMMIT_COMPOSITION); + break; + } + + case REQUEST_TO_CANCEL_COMPOSITION: { + ALOGIME("IME: REQUEST_TO_CANCEL_COMPOSITION"); + + RemoveComposition(CANCEL_IME_COMPOSITION); + AsyncNotifyIME(EditableListener::NOTIFY_IME_TO_CANCEL_COMPOSITION); + break; + } + + case NOTIFY_IME_OF_FOCUS: { + ALOGIME("IME: NOTIFY_IME_OF_FOCUS"); + + mIMEFocusCount++; + + RefPtr self(this); + RefPtr dispatcher = aTextEventDispatcher; + + // Post an event because we have to flush the text before sending a + // focus event, and we may not be able to flush text during the + // NotifyIME call. + nsAppShell::PostEvent([this, self, dispatcher] { + nsCOMPtr widget = dispatcher->GetWidget(); + + --mIMEMaskEventsCount; + if (!mIMEFocusCount || !widget || widget->Destroyed()) { + return; + } + + mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN); + + if (mIsRemote) { + if (!mEditableAttached) { + // Re-attach on focus; see OnRemovedFrom(). + jni::NativeWeakPtrHolder::AttachExisting( + mEditable, do_AddRef(this)); + mEditableAttached = true; + } + // Because GeckoEditableSupport in content process doesn't + // manage the active input context, we need to retrieve the + // input context from the widget, for use by + // OnImeReplaceText. + mInputContext = widget->GetInputContext(); + } + mDispatcher = dispatcher; + mIMEKeyEvents.Clear(); + + mIMEDelaySynchronizeReply = false; + mIMEActiveCompositionCount = 0; + FlushIMEText(); + + // IME will call requestCursorUpdates after getting context. + // So reset cursor update mode before getting context. + mIMEMonitorCursor = false; + + mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS); + }); + break; + } + + case NOTIFY_IME_OF_BLUR: { + ALOGIME("IME: NOTIFY_IME_OF_BLUR"); + + mIMEFocusCount--; + MOZ_ASSERT(mIMEFocusCount >= 0); + + RefPtr self(this); + nsAppShell::PostEvent([this, self] { + if (!mIMEFocusCount) { + mIMEDelaySynchronizeReply = false; + mIMEActiveSynchronizeCount = 0; + mIMEActiveCompositionCount = 0; + mInputContext.ShutDown(); + mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_BLUR); + OnRemovedFrom(mDispatcher); + } + }); + + // Mask events because we lost focus. Unmask on the next focus. + mIMEMaskEventsCount++; + break; + } + + case NOTIFY_IME_OF_SELECTION_CHANGE: { + ALOGIME("IME: NOTIFY_IME_OF_SELECTION_CHANGE: SelectionChangeData=%s", + ToString(aNotification.mSelectionChangeData).c_str()); + + PostFlushIMEChanges(); + mIMESelectionChanged = true; + break; + } + + case NOTIFY_IME_OF_TEXT_CHANGE: { + ALOGIME("IME: NOTIFY_IME_OF_TEXT_CHANGE: TextChangeData=%s", + ToString(aNotification.mTextChangeData).c_str()); + + /* Make sure Java's selection is up-to-date */ + PostFlushIMEChanges(); + mIMESelectionChanged = true; + AddIMETextChange(IMETextChange(aNotification)); + break; + } + + case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED: { + ALOGIME("IME: NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED"); + + // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED isn't sent per IME call. + // Receiving this event means that Gecko has already handled all IME + // composing events in queue. + // + if (mIsRemote) { + OnNotifyIMEOfCompositionEventHandled(); + } else { + // Also, when receiving this event, mIMEDelaySynchronizeReply won't + // update yet on non-e10s case since IME event is posted before updating + // it. So we have to delay handling of this event. + RefPtr self(this); + nsAppShell::PostEvent( + [this, self] { OnNotifyIMEOfCompositionEventHandled(); }); + } + break; + } + + default: + break; + } + return NS_OK; +} + +void GeckoEditableSupport::OnNotifyIMEOfCompositionEventHandled() { + // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED may be merged with multiple events, + // so reset count. + mIMEActiveCompositionCount = 0; + if (mIMEDelaySynchronizeReply) { + FlushIMEChanges(); + } + + // Hardware keyboard support requires each string rect. + if (mIMEMonitorCursor) { + UpdateCompositionRects(); + } +} + +void GeckoEditableSupport::OnRemovedFrom( + TextEventDispatcher* aTextEventDispatcher) { + mDispatcher = nullptr; + + if (mIsRemote && mEditable->HasEditableParent()) { + // When we're remote, detach every time. + OnWeakNonIntrusiveDetach(NS_NewRunnableFunction( + "GeckoEditableSupport::OnRemovedFrom", + [editable = java::GeckoEditableChild::GlobalRef(mEditable)] { + DisposeNative(editable); + })); + } +} + +void GeckoEditableSupport::WillDispatchKeyboardEvent( + TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress, + void* aData) {} + +NS_IMETHODIMP_(IMENotificationRequests) +GeckoEditableSupport::GetIMENotificationRequests() { + return IMENotificationRequests(IMENotificationRequests::NOTIFY_TEXT_CHANGE); +} + +void GeckoEditableSupport::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) { + // SetInputContext is called from chrome process only + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mEditable); + + ALOGIME( + "IME: SetInputContext: aContext=%s, " + "aAction={mCause=%s, mFocusChange=%s}", + ToString(aContext).c_str(), ToString(aAction.mCause).c_str(), + ToString(aAction.mFocusChange).c_str()); + + mInputContext = aContext; + + if (mInputContext.mIMEState.mEnabled != IMEEnabled::Disabled && + !mInputContext.mHTMLInputInputmode.EqualsLiteral("none") && + aAction.UserMightRequestOpenVKB()) { + // Don't reset keyboard when we should simply open the vkb + mEditable->NotifyIME(EditableListener::NOTIFY_IME_OPEN_VKB); + return; + } + + // Post an event to keep calls in order relative to NotifyIME. + nsAppShell::PostEvent([this, self = RefPtr(this), + context = mInputContext, action = aAction] { + nsCOMPtr widget = GetWidget(); + + if (!widget || widget->Destroyed()) { + return; + } + NotifyIMEContext(context, action); + }); +} + +void GeckoEditableSupport::NotifyIMEContext(const InputContext& aContext, + const InputContextAction& aAction) { + const bool inPrivateBrowsing = aContext.mInPrivateBrowsing; + // isUserAction is used whether opening virtual keyboard. But long press + // shouldn't open it. + const bool isUserAction = + aAction.mCause != InputContextAction::CAUSE_LONGPRESS && + !(aAction.mCause == InputContextAction::CAUSE_UNKNOWN_CHROME && + aContext.mIMEState.mEnabled == IMEEnabled::Enabled) && + (aAction.IsHandlingUserInput() || aContext.mHasHandledUserInput); + const int32_t flags = + (inPrivateBrowsing ? EditableListener::IME_FLAG_PRIVATE_BROWSING : 0) | + (isUserAction ? EditableListener::IME_FLAG_USER_ACTION : 0); + + mEditable->NotifyIMEContext( + static_cast(aContext.mIMEState.mEnabled), + aContext.mHTMLInputType, aContext.mHTMLInputInputmode, + aContext.mActionHint, aContext.mAutocapitalize, flags); +} + +InputContext GeckoEditableSupport::GetInputContext() { + // GetInputContext is called from chrome process only + MOZ_ASSERT(XRE_IsParentProcess()); + InputContext context = mInputContext; + context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; + return context; +} + +void GeckoEditableSupport::TransferParent(jni::Object::Param aEditableParent) { + AutoGeckoEditableBlocker blocker(this); + + mEditable->SetParent(aEditableParent); + + // If we are already focused, make sure the new parent has our token + // and focus information, so it can accept additional calls from us. + if (mIMEFocusCount > 0) { + mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_TOKEN); + if (mIsRemote) { + // GeckoEditableSupport::SetInputContext is called on chrome process + // only, so mInputContext may be still invalid since it is set after + // we have gotton focus. + RefPtr self(this); + nsAppShell::PostEvent([self = std::move(self)] { + NS_WARNING_ASSERTION( + self->mDispatcher, + "Text dispatcher is still null. Why don't we get focus yet?"); + self->NotifyIMEContext(self->mInputContext, InputContextAction()); + }); + } else { + NotifyIMEContext(mInputContext, InputContextAction()); + } + mEditable->NotifyIME(EditableListener::NOTIFY_IME_OF_FOCUS); + // We have focus, so don't destroy editable child. + return; + } + + if (mIsRemote && !mDispatcher) { + // Detach now if we were only attached temporarily. + OnRemovedFrom(/* dispatcher */ nullptr); + } +} + +void GeckoEditableSupport::SetOnBrowserChild(dom::BrowserChild* aBrowserChild) { + MOZ_ASSERT(!XRE_IsParentProcess()); + NS_ENSURE_TRUE_VOID(aBrowserChild); + + const dom::ContentChild* const contentChild = + dom::ContentChild::GetSingleton(); + RefPtr widget(aBrowserChild->WebWidget()); + NS_ENSURE_TRUE_VOID(contentChild && widget); + + // Get the content/tab ID in order to get the correct + // IGeckoEditableParent object, which GeckoEditableChild uses to + // communicate with the parent process. + const uint64_t contentId = contentChild->GetID(); + const uint64_t tabId = aBrowserChild->GetTabId(); + NS_ENSURE_TRUE_VOID(contentId && tabId); + + RefPtr listener = + widget->GetNativeTextEventDispatcherListener(); + + if (!listener || + listener.get() == + static_cast(widget)) { + // We need to set a new listener. + const auto editableChild = java::GeckoEditableChild::New( + /* parent */ nullptr, /* default */ false); + + // Temporarily attach so we can receive the initial editable parent. + auto editableSupport = + jni::NativeWeakPtrHolder::Attach(editableChild, + editableChild); + auto accEditableSupport(editableSupport.Access()); + MOZ_RELEASE_ASSERT(accEditableSupport); + + // Tell PuppetWidget to use our listener for IME operations. + widget->SetNativeTextEventDispatcherListener( + accEditableSupport.AsRefPtr().get()); + + accEditableSupport->mEditableAttached = true; + + // Connect the new child to a parent that corresponds to the BrowserChild. + java::GeckoServiceChildProcess::GetEditableParent(editableChild, contentId, + tabId); + return; + } + + // We need to update the existing listener to use the new parent. + + // We expect the existing TextEventDispatcherListener to be a + // GeckoEditableSupport object, so we perform a sanity check to make + // sure, by comparing their respective vtable pointers. + const RefPtr dummy = + new widget::GeckoEditableSupport(/* child */ nullptr); + NS_ENSURE_TRUE_VOID(*reinterpret_cast(listener.get()) == + *reinterpret_cast(dummy.get())); + + const auto support = + static_cast(listener.get()); + if (!support->mEditableAttached) { + // Temporarily attach so we can receive the initial editable parent. + jni::NativeWeakPtrHolder::AttachExisting( + support->GetJavaEditable(), do_AddRef(support)); + support->mEditableAttached = true; + } + + // Transfer to a new parent that corresponds to the BrowserChild. + java::GeckoServiceChildProcess::GetEditableParent(support->GetJavaEditable(), + contentId, tabId); +} + +nsIWidget* GeckoEditableSupport::GetWidget() const { + MOZ_ASSERT(NS_IsMainThread()); + return mDispatcher ? mDispatcher->GetWidget() : GetNsWindow(); +} + +nsWindow* GeckoEditableSupport::GetNsWindow() const { + MOZ_ASSERT(NS_IsMainThread()); + + auto acc(mWindow.Access()); + if (!acc) { + return nullptr; + } + + return acc->GetNsWindow(); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/GeckoEditableSupport.h b/widget/android/GeckoEditableSupport.h new file mode 100644 index 0000000000..5e8b445803 --- /dev/null +++ b/widget/android/GeckoEditableSupport.h @@ -0,0 +1,287 @@ +/* -*- 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 mozilla_widget_GeckoEditableSupport_h +#define mozilla_widget_GeckoEditableSupport_h + +#include "nsAppShell.h" +#include "nsIWidget.h" +#include "nsTArray.h" + +#include "mozilla/java/GeckoEditableChildNatives.h" +#include "mozilla/java/SessionTextInputWrappers.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEventDispatcherListener.h" +#include "mozilla/UniquePtr.h" + +class nsWindow; + +namespace mozilla { + +class TextComposition; + +namespace dom { +class BrowserChild; +} + +namespace widget { + +class GeckoEditableSupport final + : public TextEventDispatcherListener, + public java::GeckoEditableChild::Natives { + /* + Rules for managing IME between Gecko and Java: + + * Gecko controls the text content, and Java shadows the Gecko text + through text updates + * Gecko and Java maintain separate selections, and synchronize when + needed through selection updates and set-selection events + * Java controls the composition, and Gecko shadows the Java + composition through update composition events + */ + + using EditableBase = java::GeckoEditableChild::Natives; + using EditableClient = java::SessionTextInput::EditableClient; + using EditableListener = java::SessionTextInput::EditableListener; + + struct IMETextChange final { + int32_t mStart, mOldEnd, mNewEnd; + + IMETextChange() : mStart(-1), mOldEnd(-1), mNewEnd(-1) {} + + explicit IMETextChange(const IMENotification& aIMENotification) + : mStart(aIMENotification.mTextChangeData.mStartOffset), + mOldEnd(aIMENotification.mTextChangeData.mRemovedEndOffset), + mNewEnd(aIMENotification.mTextChangeData.mAddedEndOffset) { + MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE, + "IMETextChange initialized with wrong notification"); + MOZ_ASSERT(aIMENotification.mTextChangeData.IsValid(), + "The text change notification isn't initialized"); + MOZ_ASSERT(aIMENotification.mTextChangeData.IsInInt32Range(), + "The text change notification is out of range"); + } + + bool IsEmpty() const { return mStart < 0; } + }; + + enum FlushChangesFlag { + // Not retrying. + FLUSH_FLAG_NONE, + // Retrying due to IME text changes during flush. + FLUSH_FLAG_RETRY, + // Retrying due to IME sync exceptions during flush. + FLUSH_FLAG_RECOVER + }; + + enum RemoveCompositionFlag { CANCEL_IME_COMPOSITION, COMMIT_IME_COMPOSITION }; + + const bool mIsRemote; + jni::NativeWeakPtr mWindow; // Parent only + RefPtr mDispatcher; + java::GeckoEditableChild::GlobalRef mEditable; + bool mEditableAttached; + InputContext mInputContext; + AutoTArray, 4> mIMEKeyEvents; + AutoTArray mIMETextChanges; + RefPtr mIMERanges; + RefPtr mDisposeRunnable; + int32_t mIMEMaskEventsCount; // Mask events when > 0. + int32_t mIMEFocusCount; // We are focused when > 0. + bool mIMEDelaySynchronizeReply; // We reply asynchronously when true. + int32_t mIMEActiveSynchronizeCount; // The number of replies being delayed. + int32_t mIMEActiveCompositionCount; // The number of compositions expected. + uint32_t mDisposeBlockCount; + bool mIMESelectionChanged; + bool mIMETextChangedDuringFlush; + bool mIMEMonitorCursor; + + nsIWidget* GetWidget() const; + nsWindow* GetNsWindow() const; + + nsresult BeginInputTransaction(TextEventDispatcher* aDispatcher) { + if (mIsRemote) { + return aDispatcher->BeginInputTransaction(this); + } else { + return aDispatcher->BeginNativeInputTransaction(); + } + } + + virtual ~GeckoEditableSupport() {} + + RefPtr GetComposition() const; + bool RemoveComposition(RemoveCompositionFlag aFlag = COMMIT_IME_COMPOSITION); + void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg); + void AddIMETextChange(const IMETextChange& aChange); + void PostFlushIMEChanges(); + void FlushIMEChanges(FlushChangesFlag aFlags = FLUSH_FLAG_NONE); + void FlushIMEText(FlushChangesFlag aFlags = FLUSH_FLAG_NONE); + void AsyncNotifyIME(int32_t aNotification); + void UpdateCompositionRects(); + bool DoReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText); + bool DoUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags); + void OnNotifyIMEOfCompositionEventHandled(); + void NotifyIMEContext(const InputContext& aContext, + const InputContextAction& aAction); + + public: + template + static void OnNativeCall(Functor&& aCall) { + struct IMEEvent : nsAppShell::LambdaEvent { + explicit IMEEvent(Functor&& l) + : nsAppShell::LambdaEvent(std::move(l)) {} + + bool IsUIEvent() const override { + using GES = GeckoEditableSupport; + if (this->lambda.IsTarget(&GES::OnKeyEvent) || + this->lambda.IsTarget(&GES::OnImeReplaceText) || + this->lambda.IsTarget(&GES::OnImeUpdateComposition)) { + return true; + } + return false; + } + + void Run() override { + if (NS_WARN_IF(!this->lambda.GetNativeObject())) { + // Ignore stale calls after disposal. + jni::GetGeckoThreadEnv()->ExceptionClear(); + return; + } + nsAppShell::LambdaEvent::Run(); + } + }; + nsAppShell::PostEvent(mozilla::MakeUnique(std::move(aCall))); + } + + static void SetOnBrowserChild(dom::BrowserChild* aBrowserChild); + + // Constructor for main process GeckoEditableChild. + GeckoEditableSupport(jni::NativeWeakPtr aWindow, + java::GeckoEditableChild::Param aEditableChild) + : mIsRemote(!aWindow.IsAttached()), + mWindow(aWindow), + mEditable(aEditableChild), + mEditableAttached(!mIsRemote), + mIMERanges(new TextRangeArray()), + mIMEMaskEventsCount(1), // Mask IME events since there's no focus yet + mIMEFocusCount(0), + mIMEDelaySynchronizeReply(false), + mIMEActiveSynchronizeCount(0), + mDisposeBlockCount(0), + mIMESelectionChanged(false), + mIMETextChangedDuringFlush(false), + mIMEMonitorCursor(false) {} + + // Constructor for content process GeckoEditableChild. + explicit GeckoEditableSupport(java::GeckoEditableChild::Param aEditableChild) + : GeckoEditableSupport(nullptr, aEditableChild) {} + + NS_DECL_ISUPPORTS + + // TextEventDispatcherListener methods + NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher, + const IMENotification& aNotification) override; + + NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override; + + NS_IMETHOD_(void) + OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override; + + NS_IMETHOD_(void) + WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher, + WidgetKeyboardEvent& aKeyboardEvent, + uint32_t aIndexOfKeypress, void* aData) override; + + void SetInputContext(const InputContext& aContext, + const InputContextAction& aAction); + + InputContext GetInputContext(); + + bool HasIMEFocus() const { return mIMEFocusCount != 0; } + + void AddBlocker() { mDisposeBlockCount++; } + + void ReleaseBlocker() { + mDisposeBlockCount--; + + if (!mDisposeBlockCount && mDisposeRunnable) { + if (HasIMEFocus()) { + // If we have IME focus, GeckoEditableChild is already attached again. + // So disposer is unnecessary. + mDisposeRunnable = nullptr; + return; + } + + RefPtr self(this); + RefPtr disposer = std::move(mDisposeRunnable); + + nsAppShell::PostEvent( + [self = std::move(self), disposer = std::move(disposer)] { + self->mEditableAttached = false; + disposer->Run(); + }); + } + } + + bool IsGeckoEditableUsed() const { return mDisposeBlockCount != 0; } + + // GeckoEditableChild methods + using EditableBase::AttachNative; + using EditableBase::DisposeNative; + + const java::GeckoEditableChild::Ref& GetJavaEditable() { return mEditable; } + + void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer) { + RefPtr self(this); + nsAppShell::PostEvent( + [self = std::move(self), disposer = RefPtr(aDisposer)] { + if (self->IsGeckoEditableUsed()) { + // Current calling stack uses GeckoEditableChild, so we should + // not dispose it now. + self->mDisposeRunnable = disposer; + return; + } + self->mEditableAttached = false; + disposer->Run(); + }); + } + + // Transfer to a new parent. + void TransferParent(jni::Object::Param aEditableParent); + + // Handle an Android KeyEvent. + void OnKeyEvent(int32_t aAction, int32_t aKeyCode, int32_t aScanCode, + int32_t aMetaState, int32_t aKeyPressMetaState, int64_t aTime, + int32_t aDomPrintableKeyValue, int32_t aRepeatCount, + int32_t aFlags, bool aIsSynthesizedImeKey, + jni::Object::Param originalEvent); + + // Synchronize Gecko thread with the InputConnection thread. + void OnImeSynchronize(); + + // Replace a range of text with new text. + void OnImeReplaceText(int32_t aStart, int32_t aEnd, jni::String::Param aText); + + // Add styling for a range within the active composition. + void OnImeAddCompositionRange(int32_t aStart, int32_t aEnd, + int32_t aRangeType, int32_t aRangeStyle, + int32_t aRangeLineStyle, bool aRangeBoldLine, + int32_t aRangeForeColor, + int32_t aRangeBackColor, + int32_t aRangeLineColor); + + // Update styling for the active composition using previous-added ranges. + void OnImeUpdateComposition(int32_t aStart, int32_t aEnd, int32_t aFlags); + + // Set cursor mode whether IME requests + void OnImeRequestCursorUpdates(int aRequestMode); + + // Commit current composition to sync Gecko text state with Java. + void OnImeRequestCommit(); +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_GeckoEditableSupport_h diff --git a/widget/android/GeckoNetworkManager.h b/widget/android/GeckoNetworkManager.h new file mode 100644 index 0000000000..7240821b9e --- /dev/null +++ b/widget/android/GeckoNetworkManager.h @@ -0,0 +1,45 @@ +/* -*- 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 GeckoNetworkManager_h +#define GeckoNetworkManager_h + +#include "nsAppShell.h" +#include "nsCOMPtr.h" +#include "nsINetworkLinkService.h" + +#include "mozilla/java/GeckoNetworkManagerNatives.h" +#include "mozilla/Services.h" + +namespace mozilla { + +class GeckoNetworkManager final + : public java::GeckoNetworkManager::Natives { + GeckoNetworkManager() = delete; + + public: + static void OnConnectionChanged(int32_t aType, jni::String::Param aSubType, + bool aIsWifi, int32_t aGateway) { + hal::NotifyNetworkChange(hal::NetworkInformation(aType, aIsWifi, aGateway)); + + nsCOMPtr os = services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC, + aSubType->ToString().get()); + } + } + + static void OnStatusChanged(jni::String::Param aStatus) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC, + aStatus->ToString().get()); + } + } +}; + +} // namespace mozilla + +#endif // GeckoNetworkManager_h diff --git a/widget/android/GeckoProcessManager.cpp b/widget/android/GeckoProcessManager.cpp new file mode 100644 index 0000000000..274e92ed9b --- /dev/null +++ b/widget/android/GeckoProcessManager.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "GeckoProcessManager.h" + +#include "nsINetworkLinkService.h" +#include "nsISupportsImpl.h" +#include "mozilla/Services.h" + +namespace mozilla { + +/* static */ void GeckoProcessManager::Init() { + BaseNatives::Init(); + ConnectionManager::Init(); +} + +NS_IMPL_ISUPPORTS(GeckoProcessManager::ConnectionManager, nsIObserver) + +NS_IMETHODIMP GeckoProcessManager::ConnectionManager::Observe( + nsISupports* aSubject, const char* aTopic, const char16_t* aData) { + java::GeckoProcessManager::ConnectionManager::LocalRef connMgr(mJavaConnMgr); + if (!connMgr) { + return NS_OK; + } + + if (!strcmp("application-foreground", aTopic)) { + connMgr->OnForeground(); + return NS_OK; + } + + if (!strcmp("application-background", aTopic)) { + connMgr->OnBackground(); + return NS_OK; + } + + if (!strcmp(NS_NETWORK_LINK_TOPIC, aTopic)) { + const nsDependentString state(aData); + // state can be up, down, or unknown. For the purposes of socket process + // prioritization, we treat unknown as being up. + const bool isUp = !state.EqualsLiteral(NS_NETWORK_LINK_DATA_DOWN); + connMgr->OnNetworkStateChange(isUp); + return NS_OK; + } + + return NS_OK; +} + +/* static */ void GeckoProcessManager::ConnectionManager::AttachTo( + java::GeckoProcessManager::ConnectionManager::Param aInstance) { + RefPtr native(new ConnectionManager()); + BaseNatives::AttachNative(aInstance, native); + + native->mJavaConnMgr = aInstance; + + nsCOMPtr obsServ(services::GetObserverService()); + obsServ->AddObserver(native, "application-background", false); + obsServ->AddObserver(native, "application-foreground", false); +} + +void GeckoProcessManager::ConnectionManager::ObserveNetworkNotifications() { + nsCOMPtr obsServ(services::GetObserverService()); + obsServ->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); + + const bool isUp = java::GeckoAppShell::IsNetworkLinkUp(); + + java::GeckoProcessManager::ConnectionManager::LocalRef connMgr(mJavaConnMgr); + if (!connMgr) { + return; + } + + connMgr->OnNetworkStateChange(isUp); +} + +} // namespace mozilla diff --git a/widget/android/GeckoProcessManager.h b/widget/android/GeckoProcessManager.h new file mode 100644 index 0000000000..341cb5f5c5 --- /dev/null +++ b/widget/android/GeckoProcessManager.h @@ -0,0 +1,84 @@ +/* -*- 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 GeckoProcessManager_h +#define GeckoProcessManager_h + +#include "WidgetUtils.h" +#include "nsAppShell.h" +#include "nsContentUtils.h" +#include "nsIObserver.h" +#include "nsWindow.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/java/GeckoProcessManagerNatives.h" + +namespace mozilla { + +class GeckoProcessManager final + : public java::GeckoProcessManager::Natives { + using BaseNatives = java::GeckoProcessManager::Natives; + + GeckoProcessManager() = delete; + + static already_AddRefed GetWidget(int64_t aContentId, + int64_t aTabId) { + using namespace dom; + MOZ_ASSERT(NS_IsMainThread()); + + ContentProcessManager* const cpm = ContentProcessManager::GetSingleton(); + NS_ENSURE_TRUE(cpm, nullptr); + + RefPtr tab = cpm->GetTopLevelBrowserParentByProcessAndTabId( + ContentParentId(aContentId), TabId(aTabId)); + NS_ENSURE_TRUE(tab, nullptr); + + nsCOMPtr domWin = tab->GetParentWindowOuter(); + NS_ENSURE_TRUE(domWin, nullptr); + + return widget::WidgetUtils::DOMWindowToWidget(domWin); + } + + class ConnectionManager final + : public java::GeckoProcessManager::ConnectionManager::Natives< + ConnectionManager>, + public nsIObserver { + using BaseNatives = java::GeckoProcessManager::ConnectionManager::Natives< + ConnectionManager>; + + virtual ~ConnectionManager() = default; + + public: + ConnectionManager() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static void AttachTo( + java::GeckoProcessManager::ConnectionManager::Param aInstance); + void ObserveNetworkNotifications(); + + private: + java::GeckoProcessManager::ConnectionManager::WeakRef mJavaConnMgr; + }; + + public: + static void Init(); + + static void GetEditableParent(jni::Object::Param aEditableChild, + int64_t aContentId, int64_t aTabId) { + nsCOMPtr widget = GetWidget(aContentId, aTabId); + if (RefPtr window = nsWindow::From(widget)) { + java::GeckoProcessManager::SetEditableChildParent( + aEditableChild, window->GetEditableParent()); + } + } +}; + +} // namespace mozilla + +#endif // GeckoProcessManager_h diff --git a/widget/android/GeckoScreenOrientation.h b/widget/android/GeckoScreenOrientation.h new file mode 100644 index 0000000000..bbfb3ae3cf --- /dev/null +++ b/widget/android/GeckoScreenOrientation.h @@ -0,0 +1,52 @@ +/* -*- 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 GeckoScreenOrientation_h +#define GeckoScreenOrientation_h + +#include "nsAppShell.h" +#include "nsCOMPtr.h" +#include "nsIScreenManager.h" + +#include "mozilla/Hal.h" +#include "mozilla/java/GeckoScreenOrientationNatives.h" + +namespace mozilla { + +class GeckoScreenOrientation final + : public java::GeckoScreenOrientation::Natives { + GeckoScreenOrientation() = delete; + + public: + static void OnOrientationChange(int16_t aOrientation, int16_t aAngle) { + nsCOMPtr screenMgr = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + nsCOMPtr screen; + + if (!screenMgr || + NS_FAILED(screenMgr->GetPrimaryScreen(getter_AddRefs(screen))) || + !screen) { + return; + } + + nsIntRect rect; + int32_t colorDepth, pixelDepth; + + if (NS_FAILED( + screen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height)) || + NS_FAILED(screen->GetColorDepth(&colorDepth)) || + NS_FAILED(screen->GetPixelDepth(&pixelDepth))) { + return; + } + + hal::NotifyScreenConfigurationChange(hal::ScreenConfiguration( + rect, static_cast(aOrientation), aAngle, + colorDepth, pixelDepth)); + } +}; + +} // namespace mozilla + +#endif // GeckoScreenOrientation_h diff --git a/widget/android/GeckoSystemStateListener.h b/widget/android/GeckoSystemStateListener.h new file mode 100644 index 0000000000..75cb1cd9fe --- /dev/null +++ b/widget/android/GeckoSystemStateListener.h @@ -0,0 +1,31 @@ +/* -*- 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 GeckoSystemStateListener_h +#define GeckoSystemStateListener_h + +#include "mozilla/Assertions.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/java/GeckoSystemStateListenerNatives.h" + +namespace mozilla { + +class GeckoSystemStateListener final + : public java::GeckoSystemStateListener::Natives { + GeckoSystemStateListener() = delete; + + public: + static void OnDeviceChanged() { + MOZ_ASSERT(NS_IsMainThread()); + // TODO(emilio, bug 1673318): This could become more granular and avoid work + // if we get whether these are layout/style-affecting from the caller. + mozilla::LookAndFeel::NotifyChangedAllWindows( + widget::ThemeChangeKind::StyleAndLayout); + } +}; + +} // namespace mozilla + +#endif // GeckoSystemStateListener_h diff --git a/widget/android/GeckoTelemetryDelegate.h b/widget/android/GeckoTelemetryDelegate.h new file mode 100644 index 0000000000..f391630a3d --- /dev/null +++ b/widget/android/GeckoTelemetryDelegate.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 GeckoTelemetryDelegate_h__ +#define GeckoTelemetryDelegate_h__ + +#include "geckoview/streaming/GeckoViewStreamingTelemetry.h" + +#include + +#include "mozilla/java/RuntimeTelemetryNatives.h" +#include "mozilla/jni/Natives.h" + +namespace mozilla { +namespace widget { + +class GeckoTelemetryDelegate final + : public GeckoViewStreamingTelemetry::StreamingTelemetryDelegate, + public mozilla::java::RuntimeTelemetry::Proxy::Natives< + GeckoTelemetryDelegate> { + public: + // Implement Proxy native. + static void RegisterDelegateProxy( + mozilla::java::RuntimeTelemetry::Proxy::Param aProxy) { + MOZ_ASSERT(aProxy); + + GeckoViewStreamingTelemetry::RegisterDelegate( + new GeckoTelemetryDelegate(aProxy)); + } + + explicit GeckoTelemetryDelegate( + mozilla::java::RuntimeTelemetry::Proxy::Param aProxy) + : mProxy(aProxy) {} + + private: + void DispatchHistogram(bool aIsCategorical, const nsCString& aName, + const nsTArray& aSamples) { + if (!mozilla::jni::IsAvailable() || !mProxy || aSamples.Length() < 1) { + return; + } + + // Convert aSamples to an array of int64_t. We know |samples| required + // capacity needs to match |aSamples.Length()|. + nsTArray samples(aSamples.Length()); + for (size_t i = 0, l = aSamples.Length(); i < l; ++i) { + samples.AppendElement(static_cast(aSamples[i])); + } + + // LongArray::New *copies* the elements + mProxy->DispatchHistogram( + aIsCategorical, aName, + mozilla::jni::LongArray::New(samples.Elements(), samples.Length())); + } + + // Implement StreamingTelemetryDelegate. + void ReceiveHistogramSamples(const nsCString& aName, + const nsTArray& aSamples) override { + DispatchHistogram(/* isCategorical */ false, aName, aSamples); + } + + void ReceiveCategoricalHistogramSamples( + const nsCString& aName, const nsTArray& aSamples) override { + DispatchHistogram(/* isCategorical */ true, aName, aSamples); + } + + void ReceiveBoolScalarValue(const nsCString& aName, bool aValue) override { + if (!mozilla::jni::IsAvailable() || !mProxy) { + return; + } + + mProxy->DispatchBooleanScalar(aName, aValue); + } + + void ReceiveStringScalarValue(const nsCString& aName, + const nsCString& aValue) override { + if (!mozilla::jni::IsAvailable() || !mProxy) { + return; + } + + mProxy->DispatchStringScalar(aName, aValue); + } + + void ReceiveUintScalarValue(const nsCString& aName, + uint32_t aValue) override { + if (!mozilla::jni::IsAvailable() || !mProxy) { + return; + } + + mProxy->DispatchLongScalar(aName, static_cast(aValue)); + } + + mozilla::java::RuntimeTelemetry::Proxy::GlobalRef mProxy; +}; + +} // namespace widget +} // namespace mozilla + +#endif // GeckoTelemetryDelegate_h__ diff --git a/widget/android/GeckoVRManager.h b/widget/android/GeckoVRManager.h new file mode 100644 index 0000000000..de28c402cd --- /dev/null +++ b/widget/android/GeckoVRManager.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 mozilla_GeckoVRManager_h_ +#define mozilla_GeckoVRManager_h_ + +#include "mozilla/java/GeckoVRManagerWrappers.h" +#include "mozilla/jni/Utils.h" + +namespace mozilla { + +class GeckoVRManager { + public: + static void* GetExternalContext() { + return reinterpret_cast( + mozilla::java::GeckoVRManager::GetExternalContext()); + } +}; + +} // namespace mozilla + +#endif // mozilla_GeckoVRManager_h_ diff --git a/widget/android/GeckoViewSupport.h b/widget/android/GeckoViewSupport.h new file mode 100644 index 0000000000..e7019eaecd --- /dev/null +++ b/widget/android/GeckoViewSupport.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_widget_GeckoViewSupport_h +#define mozilla_widget_GeckoViewSupport_h + +#include "mozilla/java/GeckoResultWrappers.h" +#include "mozilla/java/GeckoSessionNatives.h" +#include "mozilla/java/WebResponseWrappers.h" +#include "mozilla/widget/WindowEvent.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +class GeckoViewSupport final + : public java::GeckoSession::Window::Natives { + RefPtr mWindow; + + // We hold a WeakRef because we want to allow the + // GeckoSession.Window to be garbage collected. + // Callers need to create a LocalRef from this + // before calling methods. + java::GeckoSession::Window::WeakRef mGeckoViewWindow; + + public: + typedef java::GeckoSession::Window::Natives Base; + + template + static void OnNativeCall(Functor&& aCall) { + NS_DispatchToMainThread(new WindowEvent(std::move(aCall))); + } + + GeckoViewSupport(nsWindow* aWindow, + const java::GeckoSession::Window::LocalRef& aInstance, + nsPIDOMWindowOuter* aDOMWindow) + : mWindow(aWindow), mGeckoViewWindow(aInstance), mDOMWindow(aDOMWindow) {} + + ~GeckoViewSupport(); + + nsWindow* GetNsWindow() const { return mWindow; } + + using Base::DisposeNative; + + /** + * GeckoView methods + */ + private: + nsCOMPtr mDOMWindow; + bool mIsReady{false}; + + public: + // Create and attach a window. + static void Open(const jni::Class::LocalRef& aCls, + java::GeckoSession::Window::Param aWindow, + jni::Object::Param aQueue, jni::Object::Param aCompositor, + jni::Object::Param aDispatcher, + jni::Object::Param aSessionAccessibility, + jni::Object::Param aInitData, jni::String::Param aId, + jni::String::Param aChromeURI, int32_t aScreenId, + bool aPrivateMode); + + // Close and destroy the nsWindow. + void Close(); + + // Transfer this nsWindow to new GeckoSession objects. + void Transfer(const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aQueue, jni::Object::Param aCompositor, + jni::Object::Param aDispatcher, + jni::Object::Param aSessionAccessibility, + jni::Object::Param aInitData); + + void AttachEditable(const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aEditableParent); + + void AttachAccessibility(const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aSessionAccessibility); + + void OnReady(jni::Object::Param aQueue = nullptr); + + auto OnLoadRequest(mozilla::jni::String::Param aUri, int32_t aWindowType, + int32_t aFlags, mozilla::jni::String::Param aTriggeringUri, + bool aHasUserGesture, bool aIsTopLevel) const + -> java::GeckoResult::LocalRef; + + void PassExternalResponse(java::WebResponse::Param aResponse); + + void AttachMediaSessionController( + const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aController, const int64_t aId); + + void DetachMediaSessionController( + const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aController); + + void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer) { + RefPtr disposer(aDisposer); + disposer->Run(); + } +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_GeckoViewSupport_h diff --git a/widget/android/GfxInfo.cpp b/widget/android/GfxInfo.cpp new file mode 100644 index 0000000000..981c2c1741 --- /dev/null +++ b/widget/android/GfxInfo.cpp @@ -0,0 +1,800 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "GfxInfo.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "nsUnicharUtils.h" +#include "prenv.h" +#include "nsExceptionHandler.h" +#include "nsHashKeys.h" +#include "nsVersionComparator.h" +#include "AndroidBridge.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/Preferences.h" + +#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1" + +namespace mozilla { +namespace widget { + +class GfxInfo::GLStrings { + nsCString mVendor; + nsCString mRenderer; + nsCString mVersion; + bool mReady; + + public: + GLStrings() : mReady(false) {} + + const nsCString& Vendor() { + EnsureInitialized(); + return mVendor; + } + + // This spoofed value wins, even if the environment variable + // MOZ_GFX_SPOOF_GL_VENDOR was set. + void SpoofVendor(const nsCString& s) { mVendor = s; } + + const nsCString& Renderer() { + EnsureInitialized(); + return mRenderer; + } + + // This spoofed value wins, even if the environment variable + // MOZ_GFX_SPOOF_GL_RENDERER was set. + void SpoofRenderer(const nsCString& s) { mRenderer = s; } + + const nsCString& Version() { + EnsureInitialized(); + return mVersion; + } + + // This spoofed value wins, even if the environment variable + // MOZ_GFX_SPOOF_GL_VERSION was set. + void SpoofVersion(const nsCString& s) { mVersion = s; } + + void EnsureInitialized() { + if (mReady) { + return; + } + + RefPtr gl; + nsCString discardFailureId; + gl = gl::GLContextProvider::CreateHeadless( + {gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE}, &discardFailureId); + + if (!gl) { + // Setting mReady to true here means that we won't retry. Everything will + // remain blocklisted forever. Ideally, we would like to update that once + // any GLContext is successfully created, like the compositor's GLContext. + mReady = true; + return; + } + + gl->MakeCurrent(); + + if (mVendor.IsEmpty()) { + const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR"); + if (spoofedVendor) { + mVendor.Assign(spoofedVendor); + } else { + mVendor.Assign((const char*)gl->fGetString(LOCAL_GL_VENDOR)); + } + } + + if (mRenderer.IsEmpty()) { + const char* spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER"); + if (spoofedRenderer) { + mRenderer.Assign(spoofedRenderer); + } else { + mRenderer.Assign((const char*)gl->fGetString(LOCAL_GL_RENDERER)); + } + } + + if (mVersion.IsEmpty()) { + const char* spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION"); + if (spoofedVersion) { + mVersion.Assign(spoofedVersion); + } else { + mVersion.Assign((const char*)gl->fGetString(LOCAL_GL_VERSION)); + } + } + + mReady = true; + } +}; + +#ifdef DEBUG +NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug) +#endif + +GfxInfo::GfxInfo() + : mInitialized(false), + mGLStrings(new GLStrings), + mOSVersionInteger(0), + mSDKVersion(0) {} + +GfxInfo::~GfxInfo() {} + +/* GetD2DEnabled and GetDwriteEnabled shouldn't be called until after + * gfxPlatform initialization has occurred because they depend on it for + * information. (See bug 591561) */ +nsresult GfxInfo::GetD2DEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; } + +nsresult GfxInfo::GetDWriteEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; } + +nsresult GfxInfo::GetHasBattery(bool* aHasBattery) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GfxInfo::GetDesktopEnvironment(nsAString& aDesktopEnvironment) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void GfxInfo::EnsureInitialized() { + if (mInitialized) return; + + if (!mozilla::AndroidBridge::Bridge()) { + gfxWarning() << "AndroidBridge missing during initialization"; + return; + } + + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", + "MODEL", mModel)) { + mAdapterDescription.AppendPrintf("Model: %s", + NS_LossyConvertUTF16toASCII(mModel).get()); + } + + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "PRODUCT", mProduct)) { + mAdapterDescription.AppendPrintf( + ", Product: %s", NS_LossyConvertUTF16toASCII(mProduct).get()); + } + + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "MANUFACTURER", mManufacturer)) { + mAdapterDescription.AppendPrintf( + ", Manufacturer: %s", NS_LossyConvertUTF16toASCII(mManufacturer).get()); + } + + if (mozilla::AndroidBridge::Bridge()->GetStaticIntField( + "android/os/Build$VERSION", "SDK_INT", &mSDKVersion)) { + // the HARDWARE field isn't available on Android SDK < 8, but we require 9+ + // anyway. + MOZ_ASSERT(mSDKVersion >= 8); + if (mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build", "HARDWARE", mHardware)) { + mAdapterDescription.AppendPrintf( + ", Hardware: %s", NS_LossyConvertUTF16toASCII(mHardware).get()); + } + } else { + mSDKVersion = 0; + } + + nsString release; + mozilla::AndroidBridge::Bridge()->GetStaticStringField( + "android/os/Build$VERSION", "RELEASE", release); + mOSVersion = NS_LossyConvertUTF16toASCII(release); + + mOSVersionInteger = 0; + char a[5], b[5], c[5], d[5]; + SplitDriverVersion(mOSVersion.get(), a, b, c, d); + uint8_t na = atoi(a); + uint8_t nb = atoi(b); + uint8_t nc = atoi(c); + uint8_t nd = atoi(d); + + mOSVersionInteger = (uint32_t(na) << 24) | (uint32_t(nb) << 16) | + (uint32_t(nc) << 8) | uint32_t(nd); + + mAdapterDescription.AppendPrintf( + ", OpenGL: %s -- %s -- %s", mGLStrings->Vendor().get(), + mGLStrings->Renderer().get(), mGLStrings->Version().get()); + + AddCrashReportAnnotations(); + + mScreenInfo.mScreenDimensions = + mozilla::AndroidBridge::Bridge()->getScreenSize(); + + mInitialized = true; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) { + EnsureInitialized(); + aAdapterDescription = NS_ConvertASCIItoUTF16(mAdapterDescription); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) { + EnsureInitialized(); + *aAdapterRAM = 0; + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) { + EnsureInitialized(); + aAdapterDriver.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) { + EnsureInitialized(); + aAdapterDriverVendor.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) { + EnsureInitialized(); + aAdapterDriverVersion = NS_ConvertASCIItoUTF16(mGLStrings->Version()); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) { + EnsureInitialized(); + aAdapterDriverDate.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) { + EnsureInitialized(); + aAdapterVendorID = NS_ConvertASCIItoUTF16(mGLStrings->Vendor()); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) { + EnsureInitialized(); + aAdapterDeviceID = NS_ConvertASCIItoUTF16(mGLStrings->Renderer()); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) { + EnsureInitialized(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +GfxInfo::GetDisplayInfo(nsTArray& aDisplayInfo) { + EnsureInitialized(); + nsString displayInfo; + displayInfo.AppendPrintf("%dx%d", + (int32_t)mScreenInfo.mScreenDimensions.width, + (int32_t)mScreenInfo.mScreenDimensions.height); + aDisplayInfo.AppendElement(displayInfo); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDisplayWidth(nsTArray& aDisplayWidth) { + aDisplayWidth.AppendElement((uint32_t)mScreenInfo.mScreenDimensions.width); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDisplayHeight(nsTArray& aDisplayHeight) { + aDisplayHeight.AppendElement((uint32_t)mScreenInfo.mScreenDimensions.height); + return NS_OK; +} + +NS_IMETHODIMP +GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void GfxInfo::AddCrashReportAnnotations() { + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID, + mGLStrings->Vendor()); + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID, + mGLStrings->Renderer()); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::AdapterDriverVersion, mGLStrings->Version()); +} + +const nsTArray& GfxInfo::GetGfxDriverInfo() { + if (sDriverInfo->IsEmpty()) { + APPEND_TO_DRIVER_BLOCKLIST2( + OperatingSystem::Android, DeviceFamily::All, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_STATUS_OK, + DRIVER_COMPARISON_IGNORED, GfxDriverInfo::allDriverVersions, + "FEATURE_OK_FORCE_OPENGL"); + } + + return *sDriverInfo; +} + +nsresult GfxInfo::GetFeatureStatusImpl( + int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion, + const nsTArray& aDriverInfo, nsACString& aFailureId, + OperatingSystem* aOS /* = nullptr */) { + NS_ENSURE_ARG_POINTER(aStatus); + aSuggestedDriverVersion.SetIsVoid(true); + *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + OperatingSystem os = mOS; + if (aOS) *aOS = os; + + if (sShutdownOccurred) { + return NS_OK; + } + + // OpenGL layers are never blocklisted on Android. + // This early return is so we avoid potentially slow + // GLStrings initialization on startup when we initialize GL layers. + if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS) { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + return NS_OK; + } + + EnsureInitialized(); + + if (mGLStrings->Vendor().IsEmpty() || mGLStrings->Renderer().IsEmpty()) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + return NS_OK; + } + + // Don't evaluate special cases when evaluating the downloaded blocklist. + if (aDriverInfo.IsEmpty()) { + if (aFeature == nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION) { + if (mSDKVersion < 11) { + // It's slower than software due to not having a compositing fast path + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION; + aFailureId = "FEATURE_FAILURE_CANVAS_2D_SDK"; + } else if (mGLStrings->Renderer().Find("Vivante GC1000") != -1) { + // Blocklist Vivante GC1000. See bug 1248183. + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILED_CANVAS_2D_HW"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + return NS_OK; + } + + if (aFeature == FEATURE_WEBGL_OPENGL) { + if (mGLStrings->Renderer().Find("Adreno 200") != -1 || + mGLStrings->Renderer().Find("Adreno 205") != -1) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_ADRENO_20x"; + return NS_OK; + } + + if (mSDKVersion <= 17) { + if (mGLStrings->Renderer().Find("Adreno (TM) 3") != -1) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_ADRENO_3xx"; + } + return NS_OK; + } + + if (mHardware.EqualsLiteral("ville")) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_VILLE"; + return NS_OK; + } + } + + if (aFeature == FEATURE_STAGEFRIGHT) { + NS_LossyConvertUTF16toASCII cManufacturer(mManufacturer); + NS_LossyConvertUTF16toASCII cModel(mModel); + NS_LossyConvertUTF16toASCII cHardware(mHardware); + + if (cHardware.EqualsLiteral("antares") || + cHardware.EqualsLiteral("harmony") || + cHardware.EqualsLiteral("picasso") || + cHardware.EqualsLiteral("picasso_e") || + cHardware.EqualsLiteral("ventana") || + cHardware.EqualsLiteral("rk30board")) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_STAGE_HW"; + return NS_OK; + } + + if (CompareVersions(mOSVersion.get(), "4.1.0") < 0) { + // Whitelist: + // All Samsung ICS devices, except for: + // Samsung SGH-I717 (Bug 845729) + // Samsung SGH-I727 (Bug 845729) + // Samsung SGH-I757 (Bug 845729) + // All Galaxy nexus ICS devices + // Sony Xperia Ion (LT28) ICS devices + bool isWhitelisted = + cModel.Equals("LT28h", nsCaseInsensitiveCStringComparator) || + cManufacturer.Equals("samsung", + nsCaseInsensitiveCStringComparator) || + cModel.Equals( + "galaxy nexus", + nsCaseInsensitiveCStringComparator); // some Galaxy Nexus + // have + // manufacturer=amazon + + if (cModel.Find("SGH-I717", true) != -1 || + cModel.Find("SGH-I727", true) != -1 || + cModel.Find("SGH-I757", true) != -1) { + isWhitelisted = false; + } + + if (!isWhitelisted) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_4_1_HW"; + return NS_OK; + } + } else if (CompareVersions(mOSVersion.get(), "4.2.0") < 0) { + // Whitelist: + // All JB phones except for those in blocklist below + // Blocklist: + // Samsung devices from bug 812881 and 853522. + // Motorola XT890 from bug 882342. + bool isBlocklisted = cModel.Find("GT-P3100", true) != -1 || + cModel.Find("GT-P3110", true) != -1 || + cModel.Find("GT-P3113", true) != -1 || + cModel.Find("GT-P5100", true) != -1 || + cModel.Find("GT-P5110", true) != -1 || + cModel.Find("GT-P5113", true) != -1 || + cModel.Find("XT890", true) != -1; + + if (isBlocklisted) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_4_2_HW"; + return NS_OK; + } + } else if (CompareVersions(mOSVersion.get(), "4.3.0") < 0) { + // Blocklist all Sony devices + if (cManufacturer.Find("Sony", true) != -1) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_4_3_SONY"; + return NS_OK; + } + } + } + + if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_ENCODE) { + if (mozilla::AndroidBridge::Bridge()) { + *aStatus = WebRtcHwVp8EncodeSupported(); + aFailureId = "FEATURE_FAILURE_WEBRTC_ENCODE"; + return NS_OK; + } + } + if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_DECODE) { + if (mozilla::AndroidBridge::Bridge()) { + *aStatus = WebRtcHwVp8DecodeSupported(); + aFailureId = "FEATURE_FAILURE_WEBRTC_DECODE"; + return NS_OK; + } + } + if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_H264) { + if (mozilla::AndroidBridge::Bridge()) { + *aStatus = WebRtcHwH264Supported(); + aFailureId = "FEATURE_FAILURE_WEBRTC_H264"; + return NS_OK; + } + } + if (aFeature == FEATURE_VP8_HW_DECODE || + aFeature == FEATURE_VP9_HW_DECODE) { + NS_LossyConvertUTF16toASCII model(mModel); + bool isBlocked = + // GIFV crash, see bug 1232911. + model.Equals("GT-N8013", nsCaseInsensitiveCStringComparator); + + if (isBlocked) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_VPx"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + return NS_OK; + } + + if (aFeature == FEATURE_WEBRENDER) { + bool isUnblocked = false; + const nsCString& gpu = mGLStrings->Renderer(); + NS_LossyConvertUTF16toASCII model(mModel); + +#ifdef NIGHTLY_BUILD + // On Nightly enable Webrender on all Adreno 5xx GPUs + isUnblocked |= gpu.Find("Adreno (TM) 5", /*ignoreCase*/ true) >= 0; + + // On Nightly enable Webrender on all Mali-Txxx GPUs + isUnblocked |= gpu.Find("Mali-T", /*ignoreCase*/ true) >= 0; +#endif + // Enable Webrender on all Adreno 5xx GPUs, excluding 505 and 506. + isUnblocked |= + gpu.Find("Adreno (TM) 5", /*ignoreCase*/ true) >= 0 && + gpu.Find("Adreno (TM) 505", /*ignoreCase*/ true) == kNotFound && + gpu.Find("Adreno (TM) 506", /*ignoreCase*/ true) == kNotFound; + + // Enable Webrender on all Adreno 6xx devices + isUnblocked |= gpu.Find("Adreno (TM) 6", /*ignoreCase*/ true) >= 0; + + // Enable Webrender on all Mali-Gxx GPUs... + isUnblocked |= gpu.Find("Mali-G", /*ignoreCase*/ true) >= 0 && + // Excluding G72 and G76 on Android 11, due to + // bugs 1688705 and 1688017. + !(mSDKVersion == 30 && + (gpu.Find("Mali-G72", /*ignoreCase*/ true) >= 0 || + gpu.Find("Mali-G76", /*ignoreCase*/ true) >= 0)) && + // And excluding G31 due to bug 1689947. + gpu.Find("Mali-G31", /*ignoreCase*/ true) == kNotFound; + + if (!isUnblocked) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_WEBRENDER_BLOCKED_DEVICE"; + } else { + *aStatus = nsIGfxInfo::FEATURE_ALLOW_QUALIFIED; + } + return NS_OK; + } + + if (aFeature == FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS) { + // Emulator with SwiftShader is buggy when attempting to clear picture + // cache textures with a scissor rect set. + const bool isEmulatorSwiftShader = + mGLStrings->Renderer().Find( + "Android Emulator OpenGL ES Translator (Google SwiftShader)") >= + 0; + if (isEmulatorSwiftShader) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_BUG_1603515"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + return NS_OK; + } + } + + return GfxInfoBase::GetFeatureStatusImpl( + aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os); +} + +static nsCString FeatureCacheOsVerPrefName(int32_t aFeature) { + nsCString osPrefName; + osPrefName.AppendASCII("gfxinfo.cache."); + osPrefName.AppendInt(aFeature); + osPrefName.AppendASCII(".osver"); + return osPrefName; +} + +static nsCString FeatureCacheValuePrefName(int32_t aFeature) { + nsCString osPrefName; + osPrefName.AppendASCII("gfxinfo.cache."); + osPrefName.AppendInt(aFeature); + osPrefName.AppendASCII(".value"); + return osPrefName; +} + +static bool GetCachedFeatureVal(int32_t aFeature, uint32_t aExpectedOsVer, + int32_t& aOutStatus) { + uint32_t osVer = 0; + nsresult rv = + Preferences::GetUint(FeatureCacheOsVerPrefName(aFeature).get(), &osVer); + if (NS_FAILED(rv) || osVer != aExpectedOsVer) { + return false; + } + int32_t status = 0; + rv = Preferences::GetInt(FeatureCacheValuePrefName(aFeature).get(), &status); + if (NS_FAILED(rv)) { + return false; + } + aOutStatus = status; + return true; +} + +static void SetCachedFeatureVal(int32_t aFeature, uint32_t aOsVer, + int32_t aStatus) { + // Ignore failures; not much we can do anyway. + Preferences::SetUint(FeatureCacheOsVerPrefName(aFeature).get(), aOsVer); + Preferences::SetInt(FeatureCacheValuePrefName(aFeature).get(), aStatus); +} + +int32_t GfxInfo::WebRtcHwVp8EncodeSupported() { + MOZ_ASSERT(mozilla::AndroidBridge::Bridge()); + + // The Android side of this calculation is very slow, so we cache the result + // in preferences, invalidating if the OS version changes. + + int32_t status = 0; + if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, + mOSVersionInteger, status)) { + return status; + } + + status = mozilla::AndroidBridge::Bridge()->HasHWVP8Encoder() + ? nsIGfxInfo::FEATURE_STATUS_OK + : nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + + SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, mOSVersionInteger, + status); + + return status; +} + +int32_t GfxInfo::WebRtcHwVp8DecodeSupported() { + MOZ_ASSERT(mozilla::AndroidBridge::Bridge()); + + // The Android side of this caclulation is very slow, so we cache the result + // in preferences, invalidating if the OS version changes. + + int32_t status = 0; + if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_DECODE, + mOSVersionInteger, status)) { + return status; + } + + status = mozilla::AndroidBridge::Bridge()->HasHWVP8Decoder() + ? nsIGfxInfo::FEATURE_STATUS_OK + : nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + + SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_DECODE, mOSVersionInteger, + status); + + return status; +} + +int32_t GfxInfo::WebRtcHwH264Supported() { + MOZ_ASSERT(mozilla::AndroidBridge::Bridge()); + + // The Android side of this calculation is very slow, so we cache the result + // in preferences, invalidating if the OS version changes. + + int32_t status = 0; + if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_H264, + mOSVersionInteger, status)) { + return status; + } + + status = mozilla::AndroidBridge::Bridge()->HasHWH264() + ? nsIGfxInfo::FEATURE_STATUS_OK + : nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + + SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_H264, mOSVersionInteger, + status); + + return status; +} + +#ifdef DEBUG + +// Implement nsIGfxInfoDebug + +NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) { + mGLStrings->SpoofVendor(NS_LossyConvertUTF16toASCII(aVendorID)); + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) { + mGLStrings->SpoofRenderer(NS_LossyConvertUTF16toASCII(aDeviceID)); + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) { + mGLStrings->SpoofVersion(NS_LossyConvertUTF16toASCII(aDriverVersion)); + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) { + EnsureInitialized(); + mOSVersion = aVersion; + return NS_OK; +} + +NS_IMETHODIMP GfxInfo::FireTestProcess() { return NS_OK; } + +#endif + +nsString GfxInfo::Model() { + EnsureInitialized(); + return mModel; +} + +nsString GfxInfo::Hardware() { + EnsureInitialized(); + return mHardware; +} + +nsString GfxInfo::Product() { + EnsureInitialized(); + return mProduct; +} + +nsString GfxInfo::Manufacturer() { + EnsureInitialized(); + return mManufacturer; +} + +uint32_t GfxInfo::OperatingSystemVersion() { + EnsureInitialized(); + return mOSVersionInteger; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/GfxInfo.h b/widget/android/GfxInfo.h new file mode 100644 index 0000000000..df14fe96c1 --- /dev/null +++ b/widget/android/GfxInfo.h @@ -0,0 +1,117 @@ +/* vim: se cin sw=2 ts=2 et : */ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 __mozilla_widget_GfxInfo_h__ +#define __mozilla_widget_GfxInfo_h__ + +#include "GfxInfoBase.h" +#include "GfxDriverInfo.h" + +#include "nsString.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +namespace widget { + +class GfxInfo : public GfxInfoBase { + private: + ~GfxInfo(); + + public: + GfxInfo(); + + // We only declare the subset of nsIGfxInfo that we actually implement. The + // rest is brought forward from GfxInfoBase. + NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override; + NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override; + NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override; + NS_IMETHOD GetEmbeddedInFirefoxReality( + bool* aEmbeddedInFirefoxReality) override; + NS_IMETHOD GetHasBattery(bool* aHasBattery) override; + NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override; + NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override; + NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override; + NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override; + NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override; + NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override; + NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override; + NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override; + NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override; + NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override; + NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override; + NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override; + NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override; + NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override; + NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override; + NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override; + NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override; + NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override; + NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override; + NS_IMETHOD GetAdapterDriverVersion2( + nsAString& aAdapterDriverVersion) override; + NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override; + NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override; + NS_IMETHOD GetDisplayInfo(nsTArray& aDisplayInfo) override; + NS_IMETHOD GetDisplayWidth(nsTArray& aDisplayWidth) override; + NS_IMETHOD GetDisplayHeight(nsTArray& aDisplayHeight) override; + NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override; + using GfxInfoBase::GetFeatureStatus; + using GfxInfoBase::GetFeatureSuggestedDriverVersion; + + void EnsureInitialized(); + + virtual nsString Model() override; + virtual nsString Hardware() override; + virtual nsString Product() override; + virtual nsString Manufacturer() override; + +#ifdef DEBUG + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIGFXINFODEBUG +#endif + + virtual uint32_t OperatingSystemVersion() override; + + protected: + virtual nsresult GetFeatureStatusImpl( + int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion, + const nsTArray& aDriverInfo, nsACString& aFailureId, + OperatingSystem* aOS = nullptr) override; + virtual const nsTArray& GetGfxDriverInfo() override; + + private: + struct ScreenInfo { + gfx::Rect mScreenDimensions; + }; + + private: + void AddCrashReportAnnotations(); + int32_t WebRtcHwVp8EncodeSupported(); + int32_t WebRtcHwVp8DecodeSupported(); + int32_t WebRtcHwH264Supported(); + + bool mInitialized; + + class GLStrings; + UniquePtr mGLStrings; + + nsCString mAdapterDescription; + + OperatingSystem mOS; + + nsString mModel, mHardware, mManufacturer, mProduct; + nsCString mOSVersion; + uint32_t mOSVersionInteger; + int32_t mSDKVersion; + ScreenInfo mScreenInfo; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* __mozilla_widget_GfxInfo_h__ */ diff --git a/widget/android/ImageDecoderSupport.cpp b/widget/android/ImageDecoderSupport.cpp new file mode 100644 index 0000000000..9bebcc72a6 --- /dev/null +++ b/widget/android/ImageDecoderSupport.cpp @@ -0,0 +1,176 @@ +/* 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 "ImageDecoderSupport.h" + +#include "imgINotificationObserver.h" +#include "imgITools.h" +#include "imgINotificationObserver.h" +#include "gfxUtils.h" +#include "AndroidGraphics.h" +#include "JavaExceptions.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Swizzle.h" +#include "nsNetUtil.h" + +namespace mozilla { +namespace widget { + +namespace { + +class ImageCallbackHelper; + +HashSet, PointerHasher> + gDecodeRequests; + +class ImageCallbackHelper : public imgIContainerCallback, + public imgINotificationObserver { + public: + NS_DECL_ISUPPORTS + + void CompleteExceptionally(const char* aMessage) { + mResult->CompleteExceptionally( + java::sdk::IllegalArgumentException::New(aMessage) + .Cast()); + gDecodeRequests.remove(this); + } + + void Complete(DataSourceSurface::ScopedMap& aSourceSurface, int32_t width, + int32_t height) { + auto pixels = mozilla::jni::ByteBuffer::New( + reinterpret_cast(aSourceSurface.GetData()), + aSourceSurface.GetStride() * height); + auto bitmap = java::sdk::Bitmap::CreateBitmap( + width, height, java::sdk::Config::ARGB_8888()); + bitmap->CopyPixelsFromBuffer(pixels); + mResult->Complete(bitmap); + gDecodeRequests.remove(this); + } + + ImageCallbackHelper(java::GeckoResult::Param aResult, int32_t aDesiredLength) + : mResult(aResult), mDesiredLength(aDesiredLength), mImage(nullptr) { + MOZ_ASSERT(mResult); + } + + NS_IMETHOD + OnImageReady(imgIContainer* aImage, nsresult aStatus) override { + // Let's make sure we are alive until the request completes + MOZ_ALWAYS_TRUE(gDecodeRequests.putNew(this)); + + if (NS_FAILED(aStatus)) { + CompleteExceptionally("Could not process image."); + return aStatus; + } + + mImage = aImage; + return mImage->StartDecoding( + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY, + imgIContainer::FRAME_FIRST); + } + + // This method assumes that the image is ready to be processed + nsresult SendBitmap() { + RefPtr surface; + + if (mDesiredLength > 0) { + surface = mImage->GetFrameAtSize( + gfx::IntSize(mDesiredLength, mDesiredLength), + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + } else { + surface = mImage->GetFrame( + imgIContainer::FRAME_FIRST, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + } + + RefPtr dataSurface = surface->GetDataSurface(); + + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); + + int32_t width = dataSurface->GetSize().width; + int32_t height = dataSurface->GetSize().height; + + DataSourceSurface::ScopedMap sourceMap(dataSurface, + DataSourceSurface::READ); + + // Android's Bitmap only supports R8G8B8A8, so we need to convert the + // data to the right format + RefPtr destDataSurface = + Factory::CreateDataSourceSurfaceWithStride(dataSurface->GetSize(), + SurfaceFormat::R8G8B8A8, + sourceMap.GetStride()); + NS_ENSURE_TRUE(destDataSurface, NS_ERROR_FAILURE); + + DataSourceSurface::ScopedMap destMap(destDataSurface, + DataSourceSurface::READ_WRITE); + + SwizzleData(sourceMap.GetData(), sourceMap.GetStride(), + surface->GetFormat(), destMap.GetData(), destMap.GetStride(), + SurfaceFormat::R8G8B8A8, destDataSurface->GetSize()); + + Complete(destMap, width, height); + + return NS_OK; + } + + void Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) override { + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + SendBitmap(); + // Breack the cyclic reference between `ImageDecoderListener` (which is a + // `imgIContainer`) and `ImageCallbackHelper`. + mImage = nullptr; + } + } + + private: + const java::GeckoResult::GlobalRef mResult; + int32_t mDesiredLength; + nsCOMPtr mImage; + virtual ~ImageCallbackHelper() {} +}; + +NS_IMPL_ISUPPORTS(ImageCallbackHelper, imgIContainerCallback, + imgINotificationObserver) + +} // namespace + +/* static */ void ImageDecoderSupport::Decode(jni::String::Param aUri, + int32_t aDesiredLength, + jni::Object::Param aResult) { + auto result = java::GeckoResult::LocalRef(aResult); + RefPtr helper = + new ImageCallbackHelper(result, aDesiredLength); + + nsresult rv = DecodeInternal(aUri->ToString(), helper, helper); + if (NS_FAILED(rv)) { + helper->OnImageReady(nullptr, rv); + } +} + +/* static */ nsresult ImageDecoderSupport::DecodeInternal( + const nsAString& aUri, imgIContainerCallback* aCallback, + imgINotificationObserver* aObserver) { + nsCOMPtr imgTools = do_GetService("@mozilla.org/image/tools;1"); + if (NS_WARN_IF(!imgTools)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_IMAGE); + NS_ENSURE_SUCCESS(rv, rv); + + return imgTools->DecodeImageFromChannelAsync(uri, channel, aCallback, + aObserver); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/ImageDecoderSupport.h b/widget/android/ImageDecoderSupport.h new file mode 100644 index 0000000000..d38b3e7e1b --- /dev/null +++ b/widget/android/ImageDecoderSupport.h @@ -0,0 +1,30 @@ +/* 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 ImageDecoderSupport_h__ +#define ImageDecoderSupport_h__ + +#include "mozilla/java/ImageDecoderNatives.h" + +class imgIContainerCallback; + +namespace mozilla { +namespace widget { + +class ImageDecoderSupport final + : public java::ImageDecoder::Natives { + public: + static void Decode(jni::String::Param aUri, int32_t aDesiredLength, + jni::Object::Param aResult); + + private: + static nsresult DecodeInternal(const nsAString& aUri, + imgIContainerCallback* aCallback, + imgINotificationObserver* aObserver); +}; + +} // namespace widget +} // namespace mozilla + +#endif // ImageDecoderSupport_h__ diff --git a/widget/android/MediaKeysEventSourceFactory.cpp b/widget/android/MediaKeysEventSourceFactory.cpp new file mode 100644 index 0000000000..b52919d4cc --- /dev/null +++ b/widget/android/MediaKeysEventSourceFactory.cpp @@ -0,0 +1,17 @@ +/* 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 "MediaKeysEventSourceFactory.h" + +namespace mozilla { +namespace widget { + +mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() { + // GeckoView uses MediaController.webidl for media session events and control, + // see bug 1623715. + return nullptr; +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/PrefsHelper.h b/widget/android/PrefsHelper.h new file mode 100644 index 0000000000..0cc4ec4548 --- /dev/null +++ b/widget/android/PrefsHelper.h @@ -0,0 +1,306 @@ +/* -*- 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 PrefsHelper_h +#define PrefsHelper_h + +#include "MainThreadUtils.h" +#include "nsAppShell.h" +#include "nsCOMPtr.h" +#include "nsVariant.h" + +#include "mozilla/java/PrefsHelperNatives.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" + +namespace mozilla { + +class PrefsHelper : public java::PrefsHelper::Natives { + PrefsHelper() = delete; + + static bool GetVariantPref(nsIObserverService* aObsServ, + nsIWritableVariant* aVariant, + jni::Object::Param aPrefHandler, + const jni::String::LocalRef& aPrefName) { + if (NS_FAILED(aObsServ->NotifyObservers(aVariant, "android-get-pref", + aPrefName->ToString().get()))) { + return false; + } + + uint16_t varType = aVariant->GetDataType(); + + int32_t type = java::PrefsHelper::PREF_INVALID; + bool boolVal = false; + int32_t intVal = 0; + nsAutoString strVal; + + switch (varType) { + case nsIDataType::VTYPE_BOOL: + type = java::PrefsHelper::PREF_BOOL; + if (NS_FAILED(aVariant->GetAsBool(&boolVal))) { + return false; + } + break; + case nsIDataType::VTYPE_INT32: + type = java::PrefsHelper::PREF_INT; + if (NS_FAILED(aVariant->GetAsInt32(&intVal))) { + return false; + } + break; + case nsIDataType::VTYPE_ASTRING: + type = java::PrefsHelper::PREF_STRING; + if (NS_FAILED(aVariant->GetAsAString(strVal))) { + return false; + } + break; + default: + return false; + } + + jni::StringParam jstrVal(type == java::PrefsHelper::PREF_STRING + ? jni::StringParam(strVal, aPrefName.Env()) + : jni::StringParam(nullptr)); + + if (aPrefHandler) { + java::PrefsHelper::CallPrefHandler(aPrefHandler, type, aPrefName, boolVal, + intVal, jstrVal); + } else { + java::PrefsHelper::OnPrefChange(aPrefName, type, boolVal, intVal, + jstrVal); + } + return true; + } + + static bool SetVariantPref(nsIObserverService* aObsServ, + nsIWritableVariant* aVariant, + jni::String::Param aPrefName, bool aFlush, + int32_t aType, bool aBoolVal, int32_t aIntVal, + jni::String::Param aStrVal) { + nsresult rv = NS_ERROR_FAILURE; + + switch (aType) { + case java::PrefsHelper::PREF_BOOL: + rv = aVariant->SetAsBool(aBoolVal); + break; + case java::PrefsHelper::PREF_INT: + rv = aVariant->SetAsInt32(aIntVal); + break; + case java::PrefsHelper::PREF_STRING: + rv = aVariant->SetAsAString(aStrVal->ToString()); + break; + } + + if (NS_SUCCEEDED(rv)) { + rv = aObsServ->NotifyObservers(aVariant, "android-set-pref", + aPrefName->ToString().get()); + } + + uint16_t varType = nsIDataType::VTYPE_EMPTY; + if (NS_SUCCEEDED(rv)) { + varType = aVariant->GetDataType(); + } + + // We use set-to-empty to signal the pref was handled. + const bool handled = varType == nsIDataType::VTYPE_EMPTY; + + if (NS_SUCCEEDED(rv) && handled && aFlush) { + rv = Preferences::GetService()->SavePrefFile(nullptr); + } + + if (NS_SUCCEEDED(rv)) { + return handled; + } + + NS_WARNING( + nsPrintfCString("Failed to set pref %s", aPrefName->ToCString().get()) + .get()); + // Pretend we handled the pref. + return true; + } + + public: + static void GetPrefs(const jni::Class::LocalRef& aCls, + jni::ObjectArray::Param aPrefNames, + jni::Object::Param aPrefHandler) { + nsTArray nameRefArray(aPrefNames->GetElements()); + nsCOMPtr obsServ; + nsCOMPtr value; + nsAutoString strVal; + + for (jni::Object::LocalRef& nameRef : nameRefArray) { + jni::String::LocalRef nameStr(std::move(nameRef)); + const nsCString& name = nameStr->ToCString(); + + int32_t type = java::PrefsHelper::PREF_INVALID; + bool boolVal = false; + int32_t intVal = 0; + strVal.Truncate(); + + switch (Preferences::GetType(name.get())) { + case nsIPrefBranch::PREF_BOOL: + type = java::PrefsHelper::PREF_BOOL; + boolVal = Preferences::GetBool(name.get()); + break; + + case nsIPrefBranch::PREF_INT: + type = java::PrefsHelper::PREF_INT; + intVal = Preferences::GetInt(name.get()); + break; + + case nsIPrefBranch::PREF_STRING: { + type = java::PrefsHelper::PREF_STRING; + nsresult rv = Preferences::GetLocalizedString(name.get(), strVal); + if (NS_FAILED(rv)) { + Preferences::GetString(name.get(), strVal); + } + break; + } + default: + // Pref not found; try to find it. + if (!obsServ) { + obsServ = services::GetObserverService(); + if (!obsServ) { + continue; + } + } + if (value) { + value->SetAsEmpty(); + } else { + value = new nsVariant(); + } + if (!GetVariantPref(obsServ, value, aPrefHandler, nameStr)) { + NS_WARNING( + nsPrintfCString("Failed to get pref %s", name.get()).get()); + } + continue; + } + + java::PrefsHelper::CallPrefHandler( + aPrefHandler, type, nameStr, boolVal, intVal, + jni::StringParam(type == java::PrefsHelper::PREF_STRING + ? jni::StringParam(strVal, aCls.Env()) + : jni::StringParam(nullptr))); + } + + java::PrefsHelper::CallPrefHandler(aPrefHandler, + java::PrefsHelper::PREF_FINISH, nullptr, + false, 0, nullptr); + } + + static void SetPref(jni::String::Param aPrefName, bool aFlush, int32_t aType, + bool aBoolVal, int32_t aIntVal, + jni::String::Param aStrVal) { + const nsCString& name = aPrefName->ToCString(); + + if (Preferences::GetType(name.get()) == nsIPrefBranch::PREF_INVALID) { + // No pref; try asking first. + nsCOMPtr obsServ = services::GetObserverService(); + nsCOMPtr value = new nsVariant(); + if (obsServ && SetVariantPref(obsServ, value, aPrefName, aFlush, aType, + aBoolVal, aIntVal, aStrVal)) { + // The "pref" has changed; send a notification. + GetVariantPref(obsServ, value, nullptr, + jni::String::LocalRef(aPrefName)); + return; + } + } + + switch (aType) { + case java::PrefsHelper::PREF_BOOL: + Preferences::SetBool(name.get(), aBoolVal); + break; + case java::PrefsHelper::PREF_INT: + Preferences::SetInt(name.get(), aIntVal); + break; + case java::PrefsHelper::PREF_STRING: + Preferences::SetString(name.get(), aStrVal->ToString()); + break; + default: + MOZ_ASSERT(false, "Invalid pref type"); + } + + if (aFlush) { + Preferences::GetService()->SavePrefFile(nullptr); + } + } + + static void AddObserver(const jni::Class::LocalRef& aCls, + jni::ObjectArray::Param aPrefNames, + jni::Object::Param aPrefHandler, + jni::ObjectArray::Param aPrefsToObserve) { + // Call observer immediately with existing pref values. + GetPrefs(aCls, aPrefNames, aPrefHandler); + + if (!aPrefsToObserve) { + return; + } + + nsTArray nameRefArray( + aPrefsToObserve->GetElements()); + nsAppShell* const appShell = nsAppShell::Get(); + MOZ_ASSERT(appShell); + + for (jni::Object::LocalRef& nameRef : nameRefArray) { + jni::String::LocalRef nameStr(std::move(nameRef)); + MOZ_ALWAYS_SUCCEEDS( + Preferences::AddStrongObserver(appShell, nameStr->ToCString())); + } + } + + static void RemoveObserver(const jni::Class::LocalRef& aCls, + jni::ObjectArray::Param aPrefsToUnobserve) { + nsTArray nameRefArray( + aPrefsToUnobserve->GetElements()); + nsAppShell* const appShell = nsAppShell::Get(); + MOZ_ASSERT(appShell); + + for (jni::Object::LocalRef& nameRef : nameRefArray) { + jni::String::LocalRef nameStr(std::move(nameRef)); + MOZ_ALWAYS_SUCCEEDS( + Preferences::RemoveObserver(appShell, nameStr->ToCString())); + } + } + + static void OnPrefChange(const char16_t* aData) { + const nsCString& name = NS_LossyConvertUTF16toASCII(aData); + + int32_t type = -1; + bool boolVal = false; + int32_t intVal = false; + nsAutoString strVal; + + switch (Preferences::GetType(name.get())) { + case nsIPrefBranch::PREF_BOOL: + type = java::PrefsHelper::PREF_BOOL; + boolVal = Preferences::GetBool(name.get()); + break; + case nsIPrefBranch::PREF_INT: + type = java::PrefsHelper::PREF_INT; + intVal = Preferences::GetInt(name.get()); + break; + case nsIPrefBranch::PREF_STRING: { + type = java::PrefsHelper::PREF_STRING; + nsresult rv = Preferences::GetLocalizedString(name.get(), strVal); + if (NS_FAILED(rv)) { + Preferences::GetString(name.get(), strVal); + } + break; + } + default: + NS_WARNING(nsPrintfCString("Invalid pref %s", name.get()).get()); + return; + } + + java::PrefsHelper::OnPrefChange( + name, type, boolVal, intVal, + jni::StringParam(type == java::PrefsHelper::PREF_STRING + ? jni::StringParam(strVal) + : jni::StringParam(nullptr))); + } +}; + +} // namespace mozilla + +#endif // PrefsHelper_h diff --git a/widget/android/ScreenHelperAndroid.cpp b/widget/android/ScreenHelperAndroid.cpp new file mode 100644 index 0000000000..4242bfcdaa --- /dev/null +++ b/widget/android/ScreenHelperAndroid.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set sw=2 ts=4 expandtab: + * 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 "ScreenHelperAndroid.h" +#include "AndroidRect.h" +#include "nsThreadUtils.h" + +#include + +#include "mozilla/Atomics.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/ScreenManagerHelperNatives.h" +#include "mozilla/widget/ScreenManager.h" + +using namespace mozilla; +using namespace mozilla::widget; + +static ScreenHelperAndroid* gHelper = nullptr; + +class ScreenHelperAndroid::ScreenHelperSupport final + : public java::ScreenManagerHelper::Natives { + public: + typedef java::ScreenManagerHelper::Natives Base; + + static void RefreshScreenInfo() { gHelper->Refresh(); } + + static int32_t AddDisplay(int32_t aDisplayType, int32_t aWidth, + int32_t aHeight, float aDensity) { + static Atomic nextId; + + uint32_t screenId = ++nextId; + NS_DispatchToMainThread( + NS_NewRunnableFunction( + "ScreenHelperAndroid::ScreenHelperSupport::AddDisplay", + [aDisplayType, aWidth, aHeight, aDensity, screenId] { + MOZ_ASSERT(NS_IsMainThread()); + + gHelper->AddScreen( + screenId, static_cast(aDisplayType), + LayoutDeviceIntRect(0, 0, aWidth, aHeight), aDensity); + }) + .take()); + return screenId; + } + + static void RemoveDisplay(int32_t aScreenId) { + NS_DispatchToMainThread( + NS_NewRunnableFunction( + "ScreenHelperAndroid::ScreenHelperSupport::RemoveDisplay", + [aScreenId] { + MOZ_ASSERT(NS_IsMainThread()); + + gHelper->RemoveScreen(aScreenId); + }) + .take()); + } +}; + +static already_AddRefed MakePrimaryScreen() { + MOZ_ASSERT(XRE_IsParentProcess()); + java::sdk::Rect::LocalRef rect = java::GeckoAppShell::GetScreenSize(); + LayoutDeviceIntRect bounds = LayoutDeviceIntRect( + rect->Left(), rect->Top(), rect->Width(), rect->Height()); + uint32_t depth = java::GeckoAppShell::GetScreenDepth(); + float density = java::GeckoAppShell::GetDensity(); + float dpi = java::GeckoAppShell::GetDpi(); + RefPtr screen = new Screen(bounds, bounds, depth, depth, + DesktopToLayoutDeviceScale(density), + CSSToLayoutDeviceScale(1.0f), dpi); + return screen.forget(); +} + +ScreenHelperAndroid::ScreenHelperAndroid() { + MOZ_ASSERT(!gHelper); + gHelper = this; + + ScreenHelperSupport::Base::Init(); + + Refresh(); +} + +ScreenHelperAndroid::~ScreenHelperAndroid() { gHelper = nullptr; } + +/* static */ +ScreenHelperAndroid* ScreenHelperAndroid::GetSingleton() { return gHelper; } + +void ScreenHelperAndroid::Refresh() { + mScreens.Remove(0); + + AutoTArray, 1> screenList; + RefPtr screen = MakePrimaryScreen(); + if (screen) { + mScreens.Put(0, screen); + } + + for (auto iter = mScreens.ConstIter(); !iter.Done(); iter.Next()) { + screenList.AppendElement(iter.Data()); + } + + ScreenManager& manager = ScreenManager::GetSingleton(); + manager.Refresh(std::move(screenList)); +} + +void ScreenHelperAndroid::AddScreen(uint32_t aScreenId, + DisplayType aDisplayType, + LayoutDeviceIntRect aRect, float aDensity) { + MOZ_ASSERT(aScreenId > 0); + MOZ_ASSERT(!mScreens.Get(aScreenId, nullptr)); + + RefPtr screen = + new Screen(aRect, aRect, 24, 24, DesktopToLayoutDeviceScale(aDensity), + CSSToLayoutDeviceScale(1.0f), 160.0f); + + mScreens.Put(aScreenId, screen); + Refresh(); +} + +void ScreenHelperAndroid::RemoveScreen(uint32_t aScreenId) { + mScreens.Remove(aScreenId); + Refresh(); +} + +already_AddRefed ScreenHelperAndroid::ScreenForId(uint32_t aScreenId) { + RefPtr screen = mScreens.Get(aScreenId); + return screen.forget(); +} diff --git a/widget/android/ScreenHelperAndroid.h b/widget/android/ScreenHelperAndroid.h new file mode 100644 index 0000000000..96c34d4b52 --- /dev/null +++ b/widget/android/ScreenHelperAndroid.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ts=4 sw=2 expandtab: + * 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 ScreenHelperAndroid_h___ +#define ScreenHelperAndroid_h___ + +#include "mozilla/widget/ScreenManager.h" +#include "nsDataHashtable.h" + +namespace mozilla { +namespace widget { + +class ScreenHelperAndroid final : public ScreenManager::Helper { + public: + class ScreenHelperSupport; + + ScreenHelperAndroid(); + ~ScreenHelperAndroid(); + + static ScreenHelperAndroid* GetSingleton(); + + void Refresh(); + + void AddScreen(uint32_t aScreenId, DisplayType aDisplayType, + LayoutDeviceIntRect aRect = LayoutDeviceIntRect(), + float aDensity = 1.0f); + void RemoveScreen(uint32_t aId); + already_AddRefed ScreenForId(uint32_t aScreenId); + + private: + nsDataHashtable> mScreens; +}; + +} // namespace widget +} // namespace mozilla + +#endif /* ScreenHelperAndroid_h___ */ diff --git a/widget/android/Telemetry.h b/widget/android/Telemetry.h new file mode 100644 index 0000000000..3e86ba8278 --- /dev/null +++ b/widget/android/Telemetry.h @@ -0,0 +1,86 @@ +/* -*- 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 mozilla_widget_Telemetry_h__ +#define mozilla_widget_Telemetry_h__ + +#include "mozilla/java/TelemetryUtilsNatives.h" +#include "nsAppShell.h" +#include "nsIAndroidBridge.h" + +#include "mozilla/Telemetry.h" + +namespace mozilla { +namespace widget { + +class Telemetry final : public java::TelemetryUtils::Natives { + Telemetry() = delete; + + static already_AddRefed GetObserver() { + nsAppShell* const appShell = nsAppShell::Get(); + if (!appShell) { + return nullptr; + } + + nsCOMPtr browserApp = appShell->GetBrowserApp(); + nsCOMPtr obs; + + if (!browserApp || + NS_FAILED(browserApp->GetUITelemetryObserver(getter_AddRefs(obs))) || + !obs) { + return nullptr; + } + + return obs.forget(); + } + + public: + static void AddHistogram(jni::String::Param aName, int32_t aValue) { + MOZ_ASSERT(aName); + mozilla::Telemetry::Accumulate(aName->ToCString().get(), aValue); + } + + static void AddKeyedHistogram(jni::String::Param aName, + jni::String::Param aKey, int32_t aValue) { + MOZ_ASSERT(aName && aKey); + mozilla::Telemetry::Accumulate(aName->ToCString().get(), aKey->ToCString(), + aValue); + } + + static void StartUISession(jni::String::Param aName, int64_t aTimestamp) { + MOZ_ASSERT(aName); + nsCOMPtr obs = GetObserver(); + if (obs) { + obs->StartSession(aName->ToString().get(), aTimestamp); + } + } + + static void StopUISession(jni::String::Param aName, + jni::String::Param aReason, int64_t aTimestamp) { + MOZ_ASSERT(aName); + nsCOMPtr obs = GetObserver(); + if (obs) { + obs->StopSession(aName->ToString().get(), + aReason ? aReason->ToString().get() : nullptr, + aTimestamp); + } + } + + static void AddUIEvent(jni::String::Param aAction, jni::String::Param aMethod, + int64_t aTimestamp, jni::String::Param aExtras) { + MOZ_ASSERT(aAction); + nsCOMPtr obs = GetObserver(); + if (obs) { + obs->AddEvent(aAction->ToString().get(), + aMethod ? aMethod->ToString().get() : nullptr, aTimestamp, + aExtras ? aExtras->ToString().get() : nullptr); + } + } +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_Telemetry_h__ diff --git a/widget/android/WebAuthnTokenManager.h b/widget/android/WebAuthnTokenManager.h new file mode 100644 index 0000000000..046e6eba80 --- /dev/null +++ b/widget/android/WebAuthnTokenManager.h @@ -0,0 +1,79 @@ +/* -*- 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 WebAuthnTokenManager_h +#define WebAuthnTokenManager_h + +#include "mozilla/dom/AndroidWebAuthnTokenManager.h" + +#include "mozilla/java/WebAuthnTokenManagerNatives.h" + +namespace mozilla { +class WebAuthnTokenManager final + : public java::WebAuthnTokenManager::Natives { + public: + static void WebAuthnMakeCredentialFinish( + jni::ByteArray::Param aClientDataJson, jni::ByteArray::Param aKeyHandle, + jni::ByteArray::Param aAttestationObject) { + mozilla::dom::AndroidWebAuthnResult result; + + result.mClientDataJSON.Assign( + reinterpret_cast( + aClientDataJson->GetElements().Elements()), + aClientDataJson->Length()); + result.mKeyHandle.Assign( + reinterpret_cast(aKeyHandle->GetElements().Elements()), + aKeyHandle->Length()); + result.mAttObj.Assign(reinterpret_cast( + aAttestationObject->GetElements().Elements()), + aAttestationObject->Length()); + + mozilla::dom::AndroidWebAuthnTokenManager::GetInstance() + ->HandleRegisterResult(std::move(result)); + } + + static void WebAuthnMakeCredentialReturnError(jni::String::Param aErrorCode) { + mozilla::dom::AndroidWebAuthnResult result(aErrorCode->ToString()); + mozilla::dom::AndroidWebAuthnTokenManager::GetInstance() + ->HandleRegisterResult(std::move(result)); + } + + static void WebAuthnGetAssertionFinish(jni::ByteArray::Param aClientDataJson, + jni::ByteArray::Param aKeyHandle, + jni::ByteArray::Param aAuthData, + jni::ByteArray::Param aSignature, + jni::ByteArray::Param aUserHandle) { + mozilla::dom::AndroidWebAuthnResult result; + + result.mClientDataJSON.Assign( + reinterpret_cast( + aClientDataJson->GetElements().Elements()), + aClientDataJson->Length()); + result.mKeyHandle.Assign( + reinterpret_cast(aKeyHandle->GetElements().Elements()), + aKeyHandle->Length()); + result.mAuthData.Assign( + reinterpret_cast(aAuthData->GetElements().Elements()), + aAuthData->Length()); + result.mSignature.Assign( + reinterpret_cast(aSignature->GetElements().Elements()), + aSignature->Length()); + result.mUserHandle.Assign( + reinterpret_cast(aUserHandle->GetElements().Elements()), + aUserHandle->Length()); + + mozilla::dom::AndroidWebAuthnTokenManager::GetInstance()->HandleSignResult( + std::move(result)); + } + + static void WebAuthnGetAssertionReturnError(jni::String::Param aErrorCode) { + mozilla::dom::AndroidWebAuthnResult result(aErrorCode->ToString()); + mozilla::dom::AndroidWebAuthnTokenManager::GetInstance()->HandleSignResult( + std::move(result)); + } +}; +} // namespace mozilla + +#endif diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp new file mode 100644 index 0000000000..ac498da4fd --- /dev/null +++ b/widget/android/WebExecutorSupport.cpp @@ -0,0 +1,460 @@ +/* -*- 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/. */ + +#include + +#include "WebExecutorSupport.h" + +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsINSSErrorsService.h" +#include "nsIUploadChannel2.h" +#include "nsIX509Cert.h" + +#include "nsIDNSService.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" + +#include "mozilla/java/GeckoWebExecutorWrappers.h" +#include "mozilla/java/WebMessageWrappers.h" +#include "mozilla/java/WebRequestErrorWrappers.h" +#include "mozilla/java/WebResponseWrappers.h" +#include "mozilla/net/DNS.h" // for NetAddr +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/Preferences.h" +#include "GeckoViewStreamListener.h" +#include "nsIPrivateBrowsingChannel.h" + +#include "nsNetUtil.h" // for NS_NewURI, NS_NewChannel, NS_NewStreamLoader + +#include "InetAddress.h" // for java::sdk::InetAddress and java::sdk::UnknownHostException +#include "ReferrerInfo.h" + +namespace mozilla { +using namespace net; + +namespace widget { + +static void CompleteWithError(java::GeckoResult::Param aResult, + nsresult aStatus, nsIChannel* aChannel) { + nsCOMPtr errSvc = + do_GetService("@mozilla.org/nss_errors_service;1"); + MOZ_ASSERT(errSvc); + + uint32_t errorClass; + nsresult rv = errSvc->GetErrorClass(aStatus, &errorClass); + if (NS_FAILED(rv)) { + errorClass = 0; + } + + jni::ByteArray::LocalRef certBytes; + if (aChannel) { + std::tie(certBytes, std::ignore) = + GeckoViewStreamListener::CertificateFromChannel(aChannel); + } + + java::WebRequestError::LocalRef error = java::WebRequestError::FromGeckoError( + int64_t(aStatus), NS_ERROR_GET_MODULE(aStatus), errorClass, certBytes); + + aResult->CompleteExceptionally(error.Cast()); +} + +static void CompleteWithError(java::GeckoResult::Param aResult, + nsresult aStatus) { + CompleteWithError(aResult, aStatus, nullptr); +} + +class ByteBufferStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit ByteBufferStream(jni::ByteBuffer::Param buffer) + : mBuffer(buffer), mPosition(0), mClosed(false) { + MOZ_ASSERT(mBuffer); + MOZ_ASSERT(mBuffer->Address()); + } + + NS_IMETHOD + Close() override { + mClosed = true; + return NS_OK; + } + + NS_IMETHOD + Available(uint64_t* aResult) override { + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + *aResult = (mBuffer->Capacity() - mPosition); + return NS_OK; + } + + NS_IMETHOD + Read(char* aBuf, uint32_t aCount, uint32_t* aCountRead) override { + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + *aCountRead = uint32_t( + std::min(uint64_t(mBuffer->Capacity() - mPosition), uint64_t(aCount))); + + if (*aCountRead > 0) { + memcpy(aBuf, (char*)mBuffer->Address() + mPosition, *aCountRead); + mPosition += *aCountRead; + } + + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + IsNonBlocking(bool* aResult) override { + *aResult = false; + return NS_OK; + } + + protected: + virtual ~ByteBufferStream() {} + + const jni::ByteBuffer::GlobalRef mBuffer; + uint64_t mPosition; + bool mClosed; +}; + +NS_IMPL_ISUPPORTS(ByteBufferStream, nsIInputStream) + +class LoaderListener final : public GeckoViewStreamListener { + public: + explicit LoaderListener(java::GeckoResult::Param aResult, + bool aAllowRedirects, bool testStreamFailure) + : GeckoViewStreamListener(), + mResult(aResult), + mTestStreamFailure(testStreamFailure), + mAllowRedirects(aAllowRedirects) { + MOZ_ASSERT(mResult); + } + + NS_IMETHOD + OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) override { + MOZ_ASSERT(mStream); + + if (mTestStreamFailure) { + return NS_ERROR_UNEXPECTED; + } + + // We only need this for the ReadSegments call, the value is unused. + uint32_t countRead; + nsresult rv = + aInputStream->ReadSegments(WriteSegment, this, aCount, &countRead); + NS_ENSURE_SUCCESS(rv, rv); + return rv; + } + + NS_IMETHOD + AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, + uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) override { + if (!mAllowRedirects) { + return NS_ERROR_ABORT; + } + + callback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; + } + + void SendWebResponse(java::WebResponse::Param aResponse) override { + mResult->Complete(aResponse); + } + + void CompleteWithError(nsresult aStatus, nsIChannel* aChannel) override { + ::CompleteWithError(mResult, aStatus, aChannel); + } + + virtual ~LoaderListener() {} + + const java::GeckoResult::GlobalRef mResult; + const bool mTestStreamFailure; + bool mAllowRedirects; +}; + +class DNSListener final : public nsIDNSListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + DNSListener(const nsCString& aHost, java::GeckoResult::Param aResult) + : mHost(aHost), mResult(aResult) {} + + NS_IMETHOD + OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord, + nsresult aStatus) override { + if (NS_FAILED(aStatus)) { + CompleteUnknownHostError(); + return NS_OK; + } + + nsresult rv = CompleteWithRecord(aRecord); + if (NS_FAILED(rv)) { + CompleteUnknownHostError(); + return NS_OK; + } + + return NS_OK; + } + + void CompleteUnknownHostError() { + java::sdk::UnknownHostException::LocalRef error = + java::sdk::UnknownHostException::New(); + mResult->CompleteExceptionally(error.Cast()); + } + + private: + nsresult CompleteWithRecord(nsIDNSRecord* aRecord) { + nsTArray addrs; + nsCOMPtr rec = do_QueryInterface(aRecord); + if (!rec) { + return NS_ERROR_UNEXPECTED; + } + nsresult rv = rec->GetAddresses(addrs); + NS_ENSURE_SUCCESS(rv, rv); + + jni::ByteArray::LocalRef bytes; + auto objects = + jni::ObjectArray::New(addrs.Length()); + for (size_t i = 0; i < addrs.Length(); i++) { + const auto& addr = addrs[i]; + if (addr.raw.family == AF_INET) { + bytes = jni::ByteArray::New( + reinterpret_cast(&addr.inet.ip), 4); + } else if (addr.raw.family == AF_INET6) { + bytes = jni::ByteArray::New( + reinterpret_cast(&addr.inet6.ip), 16); + } else { + // We don't handle this, skip it. + continue; + } + + objects->SetElement(i, + java::sdk::InetAddress::GetByAddress(mHost, bytes)); + } + + mResult->Complete(objects); + return NS_OK; + } + + virtual ~DNSListener() {} + + const nsCString mHost; + const java::GeckoResult::GlobalRef mResult; +}; + +NS_IMPL_ISUPPORTS(DNSListener, nsIDNSListener) + +static nsresult ConvertCacheMode(int32_t mode, int32_t& result) { + switch (mode) { + case java::WebRequest::CACHE_MODE_DEFAULT: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT; + break; + case java::WebRequest::CACHE_MODE_NO_STORE: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE; + break; + case java::WebRequest::CACHE_MODE_RELOAD: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD; + break; + case java::WebRequest::CACHE_MODE_NO_CACHE: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE; + break; + case java::WebRequest::CACHE_MODE_FORCE_CACHE: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE; + break; + case java::WebRequest::CACHE_MODE_ONLY_IF_CACHED: + result = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED; + break; + default: + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +static nsresult SetupHttpChannel(nsIHttpChannel* aHttpChannel, + nsIChannel* aChannel, + java::WebRequest::Param aRequest) { + const auto req = java::WebRequest::LocalRef(aRequest); + const auto reqBase = java::WebMessage::LocalRef(req.Cast()); + + // Method + nsresult rv = aHttpChannel->SetRequestMethod(aRequest->Method()->ToCString()); + NS_ENSURE_SUCCESS(rv, rv); + + // Headers + const auto keys = reqBase->GetHeaderKeys(); + const auto values = reqBase->GetHeaderValues(); + nsCString contentType; + for (size_t i = 0; i < keys->Length(); i++) { + const auto key = jni::String::LocalRef(keys->GetElement(i))->ToCString(); + const auto value = + jni::String::LocalRef(values->GetElement(i))->ToCString(); + + if (key.LowerCaseEqualsASCII("content-type")) { + contentType = value; + } + + // We clobber any duplicate keys here because we've already merged them + // in the upstream WebRequest. + rv = aHttpChannel->SetRequestHeader(key, value, false /* merge */); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Body + const auto body = req->Body(); + if (body) { + nsCOMPtr stream = new ByteBufferStream(body); + + nsCOMPtr uploadChannel(do_QueryInterface(aChannel, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = uploadChannel->ExplicitSetUploadStream( + stream, contentType, -1, aRequest->Method()->ToCString(), false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Referrer + RefPtr referrerUri; + const auto referrer = req->Referrer(); + if (referrer) { + rv = NS_NewURI(getter_AddRefs(referrerUri), referrer->ToString()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + } + + nsCOMPtr referrerInfo = new dom::ReferrerInfo(referrerUri); + rv = aHttpChannel->SetReferrerInfoWithoutClone(referrerInfo); + NS_ENSURE_SUCCESS(rv, rv); + + // Cache mode + nsCOMPtr internalChannel( + do_QueryInterface(aChannel, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t cacheMode; + rv = ConvertCacheMode(req->CacheMode(), cacheMode); + NS_ENSURE_SUCCESS(rv, rv); + + rv = internalChannel->SetFetchCacheMode(cacheMode); + NS_ENSURE_SUCCESS(rv, rv); + + // We don't have any UI + rv = internalChannel->SetBlockAuthPrompt(true); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult WebExecutorSupport::CreateStreamLoader( + java::WebRequest::Param aRequest, int32_t aFlags, + java::GeckoResult::Param aResult) { + const auto req = java::WebRequest::LocalRef(aRequest); + const auto reqBase = java::WebMessage::LocalRef(req.Cast()); + + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), reqBase->Uri()->ToString()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_ANONYMOUS) { + channel->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS); + } + + if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_PRIVATE) { + nsCOMPtr pbChannel = do_QueryInterface(channel); + NS_ENSURE_TRUE(pbChannel, NS_ERROR_FAILURE); + pbChannel->SetPrivate(true); + } + + nsCOMPtr cookieJarSettings = + CookieJarSettings::Create(); + MOZ_ASSERT(cookieJarSettings); + + nsCOMPtr loadInfo = channel->LoadInfo(); + loadInfo->SetCookieJarSettings(cookieJarSettings); + + // setup http/https specific things + nsCOMPtr httpChannel(do_QueryInterface(channel, &rv)); + if (httpChannel) { + rv = SetupHttpChannel(httpChannel, channel, aRequest); + NS_ENSURE_SUCCESS(rv, rv); + } + + // set up the listener + const bool allowRedirects = + !(aFlags & java::GeckoWebExecutor::FETCH_FLAGS_NO_REDIRECTS); + const bool testStreamFailure = + (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_STREAM_FAILURE_TEST); + + RefPtr listener = + new LoaderListener(aResult, allowRedirects, testStreamFailure); + + rv = channel->SetNotificationCallbacks(listener); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally, open the channel + rv = channel->AsyncOpen(listener); + + return NS_OK; +} + +void WebExecutorSupport::Fetch(jni::Object::Param aRequest, int32_t aFlags, + jni::Object::Param aResult) { + const auto request = java::WebRequest::LocalRef(aRequest); + auto result = java::GeckoResult::LocalRef(aResult); + + nsresult rv = CreateStreamLoader(request, aFlags, result); + if (NS_FAILED(rv)) { + CompleteWithError(result, rv); + } +} + +static nsresult ResolveHost(nsCString& host, java::GeckoResult::Param result) { + nsresult rv; + nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr cancelable; + RefPtr listener = new DNSListener(host, result); + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, 0, + nullptr, listener, nullptr /* aListenerTarget */, + OriginAttributes(), getter_AddRefs(cancelable)); + return rv; +} + +void WebExecutorSupport::Resolve(jni::String::Param aUri, + jni::Object::Param aResult) { + auto result = java::GeckoResult::LocalRef(aResult); + + nsCString uri = aUri->ToCString(); + nsresult rv = ResolveHost(uri, result); + if (NS_FAILED(rv)) { + java::sdk::UnknownHostException::LocalRef error = + java::sdk::UnknownHostException::New(); + result->CompleteExceptionally(error.Cast()); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/WebExecutorSupport.h b/widget/android/WebExecutorSupport.h new file mode 100644 index 0000000000..65a68fcc40 --- /dev/null +++ b/widget/android/WebExecutorSupport.h @@ -0,0 +1,32 @@ +/* -*- 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 WebExecutorSupport_h__ +#define WebExecutorSupport_h__ + +#include "mozilla/java/GeckoWebExecutorNatives.h" +#include "mozilla/java/GeckoResultWrappers.h" +#include "mozilla/java/WebRequestWrappers.h" + +namespace mozilla { +namespace widget { + +class WebExecutorSupport final + : public java::GeckoWebExecutor::Natives { + public: + static void Fetch(jni::Object::Param request, int32_t flags, + jni::Object::Param result); + static void Resolve(jni::String::Param aUri, jni::Object::Param result); + + protected: + static nsresult CreateStreamLoader(java::WebRequest::Param aRequest, + int32_t aFlags, + java::GeckoResult::Param aResult); +}; + +} // namespace widget +} // namespace mozilla + +#endif // WebExecutorSupport_h__ diff --git a/widget/android/WindowEvent.h b/widget/android/WindowEvent.h new file mode 100644 index 0000000000..fdb73bf692 --- /dev/null +++ b/widget/android/WindowEvent.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_widget_WindowEvent_h +#define mozilla_widget_WindowEvent_h + +#include "nsThreadUtils.h" +#include "mozilla/jni/Natives.h" + +namespace mozilla { +namespace widget { + +// An Event subclass that guards against stale events. +// (See the implmentation of mozilla::jni::detail::ProxyNativeCall for info +// about the default template parameters for this class) +template +class WindowEvent : public Runnable { + bool IsStaleCall() { + if (IsStatic) { + // Static calls are never stale. + return false; + } + + return jni::NativePtrTraits::IsStale(mInstance); + } + + Lambda mLambda; + const InstanceType mInstance; + + public: + WindowEvent(Lambda&& aLambda, InstanceType&& aInstance) + : Runnable("mozilla::widget::WindowEvent"), + mLambda(std::move(aLambda)), + mInstance(std::forward(aInstance)) {} + + explicit WindowEvent(Lambda&& aLambda) + : Runnable("mozilla::widget::WindowEvent"), + mLambda(std::move(aLambda)), + mInstance(mLambda.GetThisArg()) {} + + NS_IMETHOD Run() override { + if (!IsStaleCall()) { + mLambda(); + } + return NS_OK; + } +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_WindowEvent_h diff --git a/widget/android/bindings/AccessibilityEvent-classes.txt b/widget/android/bindings/AccessibilityEvent-classes.txt new file mode 100644 index 0000000000..b54e3ce105 --- /dev/null +++ b/widget/android/bindings/AccessibilityEvent-classes.txt @@ -0,0 +1,3 @@ +# We only use constants from AccessibilityEvent +[android.view.accessibility.AccessibilityEvent = skip:true] + = skip:false diff --git a/widget/android/bindings/AndroidBuild-classes.txt b/widget/android/bindings/AndroidBuild-classes.txt new file mode 100644 index 0000000000..a76aa12c66 --- /dev/null +++ b/widget/android/bindings/AndroidBuild-classes.txt @@ -0,0 +1,5 @@ +[android.os.Build] + = noLiteral:true + +[android.os.Build$VERSION] + = noLiteral:true diff --git a/widget/android/bindings/AndroidGraphics-classes.txt b/widget/android/bindings/AndroidGraphics-classes.txt new file mode 100644 index 0000000000..452ba404e8 --- /dev/null +++ b/widget/android/bindings/AndroidGraphics-classes.txt @@ -0,0 +1,10 @@ +[android.graphics.Bitmap = skip:true] +copyPixelsFromBuffer(Ljava/nio/Buffer;)V = +createBitmap(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap; = + +[android.graphics.Bitmap$Config = skip:true] +valueOf(Ljava/lang/String;)Landroid/graphics/Bitmap$Config; = +ALPHA_8 = +ARGB_8888 = +RGBA_F16 = +RGB_565 = \ No newline at end of file diff --git a/widget/android/bindings/AndroidInputType-classes.txt b/widget/android/bindings/AndroidInputType-classes.txt new file mode 100644 index 0000000000..fa1138dce4 --- /dev/null +++ b/widget/android/bindings/AndroidInputType-classes.txt @@ -0,0 +1,3 @@ +# We only use constants from InputType +[android.text.InputType = skip:true] + = skip:false diff --git a/widget/android/bindings/AndroidRect-classes.txt b/widget/android/bindings/AndroidRect-classes.txt new file mode 100644 index 0000000000..76d3094a9f --- /dev/null +++ b/widget/android/bindings/AndroidRect-classes.txt @@ -0,0 +1,2 @@ +[android.graphics.Rect] +[android.graphics.RectF] diff --git a/widget/android/bindings/InetAddress-classes.txt b/widget/android/bindings/InetAddress-classes.txt new file mode 100644 index 0000000000..65788be252 --- /dev/null +++ b/widget/android/bindings/InetAddress-classes.txt @@ -0,0 +1,6 @@ +# We only want getByAddress(String, byte[]) +[java.net.InetAddress = skip:true] +getByAddress(Ljava/lang/String;[B)Ljava/net/InetAddress; = skip:false + +[java.net.UnknownHostException = skip:true] +()V = diff --git a/widget/android/bindings/JavaBuiltins-classes.txt b/widget/android/bindings/JavaBuiltins-classes.txt new file mode 100644 index 0000000000..c6be5dde34 --- /dev/null +++ b/widget/android/bindings/JavaBuiltins-classes.txt @@ -0,0 +1,25 @@ +[java.lang.Boolean = skip:true] +# Use static fields for boxing boolean. +TRUE = +FALSE = +booleanValue = + +[java.lang.Double = skip:true] +(D)V = + +[java.lang.Integer = skip:true] +# Use valueOf() for boxing int; don't use constructor +# because some Integer values are cached. +valueOf(I)Ljava/lang/Integer; = + +[java.lang.Long = skip:true] +valueOf(J)Ljava/lang/Long; = + +[java.lang.Number = skip:true] +# Use doubleValue() for unboxing Double/Float/Long. +doubleValue = +# Use intValue() for unboxing Byte/Int/Short. +intValue = + +[java.lang.String = skip:true] +valueOf(Ljava/lang/Object;)Ljava/lang/String; = diff --git a/widget/android/bindings/JavaExceptions-classes.txt b/widget/android/bindings/JavaExceptions-classes.txt new file mode 100644 index 0000000000..fbe98b278e --- /dev/null +++ b/widget/android/bindings/JavaExceptions-classes.txt @@ -0,0 +1,5 @@ +[java.lang.IllegalStateException = skip:true] +(Ljava/lang/String;)V = + +[java.lang.IllegalArgumentException = skip:true] +(Ljava/lang/String;)V = diff --git a/widget/android/bindings/KeyEvent-classes.txt b/widget/android/bindings/KeyEvent-classes.txt new file mode 100644 index 0000000000..6001a5025b --- /dev/null +++ b/widget/android/bindings/KeyEvent-classes.txt @@ -0,0 +1,3 @@ +# We only use constants from KeyEvent +[android.view.KeyEvent = skip:true] + = skip:false diff --git a/widget/android/bindings/MediaCodec-classes.txt b/widget/android/bindings/MediaCodec-classes.txt new file mode 100644 index 0000000000..81b02a66bb --- /dev/null +++ b/widget/android/bindings/MediaCodec-classes.txt @@ -0,0 +1,9 @@ +[android.media.MediaCodec = exceptionMode:nsresult] +[android.media.MediaCodec$BufferInfo = exceptionMode:nsresult] +[android.media.MediaCodec$CryptoInfo = exceptionMode:nsresult] + +# We only use constants from KeyStatus +[android.media.MediaDrm$KeyStatus = skip:true] + = skip:false + +[android.media.MediaFormat = exceptionMode:nsresult] diff --git a/widget/android/bindings/MotionEvent-classes.txt b/widget/android/bindings/MotionEvent-classes.txt new file mode 100644 index 0000000000..17874a16af --- /dev/null +++ b/widget/android/bindings/MotionEvent-classes.txt @@ -0,0 +1,3 @@ +# We only use constants from MotionEvent +[android.view.MotionEvent = skip:true] + = skip:false diff --git a/widget/android/bindings/SurfaceTexture-classes.txt b/widget/android/bindings/SurfaceTexture-classes.txt new file mode 100644 index 0000000000..782dee0836 --- /dev/null +++ b/widget/android/bindings/SurfaceTexture-classes.txt @@ -0,0 +1,2 @@ +[android.graphics.SurfaceTexture = exceptionMode:nsresult] +[android.view.Surface = exceptionMode:nsresult] diff --git a/widget/android/bindings/ViewConfiguration-classes.txt b/widget/android/bindings/ViewConfiguration-classes.txt new file mode 100644 index 0000000000..cf8689f25a --- /dev/null +++ b/widget/android/bindings/ViewConfiguration-classes.txt @@ -0,0 +1 @@ +[android.view.ViewConfiguration = exceptionMode:nsresult] diff --git a/widget/android/bindings/moz.build b/widget/android/bindings/moz.build new file mode 100644 index 0000000000..650cd8637c --- /dev/null +++ b/widget/android/bindings/moz.build @@ -0,0 +1,53 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("GeckoView", "General") + +# List of stems to generate .cpp and .h files for. To add a stem, add it to +# this list and ensure that $(stem)-classes.txt exists in this directory. +generated = [ + "AccessibilityEvent", + "AndroidBuild", + "AndroidGraphics", + "AndroidInputType", + "AndroidRect", + "InetAddress", + "JavaBuiltins", + "JavaExceptions", + "KeyEvent", + "MediaCodec", + "MotionEvent", + "SurfaceTexture", + "ViewConfiguration", +] + +SOURCES += ["!%s.cpp" % stem for stem in generated] + +EXPORTS += ["!%s.h" % stem for stem in generated] + +# The recursive make backend treats the first output specially: it's passed as +# an open FileAvoidWrite to the invoked script. That doesn't work well with +# the Gradle task that generates all of the outputs, so we add a dummy first +# output. +t = tuple( + ["sdk_bindings"] + + ["%s.cpp" % stem for stem in generated] + + ["%s.h" % stem for stem in generated] +) + +GeneratedFile( + *t, + script="/mobile/android/gradle.py", + entry_point="generate_sdk_bindings", + inputs=["%s-classes.txt" % stem for stem in generated] +) + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/widget/android", +] diff --git a/widget/android/components.conf b/widget/android/components.conf new file mode 100644 index 0000000000..089eefac51 --- /dev/null +++ b/widget/android/components.conf @@ -0,0 +1,114 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Headers = [ + '/widget/android/nsWidgetFactory.h', +] + +InitFunc = 'nsWidgetAndroidModuleCtor' +UnloadFunc = 'nsWidgetAndroidModuleDtor' + +Classes = [ + { + 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}', + 'contract_ids': ['@mozilla.org/widget/appshell/android;1'], + 'legacy_constructor': 'nsAppShellConstructor', + 'headers': ['/widget/android/nsWidgetFactory.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS, + }, + { + 'cid': '{c401eb80-f9ea-11d3-bb6f-e732b73ebe7c}', + 'contract_ids': ['@mozilla.org/gfx/screenmanager;1'], + 'singleton': True, + 'type': 'mozilla::widget::ScreenManager', + 'headers': ['mozilla/widget/ScreenManager.h'], + 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}', + 'contract_ids': ['@mozilla.org/widget/useridleservice;1'], + 'singleton': True, + 'type': 'nsUserIdleServiceAndroid', + 'headers': ['/widget/android/nsUserIdleServiceAndroid.h'], + 'constructor': 'nsUserIdleServiceAndroid::GetInstance', + }, + { + 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/transferable;1'], + 'type': 'nsTransferable', + 'headers': ['/widget/nsTransferable.h'], + }, + { + 'js_name': 'clipboard', + 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/clipboard;1'], + 'interfaces': ['nsIClipboard'], + 'type': 'nsClipboard', + 'headers': ['/widget/android/nsClipboard.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + 'overridable': True, + }, + { + 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}', + 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'], + 'type': 'nsClipboardHelper', + 'headers': ['/widget/nsClipboardHelper.h'], + }, + { + 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}', + 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'], + 'type': 'nsPrintSettingsServiceAndroid', + 'headers': ['/widget/android/nsPrintSettingsServiceAndroid.h'], + 'init_method': 'Init', + }, + { + 'cid': '{2f977d53-5485-11d4-87e2-0010a4e75ef2}', + 'contract_ids': ['@mozilla.org/gfx/printsession;1'], + 'type': 'nsPrintSession', + 'headers': ['/widget/nsPrintSession.h'], + 'init_method': 'Init', + }, + { + 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}', + 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'], + 'type': 'nsDeviceContextSpecAndroid', + 'headers': ['/widget/android/nsDeviceContextAndroid.h'], + }, + { + 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}', + 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'], + 'type': 'nsHTMLFormatConverter', + 'headers': ['/widget/nsHTMLFormatConverter.h'], + }, + { + 'cid': '{d755a760-9f27-11df-0800-200c9a664242}', + 'contract_ids': ['@mozilla.org/gfx/info;1'], + 'type': 'mozilla::widget::GfxInfo', + 'headers': ['/widget/android/GfxInfo.h'], + 'init_method': 'Init', + }, + { + 'js_name': 'androidBridge', + 'cid': '{0fe2321d-ebd9-467d-a743-03a68d40599e}', + 'contract_ids': ['@mozilla.org/android/bridge;1'], + 'interfaces': ['nsIAndroidBridge'], + 'type': 'nsAndroidBridge', + 'headers': ['/widget/android/AndroidBridge.h'], + }, + { + 'cid': '{e9cd2b7f-8386-441b-aaf5-0b371846bfd0}', + 'contract_ids': ['@mozilla.org/network/protocol;1?name=android'], + 'type': 'nsAndroidProtocolHandler', + 'headers': ['/widget/android/nsAndroidProtocolHandler.h'], + }, + { + 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}', + 'contract_ids': ['@mozilla.org/system-alerts-service;1'], + 'type': 'mozilla::widget::AndroidAlerts', + 'headers': ['/widget/android/AndroidAlerts.h'], + }, +] diff --git a/widget/android/jni/Accessors.h b/widget/android/jni/Accessors.h new file mode 100644 index 0000000000..7496cbcb5a --- /dev/null +++ b/widget/android/jni/Accessors.h @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_Accessors_h__ +#define mozilla_jni_Accessors_h__ + +#include + +#include "mozilla/jni/Refs.h" +#include "mozilla/jni/Types.h" +#include "mozilla/jni/Utils.h" +#include "AndroidBridge.h" + +namespace mozilla { +namespace jni { + +namespace detail { + +// Helper class to convert an arbitrary type to a jvalue, e.g. Value(123).val. +struct Value { + explicit Value(jboolean z) { val.z = z; } + explicit Value(jbyte b) { val.b = b; } + explicit Value(jchar c) { val.c = c; } + explicit Value(jshort s) { val.s = s; } + explicit Value(jint i) { val.i = i; } + explicit Value(jlong j) { val.j = j; } + explicit Value(jfloat f) { val.f = f; } + explicit Value(jdouble d) { val.d = d; } + explicit Value(jobject l) { val.l = l; } + + jvalue val; +}; + +} // namespace detail + +using namespace detail; + +// Base class for Method<>, Field<>, and Constructor<>. +class Accessor { + static void GetNsresult(JNIEnv* env, nsresult* rv) { + if (env->ExceptionCheck()) { +#ifdef MOZ_CHECK_JNI + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + *rv = NS_ERROR_FAILURE; + } else { + *rv = NS_OK; + } + } + + protected: + // Called after making a JNIEnv call. + template + static void EndAccess(const typename Traits::Owner::Context& ctx, + nsresult* rv) { + if (Traits::exceptionMode == ExceptionMode::ABORT) { + MOZ_CATCH_JNI_EXCEPTION(ctx.Env()); + + } else if (Traits::exceptionMode == ExceptionMode::NSRESULT) { + GetNsresult(ctx.Env(), rv); + } + } +}; + +// Member<> is used to call a JNI method given a traits class. +template +class Method : public Accessor { + typedef Accessor Base; + typedef typename Traits::Owner::Context Context; + + protected: + static jmethodID sID; + + static void BeginAccess(const Context& ctx) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT, + "Dispatching not supported for method call"); + + if (sID) { + return; + } + + if (Traits::isStatic) { + MOZ_ALWAYS_TRUE( + sID = AndroidBridge::GetStaticMethodID( + ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature)); + } else { + MOZ_ALWAYS_TRUE( + sID = AndroidBridge::GetMethodID(ctx.Env(), ctx.ClassRef(), + Traits::name, Traits::signature)); + } + } + + static void EndAccess(const Context& ctx, nsresult* rv) { + return Base::EndAccess(ctx, rv); + } + + public: + template + static ReturnType Call(const Context& ctx, nsresult* rv, + const Args&... args) { + JNIEnv* const env = ctx.Env(); + BeginAccess(ctx); + + jvalue jargs[] = {Value(TypeAdapter::FromNative(env, args)).val...}; + + auto result = TypeAdapter::ToNative( + env, Traits::isStatic ? (env->*TypeAdapter::StaticCall)( + ctx.ClassRef(), sID, jargs) + : (env->*TypeAdapter::Call)( + ctx.Get(), sID, jargs)); + + EndAccess(ctx, rv); + return result; + } +}; + +// Define sID member. +template +jmethodID Method::sID; + +// Specialize void because C++ forbids us from +// using a "void" temporary result variable. +template +class Method : public Method { + typedef Method Base; + typedef typename Traits::Owner::Context Context; + + public: + template + static void Call(const Context& ctx, nsresult* rv, const Args&... args) { + JNIEnv* const env = ctx.Env(); + Base::BeginAccess(ctx); + + jvalue jargs[] = {Value(TypeAdapter::FromNative(env, args)).val...}; + + if (Traits::isStatic) { + env->CallStaticVoidMethodA(ctx.ClassRef(), Base::sID, jargs); + } else { + env->CallVoidMethodA(ctx.Get(), Base::sID, jargs); + } + + Base::EndAccess(ctx, rv); + } +}; + +// Constructor<> is used to construct a JNI instance given a traits class. +template +class Constructor : protected Method { + typedef typename Traits::Owner::Context Context; + typedef typename Traits::ReturnType ReturnType; + typedef Method Base; + + public: + template + static ReturnType Call(const Context& ctx, nsresult* rv, + const Args&... args) { + JNIEnv* const env = ctx.Env(); + Base::BeginAccess(ctx); + + jvalue jargs[] = {Value(TypeAdapter::FromNative(env, args)).val...}; + + auto result = TypeAdapter::ToNative( + env, env->NewObjectA(ctx.ClassRef(), Base::sID, jargs)); + + Base::EndAccess(ctx, rv); + return result; + } +}; + +// Field<> is used to access a JNI field given a traits class. +template +class Field : public Accessor { + typedef Accessor Base; + typedef typename Traits::Owner::Context Context; + typedef typename Traits::ReturnType GetterType; + typedef typename Traits::SetterType SetterType; + + private: + static jfieldID sID; + + static void BeginAccess(const Context& ctx) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT, + "Dispatching not supported for field access"); + + if (sID) { + return; + } + + if (Traits::isStatic) { + MOZ_ALWAYS_TRUE( + sID = AndroidBridge::GetStaticFieldID( + ctx.Env(), ctx.ClassRef(), Traits::name, Traits::signature)); + } else { + MOZ_ALWAYS_TRUE(sID = AndroidBridge::GetFieldID(ctx.Env(), ctx.ClassRef(), + Traits::name, + Traits::signature)); + } + } + + static void EndAccess(const Context& ctx, nsresult* rv) { + return Base::EndAccess(ctx, rv); + } + + public: + static GetterType Get(const Context& ctx, nsresult* rv) { + JNIEnv* const env = ctx.Env(); + BeginAccess(ctx); + + auto result = TypeAdapter::ToNative( + env, Traits::isStatic + ? + + (env->*TypeAdapter::StaticGet)(ctx.ClassRef(), sID) + : + + (env->*TypeAdapter::Get)(ctx.Get(), sID)); + + EndAccess(ctx, rv); + return result; + } + + static void Set(const Context& ctx, nsresult* rv, SetterType val) { + JNIEnv* const env = ctx.Env(); + BeginAccess(ctx); + + if (Traits::isStatic) { + (env->*TypeAdapter::StaticSet)( + ctx.ClassRef(), sID, TypeAdapter::FromNative(env, val)); + } else { + (env->*TypeAdapter::Set)( + ctx.Get(), sID, TypeAdapter::FromNative(env, val)); + } + + EndAccess(ctx, rv); + } +}; + +// Define sID member. +template +jfieldID Field::sID; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Accessors_h__ diff --git a/widget/android/jni/Conversions.cpp b/widget/android/jni/Conversions.cpp new file mode 100644 index 0000000000..e8d5413ca3 --- /dev/null +++ b/widget/android/jni/Conversions.cpp @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "Conversions.h" +#include "JavaBuiltins.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" + +namespace mozilla { +namespace jni { + +template +jfieldID GetValueFieldID(JNIEnv* aEnv, const char* aType) { + const jfieldID id = aEnv->GetFieldID( + typename T::Context(aEnv, nullptr).ClassRef(), "value", aType); + aEnv->ExceptionClear(); + return id; +} + +// Cached locations of the primitive types within their standard boxed objects +// to skip doing that lookup on every get. +static jfieldID gBooleanValueField; +static jfieldID gIntValueField; +static jfieldID gDoubleValueField; + +void InitConversionStatics() { + MOZ_ASSERT(NS_IsMainThread()); + JNIEnv* const env = jni::GetGeckoThreadEnv(); + gBooleanValueField = GetValueFieldID(env, "Z"); + gIntValueField = GetValueFieldID(env, "I"); + gDoubleValueField = GetValueFieldID(env, "D"); +} + +template <> +bool Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + MOZ_ASSERT(aData.IsInstanceOf()); + + bool result = false; + if (gBooleanValueField) { + if (!aEnv) { + aEnv = jni::GetEnvForThread(); + } + result = + aEnv->GetBooleanField(aData.Get(), gBooleanValueField) != JNI_FALSE; + MOZ_CATCH_JNI_EXCEPTION(aEnv); + } else { + result = java::sdk::Boolean::Ref::From(aData)->BooleanValue(); + } + + return result; +} + +template <> +int Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + MOZ_ASSERT(aData.IsInstanceOf()); + + int result = 0; + if (gIntValueField) { + if (!aEnv) { + aEnv = jni::GetEnvForThread(); + } + result = aEnv->GetIntField(aData.Get(), gIntValueField); + MOZ_CATCH_JNI_EXCEPTION(aEnv); + } else { + result = java::sdk::Number::Ref::From(aData)->IntValue(); + } + + return result; +} + +template <> +double Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + MOZ_ASSERT(aData.IsInstanceOf()); + + double result = 0; + if (gDoubleValueField) { + if (!aEnv) { + aEnv = jni::GetEnvForThread(); + } + result = aEnv->GetDoubleField(aData.Get(), gDoubleValueField); + MOZ_CATCH_JNI_EXCEPTION(aEnv); + } else { + result = java::sdk::Number::Ref::From(aData)->DoubleValue(); + } + + return result; +} + +template <> +ipc::LaunchError Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + return ipc::LaunchError{}; +} + +template <> +nsString Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + nsString result; + if (aData != NULL && aData.IsInstanceOf()) { + result = jni::String::Ref::From(aData)->ToString(); + } + return result; +} + +} // namespace jni +} // namespace mozilla diff --git a/widget/android/jni/Conversions.h b/widget/android/jni/Conversions.h new file mode 100644 index 0000000000..1d9e20acc7 --- /dev/null +++ b/widget/android/jni/Conversions.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_Conversions_h__ +#define mozilla_jni_Conversions_h__ + +#include "mozilla/jni/Refs.h" + +namespace mozilla { +namespace jni { + +template +ArgType Java2Native(mozilla::jni::Object::Param, JNIEnv* aEnv = nullptr); + +void InitConversionStatics(); + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Conversions_h__ diff --git a/widget/android/jni/GeckoBundleUtils.h b/widget/android/jni/GeckoBundleUtils.h new file mode 100644 index 0000000000..df62b0c760 --- /dev/null +++ b/widget/android/jni/GeckoBundleUtils.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_GeckoBundleUtils_h +#define mozilla_jni_GeckoBundleUtils_h + +#include "mozilla/java/GeckoBundleWrappers.h" + +namespace mozilla { +namespace jni { + +#define GECKOBUNDLE_START(name) \ + nsTArray _##name##_keys; \ + nsTArray _##name##_values; + +#define GECKOBUNDLE_PUT(name, key, value) \ + _##name##_keys.AppendElement( \ + jni::StringParam(NS_LITERAL_STRING_FROM_CSTRING(key))); \ + _##name##_values.AppendElement(value); + +#define GECKOBUNDLE_FINISH(name) \ + MOZ_ASSERT(_##name##_keys.Length() == _##name##_values.Length()); \ + auto _##name##_jkeys = \ + jni::ObjectArray::New(_##name##_keys.Length()); \ + auto _##name##_jvalues = \ + jni::ObjectArray::New(_##name##_values.Length()); \ + for (size_t i = 0; \ + i < _##name##_keys.Length() && i < _##name##_values.Length(); i++) { \ + _##name##_jkeys->SetElement(i, _##name##_keys.ElementAt(i)); \ + _##name##_jvalues->SetElement(i, _##name##_values.ElementAt(i)); \ + } \ + auto name = \ + mozilla::java::GeckoBundle::New(_##name##_jkeys, _##name##_jvalues); + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_GeckoBundleUtils_h diff --git a/widget/android/jni/GeckoResultUtils.h b/widget/android/jni/GeckoResultUtils.h new file mode 100644 index 0000000000..eee56d41c3 --- /dev/null +++ b/widget/android/jni/GeckoResultUtils.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_GeckoResultUtils_h +#define mozilla_jni_GeckoResultUtils_h + +#include "mozilla/java/GeckoResultNatives.h" +#include "mozilla/jni/Conversions.h" + +namespace mozilla { +namespace jni { + +// C++-side object bound to Java's GeckoResult.GeckoCallback. +// +// Note that we can't template this class because that breaks JNI dispatch +// (surprisingly: it compiles, but selects the wrong method specialization +// during dispatch). So instead we use a templated factory function, which +// bundles the per-ArgType conversion logic into the callback. +class GeckoResultCallback final + : public java::GeckoResult::GeckoCallback::Natives { + public: + typedef java::GeckoResult::GeckoCallback::Natives Base; + typedef std::function OuterCallback; + + void Call(mozilla::jni::Object::Param aArg) { mCallback(aArg); } + + template + static java::GeckoResult::GeckoCallback::LocalRef CreateAndAttach( + std::function&& aInnerCallback) { + auto java = java::GeckoResult::GeckoCallback::New(); + OuterCallback outerCallback = + [inner{std::move(aInnerCallback)}](mozilla::jni::Object::Param aParam) { + ArgType converted = Java2Native(aParam); + inner(converted); + }; + auto native = MakeUnique(std::move(outerCallback)); + Base::AttachNative(java, std::move(native)); + return java; + } + + explicit GeckoResultCallback(OuterCallback&& aCallback) + : mCallback(std::move(aCallback)) {} + + private: + OuterCallback mCallback; +}; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_GeckoResultUtils_h diff --git a/widget/android/jni/Natives.h b/widget/android/jni/Natives.h new file mode 100644 index 0000000000..fc243a0e97 --- /dev/null +++ b/widget/android/jni/Natives.h @@ -0,0 +1,1615 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_Natives_h__ +#define mozilla_jni_Natives_h__ + +#include + +#include +#include + +#include "mozilla/RefPtr.h" +#include "mozilla/RWLock.h" +#include "mozilla/Tuple.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/jni/Accessors.h" +#include "mozilla/jni/Refs.h" +#include "mozilla/jni/Types.h" +#include "mozilla/jni/Utils.h" +#include "nsThreadUtils.h" + +struct NativeException { + const char* str; +}; + +template +static NativeException NullHandle() { + return {__func__}; +} + +template +static NativeException NullWeakPtr() { + return {__func__}; +} + +namespace mozilla { +namespace jni { + +/** + * C++ classes implementing instance (non-static) native methods can choose + * from one of two ownership models, when associating a C++ object with a Java + * instance. + * + * * If the C++ class inherits from mozilla::SupportsWeakPtr, weak pointers + * will be used. The Java instance will store and own the pointer to a + * WeakPtr object. The C++ class itself is otherwise not owned or directly + * referenced. Note that mozilla::SupportsWeakPtr only supports being used on + * a single thread. To attach a Java instance to a C++ instance, pass in a + * mozilla::SupportsWeakPtr pointer to the C++ class (i.e. MyClass*). + * + * class MyClass : public SupportsWeakPtr + * , public MyJavaClass::Natives + * { + * // ... + * + * public: + * using MyJavaClass::Natives::DisposeNative; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives::AttachNative( + * instance, static_cast(this)); + * + * // "instance" does NOT own "this", so the C++ object + * // lifetime is separate from the Java object lifetime. + * } + * }; + * + * * If the C++ class contains public members AddRef() and Release(), the Java + * instance will store and own the pointer to a RefPtr object, which holds a + * strong reference on the C++ instance. Normal ref-counting considerations + * apply in this case; for example, disposing may cause the C++ instance to + * be deleted and the destructor to be run on the current thread, which may + * not be desirable. To attach a Java instance to a C++ instance, pass in a + * pointer to the C++ class (i.e. MyClass*). + * + * class MyClass : public RefCounted + * , public MyJavaClass::Natives + * { + * // ... + * + * public: + * using MyJavaClass::Natives::DisposeNative; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives::AttachNative(instance, this); + * + * // "instance" owns "this" through the RefPtr, so the C++ object + * // may be destroyed as soon as instance.disposeNative() is called. + * } + * }; + * + * * In other cases, the Java instance will store and own a pointer to the C++ + * object itself. This pointer must not be stored or deleted elsewhere. To + * attach a Java instance to a C++ instance, pass in a reference to a + * UniquePtr of the C++ class (i.e. UniquePtr). + * + * class MyClass : public MyJavaClass::Natives + * { + * // ... + * + * public: + * using MyJavaClass::Natives::DisposeNative; + * + * static void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives::AttachNative( + * instance, mozilla::MakeUnique()); + * + * // "instance" owns the newly created C++ object, so the C++ + * // object is destroyed as soon as instance.disposeNative() is + * // called. + * } + * }; + */ + +namespace detail { + +/** + * Type trait that determines whether a given class has a member named + * T::OnWeakNonIntrusiveDetach. + * + * Example usage: + * class Foo {}; + * class Bar { + * public: + * void OnWeakNonIntrusiveDetach(already_AddRefed aRunnable); + * }; + * + * constexpr bool foo = HasWeakNonIntrusiveDetach::value; // Expect false + * constexpr bool bar = HasWeakNonIntrusiveDetach::value; // Expect true + */ +template > +struct HasWeakNonIntrusiveDetach : std::false_type {}; + +template +struct HasWeakNonIntrusiveDetach< + T, std::void_t().OnWeakNonIntrusiveDetach( + std::declval>()))>> : std::true_type { +}; + +/** + * Type trait that determines whether a given class is refcounted, ie. it has + * both T::AddRef and T::Release methods. + * + * Example usage: + * class Foo {}; + * class Bar { + * public: + * void AddRef(); + * void Release(); + * }; + * + * constexpr bool foo = IsRefCounted::value; // Expect false + * constexpr bool bar = IsRefCounted::value; // Expect true + */ +template > +struct IsRefCounted : std::false_type {}; + +template +struct IsRefCounted().AddRef(), + std::declval().Release())>> + : std::true_type {}; + +/** + * This enum is used for classifying the type of pointer that is stored + * within a NativeWeakPtr. This classification is different from the one used + * for normal native pointers. + */ +enum class NativePtrInternalType : size_t { + OWNING = 1, + WEAK = 2, + REFPTR = 3, +}; + +/** + * NativePtrInternalPicker uses some C++ SFINAE template-fu to figure out + * what type of pointer the class specified by Impl needs to be. + * + * It does this by supplying multiple overloads of a method named Test. + * Various overloads are enabled or disabled depending on whether or not Impl + * can possibly support them. + * + * Each overload "returns" a reference to an array whose size corresponds to the + * value of each enum in NativePtrInternalType. That size is then converted back + * to the enum value, yielding the right type. + */ +template +class NativePtrInternalPicker { + // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK + template + static std::enable_if_t< + std::is_base_of::value, + char (&)[static_cast(NativePtrInternalType::WEAK)]> + Test(char); + + // Enable if Impl implements AddRef and Release, yielding type REFPTR + template + static char (&Test(int))[static_cast(NativePtrInternalType::REFPTR)]; + + // This overload uses '...' as its param to make its arguments less specific; + // the compiler prefers more-specific overloads to less-specific ones. + // OWNING is the fallback type. + template + static char (&Test(...))[static_cast(NativePtrInternalType::OWNING)]; + + public: + // Given a hypothetical function call Test, convert the size of its + // resulting array back into a NativePtrInternalType enum value. + static const NativePtrInternalType value = static_cast( + sizeof(Test('\0')) / sizeof(char)); +}; + +/** + * This enum is used for classifying the type of pointer that is stored in a + * JNIObject's handle. + * + * We have two different weak pointer types: + * * WEAK_INTRUSIVE is a pointer to a class that derives from + * mozilla::SupportsWeakPtr. + * * WEAK_NON_INTRUSIVE is a pointer to a class that does not have any + * internal support for weak pointers, but does supply a + * OnWeakNonIntrusiveDetach method. + */ +enum class NativePtrType : size_t { + OWNING = 1, + WEAK_INTRUSIVE = 2, + WEAK_NON_INTRUSIVE = 3, + REFPTR = 4, +}; + +/** + * NativePtrPicker uses some C++ SFINAE template-fu to figure out what type of + * pointer the class specified by Impl needs to be. + * + * It does this by supplying multiple overloads of a method named Test. + * Various overloads are enabled or disabled depending on whether or not Impl + * can possibly support them. + * + * Each overload "returns" a reference to an array whose size corresponds to the + * value of each enum in NativePtrInternalType. That size is then converted back + * to the enum value, yielding the right type. + */ +template +class NativePtrPicker { + // Just shorthand for each overload's return type + template + using ResultTypeT = char (&)[static_cast(PtrType)]; + + // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE + template + static auto Test(void*) + -> std::enable_if_t::value, + ResultTypeT>; + + // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type + // WEAK_NON_INTRUSIVE + template + static auto Test(void*) + -> std::enable_if_t::value, + ResultTypeT>; + + // We want the WEAK_NON_INTRUSIVE overload to take precedence over this one, + // so we only enable this overload if Impl is refcounted AND it does not + // implement OnWeakNonIntrusiveDetach. Yields type REFPTR. + template + static auto Test(void*) -> std::enable_if_t< + std::conjunction_v, + std::negation>>, + ResultTypeT>; + + // This overload uses '...' as its param to make its arguments less specific; + // the compiler prefers more-specific overloads to less-specific ones. + // OWNING is the fallback type. + template + static char (&Test(...))[static_cast(NativePtrType::OWNING)]; + + public: + // Given a hypothetical function call Test, convert the size of its + // resulting array back into a NativePtrType enum value. + static const NativePtrType value = + static_cast(sizeof(Test(nullptr))); +}; + +template +inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) { + if (!handle) { + if (!env->ExceptionCheck()) { + ThrowException(env, "java/lang/NullPointerException", + NullHandle().str); + } + return 0; + } + return handle; +} + +/** + * This struct is used to describe various traits of a native pointer of type + * Impl that will be attached to a JNIObject. + * + * See the definition of the NativePtrType::OWNING specialization for comments + * describing the required fields. + */ +template ::value> +struct NativePtrTraits; + +template +struct NativePtrTraits { + using AccessorType = + Impl*; // Pointer-like type returned by Access() (an actual pointer in + // this case, but this is not strictly necessary) + using HandleType = Impl*; // Type of the pointer stored in JNIObject.mHandle + using RefType = Impl*; // Type of the pointer returned by Get() + + /** + * Returns a RefType to the native implementation belonging to + * the given Java object. + */ + static RefType Get(JNIEnv* env, jobject instance) { + static_assert( + std::is_same::value, + "HandleType and RefType must be identical for owning pointers"); + return reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + } + + /** + * Returns a RefType to the native implementation belonging to + * the given Java object. + */ + template + static RefType Get(const LocalRef& instance) { + return Get(instance.Env(), instance.Get()); + } + + /** + * Given a RefType, returns the pointer-like AccessorType used for + * manipulating the native object. + */ + static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) { + static_assert( + std::is_same::value, + "AccessorType and RefType must be identical for owning pointers"); + return aImpl; + } + + /** + * Set the JNIObject's handle to the provided pointer, clearing any previous + * handle if necessary. + */ + template + static void Set(const LocalRef& instance, UniquePtr&& ptr) { + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), + reinterpret_cast(ptr.release())); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + /** + * Clear the JNIObject's handle. + */ + template + static void Clear(const LocalRef& instance) { + UniquePtr ptr(reinterpret_cast( + GetNativeHandle(instance.Env(), instance.Get()))); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + + if (ptr) { + SetNativeHandle(instance.Env(), instance.Get(), 0); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + } +}; + +template +struct NativePtrTraits { + using AccessorType = Impl*; + using HandleType = WeakPtr*; + using RefType = WeakPtr; + + static RefType Get(JNIEnv* env, jobject instance) { + const auto ptr = reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + return *ptr; + } + + template + static RefType Get(const LocalRef& instance) { + return Get(instance.Env(), instance.Get()); + } + + static AccessorType Access(RefType aPtr, JNIEnv* aEnv = nullptr) { + AccessorType const impl = *aPtr; + if (!impl) { + JNIEnv* env = aEnv ? aEnv : mozilla::jni::GetEnvForThread(); + ThrowException(env, "java/lang/NullPointerException", + NullWeakPtr().str); + } + + return impl; + } + + template + static void Set(const LocalRef& instance, Impl* ptr) { + // Create the new handle first before clearing any old handle, so the + // new handle is guaranteed to have different value than any old handle. + const uintptr_t handle = + reinterpret_cast(new WeakPtr(ptr)); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template + static void Clear(const LocalRef& instance) { + const auto ptr = reinterpret_cast( + GetNativeHandle(instance.Env(), instance.Get())); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + + if (ptr) { + SetNativeHandle(instance.Env(), instance.Get(), 0); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + delete ptr; + } + } +}; + +template +struct NativePtrTraits { + using AccessorType = Impl*; + using HandleType = RefPtr*; + using RefType = Impl*; + + static RefType Get(JNIEnv* env, jobject instance) { + const auto ptr = reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + if (!ptr) { + return nullptr; + } + + MOZ_ASSERT(*ptr); + return *ptr; + } + + template + static RefType Get(const LocalRef& instance) { + return Get(instance.Env(), instance.Get()); + } + + static AccessorType Access(RefType aImpl, JNIEnv* aEnv = nullptr) { + static_assert(std::is_same::value, + "AccessorType and RefType must be identical for refpointers"); + return aImpl; + } + + template + static void Set(const LocalRef& instance, RefType ptr) { + // Create the new handle first before clearing any old handle, so the + // new handle is guaranteed to have different value than any old handle. + const uintptr_t handle = reinterpret_cast(new RefPtr(ptr)); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template + static void Clear(const LocalRef& instance) { + const auto ptr = reinterpret_cast( + GetNativeHandle(instance.Env(), instance.Get())); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + + if (ptr) { + SetNativeHandle(instance.Env(), instance.Get(), 0); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + delete ptr; + } + } +}; + +} // namespace detail + +// Forward declarations +template +class NativeWeakPtr; +template +class NativeWeakPtrHolder; + +namespace detail { + +/** + * Given the class of a native implementation, as well as its + * NativePtrInternalType, resolve traits for that type that will be used by + * the NativeWeakPtrControlBlock. + * + * Note that we only implement specializations for OWNING and REFPTR types, + * as a WEAK_INTRUSIVE type should not be using NativeWeakPtr anyway. The build + * will fail if such an attempt is made. + * + * Traits need to implement two things: + * 1. A |Type| field that resolves to a pointer type to be stored in the + * JNIObject's handle. It is assumed that setting a |Type| object to nullptr + * is sufficient to delete the underlying object. + * 2. A static |AsRaw| method that converts a pointer of |Type| into a raw + * pointer. + */ +template < + typename NativeImpl, + NativePtrInternalType PtrType = + ::mozilla::jni::detail::NativePtrInternalPicker::value> +struct NativeWeakPtrControlBlockStorageTraits; + +template +struct NativeWeakPtrControlBlockStorageTraits< + NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> { + using Type = UniquePtr; + + static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } +}; + +template +struct NativeWeakPtrControlBlockStorageTraits< + NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> { + using Type = RefPtr; + + static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } +}; + +// Forward Declaration +template +class Accessor; + +/** + * This class contains the shared data that is referenced by all NativeWeakPtr + * objects that reference the same object. + * + * It retains a WeakRef to the Java object that owns this native object. + * It uses a RWLock to control access to the native pointer itself. + * Read locks are used when accessing the pointer (even when calling non-const + * methods on the native object). + * A write lock is only used when it is time to destroy the native object and + * we need to clear the value of mNativeImpl. + */ +template +class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final { + public: + using StorageTraits = NativeWeakPtrControlBlockStorageTraits; + using StorageType = typename StorageTraits::Type; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeWeakPtrControlBlock) + + NativeWeakPtrControlBlock(const NativeWeakPtrControlBlock&) = delete; + NativeWeakPtrControlBlock(NativeWeakPtrControlBlock&&) = delete; + NativeWeakPtrControlBlock& operator=(const NativeWeakPtrControlBlock&) = + delete; + NativeWeakPtrControlBlock& operator=(NativeWeakPtrControlBlock&&) = delete; + + // This is safe to call on any thread because mJavaOwner is immutable. + mozilla::jni::Object::WeakRef GetJavaOwner() const { return mJavaOwner; } + + private: + NativeWeakPtrControlBlock(::mozilla::jni::Object::Param aJavaOwner, + StorageType&& aNativeImpl) + : mJavaOwner(aJavaOwner), + mLock("mozilla::jni::detail::NativeWeakPtrControlBlock"), + mNativeImpl(std::move(aNativeImpl)) {} + + ~NativeWeakPtrControlBlock() { + // Make sure that somebody, somewhere, has detached us before destroying. + MOZ_ASSERT(!(*this)); + } + + /** + * Clear the native pointer so that subsequent accesses to the native pointer + * via this control block are no longer available. + * + * We return the native pointer to the caller so that it may proceed with + * cleaning up its resources. + */ + StorageType Clear() { + StorageType nativeImpl(nullptr); + + { // Scope for lock + AutoWriteLock lock(mLock); + std::swap(mNativeImpl, nativeImpl); + } + + return nativeImpl; + } + + void Lock() const { mLock.ReadLock(); } + + void Unlock() const { mLock.ReadUnlock(); } + +#if defined(DEBUG) + // This is kind of expensive, so we only support it in debug builds. + explicit operator bool() const { + AutoReadLock lock(mLock); + return !!mNativeImpl; + } +#endif // defined(DEBUG) + + private: + friend class Accessor; + friend class NativeWeakPtr; + friend class NativeWeakPtrHolder; + + private: + const mozilla::jni::Object::WeakRef mJavaOwner; + mutable RWLock mLock; // Protects mNativeImpl + StorageType mNativeImpl; +}; + +/** + * When a NativeWeakPtr is detached from its owning Java object, the calling + * thread invokes the implementation's OnWeakNonIntrusiveDetach to perform + * cleanup. We complete the remainder of the cleanup sequence on the Gecko + * main thread by expecting OnWeakNonIntrusiveDetach implementations to invoke + * this Runnable before exiting. It will move itself to the main thread if it + * is not already there. + */ +template +class NativeWeakPtrDetachRunnable final : public Runnable { + public: + NativeWeakPtrDetachRunnable( + already_AddRefed> aCtlBlock, + const Object::LocalRef& aOwner, + typename NativeWeakPtrControlBlockStorageTraits::Type + aNativeImpl) + : Runnable("mozilla::jni::detail::NativeWeakPtrDetachRunnable"), + mCtlBlock(aCtlBlock), + mOwner(aOwner), + mNativeImpl(std::move(aNativeImpl)), + mHasRun(false) { + MOZ_RELEASE_ASSERT(!!mCtlBlock); + MOZ_RELEASE_ASSERT(!!mNativeImpl); + } + + NS_INLINE_DECL_REFCOUNTING_INHERITED(NativeWeakPtrDetachRunnable, Runnable) + + NS_IMETHOD Run() override { + mHasRun = true; + + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(this); + return NS_OK; + } + + // Get the owner object's native implementation + auto owner = ToLocalRef(mOwner); + auto attachedNativeImpl = NativePtrTraits::Get(owner); + MOZ_RELEASE_ASSERT(!!attachedNativeImpl); + + // NativePtrTraits::ClearFinish cleans out the JNIObject's handle, which + // obviously we don't want to attempt unless that handle still points to + // our native implementation. + if (attachedNativeImpl->IsSame(mCtlBlock)) { + NativePtrTraits::ClearFinish(owner); + } + + // Now we destroy that native object. + mNativeImpl = nullptr; + return NS_OK; + } + + private: + ~NativeWeakPtrDetachRunnable() { + // Guard against somebody forgetting to call this runnable. + MOZ_RELEASE_ASSERT(mHasRun, "You must run/dispatch this runnable!"); + } + + private: + RefPtr> mCtlBlock; + Object::GlobalRef mOwner; + typename NativeWeakPtrControlBlockStorageTraits::Type mNativeImpl; + bool mHasRun; +}; + +/** + * If you want to temporarily access the object held by a NativeWeakPtr, you + * must obtain one of these Accessor objects from the pointer. Access must + * be done _exclusively_ using once of these objects! + */ +template +class MOZ_STACK_CLASS Accessor final { + public: + ~Accessor() { + if (mCtlBlock) { + mCtlBlock->Unlock(); + } + } + + // Check whether the object is still valid before doing anything else + explicit operator bool() const { return mCtlBlock && mCtlBlock->mNativeImpl; } + + // Normal member access + NativeImpl* operator->() const { + return NativeWeakPtrControlBlockStorageTraits::AsRaw( + mCtlBlock->mNativeImpl); + } + + // This allows us to support calling a pointer to a member function + template + auto operator->*(Member aMember) const { + NativeImpl* impl = + NativeWeakPtrControlBlockStorageTraits::AsRaw( + mCtlBlock->mNativeImpl); + return [impl, member = aMember](auto&&... aArgs) { + return (impl->*member)(std::forward(aArgs)...); + }; + } + + // Only available for NativeImpl types that actually use refcounting. + // The idea here is that it should be possible to obtain a strong ref from + // a NativeWeakPtr if and only if NativeImpl supports refcounting. + template + auto AsRefPtr() const -> std::enable_if_t::value, RefPtr> { + MOZ_ASSERT(I::HasThreadSafeRefCnt::value || NS_IsMainThread()); + return mCtlBlock->mNativeImpl; + } + + Accessor(const Accessor&) = delete; + Accessor(Accessor&&) = delete; + Accessor& operator=(const Accessor&) = delete; + Accessor& operator=(Accessor&&) = delete; + + private: + explicit Accessor( + const RefPtr>& aCtlBlock) + : mCtlBlock(aCtlBlock) { + if (aCtlBlock) { + aCtlBlock->Lock(); + } + } + + private: + friend class NativeWeakPtr; + friend class NativeWeakPtrHolder; + + private: + const RefPtr> mCtlBlock; +}; + +} // namespace detail + +/** + * This class implements support for thread-safe weak pointers to native objects + * that are owned by Java objects deriving from JNIObject. + * + * Any code that wants to access such a native object must have a copy of + * a NativeWeakPtr to that object. + */ +template +class NativeWeakPtr { + public: + using Accessor = detail::Accessor; + + /** + * Call this method to access the underlying object referenced by this + * NativeWeakPtr. + * + * Always check the returned Accessor object for availability before calling + * methods on it. + * + * For example, given: + * + * NativeWeakPtr foo; + * auto accessor = foo.Access(); + * if (accessor) { + * // Okay, safe to work with + * accessor->DoStuff(); + * } else { + * // The object's strong reference was cleared and is no longer available! + * } + */ + Accessor Access() const { return Accessor(mCtlBlock); } + + /** + * Detach the underlying object's strong reference from its owning Java object + * and clean it up. + */ + void Detach() { + if (!IsAttached()) { + // Never attached to begin with; no-op + return; + } + + auto native = mCtlBlock->Clear(); + if (!native) { + // Detach already in progress + return; + } + + Object::LocalRef owner(mCtlBlock->GetJavaOwner()); + MOZ_RELEASE_ASSERT(!!owner); + + // Save the raw pointer before we move native into the runnable so that we + // may call OnWeakNonIntrusiveDetach on it even after moving native into + // the runnable. + NativeImpl* rawImpl = + detail::NativeWeakPtrControlBlock::StorageTraits::AsRaw( + native); + rawImpl->OnWeakNonIntrusiveDetach( + do_AddRef(new NativeWeakPtrDetachRunnable( + mCtlBlock.forget(), owner, std::move(native)))); + } + + /** + * This method does not indicate whether or not the weak pointer is still + * valid; it only indicates whether we're actually attached to one. + */ + bool IsAttached() const { return !!mCtlBlock; } + + /** + * Does this pointer reference the same object as the one referenced by the + * provided Accessor? + */ + bool IsSame(const Accessor& aAccessor) const { + return mCtlBlock == aAccessor.mCtlBlock; + } + + /** + * Does this pointer reference the same object as the one referenced by the + * provided Control Block? + */ + bool IsSame(const RefPtr>& + aOther) const { + return mCtlBlock == aOther; + } + + NativeWeakPtr() = default; + MOZ_IMPLICIT NativeWeakPtr(decltype(nullptr)) {} + NativeWeakPtr(const NativeWeakPtr& aOther) = default; + NativeWeakPtr(NativeWeakPtr&& aOther) = default; + NativeWeakPtr& operator=(const NativeWeakPtr& aOther) = default; + NativeWeakPtr& operator=(NativeWeakPtr&& aOther) = default; + + NativeWeakPtr& operator=(decltype(nullptr)) { + mCtlBlock = nullptr; + return *this; + } + + protected: + // Construction of initial NativeWeakPtr for aCtlBlock + explicit NativeWeakPtr( + already_AddRefed> aCtlBlock) + : mCtlBlock(aCtlBlock) {} + + private: + // Construction of subsequent NativeWeakPtrs for aCtlBlock + explicit NativeWeakPtr( + const RefPtr>& aCtlBlock) + : mCtlBlock(aCtlBlock) {} + + friend class NativeWeakPtrHolder; + + protected: + RefPtr> mCtlBlock; +}; + +/** + * A pointer to an instance of this class should be stored in a Java object's + * JNIObject handle. New instances of native objects wrapped by NativeWeakPtr + * are created using the static methods of this class. + * + * Why do we have distinct methods here instead of using AttachNative like other + * pointer types that may be stored in JNIObject? + * + * Essentially, we want the creation and use of NativeWeakPtr to be as + * deliberate as possible. Forcing a different creation mechanism is part of + * that emphasis. + * + * Example: + * + * class NativeFoo { + * public: + * NativeFoo(); + * void Bar(); + * // The following method is required to be used with NativeWeakPtr + * void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer); + * }; + * + * java::Object::LocalRef javaObj(...); + * + * // Create a new Foo that is attached to javaObj + * auto weakFoo = NativeWeakPtrHolder::Attach(javaObj); + * + * // Now I can save weakFoo, access it, do whatever I want + * if (auto accWeakFoo = weakFoo.Access()) { + * accWeakFoo->Bar(); + * } + * + * // Detach from javaObj and clean up + * weakFoo.Detach(); + */ +template +class MOZ_HEAP_CLASS NativeWeakPtrHolder final + : public NativeWeakPtr { + using Base = NativeWeakPtr; + + public: + using Accessor = typename Base::Accessor; + using StorageTraits = + typename detail::NativeWeakPtrControlBlock::StorageTraits; + using StorageType = typename StorageTraits::Type; + + /** + * Create a new NativeImpl object, wrap it in a NativeWeakPtr, and store it + * in the Java object's JNIObject handle. + * + * @return A NativeWeakPtr object that references the newly-attached object. + */ + template + static NativeWeakPtr Attach(const Ref& aJavaObject, + Args&&... aArgs) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + StorageType nativeImpl(new NativeImpl(std::forward(aArgs)...)); + return AttachInternal(aJavaObject, std::move(nativeImpl)); + } + + /** + * Given a new NativeImpl object, wrap it in a NativeWeakPtr, and store it + * in the Java object's JNIObject handle. + * + * @return A NativeWeakPtr object that references the newly-attached object. + */ + template + static NativeWeakPtr AttachExisting( + const Ref& aJavaObject, + already_AddRefed aNativeImpl) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + StorageType nativeImpl(aNativeImpl); + return AttachInternal(aJavaObject, std::move(nativeImpl)); + } + + ~NativeWeakPtrHolder() = default; + + MOZ_IMPLICIT NativeWeakPtrHolder(decltype(nullptr)) = delete; + NativeWeakPtrHolder(const NativeWeakPtrHolder&) = delete; + NativeWeakPtrHolder(NativeWeakPtrHolder&&) = delete; + NativeWeakPtrHolder& operator=(const NativeWeakPtrHolder&) = delete; + NativeWeakPtrHolder& operator=(NativeWeakPtrHolder&&) = delete; + NativeWeakPtrHolder& operator=(decltype(nullptr)) = delete; + + private: + template + NativeWeakPtrHolder(const LocalRef& aJavaObject, + StorageType&& aNativeImpl) + : NativeWeakPtr( + do_AddRef(new NativeWeakPtrControlBlock( + aJavaObject, std::move(aNativeImpl)))) {} + + /** + * Internal function that actually wraps the native pointer, binds it to the + * JNIObject, and then returns the NativeWeakPtr result. + */ + template + static NativeWeakPtr AttachInternal( + const Ref& aJavaObject, StorageType&& aPtr) { + auto localJavaObject = ToLocalRef(aJavaObject); + NativeWeakPtrHolder* holder = + new NativeWeakPtrHolder(localJavaObject, std::move(aPtr)); + static_assert( + NativePtrPicker::value == NativePtrType::WEAK_NON_INTRUSIVE, + "This type is not compatible with mozilla::jni::NativeWeakPtr"); + NativePtrTraits::Set(localJavaObject, holder); + return NativeWeakPtr(holder->mCtlBlock); + } +}; + +namespace detail { + +/** + * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type. + */ +template +struct NativePtrTraits { + using AccessorType = typename NativeWeakPtrHolder::Accessor; + using HandleType = NativeWeakPtrHolder*; + using RefType = NativeWeakPtrHolder* const; + + static RefType Get(JNIEnv* env, jobject instance) { + return GetHandle(env, instance); + } + + template + static RefType Get(const LocalRef& instance) { + return GetHandle(instance.Env(), instance.Get()); + } + + static AccessorType Access(RefType aPtr) { return aPtr->Access(); } + + template + static void Set(const LocalRef& instance, HandleType ptr) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + const uintptr_t handle = reinterpret_cast(ptr); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template + static void Clear(const LocalRef& instance) { + auto ptr = reinterpret_cast( + GetNativeHandle(instance.Env(), instance.Get())); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + + if (!ptr) { + return; + } + + ptr->Detach(); + } + + // This call is not safe to do unless we know for sure that instance's + // native handle has not changed. It is up to NativeWeakPtrDetachRunnable + // to perform this check. + template + static void ClearFinish(const LocalRef& instance) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + JNIEnv* const env = instance.Env(); + auto ptr = + reinterpret_cast(GetNativeHandle(env, instance.Get())); + MOZ_CATCH_JNI_EXCEPTION(env); + MOZ_RELEASE_ASSERT(!!ptr); + + SetNativeHandle(env, instance.Get(), 0); + MOZ_CATCH_JNI_EXCEPTION(env); + // Deletion of ptr is done by the caller + } + + // The call is stale if the native object has been destroyed on the + // Gecko side, but the Java object is still attached to it through + // a weak pointer. Stale calls should be discarded. Note that it's + // an error if holder is nullptr here; we return false but the + // native call will throw an error. + template + static bool IsStale(const LocalRef& instance) { + JNIEnv* const env = mozilla::jni::GetEnvForThread(); + + // We cannot use Get here because that method throws an exception when the + // object is null, which is a valid state for a stale call. + const auto holder = + reinterpret_cast(GetNativeHandle(env, instance.Get())); + MOZ_CATCH_JNI_EXCEPTION(env); + + if (!holder || !holder->IsAttached()) { + return true; + } + + auto acc(holder->Access()); + return !acc; + } + + private: + static HandleType GetHandle(JNIEnv* env, jobject instance) { + return reinterpret_cast( + CheckNativeHandle(env, GetNativeHandle(env, instance))); + } + + template + static HandleType GetHandle(const LocalRef& instance) { + return GetHandle(instance.Env(), instance.Get()); + } + + friend class NativeWeakPtrHolder; +}; + +} // namespace detail + +using namespace detail; + +/** + * For JNI native methods that are dispatched to a proxy, i.e. using + * @WrapForJNI(dispatchTo = "proxy"), the implementing C++ class must provide a + * OnNativeCall member. Subsequently, every native call is automatically + * wrapped in a functor object, and the object is passed to OnNativeCall. The + * OnNativeCall implementation can choose to invoke the call, save it, dispatch + * it to a different thread, etc. Each copy of functor may only be invoked + * once. + * + * class MyClass : public MyJavaClass::Natives + * { + * // ... + * + * template + * class ProxyRunnable final : public Runnable + * { + * Functor mCall; + * public: + * ProxyRunnable(Functor&& call) : mCall(std::move(call)) {} + * virtual void run() override { mCall(); } + * }; + * + * public: + * template + * static void OnNativeCall(Functor&& call) + * { + * RunOnAnotherThread(new ProxyRunnable(std::move(call))); + * } + * }; + */ + +namespace detail { + +// ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied +// call may happen outside of the original JNI native call, we must save all +// JNI ref arguments as global refs to avoid the arguments going out of scope. +template +struct ProxyArg { + static_assert(mozilla::IsPod::value, "T must be primitive type"); + + // Primitive types can be saved by value. + typedef T Type; + typedef typename TypeAdapter::JNIType JNIType; + + static void Clear(JNIEnv* env, Type&) {} + + static Type From(JNIEnv* env, JNIType val) { + return TypeAdapter::ToNative(env, val); + } +}; + +template +struct ProxyArg> { + // Ref types need to be saved by global ref. + typedef typename C::GlobalRef Type; + typedef typename TypeAdapter>::JNIType JNIType; + + static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); } + + static Type From(JNIEnv* env, JNIType val) { + return Type(env, C::Ref::From(val)); + } +}; + +template +struct ProxyArg : ProxyArg {}; +template <> +struct ProxyArg : ProxyArg {}; +template +struct ProxyArg> : ProxyArg {}; + +// ProxyNativeCall implements the functor object that is passed to OnNativeCall +template +class ProxyNativeCall { + // "this arg" refers to the Class::LocalRef (for static methods) or + // Owner::LocalRef (for instance methods) that we optionally (as indicated + // by HasThisArg) pass into the destination C++ function. + using ThisArgClass = std::conditional_t; + using ThisArgJNIType = std::conditional_t; + + // Type signature of the destination C++ function, which matches the + // Method template parameter in NativeStubImpl::Wrap. + using NativeCallType = std::conditional_t< + IsStatic, + std::conditional_t, + std::conditional_t< + HasThisArg, void (Impl::*)(const typename Owner::LocalRef&, Args...), + void (Impl::*)(Args...)>>; + + // Destination C++ function. + NativeCallType mNativeCall; + // Saved this arg. + typename ThisArgClass::GlobalRef mThisArg; + // Saved arguments. + mozilla::Tuple::Type...> mArgs; + + // We cannot use IsStatic and HasThisArg directly (without going through + // extra hoops) because GCC complains about invalid overloads, so we use + // another pair of template parameters, Static and ThisArg. + + template + std::enable_if_t Call( + const Class::LocalRef& cls, std::index_sequence) const { + (*mNativeCall)(cls, mozilla::Get(mArgs)...); + } + + template + std::enable_if_t Call( + const Class::LocalRef& cls, std::index_sequence) const { + (*mNativeCall)(mozilla::Get(mArgs)...); + } + + template + std::enable_if_t Call( + const typename Owner::LocalRef& inst, + std::index_sequence) const { + auto impl = NativePtrTraits::Access(NativePtrTraits::Get(inst)); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(inst, mozilla::Get(mArgs)...); + } + + template + std::enable_if_t Call( + const typename Owner::LocalRef& inst, + std::index_sequence) const { + auto impl = NativePtrTraits::Access(NativePtrTraits::Get(inst)); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(mozilla::Get(mArgs)...); + } + + template + void Clear(JNIEnv* env, std::index_sequence) { + int dummy[] = {(ProxyArg::Clear(env, Get(mArgs)), 0)...}; + mozilla::Unused << dummy; + } + + static decltype(auto) GetNativeObject(Class::Param thisArg) { + return nullptr; + } + + static decltype(auto) GetNativeObject(typename Owner::Param thisArg) { + return NativePtrTraits::Access( + NativePtrTraits::Get(GetEnvForThread(), thisArg.Get())); + } + + public: + // The class that implements the call target. + typedef Impl TargetClass; + typedef typename ThisArgClass::Param ThisArgType; + + static const bool isStatic = IsStatic; + + ProxyNativeCall(ThisArgJNIType thisArg, NativeCallType nativeCall, + JNIEnv* env, typename ProxyArg::JNIType... args) + : mNativeCall(nativeCall), + mThisArg(env, ThisArgClass::Ref::From(thisArg)), + mArgs(ProxyArg::From(env, args)...) {} + + ProxyNativeCall(ProxyNativeCall&&) = default; + ProxyNativeCall(const ProxyNativeCall&) = default; + + // Get class ref for static calls or object ref for instance calls. + typename ThisArgClass::Param GetThisArg() const { return mThisArg; } + + // Get the native object targeted by this call. + // Returns nullptr for static calls. + decltype(auto) GetNativeObject() const { return GetNativeObject(mThisArg); } + + // Return if target is the given function pointer / pointer-to-member. + // Because we can only compare pointers of the same type, we use a + // templated overload that is chosen only if given a different type of + // pointer than our target pointer type. + bool IsTarget(NativeCallType call) const { return call == mNativeCall; } + template + bool IsTarget(T&&) const { + return false; + } + + // Redirect the call to another function / class member with the same + // signature as the original target. Crash if given a wrong signature. + void SetTarget(NativeCallType call) { mNativeCall = call; } + template + void SetTarget(T&&) const { + MOZ_CRASH(); + } + + void operator()() { + JNIEnv* const env = GetEnvForThread(); + typename ThisArgClass::LocalRef thisArg(env, mThisArg); + Call(thisArg, std::index_sequence_for{}); + + // Clear all saved global refs. We do this after the call is invoked, + // and not inside the destructor because we already have a JNIEnv here, + // so it's more efficient to clear out the saved args here. The + // downside is that the call can only be invoked once. + Clear(env, std::index_sequence_for{}); + mThisArg.Clear(env); + } +}; + +template +struct Dispatcher { + template + static std::enable_if_t + Run(ProxyArgs&&... args) { + Impl::OnNativeCall( + ProxyNativeCall(std::forward(args)...)); + } + + template + static std::enable_if_t< + Traits::dispatchTarget == DispatchTarget::GECKO_PRIORITY, void> + Run(ThisArg thisArg, ProxyArgs&&... args) { + // For a static method, do not forward the "this arg" (i.e. the class + // local ref) if the implementation does not request it. This saves us + // a pair of calls to add/delete global ref. + auto proxy = + ProxyNativeCall((HasThisArg || !IsStatic) ? thisArg : nullptr, + std::forward(args)...); + DispatchToGeckoPriorityQueue( + NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy))); + } + + template + static std::enable_if_t + Run(ThisArg thisArg, ProxyArgs&&... args) { + // For a static method, do not forward the "this arg" (i.e. the class + // local ref) if the implementation does not request it. This saves us + // a pair of calls to add/delete global ref. + auto proxy = + ProxyNativeCall((HasThisArg || !IsStatic) ? thisArg : nullptr, + std::forward(args)...); + NS_DispatchToMainThread( + NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy))); + } + + template + static std::enable_if_t + Run(ProxyArgs&&... args) { + MOZ_CRASH("Unreachable code"); + } +}; + +} // namespace detail + +// Wrapper methods that convert arguments from the JNI types to the native +// types, e.g. from jobject to jni::Object::Ref. For instance methods, the +// wrapper methods also convert calls to calls on objects. +// +// We need specialization for static/non-static because the two have different +// signatures (jobject vs jclass and Impl::*Method vs *Method). +// We need specialization for return type, because void return type requires +// us to not deal with the return value. + +// Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry +#ifdef __i386__ +# define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer)) +#else +# define MOZ_JNICALL JNICALL +#endif + +template +class NativeStub; + +template +class NativeStub> { + using Owner = typename Traits::Owner; + using ReturnType = typename Traits::ReturnType; + + static constexpr bool isStatic = Traits::isStatic; + static constexpr bool isVoid = std::is_void_v; + + struct VoidType { + using JNIType = void; + }; + using ReturnJNIType = + typename std::conditional_t>::JNIType; + + using ReturnTypeForNonVoidInstance = + std::conditional_t; + using ReturnTypeForVoidInstance = + std::conditional_t; + using ReturnTypeForNonVoidStatic = + std::conditional_t; + using ReturnTypeForVoidStatic = + std::conditional_t; + + static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid, + "Dispatched calls must have void return type"); + + public: + // Non-void instance method + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto impl = NativePtrTraits::Access( + NativePtrTraits::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return ReturnJNIType(); + } + return TypeAdapter::FromNative( + env, (impl->*Method)(TypeAdapter::ToNative(env, args)...)); + } + + // Non-void instance method with instance reference + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto impl = NativePtrTraits::Access( + NativePtrTraits::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return ReturnJNIType(); + } + auto self = Owner::LocalRef::Adopt(env, instance); + const auto res = TypeAdapter::FromNative( + env, (impl->*Method)(self, TypeAdapter::ToNative(env, args)...)); + self.Forget(); + return res; + } + + // Void instance method + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + instance, Method, env, args...); + return; + } + + auto impl = NativePtrTraits::Access( + NativePtrTraits::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return; + } + (impl->*Method)(TypeAdapter::ToNative(env, args)...); + } + + // Void instance method with instance reference + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + instance, Method, env, args...); + return; + } + + auto impl = NativePtrTraits::Access( + NativePtrTraits::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return; + } + auto self = Owner::LocalRef::Adopt(env, instance); + (impl->*Method)(self, TypeAdapter::ToNative(env, args)...); + self.Forget(); + } + + // Overload for DisposeNative + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + using LocalRef = typename Owner::LocalRef; + Dispatcher::template Run< + Traits, /* IsStatic */ true>( + /* ThisArg */ nullptr, DisposeNative, env, instance); + return; + } + + auto self = Owner::LocalRef::Adopt(env, instance); + DisposeNative(self); + self.Forget(); + } + + // Non-void static method + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jclass, typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + return TypeAdapter::FromNative( + env, (*Method)(TypeAdapter::ToNative(env, args)...)); + } + + // Non-void static method with class reference + template + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jclass cls, typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto clazz = Class::LocalRef::Adopt(env, cls); + const auto res = TypeAdapter::FromNative( + env, (*Method)(clazz, TypeAdapter::ToNative(env, args)...)); + clazz.Forget(); + return res; + } + + // Void static method + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + cls, Method, env, args...); + return; + } + + (*Method)(TypeAdapter::ToNative(env, args)...); + } + + // Void static method with class reference + template + static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, + typename TypeAdapter::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher::template Run( + cls, Method, env, args...); + return; + } + + auto clazz = Class::LocalRef::Adopt(env, cls); + (*Method)(clazz, TypeAdapter::ToNative(env, args)...); + clazz.Forget(); + } +}; + +// Generate a JNINativeMethod from a native +// method's traits class and a wrapped stub. +template +constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*, + Args...)) { + return {Traits::name, Traits::signature, reinterpret_cast(stub)}; +} + +// Class inherited by implementing class. +template +class NativeImpl { + typedef typename Cls::template Natives Natives; + + static bool sInited; + + public: + static void Init() { + if (sInited) { + return; + } + const auto& ctx = typename Cls::Context(); + ctx.Env()->RegisterNatives( + ctx.ClassRef(), Natives::methods, + sizeof(Natives::methods) / sizeof(Natives::methods[0])); + MOZ_CATCH_JNI_EXCEPTION(ctx.Env()); + sInited = true; + } + + protected: + // Associate a C++ instance with a Java instance. + static void AttachNative(const typename Cls::LocalRef& instance, + SupportsWeakPtr* ptr) { + static_assert(NativePtrPicker::value == NativePtrType::WEAK_INTRUSIVE, + "Use another AttachNative for non-WeakPtr usage"); + return NativePtrTraits::Set(instance, static_cast(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, + UniquePtr&& ptr) { + static_assert(NativePtrPicker::value == NativePtrType::OWNING, + "Use another AttachNative for WeakPtr or RefPtr usage"); + return NativePtrTraits::Set(instance, std::move(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) { + static_assert(NativePtrPicker::value == NativePtrType::REFPTR, + "Use another AttachNative for non-RefPtr usage"); + return NativePtrTraits::Set(instance, ptr); + } + + // Get the C++ instance associated with a Java instance. + // There is always a pending exception if the return value is nullptr. + static decltype(auto) GetNative(const typename Cls::LocalRef& instance) { + return NativePtrTraits::Get(instance); + } + + static void DisposeNative(const typename Cls::LocalRef& instance) { + NativePtrTraits::Clear(instance); + } + + NativeImpl() { + // Initialize on creation if not already initialized. + Init(); + } +}; + +// Define static member. +template +bool NativeImpl::sInited; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Natives_h__ diff --git a/widget/android/jni/Refs.h b/widget/android/jni/Refs.h new file mode 100644 index 0000000000..8a7276f595 --- /dev/null +++ b/widget/android/jni/Refs.h @@ -0,0 +1,1016 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_Refs_h__ +#define mozilla_jni_Refs_h__ + +#include + +#include + +#include "mozilla/jni/Utils.h" +#include "nsError.h" // for nsresult +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace jni { + +// Wrapped object reference (e.g. jobject, jclass, etc...) +template +class Ref; +// Represents a calling context for JNI methods. +template +class Context; +// Wrapped local reference that inherits from Ref. +template +class LocalRef; +// Wrapped global reference that inherits from Ref. +template +class GlobalRef; +// Wrapped weak reference that inherits from Ref. +template +class WeakRef; +// Wrapped dangling reference that's owned by someone else. +template +class DependentRef; + +// Class to hold the native types of a method's arguments. +// For example, if a method has signature (ILjava/lang/String;)V, +// its arguments class would be jni::Args +template +struct Args {}; + +class Object; + +// Base class for Ref and its specializations. +template +class Ref { + template + friend class Ref; + + using Self = Ref; + using bool_type = void (Self::*)() const; + void non_null_reference() const {} + + // A Cls-derivative that allows copying + // (e.g. when acting as a return value). + struct CopyableCtx : public Context { + CopyableCtx(JNIEnv* env, Type instance) + : Context(env, instance) {} + + CopyableCtx(const CopyableCtx& cls) + : Context(cls.Env(), cls.Get()) {} + }; + + // Private copy constructor so that there's no danger of assigning a + // temporary LocalRef/GlobalRef to a Ref, and potentially use the Ref + // after the source had been freed. + Ref(const Ref&) = default; + + protected: + static JNIEnv* FindEnv() { + return Cls::callingThread == CallingThread::GECKO ? GetGeckoThreadEnv() + : GetEnvForThread(); + } + + Type mInstance; + + // Protected jobject constructor because outside code should be using + // Ref::From. Using Ref::From makes it very easy to see which code is using + // raw JNI types for future refactoring. + explicit Ref(Type instance) : mInstance(instance) {} + + public: + using JNIType = Type; + + class AutoLock { + friend class Ref; + + JNIEnv* const mEnv; + Type mInstance; + + explicit AutoLock(Type aInstance) + : mEnv(FindEnv()), mInstance(mEnv->NewLocalRef(aInstance)) { + mEnv->MonitorEnter(mInstance); + MOZ_CATCH_JNI_EXCEPTION(mEnv); + } + + public: + AutoLock(AutoLock&& aOther) + : mEnv(aOther.mEnv), mInstance(aOther.mInstance) { + aOther.mInstance = nullptr; + } + + ~AutoLock() { Unlock(); } + + void Unlock() { + if (mInstance) { + mEnv->MonitorExit(mInstance); + mEnv->DeleteLocalRef(mInstance); + MOZ_CATCH_JNI_EXCEPTION(mEnv); + mInstance = nullptr; + } + } + }; + + // Construct a Ref form a raw JNI reference. + static Ref From(JNIType obj) { return Ref(obj); } + + // Construct a Ref form a generic object reference. + static Ref From(const Ref& obj) { + return Ref(JNIType(obj.Get())); + } + + MOZ_IMPLICIT Ref(decltype(nullptr)) : mInstance(nullptr) {} + + // Get the raw JNI reference. + JNIType Get() const { return mInstance; } + + template + bool IsInstanceOf() const { + return FindEnv()->IsInstanceOf(mInstance, typename T::Context().ClassRef()); + } + + template + typename T::Ref Cast() const { +#ifdef MOZ_CHECK_JNI + MOZ_RELEASE_ASSERT(FindEnv()->IsAssignableFrom( + Context().ClassRef(), typename T::Context().ClassRef())); +#endif + return T::Ref::From(*this); + } + + AutoLock Lock() const { return AutoLock(mInstance); } + + bool operator==(const Ref& other) const { + // Treat two references of the same object as being the same. + return mInstance == other.mInstance || + JNI_FALSE != FindEnv()->IsSameObject(mInstance, other.mInstance); + } + + bool operator!=(const Ref& other) const { return !operator==(other); } + + bool operator==(decltype(nullptr)) const { return !mInstance; } + + bool operator!=(decltype(nullptr)) const { return !!mInstance; } + + CopyableCtx operator->() const { return CopyableCtx(FindEnv(), mInstance); } + + CopyableCtx operator*() const { return operator->(); } + + // Any ref can be cast to an object ref. + operator Ref() const { + return Ref(mInstance); + } + + // Null checking (e.g. !!ref) using the safe-bool idiom. + operator bool_type() const { + return mInstance ? &Self::non_null_reference : nullptr; + } + + // We don't allow implicit conversion to jobject because that can lead + // to easy mistakes such as assigning a temporary LocalRef to a jobject, + // and using the jobject after the LocalRef has been freed. + + // We don't allow explicit conversion, to make outside code use Ref::Get. + // Using Ref::Get makes it very easy to see which code is using raw JNI + // types to make future refactoring easier. + + // operator JNIType() const = delete; +}; + +// Represents a calling context for JNI methods. +template +class Context : public Ref { + using Ref = jni::Ref; + + static jclass sClassRef; // global reference + + protected: + JNIEnv* const mEnv; + + public: + Context() : Ref(nullptr), mEnv(Ref::FindEnv()) {} + + Context(JNIEnv* env, Type instance) : Ref(instance), mEnv(env) {} + + jclass ClassRef() const { + if (!sClassRef) { + const jclass cls = GetClassRef(mEnv, Cls::name); + sClassRef = jclass(mEnv->NewGlobalRef(cls)); + mEnv->DeleteLocalRef(cls); + } + return sClassRef; + } + + JNIEnv* Env() const { return mEnv; } + + template + bool IsInstanceOf() const { + return mEnv->IsInstanceOf(Ref::mInstance, + typename T::Context(mEnv, nullptr).ClassRef()); + } + + bool operator==(const Ref& other) const { + // Treat two references of the same object as being the same. + return Ref::mInstance == other.Get() || + JNI_FALSE != mEnv->IsSameObject(Ref::mInstance, other.Get()); + } + + bool operator!=(const Ref& other) const { return !operator==(other); } + + bool operator==(decltype(nullptr)) const { return !Ref::mInstance; } + + bool operator!=(decltype(nullptr)) const { return !!Ref::mInstance; } + + Cls operator->() const { + MOZ_ASSERT(Ref::mInstance, "Null jobject"); + return Cls(*this); + } + + const Context& operator*() const { return *this; } +}; + +template +jclass Context::sClassRef; + +template +class ObjectBase { + protected: + const jni::Context& mCtx; + + jclass ClassRef() const { return mCtx.ClassRef(); } + JNIEnv* Env() const { return mCtx.Env(); } + Type Instance() const { return mCtx.Get(); } + + public: + using Ref = jni::Ref; + using Context = jni::Context; + using LocalRef = jni::LocalRef; + using GlobalRef = jni::GlobalRef; + using WeakRef = jni::WeakRef; + using Param = const Ref&; + + static const CallingThread callingThread = CallingThread::ANY; + static const char name[]; + + explicit ObjectBase(const Context& ctx) : mCtx(ctx) {} + + Cls* operator->() { return static_cast(this); } +}; + +// Binding for a plain jobject. +class Object : public ObjectBase { + public: + explicit Object(const Context& ctx) : ObjectBase(ctx) {} +}; + +// Binding for a built-in object reference other than jobject. +template +class TypedObject : public ObjectBase, T> { + public: + explicit TypedObject(const Context, T>& ctx) + : ObjectBase, T>(ctx) {} +}; + +// Binding for a boxed primitive object. +template +class BoxedObject : public ObjectBase, jobject> { + public: + explicit BoxedObject(const Context, jobject>& ctx) + : ObjectBase, jobject>(ctx) {} +}; + +template <> +const char ObjectBase::name[]; +template <> +const char ObjectBase, jstring>::name[]; +template <> +const char ObjectBase, jclass>::name[]; +template <> +const char ObjectBase, jthrowable>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jobject>::name[]; +template <> +const char ObjectBase, jbooleanArray>::name[]; +template <> +const char ObjectBase, jbyteArray>::name[]; +template <> +const char ObjectBase, jcharArray>::name[]; +template <> +const char ObjectBase, jshortArray>::name[]; +template <> +const char ObjectBase, jintArray>::name[]; +template <> +const char ObjectBase, jlongArray>::name[]; +template <> +const char ObjectBase, jfloatArray>::name[]; +template <> +const char ObjectBase, jdoubleArray>::name[]; +template <> +const char ObjectBase, jobjectArray>::name[]; + +// Define bindings for built-in types. +using String = TypedObject; +using Class = TypedObject; +using Throwable = TypedObject; + +using Boolean = BoxedObject; +using Byte = BoxedObject; +using Character = BoxedObject; +using Short = BoxedObject; +using Integer = BoxedObject; +using Long = BoxedObject; +using Float = BoxedObject; +using Double = BoxedObject; + +using BooleanArray = TypedObject; +using ByteArray = TypedObject; +using CharArray = TypedObject; +using ShortArray = TypedObject; +using IntArray = TypedObject; +using LongArray = TypedObject; +using FloatArray = TypedObject; +using DoubleArray = TypedObject; +using ObjectArray = TypedObject; + +namespace detail { + +// See explanation in LocalRef. +template +struct GenericObject { + using Type = Object; +}; +template <> +struct GenericObject { + struct Type { + using Ref = jni::Ref; + using Context = jni::Context; + }; +}; +template +struct GenericLocalRef { + template + struct Type : jni::Object {}; +}; +template <> +struct GenericLocalRef { + template + using Type = jni::LocalRef; +}; + +} // namespace detail + +template +class LocalRef : public Cls::Context { + template + friend class LocalRef; + + using Ctx = typename Cls::Context; + using Ref = typename Cls::Ref; + using JNIType = typename Ref::JNIType; + + // In order to be able to convert LocalRef to LocalRef, we + // need constructors and copy assignment operators that take in a + // LocalRef argument. However, if Cls *is* Object, we would have + // duplicated constructors and operators with LocalRef arguments. To + // avoid this conflict, we use GenericObject, which is defined as Object for + // LocalRef and defined as a dummy class for LocalRef. + using GenericObject = typename detail::GenericObject::Type; + + // Similarly, GenericLocalRef is useed to convert LocalRef to, + // LocalRef. It's defined as LocalRef for Cls == Object, + // and defined as a dummy template class for Cls != Object. + template + using GenericLocalRef = + typename detail::GenericLocalRef::template Type; + + static JNIType NewLocalRef(JNIEnv* env, JNIType obj) { + return JNIType(obj ? env->NewLocalRef(obj) : nullptr); + } + + LocalRef(JNIEnv* env, JNIType instance) : Ctx(env, instance) {} + + LocalRef& swap(LocalRef& other) { + auto instance = other.mInstance; + other.mInstance = Ctx::mInstance; + Ctx::mInstance = instance; + return *this; + } + + public: + // Construct a LocalRef from a raw JNI local reference. Unlike Ref::From, + // LocalRef::Adopt returns a LocalRef that will delete the local reference + // when going out of scope. + static LocalRef Adopt(JNIType instance) { + return LocalRef(Ref::FindEnv(), instance); + } + + static LocalRef Adopt(JNIEnv* env, JNIType instance) { + return LocalRef(env, instance); + } + + // Copy constructor. + LocalRef(const LocalRef& ref) + : Ctx(ref.mEnv, NewLocalRef(ref.mEnv, ref.mInstance)) {} + + // Move constructor. + LocalRef(LocalRef&& ref) : Ctx(ref.mEnv, ref.mInstance) { + ref.mInstance = nullptr; + } + + explicit LocalRef(JNIEnv* env = Ref::FindEnv()) : Ctx(env, nullptr) {} + + // Construct a LocalRef from any Ref, + // which means creating a new local reference. + MOZ_IMPLICIT LocalRef(const Ref& ref) : Ctx(Ref::FindEnv(), nullptr) { + Ctx::mInstance = NewLocalRef(Ctx::mEnv, ref.Get()); + } + + LocalRef(JNIEnv* env, const Ref& ref) + : Ctx(env, NewLocalRef(env, ref.Get())) {} + + // Move a LocalRef into a LocalRef without + // creating/deleting local references. + MOZ_IMPLICIT LocalRef(LocalRef&& ref) + : Ctx(ref.mEnv, JNIType(ref.mInstance)) { + ref.mInstance = nullptr; + } + + template + MOZ_IMPLICIT LocalRef(GenericLocalRef&& ref) + : Ctx(ref.mEnv, ref.mInstance) { + ref.mInstance = nullptr; + } + + // Implicitly converts nullptr to LocalRef. + MOZ_IMPLICIT LocalRef(decltype(nullptr)) : Ctx(Ref::FindEnv(), nullptr) {} + + ~LocalRef() { + if (Ctx::mInstance) { + Ctx::mEnv->DeleteLocalRef(Ctx::mInstance); + Ctx::mInstance = nullptr; + } + } + + // Get the raw JNI reference that can be used as a return value. + // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref. + typename Ref::JNIType Forget() { + const auto obj = Ctx::Get(); + Ctx::mInstance = nullptr; + return obj; + } + + LocalRef& operator=(LocalRef ref) & { return swap(ref); } + + LocalRef& operator=(const Ref& ref) & { + LocalRef newRef(Ctx::mEnv, ref); + return swap(newRef); + } + + LocalRef& operator=(LocalRef&& ref) & { + LocalRef newRef(std::move(ref)); + return swap(newRef); + } + + template + LocalRef& operator=(GenericLocalRef&& ref) & { + LocalRef newRef(std::move(ref)); + return swap(newRef); + } + + LocalRef& operator=(decltype(nullptr)) & { + LocalRef newRef(Ctx::mEnv, nullptr); + return swap(newRef); + } +}; + +template +class GlobalRef : public Cls::Ref { + using Ref = typename Cls::Ref; + using JNIType = typename Ref::JNIType; + + static JNIType NewGlobalRef(JNIEnv* env, JNIType instance) { + return JNIType(instance ? env->NewGlobalRef(instance) : nullptr); + } + + GlobalRef& swap(GlobalRef& other) { + auto instance = other.mInstance; + other.mInstance = Ref::mInstance; + Ref::mInstance = instance; + return *this; + } + + public: + GlobalRef() : Ref(nullptr) {} + + // Copy constructor + GlobalRef(const GlobalRef& ref) + : Ref(NewGlobalRef(GetEnvForThread(), ref.mInstance)) {} + + // Move constructor + GlobalRef(GlobalRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; } + + MOZ_IMPLICIT GlobalRef(const Ref& ref) + : Ref(NewGlobalRef(GetEnvForThread(), ref.Get())) {} + + GlobalRef(JNIEnv* env, const Ref& ref) : Ref(NewGlobalRef(env, ref.Get())) {} + + MOZ_IMPLICIT GlobalRef(const LocalRef& ref) + : Ref(NewGlobalRef(ref.Env(), ref.Get())) {} + + // Implicitly converts nullptr to GlobalRef. + MOZ_IMPLICIT GlobalRef(decltype(nullptr)) : Ref(nullptr) {} + + ~GlobalRef() { + if (Ref::mInstance) { + Clear(GetEnvForThread()); + } + } + + // Get the raw JNI reference that can be used as a return value. + // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref. + typename Ref::JNIType Forget() { + const auto obj = Ref::Get(); + Ref::mInstance = nullptr; + return obj; + } + + void Clear(JNIEnv* env) { + if (Ref::mInstance) { + env->DeleteGlobalRef(Ref::mInstance); + Ref::mInstance = nullptr; + } + } + + GlobalRef& operator=(GlobalRef ref) & { return swap(ref); } + + GlobalRef& operator=(const Ref& ref) & { + GlobalRef newRef(ref); + return swap(newRef); + } + + GlobalRef& operator=(const LocalRef& ref) & { + GlobalRef newRef(ref); + return swap(newRef); + } + + GlobalRef& operator=(decltype(nullptr)) & { + GlobalRef newRef(nullptr); + return swap(newRef); + } +}; + +template +class WeakRef : public Ref { + using Ref = Ref; + using JNIType = typename Ref::JNIType; + + static JNIType NewWeakRef(JNIEnv* env, JNIType instance) { + return JNIType(instance ? env->NewWeakGlobalRef(instance) : nullptr); + } + + WeakRef& swap(WeakRef& other) { + auto instance = other.mInstance; + other.mInstance = Ref::mInstance; + Ref::mInstance = instance; + return *this; + } + + public: + WeakRef() : Ref(nullptr) {} + + // Copy constructor + WeakRef(const WeakRef& ref) + : Ref(NewWeakRef(GetEnvForThread(), ref.mInstance)) {} + + // Move constructor + WeakRef(WeakRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; } + + MOZ_IMPLICIT WeakRef(const Ref& ref) + : Ref(NewWeakRef(GetEnvForThread(), ref.Get())) {} + + WeakRef(JNIEnv* env, const Ref& ref) : Ref(NewWeakRef(env, ref.Get())) {} + + MOZ_IMPLICIT WeakRef(const LocalRef& ref) + : Ref(NewWeakRef(ref.Env(), ref.Get())) {} + + // Implicitly converts nullptr to WeakRef. + MOZ_IMPLICIT WeakRef(decltype(nullptr)) : Ref(nullptr) {} + + ~WeakRef() { + if (Ref::mInstance) { + Clear(GetEnvForThread()); + } + } + + // Get the raw JNI reference that can be used as a return value. + // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref. + typename Ref::JNIType Forget() { + const auto obj = Ref::Get(); + Ref::mInstance = nullptr; + return obj; + } + + void Clear(JNIEnv* env) { + if (Ref::mInstance) { + env->DeleteWeakGlobalRef(Ref::mInstance); + Ref::mInstance = nullptr; + } + } + + WeakRef& operator=(WeakRef ref) & { return swap(ref); } + + WeakRef& operator=(const Ref& ref) & { + WeakRef newRef(ref); + return swap(newRef); + } + + WeakRef& operator=(const LocalRef& ref) & { + WeakRef newRef(ref); + return swap(newRef); + } + + WeakRef& operator=(decltype(nullptr)) & { + WeakRef newRef(nullptr); + return swap(newRef); + } + + void operator->() const = delete; + void operator*() const = delete; +}; + +template +class DependentRef : public Cls::Ref { + using Ref = typename Cls::Ref; + + public: + explicit DependentRef(typename Ref::JNIType instance) : Ref(instance) {} + + DependentRef(const DependentRef& ref) : Ref(ref.Get()) {} +}; + +class StringParam; + +template <> +class TypedObject : public ObjectBase, jstring> { + using Base = ObjectBase, jstring>; + + public: + using Param = const StringParam&; + + explicit TypedObject(const Context& ctx) : Base(ctx) {} + + size_t Length() const { + const size_t ret = Base::Env()->GetStringLength(Base::Instance()); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + return ret; + } + + nsString ToString() const { + const jchar* const str = + Base::Env()->GetStringChars(Base::Instance(), nullptr); + const jsize len = Base::Env()->GetStringLength(Base::Instance()); + + nsString result(reinterpret_cast(str), len); + Base::Env()->ReleaseStringChars(Base::Instance(), str); + return result; + } + + nsCString ToCString() const { return NS_ConvertUTF16toUTF8(ToString()); } + + // Convert jstring to a nsString. + operator nsString() const { return ToString(); } + + // Convert jstring to a nsCString. + operator nsCString() const { return ToCString(); } +}; + +// Define a custom parameter type for String, +// which accepts both String::Ref and nsAString/nsACString +class StringParam : public String::Ref { + using Ref = String::Ref; + + private: + // Not null if we should delete ref on destruction. + JNIEnv* const mEnv; + + static jstring GetString(JNIEnv* env, const nsAString& str) { + const jstring result = env->NewString( + reinterpret_cast(str.BeginReading()), str.Length()); + if (!result) { + NS_ABORT_OOM(str.Length() * sizeof(char16_t)); + } + MOZ_CATCH_JNI_EXCEPTION(env); + return result; + } + + public: + MOZ_IMPLICIT StringParam(decltype(nullptr)) : Ref(nullptr), mEnv(nullptr) {} + + MOZ_IMPLICIT StringParam(const Ref& ref) : Ref(ref.Get()), mEnv(nullptr) {} + + MOZ_IMPLICIT StringParam(const nsAString& str, JNIEnv* env = Ref::FindEnv()) + : Ref(GetString(env, str)), mEnv(env) {} + + MOZ_IMPLICIT StringParam(const nsLiteralString& str, + JNIEnv* env = Ref::FindEnv()) + : Ref(GetString(env, str)), mEnv(env) {} + + MOZ_IMPLICIT StringParam(const char16_t* str, JNIEnv* env = Ref::FindEnv()) + : Ref(GetString(env, nsDependentString(str))), mEnv(env) {} + + MOZ_IMPLICIT StringParam(const nsACString& str, JNIEnv* env = Ref::FindEnv()) + : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {} + + MOZ_IMPLICIT StringParam(const nsLiteralCString& str, + JNIEnv* env = Ref::FindEnv()) + : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {} + + MOZ_IMPLICIT StringParam(const char* str, JNIEnv* env = Ref::FindEnv()) + : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {} + + StringParam(StringParam&& other) : Ref(other.Get()), mEnv(other.mEnv) { + other.mInstance = nullptr; + } + + ~StringParam() { + if (mEnv && Get()) { + mEnv->DeleteLocalRef(Get()); + } + } + + operator String::LocalRef() const { + // We can't return our existing ref because the returned + // LocalRef could be freed first, so we need a new local ref. + return String::LocalRef(mEnv ? mEnv : Ref::FindEnv(), *this); + } +}; + +namespace detail { +template +struct TypeAdapter; +} + +// Ref specialization for arrays. +template +class ArrayRefBase : public ObjectBase, JNIType> { + using Base = ObjectBase, JNIType>; + + public: + explicit ArrayRefBase(const Context, JNIType>& ctx) + : Base(ctx) {} + + static typename Base::LocalRef New(const ElementType* data, size_t length) { + using JNIElemType = typename detail::TypeAdapter::JNIType; + static_assert(sizeof(ElementType) == sizeof(JNIElemType), + "Size of native type must match size of JNI type"); + JNIEnv* const jenv = mozilla::jni::GetEnvForThread(); + auto result = (jenv->*detail::TypeAdapter::NewArray)(length); + MOZ_CATCH_JNI_EXCEPTION(jenv); + (jenv->*detail::TypeAdapter::SetArray)( + result, jsize(0), length, reinterpret_cast(data)); + MOZ_CATCH_JNI_EXCEPTION(jenv); + return Base::LocalRef::Adopt(jenv, result); + } + + size_t Length() const { + const size_t ret = Base::Env()->GetArrayLength(Base::Instance()); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + return ret; + } + + ElementType GetElement(size_t index) const { + using JNIElemType = typename detail::TypeAdapter::JNIType; + static_assert(sizeof(ElementType) == sizeof(JNIElemType), + "Size of native type must match size of JNI type"); + + ElementType ret; + (Base::Env()->*detail::TypeAdapter::GetArray)( + Base::Instance(), jsize(index), 1, + reinterpret_cast(&ret)); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + return ret; + } + + nsTArray GetElements() const { + using JNIElemType = typename detail::TypeAdapter::JNIType; + static_assert(sizeof(ElementType) == sizeof(JNIElemType), + "Size of native type must match size of JNI type"); + + const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance())); + + nsTArray array(len); + array.SetLength(len); + CopyTo(array.Elements(), len); + return array; + } + + // returns number of elements copied + size_t CopyTo(ElementType* buffer, size_t size) const { + using JNIElemType = typename detail::TypeAdapter::JNIType; + static_assert(sizeof(ElementType) == sizeof(JNIElemType), + "Size of native type must match size of JNI type"); + + const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance())); + const size_t amountToCopy = (len > size ? size : len); + (Base::Env()->*detail::TypeAdapter::GetArray)( + Base::Instance(), 0, jsize(amountToCopy), + reinterpret_cast(buffer)); + return amountToCopy; + } + + ElementType operator[](size_t index) const { return GetElement(index); } + + operator nsTArray() const { return GetElements(); } +}; + +#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType) \ + template <> \ + class TypedObject : public ArrayRefBase { \ + public: \ + explicit TypedObject(const Context& ctx) \ + : ArrayRefBase(ctx) {} \ + } + +DEFINE_PRIMITIVE_ARRAY_REF(jbooleanArray, bool); +DEFINE_PRIMITIVE_ARRAY_REF(jbyteArray, int8_t); +DEFINE_PRIMITIVE_ARRAY_REF(jcharArray, char16_t); +DEFINE_PRIMITIVE_ARRAY_REF(jshortArray, int16_t); +DEFINE_PRIMITIVE_ARRAY_REF(jintArray, int32_t); +DEFINE_PRIMITIVE_ARRAY_REF(jlongArray, int64_t); +DEFINE_PRIMITIVE_ARRAY_REF(jfloatArray, float); +DEFINE_PRIMITIVE_ARRAY_REF(jdoubleArray, double); + +#undef DEFINE_PRIMITIVE_ARRAY_REF + +class ByteBuffer : public ObjectBase { + public: + explicit ByteBuffer(const Context& ctx) + : ObjectBase(ctx) {} + + static LocalRef New(void* data, size_t capacity) { + JNIEnv* const env = GetEnvForThread(); + const auto ret = + LocalRef::Adopt(env, env->NewDirectByteBuffer(data, jlong(capacity))); + MOZ_CATCH_JNI_EXCEPTION(env); + return ret; + } + + void* Address() { + void* const ret = Env()->GetDirectBufferAddress(Instance()); + MOZ_CATCH_JNI_EXCEPTION(Env()); + return ret; + } + + size_t Capacity() { + const size_t ret = size_t(Env()->GetDirectBufferCapacity(Instance())); + MOZ_CATCH_JNI_EXCEPTION(Env()); + return ret; + } +}; + +template <> +const char ObjectBase::name[]; + +template <> +class TypedObject + : public ObjectBase, jobjectArray> { + using Base = ObjectBase, jobjectArray>; + + public: + template + static Base::LocalRef New(size_t length, + typename Cls::Param initialElement = nullptr) { + JNIEnv* const env = GetEnvForThread(); + jobjectArray array = env->NewObjectArray( + jsize(length), typename Cls::Context(env, nullptr).ClassRef(), + initialElement.Get()); + MOZ_CATCH_JNI_EXCEPTION(env); + return Base::LocalRef::Adopt(env, array); + } + + explicit TypedObject(const Context& ctx) : Base(ctx) {} + + size_t Length() const { + const size_t ret = Base::Env()->GetArrayLength(Base::Instance()); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + return ret; + } + + Object::LocalRef GetElement(size_t index) const { + auto ret = Object::LocalRef::Adopt( + Base::Env(), + Base::Env()->GetObjectArrayElement(Base::Instance(), jsize(index))); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + return ret; + } + + nsTArray GetElements() const { + const jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance())); + + nsTArray array((size_t(len))); + for (jsize i = 0; i < len; i++) { + array.AppendElement(Object::LocalRef::Adopt( + Base::Env(), + Base::Env()->GetObjectArrayElement(Base::Instance(), i))); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + } + return array; + } + + Object::LocalRef operator[](size_t index) const { return GetElement(index); } + + operator nsTArray() const { return GetElements(); } + + void SetElement(size_t index, Object::Param element) const { + Base::Env()->SetObjectArrayElement(Base::Instance(), jsize(index), + element.Get()); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + } +}; + +// Support conversion from LocalRef* to LocalRef*: +// LocalRef foo; +// Foo::GetFoo(&foo); // error because parameter type is LocalRef*. +// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument. +template +class ReturnToLocal { + private: + LocalRef* const localRef; + LocalRef objRef; + + public: + explicit ReturnToLocal(LocalRef* ref) : localRef(ref) {} + operator LocalRef*() { return &objRef; } + + ~ReturnToLocal() { + if (objRef) { + *localRef = std::move(objRef); + } + } +}; + +template +ReturnToLocal ReturnTo(LocalRef* ref) { + return ReturnToLocal(ref); +} + +// Support conversion from GlobalRef* to LocalRef*: +// GlobalRef foo; +// Foo::GetFoo(&foo); // error because parameter type is LocalRef*. +// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument. +template +class ReturnToGlobal { + private: + GlobalRef* const globalRef; + LocalRef objRef; + LocalRef clsRef; + + public: + explicit ReturnToGlobal(GlobalRef* ref) : globalRef(ref) {} + operator LocalRef*() { return &objRef; } + operator LocalRef*() { return &clsRef; } + + ~ReturnToGlobal() { + if (objRef) { + *globalRef = (clsRef = std::move(objRef)); + } else if (clsRef) { + *globalRef = clsRef; + } + } +}; + +template +ReturnToGlobal ReturnTo(GlobalRef* ref) { + return ReturnToGlobal(ref); +} + +// Make a LocalRef from any other Ref +template +LocalRef ToLocalRef(const Ref& aRef) { + return LocalRef(aRef); +} + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Refs_h__ diff --git a/widget/android/jni/Types.h b/widget/android/jni/Types.h new file mode 100644 index 0000000000..2c0905979a --- /dev/null +++ b/widget/android/jni/Types.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_Types_h__ +#define mozilla_jni_Types_h__ + +#include + +#include "mozilla/jni/Refs.h" + +namespace mozilla { +namespace jni { +namespace detail { + +// TypeAdapter specializations are the interfaces between native/C++ types such +// as int32_t and JNI types such as jint. The template parameter T is the native +// type, and each TypeAdapter specialization can have the following members: +// +// * Call: JNIEnv member pointer for making a method call that returns T. +// * StaticCall: JNIEnv member pointer for making a static call that returns T. +// * Get: JNIEnv member pointer for getting a field of type T. +// * StaticGet: JNIEnv member pointer for getting a static field of type T. +// * Set: JNIEnv member pointer for setting a field of type T. +// * StaticGet: JNIEnv member pointer for setting a static field of type T. +// * ToNative: static function that converts the JNI type to the native type. +// * FromNative: static function that converts the native type to the JNI type. + +template +struct TypeAdapter; + +// TypeAdapter> applies when jobject is a return value. +template +struct TypeAdapter> { + using JNIType = typename Cls::Ref::JNIType; + + static constexpr auto Call = &JNIEnv::CallObjectMethodA; + static constexpr auto StaticCall = &JNIEnv::CallStaticObjectMethodA; + static constexpr auto Get = &JNIEnv::GetObjectField; + static constexpr auto StaticGet = &JNIEnv::GetStaticObjectField; + + // Declare instance as jobject because JNI methods return + // jobject even if the return value is really jstring, etc. + static LocalRef ToNative(JNIEnv* env, jobject instance) { + return LocalRef::Adopt(env, JNIType(instance)); + } + + static JNIType FromNative(JNIEnv*, LocalRef&& instance) { + return instance.Forget(); + } +}; + +// clang is picky about function types, including attributes that modify the +// calling convention, lining up. GCC appears to be somewhat less so. +#ifdef __clang__ +# define MOZ_JNICALL_ABI JNICALL +#else +# define MOZ_JNICALL_ABI +#endif + +// NDK r18 made jvalue* method parameters const. We detect the change directly +// instead of using ndk-version.h in order to remain compatible with r15 for +// now, which doesn't include those headers. +class CallArgs { + static const jvalue* test(void (JNIEnv::*)(jobject, jmethodID, + const jvalue*)); + static jvalue* test(void (JNIEnv::*)(jobject, jmethodID, jvalue*)); + + public: + using JValueType = decltype(test(&JNIEnv::CallVoidMethodA)); +}; + +template +constexpr jobject (JNIEnv::*TypeAdapter>::Call)( + jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; +template +constexpr jobject (JNIEnv::*TypeAdapter>::StaticCall)( + jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; +template +constexpr jobject (JNIEnv::*TypeAdapter>::Get)(jobject, jfieldID); +template +constexpr jobject (JNIEnv::*TypeAdapter>::StaticGet)(jclass, + jfieldID); + +// TypeAdapter> applies when jobject is a parameter value. +template +struct TypeAdapter> { + using JNIType = typename Ref::JNIType; + + static constexpr auto Set = &JNIEnv::SetObjectField; + static constexpr auto StaticSet = &JNIEnv::SetStaticObjectField; + + static DependentRef ToNative(JNIEnv* env, JNIType instance) { + return DependentRef(instance); + } + + static JNIType FromNative(JNIEnv*, const Ref& instance) { + return instance.Get(); + } +}; + +template +constexpr void (JNIEnv::*TypeAdapter>::Set)(jobject, jfieldID, + jobject); +template +constexpr void (JNIEnv::*TypeAdapter>::StaticSet)(jclass, jfieldID, + jobject); + +// jstring has its own Param type. +template <> +struct TypeAdapter : public TypeAdapter {}; + +template +struct TypeAdapter : public TypeAdapter {}; + +#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName) \ + \ + template <> \ + struct TypeAdapter { \ + using JNI##Type = JNIType; \ + \ + static constexpr auto Call = &JNIEnv::Call##JNIName##MethodA; \ + static constexpr auto StaticCall = &JNIEnv::CallStatic##JNIName##MethodA; \ + static constexpr auto Get = &JNIEnv::Get##JNIName##Field; \ + static constexpr auto StaticGet = &JNIEnv::GetStatic##JNIName##Field; \ + static constexpr auto Set = &JNIEnv::Set##JNIName##Field; \ + static constexpr auto StaticSet = &JNIEnv::SetStatic##JNIName##Field; \ + static constexpr auto GetArray = &JNIEnv::Get##JNIName##ArrayRegion; \ + static constexpr auto SetArray = &JNIEnv::Set##JNIName##ArrayRegion; \ + static constexpr auto NewArray = &JNIEnv::New##JNIName##Array; \ + \ + static JNIType FromNative(JNIEnv*, NativeType val) { \ + return static_cast(val); \ + } \ + static NativeType ToNative(JNIEnv*, JNIType val) { \ + return static_cast(val); \ + } \ + } + +DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte); +DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long); +DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float); +DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double); + +#undef DEFINE_PRIMITIVE_TYPE_ADAPTER + +} // namespace detail + +using namespace detail; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Types_h__ diff --git a/widget/android/jni/Utils.cpp b/widget/android/jni/Utils.cpp new file mode 100644 index 0000000000..974b46a408 --- /dev/null +++ b/widget/android/jni/Utils.cpp @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "Utils.h" +#include "Types.h" + +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/GeckoThreadWrappers.h" + +#include "AndroidBuild.h" +#include "nsAppShell.h" +#include "nsExceptionHandler.h" + +namespace mozilla { +namespace jni { + +namespace detail { + +#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName, ABIName) \ + \ + constexpr JNIType (JNIEnv::*TypeAdapter::Call)( \ + jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \ + constexpr JNIType (JNIEnv::*TypeAdapter::StaticCall)( \ + jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \ + constexpr JNIType (JNIEnv::*TypeAdapter::Get)(jobject, jfieldID) \ + ABIName; \ + constexpr JNIType (JNIEnv::*TypeAdapter::StaticGet)( \ + jclass, jfieldID) ABIName; \ + constexpr void (JNIEnv::*TypeAdapter::Set)(jobject, jfieldID, \ + JNIType) ABIName; \ + constexpr void (JNIEnv::*TypeAdapter::StaticSet)( \ + jclass, jfieldID, JNIType) ABIName; \ + constexpr void (JNIEnv::*TypeAdapter::GetArray)( \ + JNIType##Array, jsize, jsize, JNIType*) + +DEFINE_PRIMITIVE_TYPE_ADAPTER(bool, jboolean, Boolean, /*nothing*/); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int8_t, jbyte, Byte, /*nothing*/); +DEFINE_PRIMITIVE_TYPE_ADAPTER(char16_t, jchar, Char, /*nothing*/); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int16_t, jshort, Short, /*nothing*/); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int32_t, jint, Int, /*nothing*/); +DEFINE_PRIMITIVE_TYPE_ADAPTER(int64_t, jlong, Long, /*nothing*/); +DEFINE_PRIMITIVE_TYPE_ADAPTER(float, jfloat, Float, MOZ_JNICALL_ABI); +DEFINE_PRIMITIVE_TYPE_ADAPTER(double, jdouble, Double, MOZ_JNICALL_ABI); + +#undef DEFINE_PRIMITIVE_TYPE_ADAPTER + +} // namespace detail + +template <> +const char ObjectBase::name[] = "java/lang/Object"; +template <> +const char ObjectBase, jstring>::name[] = + "java/lang/String"; +template <> +const char ObjectBase, jclass>::name[] = "java/lang/Class"; +template <> +const char ObjectBase, jthrowable>::name[] = + "java/lang/Throwable"; +template <> +const char ObjectBase, jobject>::name[] = + "java/lang/Boolean"; +template <> +const char ObjectBase, jobject>::name[] = "java/lang/Byte"; +template <> +const char ObjectBase, jobject>::name[] = + "java/lang/Character"; +template <> +const char ObjectBase, jobject>::name[] = "java/lang/Short"; +template <> +const char ObjectBase, jobject>::name[] = "java/lang/Integer"; +template <> +const char ObjectBase, jobject>::name[] = "java/lang/Long"; +template <> +const char ObjectBase, jobject>::name[] = "java/lang/Float"; +template <> +const char ObjectBase, jobject>::name[] = + "java/lang/Double"; +template <> +const char ObjectBase, jbooleanArray>::name[] = "[Z"; +template <> +const char ObjectBase, jbyteArray>::name[] = "[B"; +template <> +const char ObjectBase, jcharArray>::name[] = "[C"; +template <> +const char ObjectBase, jshortArray>::name[] = "[S"; +template <> +const char ObjectBase, jintArray>::name[] = "[I"; +template <> +const char ObjectBase, jlongArray>::name[] = "[J"; +template <> +const char ObjectBase, jfloatArray>::name[] = "[F"; +template <> +const char ObjectBase, jdoubleArray>::name[] = "[D"; +template <> +const char ObjectBase, jobjectArray>::name[] = + "[Ljava/lang/Object;"; +template <> +const char ObjectBase::name[] = "java/nio/ByteBuffer"; + +JavaVM* sJavaVM; +JNIEnv* sGeckoThreadEnv; + +namespace { + +pthread_key_t sThreadEnvKey; +jclass sOOMErrorClass; +jobject sClassLoader; +jmethodID sClassLoaderLoadClass; + +void UnregisterThreadEnv(void* env) { + if (!env) { + // We were never attached. + return; + } + // The thread may have already been detached. In that case, it's still + // okay to call DetachCurrentThread(); it'll simply return an error. + // However, we must not access | env | because it may be invalid. + MOZ_ASSERT(sJavaVM); + sJavaVM->DetachCurrentThread(); +} + +} // namespace + +void SetGeckoThreadEnv(JNIEnv* aEnv) { + MOZ_ASSERT(aEnv); + MOZ_ASSERT(!sGeckoThreadEnv || sGeckoThreadEnv == aEnv); + + if (!sGeckoThreadEnv && + pthread_key_create(&sThreadEnvKey, UnregisterThreadEnv)) { + MOZ_CRASH("Failed to initialize required TLS"); + } + + sGeckoThreadEnv = aEnv; + MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, aEnv)); + + MOZ_ALWAYS_TRUE(!aEnv->GetJavaVM(&sJavaVM)); + MOZ_ASSERT(sJavaVM); + + sOOMErrorClass = + Class::GlobalRef( + Class::LocalRef::Adopt(aEnv->FindClass("java/lang/OutOfMemoryError"))) + .Forget(); + aEnv->ExceptionClear(); + + sClassLoader = Object::GlobalRef(java::GeckoThread::ClsLoader()).Forget(); + sClassLoaderLoadClass = aEnv->GetMethodID( + Class::LocalRef::Adopt(aEnv->GetObjectClass(sClassLoader)).Get(), + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + MOZ_ASSERT(sClassLoader && sClassLoaderLoadClass); +} + +JNIEnv* GetEnvForThread() { + MOZ_ASSERT(sGeckoThreadEnv); + + JNIEnv* env = static_cast(pthread_getspecific(sThreadEnvKey)); + if (env) { + return env; + } + + // We don't have a saved JNIEnv, so try to get one. + // AttachCurrentThread() does the same thing as GetEnv() when a thread is + // already attached, so we don't have to call GetEnv() at all. + if (!sJavaVM->AttachCurrentThread(&env, nullptr)) { + MOZ_ASSERT(env); + MOZ_ALWAYS_TRUE(!pthread_setspecific(sThreadEnvKey, env)); + return env; + } + + MOZ_CRASH("Failed to get JNIEnv for thread"); + return nullptr; // unreachable +} + +bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage) { + MOZ_ASSERT(aEnv, "Invalid thread JNI env"); + + Class::LocalRef cls = Class::LocalRef::Adopt(aEnv->FindClass(aClass)); + MOZ_ASSERT(cls, "Cannot find exception class"); + + return !aEnv->ThrowNew(cls.Get(), aMessage); +} + +bool HandleUncaughtException(JNIEnv* aEnv) { + MOZ_ASSERT(aEnv, "Invalid thread JNI env"); + + if (!aEnv->ExceptionCheck()) { + return false; + } + +#ifdef MOZ_CHECK_JNI + aEnv->ExceptionDescribe(); +#endif + + Throwable::LocalRef e = + Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred()); + MOZ_ASSERT(e); + aEnv->ExceptionClear(); + + String::LocalRef stack = java::GeckoAppShell::GetExceptionStackTrace(e); + if (stack && ReportException(aEnv, e.Get(), stack.Get())) { + return true; + } + + aEnv->ExceptionClear(); + java::GeckoAppShell::HandleUncaughtException(e); + + if (NS_WARN_IF(aEnv->ExceptionCheck())) { + aEnv->ExceptionDescribe(); + aEnv->ExceptionClear(); + } + + return true; +} + +bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack) { + bool result = true; + + result &= NS_SUCCEEDED(CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::JavaStackTrace, + String::Ref::From(aStack)->ToCString())); + + auto appNotes = java::GeckoAppShell::GetAppNotes(); + if (NS_WARN_IF(aEnv->ExceptionCheck())) { + aEnv->ExceptionDescribe(); + aEnv->ExceptionClear(); + } else if (appNotes) { + CrashReporter::AppendAppNotesToCrashReport("\n"_ns + appNotes->ToCString()); + } + + if (sOOMErrorClass && aEnv->IsInstanceOf(aExc, sOOMErrorClass)) { + NS_ABORT_OOM(0); // Unknown OOM size + } + return result; +} + +namespace { + +jclass sJNIObjectClass; +jfieldID sJNIObjectHandleField; + +bool EnsureJNIObject(JNIEnv* env, jobject instance) { + if (!sJNIObjectClass) { + sJNIObjectClass = + Class::GlobalRef(Class::LocalRef::Adopt(GetClassRef( + env, "org/mozilla/gecko/mozglue/JNIObject"))) + .Forget(); + + sJNIObjectHandleField = env->GetFieldID(sJNIObjectClass, "mHandle", "J"); + } + + MOZ_ASSERT(env->IsInstanceOf(instance, sJNIObjectClass), + "Java class is not derived from JNIObject"); + return true; +} + +} // namespace + +uintptr_t GetNativeHandle(JNIEnv* env, jobject instance) { + if (!EnsureJNIObject(env, instance)) { + return 0; + } + + return static_cast( + env->GetLongField(instance, sJNIObjectHandleField)); +} + +void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle) { + if (!EnsureJNIObject(env, instance)) { + return; + } + + env->SetLongField(instance, sJNIObjectHandleField, + static_cast(handle)); +} + +jclass GetClassRef(JNIEnv* aEnv, const char* aClassName) { + // First try the default class loader. + auto classRef = Class::LocalRef::Adopt(aEnv, aEnv->FindClass(aClassName)); + + if ((!classRef || aEnv->ExceptionCheck()) && sClassLoader) { + // If the default class loader failed but we have an app class loader, try + // that. Clear the pending exception from failed FindClass call above. + aEnv->ExceptionClear(); + classRef = Class::LocalRef::Adopt( + aEnv, + jclass(aEnv->CallObjectMethod(sClassLoader, sClassLoaderLoadClass, + StringParam(aClassName, aEnv).Get()))); + } + + if (classRef && !aEnv->ExceptionCheck()) { + return classRef.Forget(); + } + + __android_log_print( + ANDROID_LOG_ERROR, "Gecko", + ">>> FATAL JNI ERROR! FindClass(\"%s\") failed. " + "Does the class require a newer API version? " + "Or did ProGuard optimize away something it shouldn't have?", + aClassName); + aEnv->ExceptionDescribe(); + MOZ_CRASH("Cannot find JNI class"); + return nullptr; +} + +void DispatchToGeckoPriorityQueue(already_AddRefed aCall) { + class RunnableEvent : public nsAppShell::Event { + nsCOMPtr mCall; + + public: + explicit RunnableEvent(already_AddRefed aCall) + : mCall(aCall) {} + void Run() override { NS_ENSURE_SUCCESS_VOID(mCall->Run()); } + }; + + nsAppShell::PostEvent(MakeUnique(std::move(aCall))); +} + +int GetAPIVersion() { + static int32_t apiVersion = 0; + if (!apiVersion && IsAvailable()) { + apiVersion = java::sdk::VERSION::SDK_INT(); + } + return apiVersion; +} + +pid_t GetUIThreadId() { + static pid_t uiThreadId; + if (!uiThreadId) { + uiThreadId = pid_t(java::GeckoThread::UiThreadId()); + } + return uiThreadId; +} + +} // namespace jni +} // namespace mozilla diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h new file mode 100644 index 0000000000..037295c82c --- /dev/null +++ b/widget/android/jni/Utils.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_jni_Utils_h__ +#define mozilla_jni_Utils_h__ + +#include + +#include "nsIRunnable.h" + +#include "mozilla/UniquePtr.h" + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) +# define MOZ_CHECK_JNI +#endif + +#ifdef MOZ_CHECK_JNI +# include +# include "mozilla/Assertions.h" +# include "APKOpen.h" +# include "MainThreadUtils.h" +#endif + +namespace mozilla { +namespace jni { + +// How exception during a JNI call should be treated. +enum class ExceptionMode { + // Abort on unhandled excepion (default). + ABORT, + // Ignore the exception and return to caller. + IGNORE, + // Catch any exception and return a nsresult. + NSRESULT, +}; + +// Thread that a particular JNI call is allowed on. +enum class CallingThread { + // Can be called from any thread (default). + ANY, + // Can be called from the Gecko thread. + GECKO, + // Can be called from the Java UI thread. + UI, +}; + +// If and where a JNI call will be dispatched. +enum class DispatchTarget { + // Call happens synchronously on the calling thread (default). + CURRENT, + // Call happens synchronously on the calling thread, but the call is + // wrapped in a function object and is passed thru UsesNativeCallProxy. + // Method must return void. + PROXY, + // Call is dispatched asynchronously on the Gecko thread to the XPCOM + // (nsThread) event queue. Method must return void. + GECKO, + // Call is dispatched asynchronously on the Gecko thread to the widget + // (nsAppShell) event queue. In most cases, events in the widget event + // queue (aka native event queue) are favored over events in the XPCOM + // event queue. Method must return void. + GECKO_PRIORITY, +}; + +extern JavaVM* sJavaVM; +extern JNIEnv* sGeckoThreadEnv; + +inline bool IsAvailable() { return !!sGeckoThreadEnv; } + +inline JavaVM* GetVM() { +#ifdef MOZ_CHECK_JNI + MOZ_ASSERT(sJavaVM); +#endif + return sJavaVM; +} + +inline JNIEnv* GetGeckoThreadEnv() { +#ifdef MOZ_CHECK_JNI + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Must be on Gecko thread"); + MOZ_RELEASE_ASSERT(sGeckoThreadEnv, "Must have a JNIEnv"); +#endif + return sGeckoThreadEnv; +} + +void SetGeckoThreadEnv(JNIEnv* aEnv); + +JNIEnv* GetEnvForThread(); + +#ifdef MOZ_CHECK_JNI +# define MOZ_ASSERT_JNI_THREAD(thread) \ + do { \ + if ((thread) == mozilla::jni::CallingThread::GECKO) { \ + MOZ_RELEASE_ASSERT(::NS_IsMainThread()); \ + } else if ((thread) == mozilla::jni::CallingThread::UI) { \ + const bool isOnUiThread = (GetUIThreadId() == ::gettid()); \ + MOZ_RELEASE_ASSERT(isOnUiThread); \ + } \ + } while (0) +#else +# define MOZ_ASSERT_JNI_THREAD(thread) \ + do { \ + } while (0) +#endif + +bool ThrowException(JNIEnv* aEnv, const char* aClass, const char* aMessage); + +inline bool ThrowException(JNIEnv* aEnv, const char* aMessage) { + return ThrowException(aEnv, "java/lang/Exception", aMessage); +} + +inline bool ThrowException(const char* aClass, const char* aMessage) { + return ThrowException(GetEnvForThread(), aClass, aMessage); +} + +inline bool ThrowException(const char* aMessage) { + return ThrowException(GetEnvForThread(), aMessage); +} + +bool HandleUncaughtException(JNIEnv* aEnv); + +bool ReportException(JNIEnv* aEnv, jthrowable aExc, jstring aStack); + +#define MOZ_CATCH_JNI_EXCEPTION(env) \ + do { \ + if (mozilla::jni::HandleUncaughtException((env))) { \ + MOZ_CRASH("JNI exception"); \ + } \ + } while (0) + +uintptr_t GetNativeHandle(JNIEnv* env, jobject instance); + +void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle); + +jclass GetClassRef(JNIEnv* aEnv, const char* aClassName); + +void DispatchToGeckoPriorityQueue(already_AddRefed aCall); + +int GetAPIVersion(); + +pid_t GetUIThreadId(); + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Utils_h__ diff --git a/widget/android/jni/moz.build b/widget/android/jni/moz.build new file mode 100644 index 0000000000..422c69d590 --- /dev/null +++ b/widget/android/jni/moz.build @@ -0,0 +1,33 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("GeckoView", "General") + +EXPORTS.mozilla.jni += [ + "Accessors.h", + "Conversions.h", + "GeckoBundleUtils.h", + "GeckoResultUtils.h", + "Natives.h", + "Refs.h", + "Types.h", + "Utils.h", +] + +UNIFIED_SOURCES += [ + "Conversions.cpp", + "Utils.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/widget", + "/widget/android", +] diff --git a/widget/android/moz.build b/widget/android/moz.build new file mode 100644 index 0000000000..25caff0869 --- /dev/null +++ b/widget/android/moz.build @@ -0,0 +1,198 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Widget: Android") + SCHEDULES.exclusive = ["android"] + +with Files("*CompositorWidget*"): + BUG_COMPONENT = ("Core", "Graphics") + +DIRS += [ + "bindings", + "jni", +] + +XPIDL_SOURCES += [ + "nsIAndroidBridge.idl", +] + +XPIDL_MODULE = "widget_android" + +EXPORTS += [ + "AndroidBridge.h", +] + +classes_with_WrapForJNI = [ + "AndroidGamepadManager", + "AndroidVsync", + "Base64Utils", + "Clipboard", + "CodecProxy", + "EnterpriseRoots", + "EventCallback", + "EventDispatcher", + "GeckoAppShell", + "GeckoAudioInfo", + "GeckoBatteryManager", + "GeckoBundle", + "GeckoEditableChild", + "GeckoHLSDemuxerWrapper", + "GeckoHLSResourceWrapper", + "GeckoHLSSample", + "GeckoInputStream", + "GeckoJavaSampler", + "GeckoNetworkManager", + "GeckoProcessManager", + "GeckoProcessType", + "GeckoResult", + "GeckoRuntime", + "GeckoScreenOrientation", + "GeckoServiceChildProcess", + "GeckoSession", + "GeckoSurface", + "GeckoSurfaceTexture", + "GeckoSystemStateListener", + "GeckoThread", + "GeckoVRManager", + "GeckoVideoInfo", + "GeckoWebExecutor", + "HardwareCodecCapabilityUtils", + "ImageDecoder", + "MediaDrmProxy", + "PanZoomController", + "PrefsHelper", + "RuntimeTelemetry", + "Sample", + "SampleBuffer", + "ScreenManagerHelper", + "ServiceAllocator", + "SessionAccessibility", + "SessionKeyInfo", + "SessionTextInput", + "SpeechSynthesisService", + "SurfaceAllocator", + "SurfaceTextureListener", + "TelemetryUtils", + "WebAuthnTokenManager", + "WebMessage", + "WebNotification", + "WebNotificationDelegate", + "WebRequest", + "WebRequestError", + "WebResponse", + "XPCOMEventTarget", +] + +natives_from_WrapForJNI = sorted( + ["GeneratedJNI/{}Natives.h".format(c) for c in classes_with_WrapForJNI] +) + +wrappers_from_WrapForJNI = sorted( + ["GeneratedJNI/{}Wrappers.h".format(c) for c in classes_with_WrapForJNI] +) + +sources_from_WrapForJNI = sorted( + "GeneratedJNI{}Wrappers.cpp".format(c) for c in classes_with_WrapForJNI +) + +EXPORTS.mozilla.widget += [ + "AndroidCompositorWidget.h", + "AndroidUiThread.h", + "AndroidView.h", + "AndroidVsync.h", + "EventDispatcher.h", + "GeckoViewSupport.h", + "nsWindow.h", + "WindowEvent.h", +] + +EXPORTS.mozilla.java += ["!{}".format(c) for c in natives_from_WrapForJNI] + +EXPORTS.mozilla.java += ["!{}".format(c) for c in wrappers_from_WrapForJNI] + +SOURCES += ["!{}".format(c) for c in sources_from_WrapForJNI] + +SOURCES += [ + "MediaKeysEventSourceFactory.cpp", +] + +UNIFIED_SOURCES += [ + "AndroidAlerts.cpp", + "AndroidBridge.cpp", + "AndroidCompositorWidget.cpp", + "AndroidContentController.cpp", + "AndroidUiThread.cpp", + "AndroidVsync.cpp", + "EventDispatcher.cpp", + "GeckoEditableSupport.cpp", + "GeckoProcessManager.cpp", + "GfxInfo.cpp", + "ImageDecoderSupport.cpp", + "nsAndroidProtocolHandler.cpp", + "nsAppShell.cpp", + "nsClipboard.cpp", + "nsDeviceContextAndroid.cpp", + "nsLookAndFeel.cpp", + "nsNativeBasicThemeAndroid.cpp", + "nsNativeThemeAndroid.cpp", + "nsPrintSettingsServiceAndroid.cpp", + "nsUserIdleServiceAndroid.cpp", + "nsWidgetFactory.cpp", + "nsWindow.cpp", + "ScreenHelperAndroid.cpp", + "WebExecutorSupport.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +# The recursive make backend treats the first output specially: it's passed as +# an open FileAvoidWrite to the invoked script. That doesn't work well with +# the Gradle task that generates all of the outputs, so we add a dummy first +# output. + +t = tuple( + ["generated_jni_wrappers"] + + natives_from_WrapForJNI + + sources_from_WrapForJNI + + wrappers_from_WrapForJNI +) + +GeneratedFile( + *t, + script="/mobile/android/gradle.py", + entry_point="generate_generated_jni_wrappers" +) + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/docshell/base", + "/dom/base", + "/dom/system/android", + "/gfx/2d", + "/gfx/vr", + "/layout/forms", + "/layout/painting", + "/netwerk/base", + "/netwerk/cache", + "/toolkit/components/telemetry", + "/widget", + "/xpcom/threads", +] + +CXXFLAGS += ["-Wno-error=shadow"] + +OS_LIBS += ["android"] + +if CONFIG["MOZ_NATIVE_DEVICES"]: + DEFINES["MOZ_NATIVE_DEVICES"] = True + +# DEFINES['DEBUG_WIDGETS'] = True diff --git a/widget/android/nsAndroidProtocolHandler.cpp b/widget/android/nsAndroidProtocolHandler.cpp new file mode 100644 index 0000000000..6453db0df6 --- /dev/null +++ b/widget/android/nsAndroidProtocolHandler.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et cin: */ +/* 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 "nsAndroidProtocolHandler.h" +#include "nsCOMPtr.h" +#include "nsIChannel.h" +#include "android/log.h" +#include "nsBaseChannel.h" +#include "AndroidBridge.h" +#include "mozilla/java/GeckoAppShellWrappers.h" + +using namespace mozilla; + +class AndroidInputStream : public nsIInputStream { + public: + explicit AndroidInputStream(jni::Object::Param connection) { + mBridgeInputStream = java::GeckoAppShell::CreateInputStream(connection); + mBridgeChannel = AndroidBridge::ChannelCreate(mBridgeInputStream); + } + + private: + virtual ~AndroidInputStream() {} + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + + private: + jni::Object::GlobalRef mBridgeInputStream; + jni::Object::GlobalRef mBridgeChannel; +}; + +NS_IMPL_ISUPPORTS(AndroidInputStream, nsIInputStream) + +NS_IMETHODIMP AndroidInputStream::Close(void) { + AndroidBridge::InputStreamClose(mBridgeInputStream); + return NS_OK; +} + +NS_IMETHODIMP AndroidInputStream::Available(uint64_t* _retval) { + *_retval = AndroidBridge::InputStreamAvailable(mBridgeInputStream); + return NS_OK; +} + +NS_IMETHODIMP AndroidInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* _retval) { + return AndroidBridge::InputStreamRead(mBridgeChannel, aBuf, aCount, _retval); +} + +NS_IMETHODIMP AndroidInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP AndroidInputStream::IsNonBlocking(bool* _retval) { + *_retval = false; + return NS_OK; +} + +class AndroidChannel : public nsBaseChannel { + private: + AndroidChannel(nsIURI* aURI, jni::Object::Param aConnection) { + mConnection = aConnection; + SetURI(aURI); + + auto type = java::GeckoAppShell::ConnectionGetMimeType(mConnection); + if (type) { + SetContentType(type->ToCString()); + } + } + + public: + static AndroidChannel* CreateChannel(nsIURI* aURI) { + nsCString spec; + aURI->GetSpec(spec); + + auto connection = java::GeckoAppShell::GetConnection(spec); + return connection ? new AndroidChannel(aURI, connection) : nullptr; + } + + virtual ~AndroidChannel() {} + + virtual nsresult OpenContentStream(bool async, nsIInputStream** result, + nsIChannel** channel) { + nsCOMPtr stream = new AndroidInputStream(mConnection); + NS_ADDREF(*result = stream); + return NS_OK; + } + + private: + jni::Object::GlobalRef mConnection; +}; + +NS_IMPL_ISUPPORTS(nsAndroidProtocolHandler, nsIProtocolHandler, + nsISupportsWeakReference) + +NS_IMETHODIMP +nsAndroidProtocolHandler::GetScheme(nsACString& result) { + result.AssignLiteral("android"); + return NS_OK; +} + +NS_IMETHODIMP +nsAndroidProtocolHandler::GetDefaultPort(int32_t* result) { + *result = -1; // no port for android: URLs + return NS_OK; +} + +NS_IMETHODIMP +nsAndroidProtocolHandler::AllowPort(int32_t port, const char* scheme, + bool* _retval) { + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsAndroidProtocolHandler::GetProtocolFlags(uint32_t* result) { + *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE | + URI_NORELATIVE | URI_DANGEROUS_TO_LOAD; + return NS_OK; +} + +NS_IMETHODIMP +nsAndroidProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** aResult) { + nsCOMPtr channel = AndroidChannel::CreateChannel(aURI); + if (!channel) return NS_ERROR_FAILURE; + + // set the loadInfo on the new channel + nsresult rv = channel->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = channel); + return NS_OK; +} diff --git a/widget/android/nsAndroidProtocolHandler.h b/widget/android/nsAndroidProtocolHandler.h new file mode 100644 index 0000000000..811b02e45c --- /dev/null +++ b/widget/android/nsAndroidProtocolHandler.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsAndroidProtocolHandler_h___ +#define nsAndroidProtocolHandler_h___ + +#include "nsIProtocolHandler.h" +#include "nsWeakReference.h" +#include "mozilla/Attributes.h" + +#define NS_ANDROIDPROTOCOLHANDLER_CID \ + { /* e9cd2b7f-8386-441b-aaf5-0b371846bfd0 */ \ + 0xe9cd2b7f, 0x8386, 0x441b, { 0x0b, 0x37, 0x18, 0x46, 0xbf, 0xd0 } \ + } + +class nsAndroidProtocolHandler final : public nsIProtocolHandler, + public nsSupportsWeakReference { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIProtocolHandler methods: + NS_DECL_NSIPROTOCOLHANDLER + + // nsAndroidProtocolHandler methods: + nsAndroidProtocolHandler() {} + + private: + ~nsAndroidProtocolHandler() {} +}; + +#endif /* nsAndroidProtocolHandler_h___ */ diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp new file mode 100644 index 0000000000..4dc7e3213a --- /dev/null +++ b/widget/android/nsAppShell.cpp @@ -0,0 +1,781 @@ +/* -*- Mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* 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 "nsAppShell.h" + +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "mozilla/Hal.h" +#include "nsExceptionHandler.h" +#include "nsIScreen.h" +#include "nsWindow.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsIAppStartup.h" +#include "nsIGeolocationProvider.h" +#include "nsCacheService.h" +#include "nsIDOMWakeLockListener.h" +#include "nsIPowerManagerService.h" +#include "nsISpeculativeConnect.h" +#include "nsIURIFixup.h" +#include "nsCategoryManagerUtils.h" +#include "mozilla/dom/GeolocationPosition.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Components.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "mozilla/Hal.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/Document.h" +#include "mozilla/intl/OSPreferences.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/java/GeckoAppShellNatives.h" +#include "mozilla/java/GeckoThreadNatives.h" +#include "mozilla/java/XPCOMEventTargetNatives.h" +#include "mozilla/widget/ScreenManager.h" +#include "prenv.h" + +#include "AndroidBridge.h" +#include "AndroidBridgeUtilities.h" +#include "AndroidSurfaceTexture.h" +#include +#include +#include + +#include "GeckoProfiler.h" +#ifdef MOZ_ANDROID_HISTORY +# include "nsNetUtil.h" +# include "nsIURI.h" +# include "IHistory.h" +#endif + +#ifdef MOZ_LOGGING +# include "mozilla/Logging.h" +#endif + +#include "AndroidAlerts.h" +#include "AndroidUiThread.h" +#include "GeckoBatteryManager.h" +#include "GeckoEditableSupport.h" +#include "GeckoNetworkManager.h" +#include "GeckoProcessManager.h" +#include "GeckoScreenOrientation.h" +#include "GeckoSystemStateListener.h" +#include "GeckoTelemetryDelegate.h" +#include "GeckoVRManager.h" +#include "ImageDecoderSupport.h" +#include "PrefsHelper.h" +#include "ScreenHelperAndroid.h" +#include "Telemetry.h" +#include "WebExecutorSupport.h" +#include "Base64UtilsSupport.h" +#include "WebAuthnTokenManager.h" + +#ifdef DEBUG_ANDROID_EVENTS +# define EVLOG(args...) ALOG(args) +#else +# define EVLOG(args...) \ + do { \ + } while (0) +#endif + +using namespace mozilla; +using namespace mozilla::widget; + +nsIGeolocationUpdate* gLocationCallback = nullptr; + +nsAppShell* nsAppShell::sAppShell; +StaticAutoPtr nsAppShell::sAppShellLock; + +uint32_t nsAppShell::Queue::sLatencyCount[]; +uint64_t nsAppShell::Queue::sLatencyTime[]; + +NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver) + +class WakeLockListener final : public nsIDOMMozWakeLockListener { + private: + ~WakeLockListener() {} + + public: + NS_DECL_ISUPPORTS; + + nsresult Callback(const nsAString& topic, const nsAString& state) override { + java::GeckoAppShell::NotifyWakeLockChanged(topic, state); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener) +nsCOMPtr sPowerManagerService = nullptr; +StaticRefPtr sWakeLockListener; + +class GeckoThreadSupport final + : public java::GeckoThread::Natives { + // When this number goes above 0, the app is paused. When less than or + // equal to zero, the app is resumed. + static int32_t sPauseCount; + + public: + static void SpeculativeConnect(jni::String::Param aUriStr) { + if (!NS_IsMainThread()) { + // We will be on the main thread if the call was queued on the Java + // side during startup. Otherwise, the call was not queued, which + // means Gecko is already sufficiently loaded, and we don't really + // care about speculative connections at this point. + return; + } + + nsCOMPtr ioServ = do_GetIOService(); + nsCOMPtr specConn = do_QueryInterface(ioServ); + if (!specConn) { + return; + } + + nsCOMPtr uri = nsAppShell::ResolveURI(aUriStr->ToCString()); + if (!uri) { + return; + } + + OriginAttributes attrs; + nsCOMPtr principal = + BasePrincipal::CreateContentPrincipal(uri, attrs); + specConn->SpeculativeConnect(uri, principal, nullptr); + } + + static void OnPause() { + MOZ_ASSERT(NS_IsMainThread()); + + sPauseCount++; + // If sPauseCount is now 1, we just crossed the threshold from "resumed" + // "paused". so we should notify observers and so on. + if (sPauseCount != 1) { + return; + } + + nsCOMPtr obsServ = + mozilla::services::GetObserverService(); + obsServ->NotifyObservers(nullptr, "application-background", nullptr); + + obsServ->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); + + // If we are OOM killed with the disk cache enabled, the entire + // cache will be cleared (bug 105843), so shut down the cache here + // and re-init on foregrounding + if (nsCacheService::GlobalInstance()) { + nsCacheService::GlobalInstance()->Shutdown(); + } + + // We really want to send a notification like profile-before-change, + // but profile-before-change ends up shutting some things down instead + // of flushing data + Preferences* prefs = static_cast(Preferences::GetService()); + if (prefs) { + // Force a main thread blocking save + prefs->SavePrefFileBlocking(); + } + } + + static void OnResume() { + MOZ_ASSERT(NS_IsMainThread()); + + sPauseCount--; + // If sPauseCount is now 0, we just crossed the threshold from "paused" + // to "resumed", so we should notify observers and so on. + if (sPauseCount != 0) { + return; + } + + // If we are OOM killed with the disk cache enabled, the entire + // cache will be cleared (bug 105843), so shut down cache on backgrounding + // and re-init here + if (nsCacheService::GlobalInstance()) { + nsCacheService::GlobalInstance()->Init(); + } + + // We didn't return from one of our own activities, so restore + // to foreground status + nsCOMPtr obsServ = + mozilla::services::GetObserverService(); + obsServ->NotifyObservers(nullptr, "application-foreground", nullptr); + } + + static void CreateServices(jni::String::Param aCategory, + jni::String::Param aData) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCString category(aCategory->ToCString()); + + NS_CreateServicesFromCategory(category.get(), + nullptr, // aOrigin + category.get(), + aData ? aData->ToString().get() : nullptr); + } + + static int64_t RunUiThreadCallback() { return RunAndroidUiTasks(); } + + static void ForceQuit() { + nsCOMPtr appStartup = components::AppStartup::Service(); + + if (appStartup) { + bool userAllowedQuit = true; + appStartup->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit); + } + } + + static void Crash() { + printf_stderr("Intentionally crashing...\n"); + MOZ_CRASH("intentional crash"); + } +}; + +int32_t GeckoThreadSupport::sPauseCount; + +class GeckoAppShellSupport final + : public java::GeckoAppShell::Natives { + public: + static void ReportJavaCrash(const jni::Class::LocalRef& aCls, + jni::Throwable::Param aException, + jni::String::Param aStack) { + if (!jni::ReportException(aCls.Env(), aException.Get(), aStack.Get())) { + // Only crash below if crash reporter is initialized and annotation + // succeeded. Otherwise try other means of reporting the crash in + // Java. + return; + } + + MOZ_CRASH("Uncaught Java exception"); + } + + static void NotifyObservers(jni::String::Param aTopic, + jni::String::Param aData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTopic); + + nsCOMPtr obsServ = services::GetObserverService(); + if (!obsServ) { + return; + } + + obsServ->NotifyObservers(nullptr, aTopic->ToCString().get(), + aData ? aData->ToString().get() : nullptr); + } + + static void AppendAppNotesToCrashReport(jni::String::Param aNotes) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aNotes); + CrashReporter::AppendAppNotesToCrashReport(aNotes->ToCString()); + } + + static void OnSensorChanged(int32_t aType, float aX, float aY, float aZ, + float aW, int64_t aTime) { + AutoTArray values; + + switch (aType) { + // Bug 938035, transfer HAL data for orientation sensor to meet w3c + // spec, ex: HAL report alpha=90 means East but alpha=90 means West + // in w3c spec + case hal::SENSOR_ORIENTATION: + values.AppendElement(360.0f - aX); + values.AppendElement(-aY); + values.AppendElement(-aZ); + break; + + case hal::SENSOR_LINEAR_ACCELERATION: + case hal::SENSOR_ACCELERATION: + case hal::SENSOR_GYROSCOPE: + case hal::SENSOR_PROXIMITY: + values.AppendElement(aX); + values.AppendElement(aY); + values.AppendElement(aZ); + break; + + case hal::SENSOR_LIGHT: + values.AppendElement(aX); + break; + + case hal::SENSOR_ROTATION_VECTOR: + case hal::SENSOR_GAME_ROTATION_VECTOR: + values.AppendElement(aX); + values.AppendElement(aY); + values.AppendElement(aZ); + values.AppendElement(aW); + break; + + default: + __android_log_print(ANDROID_LOG_ERROR, "Gecko", + "Unknown sensor type %d", aType); + } + + hal::SensorData sdata(hal::SensorType(aType), aTime, values); + hal::NotifySensorChange(sdata); + } + + static void OnLocationChanged(double aLatitude, double aLongitude, + double aAltitude, float aAccuracy, + float aAltitudeAccuracy, float aHeading, + float aSpeed, int64_t aTime) { + if (!gLocationCallback) { + return; + } + + RefPtr geoPosition( + new nsGeoPosition(aLatitude, aLongitude, aAltitude, aAccuracy, + aAltitudeAccuracy, aHeading, aSpeed, aTime)); + gLocationCallback->Update(geoPosition); + } + + static void NotifyAlertListener(jni::String::Param aName, + jni::String::Param aTopic, + jni::String::Param aCookie) { + if (!aName || !aTopic || !aCookie) { + return; + } + + widget::AndroidAlerts::NotifyListener(aName->ToString(), + aTopic->ToCString().get(), + aCookie->ToString().get()); + } +}; + +class XPCOMEventTargetWrapper final + : public java::XPCOMEventTarget::Natives { + public: + // Wraps a java runnable into an XPCOM runnable and dispatches it to mTarget. + void DispatchNative(mozilla::jni::Object::Param aJavaRunnable) { + java::XPCOMEventTarget::JNIRunnable::GlobalRef r = + java::XPCOMEventTarget::JNIRunnable::Ref::From(aJavaRunnable); + mTarget->Dispatch(NS_NewRunnableFunction( + "XPCOMEventTargetWrapper::DispatchNative", + [runnable = std::move(r)]() { runnable->Run(); })); + } + + bool IsOnCurrentThread() { return mTarget->IsOnCurrentThread(); } + + static void Init() { + java::XPCOMEventTarget::Natives::Init(); + CreateWrapper(u"main"_ns, do_GetMainThread()); + if (XRE_IsParentProcess()) { + CreateWrapper(u"launcher"_ns, ipc::GetIPCLauncher()); + } + } + + static void CreateWrapper(mozilla::jni::String::Param aName, + nsCOMPtr aTarget) { + auto java = java::XPCOMEventTarget::New(); + auto native = MakeUnique(aTarget.forget()); + AttachNative(java, std::move(native)); + + java::XPCOMEventTarget::SetTarget(aName, java); + } + + static void ResolveAndDispatchNative(mozilla::jni::String::Param aName, + mozilla::jni::Object::Param aRunnable) { + java::XPCOMEventTarget::ResolveAndDispatch(aName, aRunnable); + } + + explicit XPCOMEventTargetWrapper(already_AddRefed aTarget) + : mTarget(aTarget) {} + + private: + nsCOMPtr mTarget; +}; + +nsAppShell::nsAppShell() + : mSyncRunFinished(*(sAppShellLock = new Mutex("nsAppShell")), + "nsAppShell.SyncRun"), + mSyncRunQuit(false) { + { + MutexAutoLock lock(*sAppShellLock); + sAppShell = this; + } + + hal::Init(); + + if (!XRE_IsParentProcess()) { + if (jni::IsAvailable()) { + GeckoThreadSupport::Init(); + GeckoAppShellSupport::Init(); + XPCOMEventTargetWrapper::Init(); + mozilla::GeckoSystemStateListener::Init(); + mozilla::widget::Telemetry::Init(); + mozilla::widget::GeckoTelemetryDelegate::Init(); + + // Set the corresponding state in GeckoThread. + java::GeckoThread::SetState(java::GeckoThread::State::RUNNING()); + } + return; + } + + if (jni::IsAvailable()) { + ScreenManager& screenManager = ScreenManager::GetSingleton(); + screenManager.SetHelper(mozilla::MakeUnique()); + + // Initialize JNI and Set the corresponding state in GeckoThread. + AndroidBridge::ConstructBridge(); + GeckoAppShellSupport::Init(); + GeckoThreadSupport::Init(); + XPCOMEventTargetWrapper::Init(); + mozilla::GeckoBatteryManager::Init(); + mozilla::GeckoNetworkManager::Init(); + mozilla::GeckoProcessManager::Init(); + mozilla::GeckoScreenOrientation::Init(); + mozilla::GeckoSystemStateListener::Init(); + mozilla::PrefsHelper::Init(); + mozilla::widget::Telemetry::Init(); + mozilla::widget::ImageDecoderSupport::Init(); + mozilla::widget::WebExecutorSupport::Init(); + mozilla::widget::Base64UtilsSupport::Init(); + nsWindow::InitNatives(); + mozilla::gl::AndroidSurfaceTexture::Init(); + mozilla::WebAuthnTokenManager::Init(); + mozilla::widget::GeckoTelemetryDelegate::Init(); + + java::GeckoThread::SetState(java::GeckoThread::State::JNI_READY()); + + CreateAndroidUiThread(); + } + + sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); + + if (sPowerManagerService) { + sWakeLockListener = new WakeLockListener(); + } else { + NS_WARNING( + "Failed to retrieve PowerManagerService, wakelocks will be broken!"); + } +} + +nsAppShell::~nsAppShell() { + { + // Release any thread waiting for a sync call to finish. + MutexAutoLock lock(*sAppShellLock); + sAppShell = nullptr; + mSyncRunFinished.NotifyAll(); + } + + while (mEventQueue.Pop(/* mayWait */ false)) { + NS_WARNING("Discarded event on shutdown"); + } + + if (sPowerManagerService) { + sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); + + sPowerManagerService = nullptr; + sWakeLockListener = nullptr; + } + + hal::Shutdown(); + + if (jni::IsAvailable() && XRE_IsParentProcess()) { + DestroyAndroidUiThread(); + AndroidBridge::DeconstructBridge(); + } +} + +void nsAppShell::NotifyNativeEvent() { mEventQueue.Signal(); } + +void nsAppShell::RecordLatencies() { + if (!mozilla::Telemetry::CanRecordExtended()) { + return; + } + + const mozilla::Telemetry::HistogramID timeIDs[] = { + mozilla::Telemetry::HistogramID::FENNEC_LOOP_UI_LATENCY, + mozilla::Telemetry::HistogramID::FENNEC_LOOP_OTHER_LATENCY}; + + static_assert(ArrayLength(Queue::sLatencyCount) == Queue::LATENCY_COUNT, + "Count array length mismatch"); + static_assert(ArrayLength(Queue::sLatencyTime) == Queue::LATENCY_COUNT, + "Time array length mismatch"); + static_assert(ArrayLength(timeIDs) == Queue::LATENCY_COUNT, + "Time ID array length mismatch"); + + for (size_t i = 0; i < Queue::LATENCY_COUNT; i++) { + if (!Queue::sLatencyCount[i]) { + continue; + } + + const uint64_t time = + Queue::sLatencyTime[i] / 1000ull / Queue::sLatencyCount[i]; + if (time) { + mozilla::Telemetry::Accumulate( + timeIDs[i], uint32_t(std::min(UINT32_MAX, time))); + } + + // Reset latency counts. + Queue::sLatencyCount[i] = 0; + Queue::sLatencyTime[i] = 0; + } +} + +nsresult nsAppShell::Init() { + nsresult rv = nsBaseAppShell::Init(); + nsCOMPtr obsServ = + mozilla::services::GetObserverService(); + if (obsServ) { + obsServ->AddObserver(this, "browser-delayed-startup-finished", false); + obsServ->AddObserver(this, "geckoview-startup-complete", false); + obsServ->AddObserver(this, "profile-after-change", false); + obsServ->AddObserver(this, "quit-application", false); + obsServ->AddObserver(this, "quit-application-granted", false); + obsServ->AddObserver(this, "xpcom-shutdown", false); + + if (XRE_IsParentProcess()) { + obsServ->AddObserver(this, "chrome-document-loaded", false); + } else { + obsServ->AddObserver(this, "content-document-global-created", false); + obsServ->AddObserver(this, "geckoview-content-global-transferred", false); + } + } + + if (sPowerManagerService) + sPowerManagerService->AddWakeLockListener(sWakeLockListener); + + return rv; +} + +NS_IMETHODIMP +nsAppShell::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + bool removeObserver = false; + + if (!strcmp(aTopic, "xpcom-shutdown")) { + { + // Release any thread waiting for a sync call to finish. + mozilla::MutexAutoLock shellLock(*sAppShellLock); + mSyncRunQuit = true; + mSyncRunFinished.NotifyAll(); + } + // We need to ensure no observers stick around after XPCOM shuts down + // or we'll see crashes, as the app shell outlives XPConnect. + mObserversHash.Clear(); + return nsBaseAppShell::Observe(aSubject, aTopic, aData); + + } else if (!strcmp(aTopic, "browser-delayed-startup-finished")) { + NS_CreateServicesFromCategory("browser-delayed-startup-finished", nullptr, + "browser-delayed-startup-finished"); + } else if (!strcmp(aTopic, "geckoview-startup-complete")) { + if (jni::IsAvailable()) { + java::GeckoThread::CheckAndSetState( + java::GeckoThread::State::PROFILE_READY(), + java::GeckoThread::State::RUNNING()); + } + } else if (!strcmp(aTopic, "profile-after-change")) { + if (jni::IsAvailable()) { + java::GeckoThread::SetState(java::GeckoThread::State::PROFILE_READY()); + + // Gecko on Android follows the Android app model where it never + // stops until it is killed by the system or told explicitly to + // quit. Therefore, we should *not* exit Gecko when there is no + // window or the last window is closed. nsIAppStartup::Quit will + // still force Gecko to exit. + nsCOMPtr appStartup = components::AppStartup::Service(); + if (appStartup) { + appStartup->EnterLastWindowClosingSurvivalArea(); + } + } + removeObserver = true; + + } else if (!strcmp(aTopic, "chrome-document-loaded")) { + // Set the global ready state and enable the window event dispatcher + // for this particular GeckoView. + nsCOMPtr doc = do_QueryInterface(aSubject); + MOZ_ASSERT(doc); + if (const RefPtr window = nsWindow::From(doc->GetWindow())) { + window->OnGeckoViewReady(); + } + } else if (!strcmp(aTopic, "quit-application")) { + if (jni::IsAvailable()) { + const bool restarting = aData && u"restart"_ns.Equals(aData); + java::GeckoThread::SetState(restarting + ? java::GeckoThread::State::RESTARTING() + : java::GeckoThread::State::EXITING()); + } + removeObserver = true; + + } else if (!strcmp(aTopic, "quit-application-granted")) { + if (jni::IsAvailable()) { + // We are told explicitly to quit, perhaps due to + // nsIAppStartup::Quit being called. We should release our hold on + // nsIAppStartup and let it continue to quit. + nsCOMPtr appStartup = components::AppStartup::Service(); + if (appStartup) { + appStartup->ExitLastWindowClosingSurvivalArea(); + } + } + removeObserver = true; + + } else if (!strcmp(aTopic, "nsPref:changed")) { + if (jni::IsAvailable()) { + mozilla::PrefsHelper::OnPrefChange(aData); + } + + } else if (!strcmp(aTopic, "content-document-global-created")) { + // Associate the PuppetWidget of the newly-created BrowserChild with a + // GeckoEditableChild instance. + MOZ_ASSERT(!XRE_IsParentProcess()); + + nsCOMPtr domWindow = do_QueryInterface(aSubject); + MOZ_ASSERT(domWindow); + nsCOMPtr domWidget = widget::WidgetUtils::DOMWindowToWidget( + nsPIDOMWindowOuter::From(domWindow)); + NS_ENSURE_TRUE(domWidget, NS_OK); + + widget::GeckoEditableSupport::SetOnBrowserChild( + domWidget->GetOwningBrowserChild()); + + } else if (!strcmp(aTopic, "geckoview-content-global-transferred")) { + // We're transferring to a new GeckoEditableParent, so notify the + // existing GeckoEditableChild instance associated with the docshell. + nsCOMPtr docShell = do_QueryInterface(aSubject); + widget::GeckoEditableSupport::SetOnBrowserChild( + dom::BrowserChild::GetFrom(docShell)); + } + + if (removeObserver) { + nsCOMPtr obsServ = + mozilla::services::GetObserverService(); + if (obsServ) { + obsServ->RemoveObserver(this, aTopic); + } + } + return NS_OK; +} + +bool nsAppShell::ProcessNextNativeEvent(bool mayWait) { + EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait); + + AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent", OTHER); + + mozilla::UniquePtr curEvent; + + { + curEvent = mEventQueue.Pop(/* mayWait */ false); + + if (!curEvent && mayWait) { + // This processes messages in the Android Looper. Note that we only + // get here if the normal Gecko event loop has been awoken + // (bug 750713). Looper messages effectively have the lowest + // priority because we only process them before we're about to + // wait for new events. + if (jni::IsAvailable() && XRE_IsParentProcess() && + AndroidBridge::Bridge()->PumpMessageLoop()) { + return true; + } + + AUTO_PROFILER_LABEL("nsAppShell::ProcessNextNativeEvent:Wait", IDLE); + mozilla::BackgroundHangMonitor().NotifyWait(); + + curEvent = mEventQueue.Pop(/* mayWait */ true); + } + } + + if (!curEvent) return false; + + mozilla::BackgroundHangMonitor().NotifyActivity(); + + curEvent->Run(); + return true; +} + +bool nsAppShell::SyncRunEvent( + Event&& event, UniquePtr (*eventFactory)(UniquePtr&&), + const TimeDuration timeout) { + // Perform the call on the Gecko thread in a separate lambda, and wait + // on the monitor on the current thread. + MOZ_ASSERT(!NS_IsMainThread()); + + // This is the lock to check that app shell is still alive, + // and to wait on for the sync call to complete. + mozilla::MutexAutoLock shellLock(*sAppShellLock); + nsAppShell* const appShell = sAppShell; + + if (MOZ_UNLIKELY(!appShell)) { + // Post-shutdown. + return false; + } + + bool finished = false; + auto runAndNotify = [&event, &finished] { + nsAppShell* const appShell = nsAppShell::Get(); + if (MOZ_UNLIKELY(!appShell || appShell->mSyncRunQuit)) { + return false; + } + event.Run(); + finished = true; + mozilla::MutexAutoLock shellLock(*sAppShellLock); + appShell->mSyncRunFinished.NotifyAll(); + return finished; + }; + + UniquePtr runAndNotifyEvent = + mozilla::MakeUnique>( + std::move(runAndNotify)); + + if (eventFactory) { + runAndNotifyEvent = (*eventFactory)(std::move(runAndNotifyEvent)); + } + + appShell->mEventQueue.Post(std::move(runAndNotifyEvent)); + + while (!finished && MOZ_LIKELY(sAppShell && !sAppShell->mSyncRunQuit)) { + appShell->mSyncRunFinished.Wait(timeout); + } + + return finished; +} + +already_AddRefed nsAppShell::ResolveURI(const nsCString& aUriStr) { + nsCOMPtr ioServ = do_GetIOService(); + nsCOMPtr uri; + + if (NS_SUCCEEDED( + ioServ->NewURI(aUriStr, nullptr, nullptr, getter_AddRefs(uri)))) { + return uri.forget(); + } + + nsCOMPtr fixup = components::URIFixup::Service(); + nsCOMPtr fixupInfo; + if (fixup && + NS_SUCCEEDED(fixup->GetFixupURIInfo(aUriStr, nsIURIFixup::FIXUP_FLAG_NONE, + getter_AddRefs(fixupInfo))) && + NS_SUCCEEDED(fixupInfo->GetPreferredURI(getter_AddRefs(uri)))) { + return uri.forget(); + } + return nullptr; +} + +nsresult nsAppShell::AddObserver(const nsAString& aObserverKey, + nsIObserver* aObserver) { + NS_ASSERTION(aObserver != nullptr, + "nsAppShell::AddObserver: aObserver is null!"); + mObserversHash.Put(aObserverKey, aObserver); + return NS_OK; +} + +// Used by IPC code +namespace mozilla { + +bool ProcessNextEvent() { + nsAppShell* const appShell = nsAppShell::Get(); + if (!appShell) { + return false; + } + + return appShell->ProcessNextNativeEvent(true) ? true : false; +} + +void NotifyEvent() { + nsAppShell* const appShell = nsAppShell::Get(); + if (!appShell) { + return; + } + appShell->NotifyNativeEvent(); +} + +} // namespace mozilla diff --git a/widget/android/nsAppShell.h b/widget/android/nsAppShell.h new file mode 100644 index 0000000000..62c7c6205b --- /dev/null +++ b/widget/android/nsAppShell.h @@ -0,0 +1,250 @@ +/* -*- Mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* 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 nsAppShell_h__ +#define nsAppShell_h__ + +#include + +#include +#include + +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" // for mozilla::TimeDuration +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/jni/Natives.h" +#include "nsBaseAppShell.h" +#include "nsCOMPtr.h" +#include "nsIAndroidBridge.h" +#include "nsInterfaceHashtable.h" +#include "nsTArray.h" + +namespace mozilla { +bool ProcessNextEvent(); +void NotifyEvent(); +} // namespace mozilla + +class nsWindow; + +class nsAppShell : public nsBaseAppShell { + public: + struct Event : mozilla::LinkedListElement { + static uint64_t GetTime() { + timespec time; + if (clock_gettime(CLOCK_MONOTONIC, &time)) { + return 0ull; + } + return uint64_t(time.tv_sec) * 1000000000ull + time.tv_nsec; + } + + uint64_t mPostTime{0}; + + bool HasSameTypeAs(const Event* other) const { + // Compare vtable addresses to determine same type. + return *reinterpret_cast(this) == + *reinterpret_cast(other); + } + + virtual ~Event() {} + virtual void Run() = 0; + + virtual void PostTo(mozilla::LinkedList& queue) { + queue.insertBack(this); + } + + virtual bool IsUIEvent() const { return false; } + }; + + template + class LambdaEvent : public Event { + protected: + T lambda; + + public: + explicit LambdaEvent(T&& l) : lambda(std::move(l)) {} + void Run() override { lambda(); } + }; + + class ProxyEvent : public Event { + protected: + mozilla::UniquePtr baseEvent; + + public: + explicit ProxyEvent(mozilla::UniquePtr&& event) + : baseEvent(std::move(event)) {} + + void PostTo(mozilla::LinkedList& queue) override { + baseEvent->PostTo(queue); + } + + void Run() override { baseEvent->Run(); } + }; + + static nsAppShell* Get() { + MOZ_ASSERT(NS_IsMainThread()); + return sAppShell; + } + + nsAppShell(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + + nsresult Init(); + + void NotifyNativeEvent(); + bool ProcessNextNativeEvent(bool mayWait) override; + + // Post a subclass of Event. + // e.g. PostEvent(mozilla::MakeUnique()); + template + static void PostEvent(mozilla::UniquePtr&& event) { + mozilla::MutexAutoLock lock(*sAppShellLock); + if (!sAppShell) { + return; + } + sAppShell->mEventQueue.Post(std::move(event)); + } + + // Post a event that will call a lambda + // e.g. PostEvent([=] { /* do something */ }); + template + static void PostEvent(T&& lambda) { + mozilla::MutexAutoLock lock(*sAppShellLock); + if (!sAppShell) { + return; + } + sAppShell->mEventQueue.Post( + mozilla::MakeUnique>(std::move(lambda))); + } + + // Post a event and wait for it to finish running on the Gecko thread. + static bool SyncRunEvent( + Event&& event, + mozilla::UniquePtr (*eventFactory)(mozilla::UniquePtr&&) = + nullptr, + const mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever()); + + template + static std::enable_if_t::value, void> SyncRunEvent( + T&& lambda) { + SyncRunEvent(LambdaEvent(std::forward(lambda))); + } + + static already_AddRefed ResolveURI(const nsCString& aUriStr); + + void SetBrowserApp(nsIAndroidBrowserApp* aBrowserApp) { + mBrowserApp = aBrowserApp; + } + + nsIAndroidBrowserApp* GetBrowserApp() { return mBrowserApp; } + + protected: + static nsAppShell* sAppShell; + static mozilla::StaticAutoPtr sAppShellLock; + + static void RecordLatencies(); + + virtual ~nsAppShell(); + + nsresult AddObserver(const nsAString& aObserverKey, nsIObserver* aObserver); + + class NativeCallbackEvent : public Event { + // Capturing the nsAppShell instance is safe because if the app + // shell is destroyed, this lambda will not be called either. + nsAppShell* const appShell; + + public: + explicit NativeCallbackEvent(nsAppShell* as) : appShell(as) {} + void Run() override { appShell->NativeEventCallback(); } + }; + + void ScheduleNativeEventCallback() override { + mEventQueue.Post(mozilla::MakeUnique(this)); + } + + class Queue { + private: + mozilla::Monitor mMonitor; + mozilla::LinkedList mQueue; + + public: + enum { LATENCY_UI, LATENCY_OTHER, LATENCY_COUNT }; + static uint32_t sLatencyCount[LATENCY_COUNT]; + static uint64_t sLatencyTime[LATENCY_COUNT]; + + Queue() : mMonitor("nsAppShell.Queue") {} + + void Signal() { + mozilla::MonitorAutoLock lock(mMonitor); + lock.NotifyAll(); + } + + void Post(mozilla::UniquePtr&& event) { + MOZ_ASSERT(event && !event->isInList()); + + mozilla::MonitorAutoLock lock(mMonitor); + event->PostTo(mQueue); + if (event->isInList()) { + event->mPostTime = Event::GetTime(); + // Ownership of event object transfers to the queue. + mozilla::Unused << event.release(); + } + lock.NotifyAll(); + } + + mozilla::UniquePtr Pop(bool mayWait) { +#ifdef EARLY_BETA_OR_EARLIER + bool isQueueEmpty = false; + if (mayWait) { + mozilla::MonitorAutoLock lock(mMonitor); + isQueueEmpty = mQueue.isEmpty(); + } + if (isQueueEmpty) { + // Record latencies when we're about to be idle. + // Note: We can't call this while holding the lock because + // nsAppShell::RecordLatencies may try to dispatch an event to the main + // thread which tries to acquire the lock again. + nsAppShell::RecordLatencies(); + } +#endif + mozilla::MonitorAutoLock lock(mMonitor); + + if (mayWait && mQueue.isEmpty()) { + lock.Wait(); + } + + // Ownership of event object transfers to the return value. + mozilla::UniquePtr event(mQueue.popFirst()); + if (!event || !event->mPostTime) { + return event; + } + +#ifdef EARLY_BETA_OR_EARLIER + const size_t latencyType = + event->IsUIEvent() ? LATENCY_UI : LATENCY_OTHER; + const uint64_t latency = Event::GetTime() - event->mPostTime; + + sLatencyCount[latencyType]++; + sLatencyTime[latencyType] += latency; +#endif + return event; + } + + } mEventQueue; + + private: + mozilla::CondVar mSyncRunFinished; + bool mSyncRunQuit; + + nsCOMPtr mBrowserApp; + nsInterfaceHashtable mObserversHash; +}; + +#endif // nsAppShell_h__ diff --git a/widget/android/nsClipboard.cpp b/widget/android/nsClipboard.cpp new file mode 100644 index 0000000000..6008b5264f --- /dev/null +++ b/widget/android/nsClipboard.cpp @@ -0,0 +1,163 @@ +/* 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 "mozilla/java/ClipboardWrappers.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "nsClipboard.h" +#include "nsISupportsPrimitives.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsPrimitiveHelpers.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard) + +/* The Android clipboard only supports text and doesn't support mime types + * so we assume all clipboard data is text/unicode for now. Documentation + * indicates that support for other data types is planned for future + * releases. + */ + +nsClipboard::nsClipboard() {} + +NS_IMETHODIMP +nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner, + int32_t aWhichClipboard) { + if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED; + + if (!jni::IsAvailable()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsTArray flavors; + aTransferable->FlavorsTransferableCanImport(flavors); + + nsAutoString html; + nsAutoString text; + + for (auto& flavorStr : flavors) { + if (flavorStr.EqualsLiteral(kUnicodeMime)) { + nsCOMPtr item; + nsresult rv = + aTransferable->GetTransferData(kUnicodeMime, getter_AddRefs(item)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + nsCOMPtr supportsString = do_QueryInterface(item); + if (supportsString) { + supportsString->GetData(text); + } + } else if (flavorStr.EqualsLiteral(kHTMLMime)) { + nsCOMPtr item; + nsresult rv = + aTransferable->GetTransferData(kHTMLMime, getter_AddRefs(item)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + nsCOMPtr supportsString = do_QueryInterface(item); + if (supportsString) { + supportsString->GetData(html); + } + } + } + + if (!html.IsEmpty() && + java::Clipboard::SetHTML(java::GeckoAppShell::GetApplicationContext(), + text, html)) { + return NS_OK; + } + if (!text.IsEmpty() && + java::Clipboard::SetText(java::GeckoAppShell::GetApplicationContext(), + text)) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) { + if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED; + + if (!jni::IsAvailable()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsTArray flavors; + aTransferable->FlavorsTransferableCanImport(flavors); + + for (auto& flavorStr : flavors) { + if (flavorStr.EqualsLiteral(kUnicodeMime) || + flavorStr.EqualsLiteral(kHTMLMime)) { + auto text = java::Clipboard::GetData( + java::GeckoAppShell::GetApplicationContext(), flavorStr); + if (!text) { + continue; + } + nsString buffer = text->ToString(); + if (buffer.IsEmpty()) { + continue; + } + nsCOMPtr wrapper; + nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, buffer.get(), + buffer.Length() * 2, + getter_AddRefs(wrapper)); + if (wrapper) { + aTransferable->SetTransferData(flavorStr.get(), wrapper); + return NS_OK; + } + } + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsClipboard::EmptyClipboard(int32_t aWhichClipboard) { + if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED; + + if (!jni::IsAvailable()) { + return NS_ERROR_NOT_AVAILABLE; + } + + java::Clipboard::ClearText(java::GeckoAppShell::GetApplicationContext()); + + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::HasDataMatchingFlavors(const nsTArray& aFlavorList, + int32_t aWhichClipboard, bool* aHasText) { + *aHasText = false; + if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED; + + if (!jni::IsAvailable()) { + return NS_ERROR_NOT_AVAILABLE; + } + + for (auto& flavor : aFlavorList) { + bool hasData = + java::Clipboard::HasData(java::GeckoAppShell::GetApplicationContext(), + NS_ConvertASCIItoUTF16(flavor)); + if (hasData) { + *aHasText = true; + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::SupportsSelectionClipboard(bool* aIsSupported) { + *aIsSupported = false; + return NS_OK; +} + +NS_IMETHODIMP +nsClipboard::SupportsFindClipboard(bool* _retval) { + *_retval = false; + return NS_OK; +} diff --git a/widget/android/nsClipboard.h b/widget/android/nsClipboard.h new file mode 100644 index 0000000000..f714effaed --- /dev/null +++ b/widget/android/nsClipboard.h @@ -0,0 +1,22 @@ +/* -*- 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 NS_CLIPBOARD_H +#define NS_CLIPBOARD_H + +#include "nsIClipboard.h" + +class nsClipboard final : public nsIClipboard { + private: + ~nsClipboard() {} + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICLIPBOARD + + nsClipboard(); +}; + +#endif diff --git a/widget/android/nsDeviceContextAndroid.cpp b/widget/android/nsDeviceContextAndroid.cpp new file mode 100644 index 0000000000..68cd4c62f9 --- /dev/null +++ b/widget/android/nsDeviceContextAndroid.cpp @@ -0,0 +1,79 @@ +/* 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 "nsDeviceContextAndroid.h" + +#include "mozilla/gfx/PrintTargetPDF.h" +#include "mozilla/RefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsIFileStreams.h" +#include "nsIPrintSettings.h" +#include "nsDirectoryServiceDefs.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +NS_IMPL_ISUPPORTS(nsDeviceContextSpecAndroid, nsIDeviceContextSpec) + +already_AddRefed nsDeviceContextSpecAndroid::MakePrintTarget() { + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsAutoCString filename("tmp-printing.pdf"); + mTempFile->AppendNative(filename); + rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + rv = stream->Init(mTempFile, -1, -1, 0); + NS_ENSURE_SUCCESS(rv, nullptr); + + // XXX: what should we do here for size? screen size? + IntSize size(480, 800); + + return PrintTargetPDF::CreateOrNull(stream, size); +} + +NS_IMETHODIMP +nsDeviceContextSpecAndroid::Init(nsIWidget* aWidget, nsIPrintSettings* aPS, + bool aIsPrintPreview) { + mPrintSettings = aPS; + return NS_OK; +} + +NS_IMETHODIMP +nsDeviceContextSpecAndroid::BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, + int32_t aEndPage) { + return NS_OK; +} + +NS_IMETHODIMP +nsDeviceContextSpecAndroid::EndDocument() { + nsString targetPath; + nsCOMPtr destFile; + mPrintSettings->GetToFileName(targetPath); + + nsresult rv = NS_NewLocalFile(targetPath, false, getter_AddRefs(destFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString destLeafName; + rv = destFile->GetLeafName(destLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr destDir; + rv = destFile->GetParent(getter_AddRefs(destDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mTempFile->MoveTo(destDir, destLeafName); + NS_ENSURE_SUCCESS(rv, rv); + + destFile->SetPermissions(0666); + return NS_OK; +} diff --git a/widget/android/nsDeviceContextAndroid.h b/widget/android/nsDeviceContextAndroid.h new file mode 100644 index 0000000000..546f079594 --- /dev/null +++ b/widget/android/nsDeviceContextAndroid.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsDeviceContextAndroid_h__ +#define nsDeviceContextAndroid_h__ + +#include "nsIDeviceContextSpec.h" +#include "nsCOMPtr.h" + +class nsDeviceContextSpecAndroid final : public nsIDeviceContextSpec { + private: + ~nsDeviceContextSpecAndroid() {} + + public: + NS_DECL_ISUPPORTS + + already_AddRefed MakePrintTarget() final; + + NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS, + bool aIsPrintPreview) override; + NS_IMETHOD BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) override; + NS_IMETHOD EndDocument() override; + NS_IMETHOD BeginPage() override { return NS_OK; } + NS_IMETHOD EndPage() override { return NS_OK; } + + private: + nsCOMPtr mPrintSettings; + nsCOMPtr mTempFile; +}; +#endif // nsDeviceContextAndroid_h__ diff --git a/widget/android/nsIAndroidBridge.idl b/widget/android/nsIAndroidBridge.idl new file mode 100644 index 0000000000..40cfa61692 --- /dev/null +++ b/widget/android/nsIAndroidBridge.idl @@ -0,0 +1,87 @@ +/* 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 mozIDOMWindowProxy; + +[scriptable, uuid(e8420a7b-659b-4325-968b-a114a6a067aa)] +interface nsIBrowserTab : nsISupports { + readonly attribute mozIDOMWindowProxy window; +}; + +[scriptable, uuid(08426a73-e70b-4680-9282-630932e2b2bb)] +interface nsIUITelemetryObserver : nsISupports { + void startSession(in wstring name, + in long long timestamp); + void stopSession(in wstring name, + in wstring reason, + in long long timestamp); + void addEvent(in wstring action, + in wstring method, + in long long timestamp, + in wstring extras); +}; + +[scriptable, uuid(0370450f-2e9c-4d16-b333-8ca6ce31a5ff)] +interface nsIAndroidBrowserApp : nsISupports { + readonly attribute nsIBrowserTab selectedTab; + nsIBrowserTab getBrowserTab(in int32_t tabId); + nsIUITelemetryObserver getUITelemetryObserver(); +}; + +[scriptable, uuid(e64c39b8-b8ec-477d-aef5-89d517ff9219)] +interface nsIAndroidEventCallback : nsISupports +{ + [implicit_jscontext] + void onSuccess([optional] in jsval data); + [implicit_jscontext] + void onError([optional] in jsval data); +}; + +[scriptable, function, uuid(819ee2db-d3b8-46dd-a476-40f89c49133c)] +interface nsIAndroidEventFinalizer : nsISupports +{ + void onFinalize(); +}; + +[scriptable, function, uuid(73569a75-78eb-4c7f-82b9-2d4f5ccf44c3)] +interface nsIAndroidEventListener : nsISupports +{ + void onEvent(in AString event, + [optional] in jsval data, + [optional] in nsIAndroidEventCallback callback); +}; + +[scriptable, uuid(e98bf792-4145-411e-b298-8219d9b03817)] +interface nsIAndroidEventDispatcher : nsISupports +{ + [implicit_jscontext] + void dispatch(in jsval event, + [optional] in jsval data, + [optional] in nsIAndroidEventCallback callback, + [optional] in nsIAndroidEventFinalizer finalizer); + [implicit_jscontext] + void registerListener(in nsIAndroidEventListener listener, + in jsval events); + [implicit_jscontext] + void unregisterListener(in nsIAndroidEventListener listener, + in jsval events); +}; + +[scriptable, uuid(60a78a94-6117-432f-9d49-304913a931c5)] +interface nsIAndroidView : nsIAndroidEventDispatcher +{ + [implicit_jscontext] readonly attribute jsval initData; +}; + +[scriptable, uuid(1beb70d3-70f3-4742-98cc-a3d301b26c0c)] +interface nsIAndroidBridge : nsIAndroidEventDispatcher +{ + attribute nsIAndroidBrowserApp browserApp; + readonly attribute boolean isFennec; + void contentDocumentChanged(in mozIDOMWindowProxy window); + boolean isContentDocumentDisplayed(in mozIDOMWindowProxy window); + nsIAndroidEventDispatcher getDispatcherByName(in string name); +}; diff --git a/widget/android/nsLookAndFeel.cpp b/widget/android/nsLookAndFeel.cpp new file mode 100644 index 0000000000..b6dec95a9b --- /dev/null +++ b/widget/android/nsLookAndFeel.cpp @@ -0,0 +1,531 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/dom/ContentChild.h" +#include "nsStyleConsts.h" +#include "nsXULAppAPI.h" +#include "nsLookAndFeel.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/GeckoRuntimeWrappers.h" +#include "mozilla/java/GeckoSystemStateListenerWrappers.h" + +using namespace mozilla; +using mozilla::dom::ContentChild; + +static const char16_t UNICODE_BULLET = 0x2022; + +nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache) { + if (aCache) { + DoSetCache(*aCache); + } +} + +nsLookAndFeel::~nsLookAndFeel() {} + +#define BG_PRELIGHT_COLOR NS_RGB(0xee, 0xee, 0xee) +#define FG_PRELIGHT_COLOR NS_RGB(0x77, 0x77, 0x77) +#define BLACK_COLOR NS_RGB(0x00, 0x00, 0x00) +#define DARK_GRAY_COLOR NS_RGB(0x40, 0x40, 0x40) +#define GRAY_COLOR NS_RGB(0x80, 0x80, 0x80) +#define LIGHT_GRAY_COLOR NS_RGB(0xa0, 0xa0, 0xa0) +#define RED_COLOR NS_RGB(0xff, 0x00, 0x00) + +nsresult nsLookAndFeel::GetSystemColors() { + if (!jni::IsAvailable()) { + return NS_ERROR_FAILURE; + } + + auto arr = java::GeckoAppShell::GetSystemColors(); + if (!arr) { + return NS_ERROR_FAILURE; + } + + JNIEnv* const env = arr.Env(); + uint32_t len = static_cast(env->GetArrayLength(arr.Get())); + jint* elements = env->GetIntArrayElements(arr.Get(), 0); + + uint32_t colorsCount = sizeof(AndroidSystemColors) / sizeof(nscolor); + if (len < colorsCount) colorsCount = len; + + // Convert Android colors to nscolor by switching R and B in the ARGB 32 bit + // value + nscolor* colors = (nscolor*)&mSystemColors; + + for (uint32_t i = 0; i < colorsCount; i++) { + uint32_t androidColor = static_cast(elements[i]); + uint8_t r = (androidColor & 0x00ff0000) >> 16; + uint8_t b = (androidColor & 0x000000ff); + colors[i] = (androidColor & 0xff00ff00) | (b << 16) | r; + } + + env->ReleaseIntArrayElements(arr.Get(), elements, 0); + + return NS_OK; +} + +void nsLookAndFeel::NativeInit() { + EnsureInitSystemColors(); + EnsureInitShowPassword(); + RecordTelemetry(); +} + +/* virtual */ +void nsLookAndFeel::RefreshImpl() { + nsXPLookAndFeel::RefreshImpl(); + + mInitializedSystemColors = false; + mInitializedShowPassword = false; + mPrefersReducedMotionCached = false; + mSystemUsesDarkThemeCached = false; +} + +nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) { + nsresult rv = NS_OK; + + EnsureInitSystemColors(); + if (!mInitializedSystemColors) { + // Failure to initialize colors is an error condition. Return black. + aColor = 0; + return NS_ERROR_FAILURE; + } + + // XXX we'll want to use context.obtainStyledAttributes on the java side to + // get all of these; see TextView.java for a good exmaple. + + switch (aID) { + // These colors don't seem to be used for anything anymore in Mozilla + // (except here at least TextSelectBackground and TextSelectForeground) + // The CSS2 colors below are used. + case ColorID::WindowBackground: + aColor = NS_RGB(0xFF, 0xFF, 0xFF); + break; + case ColorID::WindowForeground: + aColor = mSystemColors.textColorPrimary; + break; + case ColorID::WidgetBackground: + aColor = mSystemColors.colorBackground; + break; + case ColorID::WidgetForeground: + aColor = mSystemColors.colorForeground; + break; + case ColorID::WidgetSelectBackground: + aColor = mSystemColors.textColorHighlight; + break; + case ColorID::WidgetSelectForeground: + aColor = mSystemColors.textColorPrimaryInverse; + break; + case ColorID::Widget3DHighlight: + aColor = LIGHT_GRAY_COLOR; + break; + case ColorID::Widget3DShadow: + aColor = DARK_GRAY_COLOR; + break; + case ColorID::TextBackground: + // not used? + aColor = mSystemColors.colorBackground; + break; + case ColorID::TextForeground: + // not used? + aColor = mSystemColors.textColorPrimary; + break; + case ColorID::TextSelectBackground: + /* matched to action_accent in java codebase */ + aColor = NS_RGBA(10, 132, 255, 153); + break; + case ColorID::TextSelectForeground: + aColor = NS_RGB(0, 0, 0); + break; + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedConvertedTextBackground: + // still used + aColor = mSystemColors.textColorHighlight; + break; + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedConvertedTextForeground: + // still used + aColor = mSystemColors.textColorPrimaryInverse; + break; + case ColorID::IMERawInputBackground: + case ColorID::IMEConvertedTextBackground: + aColor = NS_TRANSPARENT; + break; + case ColorID::IMERawInputForeground: + case ColorID::IMEConvertedTextForeground: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::IMERawInputUnderline: + case ColorID::IMEConvertedTextUnderline: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::IMESelectedRawTextUnderline: + case ColorID::IMESelectedConvertedTextUnderline: + aColor = NS_TRANSPARENT; + break; + case ColorID::SpellCheckerUnderline: + aColor = RED_COLOR; + break; + + // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors + case ColorID::Activeborder: + // active window border + aColor = mSystemColors.colorBackground; + break; + case ColorID::Activecaption: + // active window caption background + aColor = mSystemColors.colorBackground; + break; + case ColorID::Appworkspace: + // MDI background color + aColor = mSystemColors.colorBackground; + break; + case ColorID::Background: + // desktop background + aColor = mSystemColors.colorBackground; + break; + case ColorID::Graytext: + // disabled text in windows, menus, etc. + aColor = NS_RGB(0xb1, 0xa5, 0x98); + break; + case ColorID::MozCellhighlight: + case ColorID::MozHtmlCellhighlight: + case ColorID::Highlight: + // background of selected item + aColor = NS_RGB(0xfa, 0xd1, 0x84); + break; + case ColorID::MozCellhighlighttext: + case ColorID::MozHtmlCellhighlighttext: + case ColorID::Highlighttext: + case ColorID::Fieldtext: + aColor = NS_RGB(0x1a, 0x1a, 0x1a); + break; + case ColorID::Inactiveborder: + // inactive window border + aColor = mSystemColors.colorBackground; + break; + case ColorID::Inactivecaption: + // inactive window caption + aColor = mSystemColors.colorBackground; + break; + case ColorID::Inactivecaptiontext: + // text in inactive window caption + aColor = mSystemColors.textColorTertiary; + break; + case ColorID::Infobackground: + aColor = NS_RGB(0xf5, 0xf5, 0xb5); + break; + case ColorID::Infotext: + aColor = BLACK_COLOR; + break; + case ColorID::Menu: + aColor = NS_RGB(0xf7, 0xf5, 0xf3); + break; + case ColorID::Scrollbar: + // scrollbar gray area + aColor = mSystemColors.colorBackground; + break; + + case ColorID::Threedface: + case ColorID::Buttonface: + case ColorID::Threedlightshadow: + aColor = NS_RGB(0xec, 0xe7, 0xe2); + break; + + case ColorID::Buttonhighlight: + case ColorID::Field: + case ColorID::Threedhighlight: + case ColorID::MozCombobox: + case ColorID::MozEventreerow: + aColor = NS_RGB(0xff, 0xff, 0xff); + break; + + case ColorID::Buttonshadow: + case ColorID::Threedshadow: + aColor = NS_RGB(0xae, 0xa1, 0x94); + break; + + case ColorID::Threeddarkshadow: + // 3-D shadow outer edge color + aColor = BLACK_COLOR; + break; + + case ColorID::MozDialog: + case ColorID::Window: + case ColorID::Windowframe: + aColor = NS_RGB(0xef, 0xeb, 0xe7); + break; + case ColorID::Buttontext: + case ColorID::Captiontext: + case ColorID::Menutext: + case ColorID::MozButtonhovertext: + case ColorID::MozDialogtext: + case ColorID::MozComboboxtext: + case ColorID::Windowtext: + case ColorID::MozColheadertext: + case ColorID::MozColheaderhovertext: + aColor = NS_RGB(0x10, 0x10, 0x10); + break; + case ColorID::MozDragtargetzone: + aColor = mSystemColors.textColorHighlight; + break; + case ColorID::MozButtondefault: + // default button border color + aColor = BLACK_COLOR; + break; + case ColorID::MozButtonhoverface: + aColor = NS_RGB(0xf3, 0xf0, 0xed); + break; + case ColorID::MozMenuhover: + aColor = BG_PRELIGHT_COLOR; + break; + case ColorID::MozMenuhovertext: + aColor = FG_PRELIGHT_COLOR; + break; + case ColorID::MozOddtreerow: + aColor = NS_TRANSPARENT; + break; + case ColorID::MozNativehyperlinktext: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::MozMenubartext: + aColor = mSystemColors.colorForeground; + break; + case ColorID::MozMenubarhovertext: + aColor = FG_PRELIGHT_COLOR; + break; + default: + /* default color is BLACK */ + aColor = 0; + rv = NS_ERROR_FAILURE; + break; + } + + return rv; +} + +nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { + nsresult rv = NS_OK; + + switch (aID) { + case IntID::ScrollButtonLeftMouseButtonAction: + aResult = 0; + break; + + case IntID::ScrollButtonMiddleMouseButtonAction: + case IntID::ScrollButtonRightMouseButtonAction: + aResult = 3; + break; + + case IntID::CaretBlinkTime: + aResult = 500; + break; + + case IntID::CaretWidth: + aResult = 1; + break; + + case IntID::ShowCaretDuringSelection: + aResult = 0; + break; + + case IntID::SelectTextfieldsOnKeyFocus: + // Select textfield content when focused by kbd + // used by EventStateManager::sTextfieldSelectModel + aResult = 1; + break; + + case IntID::SubmenuDelay: + aResult = 200; + break; + + case IntID::TooltipDelay: + aResult = 500; + break; + + case IntID::MenusCanOverlapOSBar: + // we want XUL popups to be able to overlap the task bar. + aResult = 1; + break; + + case IntID::ScrollArrowStyle: + aResult = eScrollArrowStyle_Single; + break; + + case IntID::ScrollSliderStyle: + aResult = eScrollThumbStyle_Proportional; + break; + + case IntID::TouchEnabled: + aResult = 1; + break; + + case IntID::WindowsDefaultTheme: + case IntID::WindowsThemeIdentifier: + case IntID::OperatingSystemVersionIdentifier: + aResult = 0; + rv = NS_ERROR_NOT_IMPLEMENTED; + break; + + case IntID::SpellCheckerUnderlineStyle: + aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY; + break; + + case IntID::ScrollbarButtonAutoRepeatBehavior: + aResult = 0; + break; + + case IntID::ContextMenuOffsetVertical: + case IntID::ContextMenuOffsetHorizontal: + aResult = 2; + break; + + case IntID::PrefersReducedMotion: + if (!mPrefersReducedMotionCached && XRE_IsParentProcess()) { + mPrefersReducedMotion = + java::GeckoSystemStateListener::PrefersReducedMotion(); + mPrefersReducedMotionCached = true; + } + aResult = mPrefersReducedMotion; + break; + + case IntID::PrimaryPointerCapabilities: + aResult = java::GeckoAppShell::GetPrimaryPointerCapabilities(); + break; + case IntID::AllPointerCapabilities: + aResult = java::GeckoAppShell::GetAllPointerCapabilities(); + break; + + case IntID::SystemUsesDarkTheme: { + if (!mSystemUsesDarkThemeCached && XRE_IsParentProcess()) { + // Bail out if AndroidBridge hasn't initialized since we try to query + // this value via nsMediaFeatures::InitSystemMetrics without + // initializing AndroidBridge on xpcshell tests. + if (!jni::IsAvailable()) { + return NS_ERROR_FAILURE; + } + + java::GeckoRuntime::LocalRef runtime = + java::GeckoRuntime::GetInstance(); + mSystemUsesDarkTheme = runtime && runtime->UsesDarkTheme(); + mSystemUsesDarkThemeCached = true; + } + + aResult = mSystemUsesDarkTheme; + break; + } + + case IntID::DragThresholdX: + case IntID::DragThresholdY: + // Threshold where a tap becomes a drag, in 1/240" reference pixels. + aResult = 25; + break; + + default: + aResult = 0; + rv = NS_ERROR_FAILURE; + } + + return rv; +} + +nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) { + nsresult rv = NS_OK; + + switch (aID) { + case FloatID::IMEUnderlineRelativeSize: + aResult = 1.0f; + break; + + case FloatID::SpellCheckerUnderlineRelativeSize: + aResult = 1.0f; + break; + + default: + aResult = -1.0; + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +/*virtual*/ +bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName, + gfxFontStyle& aFontStyle) { + aFontName.AssignLiteral("\"Roboto\""); + aFontStyle.style = FontSlantStyle::Normal(); + aFontStyle.weight = FontWeight::Normal(); + aFontStyle.stretch = FontStretch::Normal(); + aFontStyle.size = 9.0 * 96.0f / 72.0f; + aFontStyle.systemFont = true; + return true; +} + +/*virtual*/ +bool nsLookAndFeel::GetEchoPasswordImpl() { + EnsureInitShowPassword(); + return mShowPassword; +} + +uint32_t nsLookAndFeel::GetPasswordMaskDelayImpl() { + // This value is hard-coded in Android OS's PasswordTransformationMethod.java + return 1500; +} + +/* virtual */ +char16_t nsLookAndFeel::GetPasswordCharacterImpl() { + // This value is hard-coded in Android OS's PasswordTransformationMethod.java + return UNICODE_BULLET; +} + +void nsLookAndFeel::EnsureInitSystemColors() { + if (!mInitializedSystemColors) { + mInitializedSystemColors = NS_SUCCEEDED(GetSystemColors()); + } +} + +void nsLookAndFeel::EnsureInitShowPassword() { + if (!mInitializedShowPassword && jni::IsAvailable()) { + mShowPassword = java::GeckoAppShell::GetShowPasswordSetting(); + mInitializedShowPassword = true; + } +} + +widget::LookAndFeelCache nsLookAndFeel::GetCacheImpl() { + LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl(); + + const IntID kIdsToCache[] = {IntID::PrefersReducedMotion, + IntID::SystemUsesDarkTheme}; + + for (IntID id : kIdsToCache) { + cache.mInts().AppendElement(LookAndFeelInt(id, GetInt(id))); + } + + return cache; +} + +void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) { + DoSetCache(aCache); +} + +void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) { + for (const auto& entry : aCache.mInts()) { + switch (entry.id()) { + case IntID::PrefersReducedMotion: + mPrefersReducedMotion = entry.value(); + mPrefersReducedMotionCached = true; + break; + case IntID::SystemUsesDarkTheme: + mSystemUsesDarkTheme = !!entry.value(); + mSystemUsesDarkThemeCached = true; + break; + default: + MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache"); + break; + } + } +} diff --git a/widget/android/nsLookAndFeel.h b/widget/android/nsLookAndFeel.h new file mode 100644 index 0000000000..9d3edaca71 --- /dev/null +++ b/widget/android/nsLookAndFeel.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 __nsLookAndFeel +#define __nsLookAndFeel + +#include "nsXPLookAndFeel.h" +#include "AndroidBridge.h" + +class nsLookAndFeel final : public nsXPLookAndFeel { + public: + explicit nsLookAndFeel(const LookAndFeelCache* aCache); + virtual ~nsLookAndFeel(); + + void NativeInit() final; + virtual void RefreshImpl() override; + nsresult NativeGetInt(IntID aID, int32_t& aResult) override; + nsresult NativeGetFloat(FloatID aID, float& aResult) override; + nsresult NativeGetColor(ColorID aID, nscolor& aResult) override; + bool NativeGetFont(FontID aID, nsString& aName, + gfxFontStyle& aStyle) override; + virtual bool GetEchoPasswordImpl() override; + virtual uint32_t GetPasswordMaskDelayImpl() override; + virtual char16_t GetPasswordCharacterImpl() override; + LookAndFeelCache GetCacheImpl() override; + void SetCacheImpl(const LookAndFeelCache& aCache) override; + + protected: + void DoSetCache(const LookAndFeelCache& aCache); + + bool mInitializedSystemColors = false; + mozilla::AndroidSystemColors mSystemColors; + bool mInitializedShowPassword = false; + bool mShowPassword = false; + + bool mSystemUsesDarkTheme = false; + bool mSystemUsesDarkThemeCached = false; + + bool mPrefersReducedMotion = false; + bool mPrefersReducedMotionCached = false; + + nsresult GetSystemColors(); + + void EnsureInitSystemColors(); + void EnsureInitShowPassword(); +}; + +#endif diff --git a/widget/android/nsNativeBasicThemeAndroid.cpp b/widget/android/nsNativeBasicThemeAndroid.cpp new file mode 100644 index 0000000000..425baf6239 --- /dev/null +++ b/widget/android/nsNativeBasicThemeAndroid.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsNativeBasicThemeAndroid.h" + +already_AddRefed do_GetBasicNativeThemeDoNotUseDirectly() { + static StaticRefPtr gInstance; + if (MOZ_UNLIKELY(!gInstance)) { + gInstance = new nsNativeBasicThemeAndroid(); + ClearOnShutdown(&gInstance); + } + return do_AddRef(gInstance); +} diff --git a/widget/android/nsNativeBasicThemeAndroid.h b/widget/android/nsNativeBasicThemeAndroid.h new file mode 100644 index 0000000000..a17efbfe5f --- /dev/null +++ b/widget/android/nsNativeBasicThemeAndroid.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 nsNativeBasicThemeAndroid_h +#define nsNativeBasicThemeAndroid_h + +#include "nsNativeBasicTheme.h" + +class nsNativeBasicThemeAndroid : public nsNativeBasicTheme { + public: + nsNativeBasicThemeAndroid() = default; + + protected: + virtual ~nsNativeBasicThemeAndroid() = default; +}; + +#endif diff --git a/widget/android/nsNativeThemeAndroid.cpp b/widget/android/nsNativeThemeAndroid.cpp new file mode 100644 index 0000000000..3f42a38374 --- /dev/null +++ b/widget/android/nsNativeThemeAndroid.cpp @@ -0,0 +1,926 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsNativeThemeAndroid.h" + +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsCSSRendering.h" +#include "nsDateTimeControlFrame.h" +#include "nsDeviceContext.h" +#include "nsLayoutUtils.h" +#include "PathHelpers.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; + +namespace mozilla { +namespace widget { + +static const sRGBColor sBackgroundColor(sRGBColor(1.0f, 1.0f, 1.0f)); +static const sRGBColor sBackgroundActiveColor(sRGBColor(0.88f, 0.88f, 0.9f)); +static const sRGBColor sBackgroundActiveColorDisabled(sRGBColor(0.88f, 0.88f, + 0.9f, 0.4f)); +static const sRGBColor sBorderColor(sRGBColor(0.62f, 0.62f, 0.68f)); +static const sRGBColor sBorderColorDisabled(sRGBColor(0.44f, 0.44f, 0.44f, + 0.4f)); +static const sRGBColor sBorderHoverColor(sRGBColor(0.5f, 0.5f, 0.56f)); +static const sRGBColor sBorderHoverColorDisabled(sRGBColor(0.5f, 0.5f, 0.56f, + 0.4f)); +static const sRGBColor sBorderFocusColor(sRGBColor(0.04f, 0.52f, 1.0f)); +static const sRGBColor sCheckBackgroundColor(sRGBColor(0.18f, 0.39f, 0.89f)); +static const sRGBColor sCheckBackgroundColorDisabled(sRGBColor(0.18f, 0.39f, + 0.89f, 0.4f)); +static const sRGBColor sCheckBackgroundHoverColor(sRGBColor(0.02f, 0.24f, + 0.58f)); +static const sRGBColor sCheckBackgroundHoverColorDisabled( + sRGBColor(0.02f, 0.24f, 0.58f, 0.4f)); +static const sRGBColor sCheckBackgroundActiveColor(sRGBColor(0.03f, 0.19f, + 0.45f)); +static const sRGBColor sCheckBackgroundActiveColorDisabled( + sRGBColor(0.03f, 0.19f, 0.45f, 0.4f)); +static const sRGBColor sDisabledColor(sRGBColor(0.89f, 0.89f, 0.89f)); +static const sRGBColor sActiveColor(sRGBColor(0.47f, 0.47f, 0.48f)); +static const sRGBColor sInputHoverColor(sRGBColor(0.05f, 0.05f, 0.05f, 0.5f)); +static const sRGBColor sRangeInputBackgroundColor(sRGBColor(0.89f, 0.89f, + 0.89f)); +static const sRGBColor sButtonColor(sRGBColor(0.98f, 0.98f, 0.98f)); +static const sRGBColor sButtonHoverColor(sRGBColor(0.94f, 0.94f, 0.96f)); +static const sRGBColor sButtonActiveColor(sRGBColor(0.88f, 0.88f, 0.90f)); +static const sRGBColor sWhiteColor(sRGBColor(1.0f, 1.0f, 1.0f, 0.0f)); + +static const CSSIntCoord kMinimumAndroidWidgetSize = 17; + +} // namespace widget +} // namespace mozilla + +NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeAndroid, nsNativeTheme, nsITheme) + +static uint32_t GetDPIRatio(nsIFrame* aFrame) { + return AppUnitsPerCSSPixel() / aFrame->PresContext() + ->DeviceContext() + ->AppUnitsPerDevPixelAtUnitFullZoom(); +} + +static bool IsDateTimeResetButton(nsIFrame* aFrame) { + nsIFrame* parent = aFrame->GetParent(); + if (parent && (parent = parent->GetParent()) && + (parent = parent->GetParent())) { + nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent); + if (dateTimeFrame) { + return true; + } + } + return false; +} + +static bool IsDateTimeTextField(nsIFrame* aFrame) { + nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(aFrame); + return dateTimeFrame; +} + +static void ComputeCheckColors(const EventStates& aState, + sRGBColor& aBackgroundColor, + sRGBColor& aBorderColor) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUS); + bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED); + + sRGBColor fillColor = sBackgroundColor; + sRGBColor borderColor = sBorderColor; + if (isDisabled) { + if (isChecked) { + fillColor = borderColor = sCheckBackgroundColorDisabled; + } else { + fillColor = sBackgroundColor; + borderColor = sBorderColorDisabled; + } + } else { + if (isChecked) { + if (isPressed) { + fillColor = borderColor = sCheckBackgroundActiveColor; + } else if (isHovered) { + fillColor = borderColor = sCheckBackgroundHoverColor; + } else { + fillColor = borderColor = sCheckBackgroundColor; + } + } else if (isPressed) { + fillColor = sBackgroundActiveColor; + borderColor = sBorderHoverColor; + } else if (isFocused) { + fillColor = sBackgroundActiveColor; + borderColor = sBorderFocusColor; + } else if (isHovered) { + fillColor = sBackgroundColor; + borderColor = sBorderHoverColor; + } else { + fillColor = sBackgroundColor; + borderColor = sBorderColor; + } + } + + aBackgroundColor = fillColor; + aBorderColor = borderColor; +} + +// Checkbox and radio need to preserve aspect-ratio for compat. +static Rect FixAspectRatio(const Rect& aRect) { + Rect rect(aRect); + if (rect.width == rect.height) { + return rect; + } + + if (rect.width > rect.height) { + auto diff = rect.width - rect.height; + rect.width = rect.height; + rect.x += diff / 2; + } else { + auto diff = rect.height - rect.width; + rect.height = rect.width; + rect.y += diff / 2; + } + + return rect; +} + +// This pushes and pops a clip rect to the draw target. +// +// This is done to reduce fuzz in places where we may have antialiasing, because +// skia is not clip-invariant: given different clips, it does not guarantee the +// same result, even if the painted content doesn't intersect the clips. +// +// This is a bit sad, overall, but... +struct MOZ_RAII AutoClipRect { + AutoClipRect(DrawTarget& aDt, const Rect& aRect) : mDt(aDt) { + mDt.PushClipRect(aRect); + } + + ~AutoClipRect() { mDt.PopClip(); } + + private: + DrawTarget& mDt; +}; + +static void PaintRoundedRectWithBorder(DrawTarget* aDrawTarget, + const Rect& aRect, + const sRGBColor& aBackgroundColor, + const sRGBColor& aBorderColor, + CSSCoord aBorderWidth, CSSCoord aRadius, + uint32_t aDpi) { + const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi); + const LayoutDeviceCoord radius(aRadius * aDpi); + + Rect rect(aRect); + // Deflate the rect by half the border width, so that the middle of the stroke + // fills exactly the area we want to fill and not more. + rect.Deflate(borderWidth * 0.5f); + + RectCornerRadii radii(radius, radius, radius, radius); + RefPtr roundedRect = MakePathForRoundedRect(*aDrawTarget, rect, radii); + + aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor))); + aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)), + StrokeOptions(borderWidth)); +} + +static void PaintCheckboxControl(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kBorderWidth = 2.0f; + const CSSCoord kRadius = 4.0f; + + sRGBColor backgroundColor; + sRGBColor borderColor; + ComputeCheckColors(aState, backgroundColor, borderColor); + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kBorderWidth, kRadius, aDpi); +} + +static void PaintCheckMark(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + // Points come from the coordinates on a 7X7 unit box centered at 0,0 + const float checkPolygonX[] = {-2.5, -0.7, 2.5}; + const float checkPolygonY[] = {-0.3, 1.7, -1.5}; + const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float); + const int32_t checkSize = 8; + + auto center = aRect.Center(); + + // Scale the checkmark based on the smallest dimension + nscoord paintScale = std::min(aRect.width, aRect.height) / checkSize; + RefPtr builder = aDrawTarget->CreatePathBuilder(); + Point p = center + + Point(checkPolygonX[0] * paintScale, checkPolygonY[0] * paintScale); + builder->MoveTo(p); + for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) { + p = center + Point(checkPolygonX[polyIndex] * paintScale, + checkPolygonY[polyIndex] * paintScale); + builder->LineTo(p); + } + RefPtr path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sBackgroundColor)), + StrokeOptions(2.0f * aDpi)); +} + +static void PaintIndeterminateMark(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + + Rect rect(aRect); + rect.y += (rect.height - rect.height / 4) / 2; + rect.height /= 4; + + aDrawTarget->FillRect( + rect, ColorPattern( + ToDeviceColor(isDisabled ? sDisabledColor : sBackgroundColor))); +} + +static void PaintStrokedEllipse(DrawTarget* aDrawTarget, const Rect& aRect, + const sRGBColor& aBackgroundColor, + const sRGBColor& aBorderColor, + const CSSCoord aBorderWidth, uint32_t aDpi) { + const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi); + RefPtr builder = aDrawTarget->CreatePathBuilder(); + + // Deflate for the same reason as PaintRoundedRectWithBorder. Note that the + // size is the diameter, so we just shrink by the border width once. + Size size(aRect.Size() - Size(borderWidth, borderWidth)); + AppendEllipseToPath(builder, aRect.Center(), size); + RefPtr ellipse = builder->Finish(); + + aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor))); + aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)), + StrokeOptions(borderWidth)); +} + +static void PaintRadioControl(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kBorderWidth = 2.0f; + + sRGBColor backgroundColor; + sRGBColor borderColor; + ComputeCheckColors(aState, backgroundColor, borderColor); + + PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor, + kBorderWidth, aDpi); +} + +static void PaintCheckedRadioButton(DrawTarget* aDrawTarget, const Rect& aRect, + uint32_t aDpi) { + Rect rect(aRect); + rect.x += 4.5f * aDpi; + rect.width -= 9.0f * aDpi; + rect.y += 4.5f * aDpi; + rect.height -= 9.0f * aDpi; + + RefPtr builder = aDrawTarget->CreatePathBuilder(); + AppendEllipseToPath(builder, rect.Center(), rect.Size()); + RefPtr ellipse = builder->Finish(); + aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(sBackgroundColor))); +} + +static sRGBColor ComputeBorderColor(const EventStates& aState) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUS); + if (isFocused) { + return sBorderFocusColor; + } + if (isHovered) { + return sBorderHoverColor; + } + return sBorderColor; +} + +static void PaintTextField(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + const sRGBColor& backgroundColor = + isDisabled ? sDisabledColor : sBackgroundColor; + const sRGBColor borderColor = ComputeBorderColor(aState); + + const CSSCoord kRadius = 4.0f; + + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kTextFieldBorderWidth, kRadius, aDpi); +} + +std::pair ComputeButtonColors( + const EventStates& aState, bool aIsDatetimeResetButton = false) { + bool isActive = + aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE); + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + + const sRGBColor& backgroundColor = [&] { + if (isDisabled) { + return sDisabledColor; + } + if (aIsDatetimeResetButton) { + return sWhiteColor; + } + if (isActive) { + return sButtonActiveColor; + } + if (isHovered) { + return sButtonHoverColor; + } + return sButtonColor; + }(); + + const sRGBColor borderColor = ComputeBorderColor(aState); + + return std::make_pair(backgroundColor, borderColor); +} + +static void PaintMenulist(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kRadius = 4.0f; + + sRGBColor backgroundColor, borderColor; + std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState); + + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kMenulistBorderWidth, kRadius, aDpi); +} + +static void PaintArrow(DrawTarget* aDrawTarget, const Rect& aRect, + const int32_t aArrowPolygonX[], + const int32_t aArrowPolygonY[], + const int32_t aArrowNumPoints, const int32_t aArrowSize, + const sRGBColor aFillColor, uint32_t aDpi) { + nscoord paintScale = std::min(aRect.width, aRect.height) / aArrowSize; + RefPtr builder = aDrawTarget->CreatePathBuilder(); + Point p = aRect.Center() + Point(aArrowPolygonX[0] * paintScale, + aArrowPolygonY[0] * paintScale); + + builder->MoveTo(p); + for (int32_t polyIndex = 1; polyIndex < aArrowNumPoints; polyIndex++) { + p = aRect.Center() + Point(aArrowPolygonX[polyIndex] * paintScale, + aArrowPolygonY[polyIndex] * paintScale); + builder->LineTo(p); + } + RefPtr path = builder->Finish(); + + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(aFillColor)), + StrokeOptions(2.0f * aDpi)); +} + +static void PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + bool isHTML = nsNativeTheme::IsHTMLContent(aFrame); + + if (!isHTML && nsNativeTheme::CheckBooleanAttr(aFrame, nsGkAtoms::open)) { + isHovered = false; + } + + const int32_t arrowSize = 8; + int32_t arrowPolygonX[] = {-4, -2, 0}; + int32_t arrowPolygonY[] = {-1, 1, -1}; + const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); + + PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, + arrowSize, + isPressed ? sActiveColor + : isHovered ? sBorderHoverColor + : sBorderColor, + aDpi); +} + +static void PaintSpinnerButton(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, + StyleAppearance aAppearance, uint32_t aDpi) { + bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED); + bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER | + NS_EVENT_STATE_ACTIVE); + bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER); + + const int32_t arrowSize = 8; + int32_t arrowPolygonX[] = {0, 2, 4}; + int32_t arrowPolygonY[] = {-3, -1, -3}; + const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); + + if (aAppearance == StyleAppearance::SpinnerUpbutton) { + for (int32_t i = 0; i < arrowNumPoints; i++) { + arrowPolygonY[i] *= -1; + } + } + + PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, + arrowSize, + isPressed ? sActiveColor + : isHovered ? sBorderHoverColor + : sBorderColor, + aDpi); +} + +static void PaintRangeInputBackground(DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState, uint32_t aDpi, + bool aHorizontal) { + Rect rect(aRect); + const LayoutDeviceCoord kVerticalSize = + kMinimumAndroidWidgetSize * 0.25f * aDpi; + + if (aHorizontal) { + rect.y += (rect.height - kVerticalSize) / 2; + rect.height = kVerticalSize; + } else { + rect.x += (rect.width - kVerticalSize) / 2; + rect.width = kVerticalSize; + } + + aDrawTarget->FillRect( + rect, ColorPattern(ToDeviceColor(sRangeInputBackgroundColor))); + aDrawTarget->StrokeRect(rect, + ColorPattern(ToDeviceColor(sButtonActiveColor))); +} + +static void PaintScrollbarthumbHorizontal(DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState) { + sRGBColor thumbColor = sScrollbarThumbColor; + if (aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) { + thumbColor = sScrollbarThumbColorActive; + } else if (aState.HasState(NS_EVENT_STATE_HOVER)) { + thumbColor = sScrollbarThumbColorHover; + } + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor))); +} + +static void PaintScrollbarthumbVertical(DrawTarget* aDrawTarget, + const Rect& aRect, + const EventStates& aState) { + sRGBColor thumbColor = sScrollbarThumbColor; + if (aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE)) { + thumbColor = sScrollbarThumbColorActive; + } else if (aState.HasState(NS_EVENT_STATE_HOVER)) { + thumbColor = sScrollbarThumbColorHover; + } + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor))); +} + +static void PaintScrollbarHorizontal(DrawTarget* aDrawTarget, + const Rect& aRect) { + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(sScrollbarColor))); + RefPtr builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(aRect.x, aRect.y)); + builder->LineTo(Point(aRect.x + aRect.width, aRect.y)); + RefPtr path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor))); +} + +static void PaintScrollbarVerticalAndCorner(DrawTarget* aDrawTarget, + const Rect& aRect, uint32_t aDpi) { + aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(sScrollbarColor))); + RefPtr builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(aRect.x, aRect.y)); + builder->LineTo(Point(aRect.x, aRect.y + aRect.height)); + RefPtr path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)), + StrokeOptions(1.0f * aDpi)); +} + +static void PaintScrollbarbutton(DrawTarget* aDrawTarget, + StyleAppearance aAppearance, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + bool isActive = + aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE); + bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER); + + aDrawTarget->FillRect( + aRect, ColorPattern(ToDeviceColor(isActive ? sScrollbarButtonActiveColor + : isHovered ? sScrollbarButtonHoverColor + : sScrollbarColor))); + + // Start with Up arrow. + int32_t arrowPolygonX[] = {3, 0, -3}; + int32_t arrowPolygonY[] = {2, -1, 2}; + const int32_t arrowNumPoints = ArrayLength(arrowPolygonX); + const int32_t arrowSize = 14; + + switch (aAppearance) { + case StyleAppearance::ScrollbarbuttonUp: + break; + case StyleAppearance::ScrollbarbuttonDown: + for (int32_t i = 0; i < arrowNumPoints; i++) { + arrowPolygonY[i] *= -1; + } + break; + case StyleAppearance::ScrollbarbuttonLeft: + for (int32_t i = 0; i < arrowNumPoints; i++) { + int32_t temp = arrowPolygonX[i]; + arrowPolygonX[i] = arrowPolygonY[i]; + arrowPolygonY[i] = temp; + } + break; + case StyleAppearance::ScrollbarbuttonRight: + for (int32_t i = 0; i < arrowNumPoints; i++) { + int32_t temp = arrowPolygonX[i]; + arrowPolygonX[i] = arrowPolygonY[i] * -1; + arrowPolygonY[i] = temp; + } + break; + default: + return; + } + + PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints, + arrowSize, + isActive ? sScrollbarArrowColorActive + : isHovered ? sScrollbarArrowColorHover + : sScrollbarArrowColor, + aDpi); + + RefPtr builder = aDrawTarget->CreatePathBuilder(); + builder->MoveTo(Point(aRect.x, aRect.y)); + if (aAppearance == StyleAppearance::ScrollbarbuttonUp || + aAppearance == StyleAppearance::ScrollbarbuttonDown) { + builder->LineTo(Point(aRect.x, aRect.y + aRect.height)); + } else { + builder->LineTo(Point(aRect.x + aRect.width, aRect.y)); + } + + RefPtr path = builder->Finish(); + aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)), + StrokeOptions(1.0f * aDpi)); +} + +static void PaintButton(nsIFrame* aFrame, DrawTarget* aDrawTarget, + const Rect& aRect, const EventStates& aState, + uint32_t aDpi) { + const CSSCoord kRadius = 4.0f; + + // FIXME: The DateTimeResetButton bit feels like a bit of a hack. + sRGBColor backgroundColor, borderColor; + std::tie(backgroundColor, borderColor) = + ComputeButtonColors(aState, IsDateTimeResetButton(aFrame)); + + PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor, + kButtonBorderWidth, kRadius, aDpi); +} + +static void PaintRangeThumb(DrawTarget* aDrawTarget, const Rect& aRect, + const EventStates& aState, uint32_t aDpi) { + const CSSCoord kBorderWidth = 2.0f; + + sRGBColor backgroundColor, borderColor; + std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState); + + PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor, + kBorderWidth, aDpi); +} + +NS_IMETHODIMP +nsNativeThemeAndroid::DrawWidgetBackground(gfxContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& /* aDirtyRect */) { + DrawTarget* dt = aContext->GetDrawTarget(); + const nscoord twipsPerPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + EventStates eventState = GetContentState(aFrame, aAppearance); + + Rect devPxRect = NSRectToSnappedRect(aRect, twipsPerPixel, *dt); + AutoClipRect clip(*dt, devPxRect); + + if (aAppearance == StyleAppearance::MozMenulistArrowButton) { + bool isHTML = IsHTMLContent(aFrame); + nsIFrame* parentFrame = aFrame->GetParent(); + bool isMenulist = !isHTML && parentFrame->IsMenuFrame(); + // HTML select and XUL menulist dropdown buttons get state from the + // parent. + if (isHTML || isMenulist) { + aFrame = parentFrame; + eventState = GetContentState(parentFrame, aAppearance); + } + } + + uint32_t dpi = GetDPIRatio(aFrame); + + switch (aAppearance) { + case StyleAppearance::Radio: { + auto rect = FixAspectRatio(devPxRect); + PaintRadioControl(dt, rect, eventState, dpi); + if (IsSelected(aFrame)) { + PaintCheckedRadioButton(dt, rect, dpi); + } + break; + } + case StyleAppearance::Checkbox: { + auto rect = FixAspectRatio(devPxRect); + PaintCheckboxControl(dt, rect, eventState, dpi); + if (IsChecked(aFrame)) { + PaintCheckMark(dt, rect, eventState, dpi); + } + if (GetIndeterminate(aFrame)) { + PaintIndeterminateMark(dt, rect, eventState); + } + break; + } + case StyleAppearance::Textarea: + case StyleAppearance::Textfield: + case StyleAppearance::NumberInput: + PaintTextField(dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + PaintMenulist(dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::MozMenulistArrowButton: + PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + PaintSpinnerButton(dt, devPxRect, eventState, aAppearance, dpi); + break; + case StyleAppearance::Range: + PaintRangeInputBackground(dt, devPxRect, eventState, dpi, + IsRangeHorizontal(aFrame)); + break; + case StyleAppearance::RangeThumb: + // TODO(emilio): Do we want to enforce it being a circle using + // FixAspectRatio here? For now let authors tweak, it's a custom pseudo so + // it doesn't probably have much compat impact if at all. + PaintRangeThumb(dt, devPxRect, eventState, dpi); + break; + case StyleAppearance::ScrollbarthumbHorizontal: + PaintScrollbarthumbHorizontal(dt, devPxRect, eventState); + break; + case StyleAppearance::ScrollbarthumbVertical: + PaintScrollbarthumbVertical(dt, devPxRect, eventState); + break; + case StyleAppearance::ScrollbarHorizontal: + PaintScrollbarHorizontal(dt, devPxRect); + break; + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::Scrollcorner: + PaintScrollbarVerticalAndCorner(dt, devPxRect, dpi); + break; + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + PaintScrollbarbutton(dt, aAppearance, devPxRect, eventState, dpi); + break; + case StyleAppearance::Button: + PaintButton(aFrame, dt, devPxRect, eventState, dpi); + break; + default: + MOZ_ASSERT_UNREACHABLE( + "Should not get here with a widget type we don't support."); + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; +} + +/*bool +nsNativeThemeAndroid::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder& +aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const +mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* +aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) { +}*/ + +LayoutDeviceIntMargin nsNativeThemeAndroid::GetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) { + uint32_t dpi = GetDPIRatio(aFrame); + switch (aAppearance) { + case StyleAppearance::Textfield: + case StyleAppearance::Textarea: + case StyleAppearance::NumberInput: { + const LayoutDeviceIntCoord w = kTextFieldBorderWidth * dpi; + return LayoutDeviceIntMargin(w, w, w, w); + } + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: { + const LayoutDeviceIntCoord w = kMenulistBorderWidth * dpi; + return LayoutDeviceIntMargin(w, w, w, w); + } + case StyleAppearance::Button: { + const LayoutDeviceIntCoord w = kButtonBorderWidth * dpi; + return LayoutDeviceIntMargin(w, w, w, w); + } + default: + return LayoutDeviceIntMargin(); + } +} + +bool nsNativeThemeAndroid::GetWidgetPadding(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult) { + uint32_t dpiRatio = GetDPIRatio(aFrame); + switch (aAppearance) { + // Radios and checkboxes return a fixed size in GetMinimumWidgetSize + // and have a meaningful baseline, so they can't have + // author-specified padding. + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + aResult->SizeTo(0, 0, 0, 0); + return true; + case StyleAppearance::MozMenulistArrowButton: + aResult->SizeTo(1 * dpiRatio, 4 * dpiRatio, 1 * dpiRatio, 4 * dpiRatio); + return true; + default: + break; + } + + // Respect author padding. + // + // TODO(emilio): Consider just unconditionally returning false, so that the + // default size of all elements matches other platforms and the UA stylesheet. + if (aFrame->PresContext()->HasAuthorSpecifiedRules( + aFrame, NS_AUTHOR_SPECIFIED_PADDING)) { + return false; + } + + uint32_t dpi = GetDPIRatio(aFrame); + switch (aAppearance) { + case StyleAppearance::Textarea: + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::NumberInput: + aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi); + return true; + case StyleAppearance::Button: + aResult->SizeTo(6 * dpi, 21 * dpi, 6 * dpi, 21 * dpi); + return true; + case StyleAppearance::Textfield: + if (IsDateTimeTextField(aFrame)) { + aResult->SizeTo(7 * dpi, 7 * dpi, 5 * dpi, 7 * dpi); + return true; + } + aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi); + return true; + default: + return false; + } +} + +bool nsNativeThemeAndroid::GetWidgetOverflow(nsDeviceContext* aContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + nsRect* aOverflowRect) { + // TODO(bug 1620360): This should return non-zero for + // StyleAppearance::FocusOutline, if we implement outline-style: auto. + return false; +} + +NS_IMETHODIMP +nsNativeThemeAndroid::GetMinimumWidgetSize(nsPresContext* aPresContext, + nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntSize* aResult, + bool* aIsOverridable) { + uint32_t dpiRatio = GetDPIRatio(aFrame); + aResult->width = aResult->height = + static_cast(kMinimumAndroidWidgetSize) * dpiRatio; + *aIsOverridable = true; + if (aAppearance == StyleAppearance::MozMenulistArrowButton) { + aResult->width = + static_cast(kMinimumDropdownArrowButtonWidth) * dpiRatio; + } + return NS_OK; +} + +nsITheme::Transparency nsNativeThemeAndroid::GetWidgetTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) { + return eUnknownTransparency; +} + +NS_IMETHODIMP +nsNativeThemeAndroid::WidgetStateChanged(nsIFrame* aFrame, + StyleAppearance aAppearance, + nsAtom* aAttribute, + bool* aShouldRepaint, + const nsAttrValue* aOldValue) { + if (!aAttribute) { + // Hover/focus/active changed. Always repaint. + *aShouldRepaint = true; + } else { + // Check the attribute to see if it's relevant. + // disabled, checked, dlgtype, default, etc. + *aShouldRepaint = false; + if ((aAttribute == nsGkAtoms::disabled) || + (aAttribute == nsGkAtoms::checked) || + (aAttribute == nsGkAtoms::selected) || + (aAttribute == nsGkAtoms::visuallyselected) || + (aAttribute == nsGkAtoms::menuactive) || + (aAttribute == nsGkAtoms::sortDirection) || + (aAttribute == nsGkAtoms::focused) || + (aAttribute == nsGkAtoms::_default) || + (aAttribute == nsGkAtoms::open) || (aAttribute == nsGkAtoms::hover)) { + *aShouldRepaint = true; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeThemeAndroid::ThemeChanged() { return NS_OK; } + +bool nsNativeThemeAndroid::WidgetAppearanceDependsOnWindowFocus( + StyleAppearance) { + return false; +} + +nsITheme::ThemeGeometryType nsNativeThemeAndroid::ThemeGeometryTypeForWidget( + nsIFrame* aFrame, StyleAppearance aAppearance) { + return eThemeGeometryTypeUnknown; +} + +bool nsNativeThemeAndroid::ThemeSupportsWidget(nsPresContext* aPresContext, + nsIFrame* aFrame, + StyleAppearance aAppearance) { + if (IsWidgetScrollbarPart(aAppearance)) { + const auto* style = nsLayoutUtils::StyleForScrollbar(aFrame); + // We don't currently handle custom scrollbars on + // nsNativeThemeAndroid. We could, potentially. + if (style->StyleUI()->HasCustomScrollbars() || + style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) { + return false; + } + } + + switch (aAppearance) { + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + case StyleAppearance::Textarea: + case StyleAppearance::Textfield: + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + case StyleAppearance::ScrollbarbuttonUp: + case StyleAppearance::ScrollbarbuttonDown: + case StyleAppearance::ScrollbarbuttonLeft: + case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::ScrollbarthumbHorizontal: + case StyleAppearance::ScrollbarthumbVertical: + case StyleAppearance::ScrollbarHorizontal: + case StyleAppearance::ScrollbarNonDisappearing: + case StyleAppearance::ScrollbarVertical: + case StyleAppearance::Scrollcorner: + case StyleAppearance::Button: + case StyleAppearance::Listbox: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::NumberInput: + case StyleAppearance::MozMenulistArrowButton: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + return !IsWidgetStyled(aPresContext, aFrame, aAppearance); + default: + return false; + } +} + +bool nsNativeThemeAndroid::WidgetIsContainer(StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::MozMenulistArrowButton: + case StyleAppearance::Radio: + case StyleAppearance::Checkbox: + return false; + default: + return true; + } +} + +bool nsNativeThemeAndroid::ThemeDrawsFocusForWidget( + StyleAppearance aAppearance) { + switch (aAppearance) { + case StyleAppearance::Range: + // TODO(emilio): Checkbox / Radio don't have focus indicators when checked. + // If they did, we could just return true here unconditionally. + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: + return false; + default: + return true; + } +} + +bool nsNativeThemeAndroid::ThemeNeedsComboboxDropmarker() { return true; } + +already_AddRefed do_GetNativeThemeDoNotUseDirectly() { + static StaticRefPtr gInstance; + if (MOZ_UNLIKELY(!gInstance)) { + gInstance = new nsNativeThemeAndroid(); + ClearOnShutdown(&gInstance); + } + return do_AddRef(gInstance); +} diff --git a/widget/android/nsNativeThemeAndroid.h b/widget/android/nsNativeThemeAndroid.h new file mode 100644 index 0000000000..a450ae3085 --- /dev/null +++ b/widget/android/nsNativeThemeAndroid.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 nsNativeThemeAndroid_h +#define nsNativeThemeAndroid_h + +#include "nsITheme.h" +#include "nsNativeTheme.h" + +class nsNativeThemeAndroid : private nsNativeTheme, public nsITheme { + public: + nsNativeThemeAndroid() = default; + + NS_DECL_ISUPPORTS_INHERITED + + // The nsITheme interface. + NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + const nsRect& aRect, + const nsRect& aDirtyRect) override; + /*bool CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder& + aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const + mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* + aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& + aRect) override;*/ + [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder( + nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance) override; + bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + LayoutDeviceIntMargin* aResult) override; + virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + nsRect* aOverflowRect) override; + NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame, + StyleAppearance aAppearance, + mozilla::LayoutDeviceIntSize* aResult, + bool* aIsOverridable) override; + virtual Transparency GetWidgetTransparency( + nsIFrame* aFrame, StyleAppearance aAppearance) override; + NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance, + nsAtom* aAttribute, bool* aShouldRepaint, + const nsAttrValue* aOldValue) override; + NS_IMETHOD ThemeChanged() override; + virtual bool WidgetAppearanceDependsOnWindowFocus( + StyleAppearance aAppearance) override; + /*virtual bool NeedToClearBackgroundBehindWidget(nsIFrame* aFrame, + StyleAppearance aAppearance) + override;*/ + virtual ThemeGeometryType ThemeGeometryTypeForWidget( + nsIFrame* aFrame, StyleAppearance aAppearance) override; + bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, + StyleAppearance aAppearance) override; + bool WidgetIsContainer(StyleAppearance aAppearance) override; + bool ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override; + bool ThemeNeedsComboboxDropmarker() override; + + protected: + virtual ~nsNativeThemeAndroid() = default; +}; + +#endif diff --git a/widget/android/nsPrintSettingsServiceAndroid.cpp b/widget/android/nsPrintSettingsServiceAndroid.cpp new file mode 100644 index 0000000000..67f978c6e3 --- /dev/null +++ b/widget/android/nsPrintSettingsServiceAndroid.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsPrintSettingsServiceAndroid.h" + +#include "nsPrintSettingsImpl.h" + +class nsPrintSettingsAndroid : public nsPrintSettings { + public: + nsPrintSettingsAndroid() { + // The aim here is to set up the objects enough that silent printing works + SetOutputFormat(nsIPrintSettings::kOutputFormatPDF); + SetPrinterName(u"PDF printer"_ns); + } +}; + +nsresult nsPrintSettingsServiceAndroid::_CreatePrintSettings( + nsIPrintSettings** _retval) { + nsPrintSettings* printSettings = new nsPrintSettingsAndroid(); + NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*_retval = printSettings); + (void)InitPrintSettingsFromPrefs(*_retval, false, + nsIPrintSettings::kInitSaveAll); + return NS_OK; +} + +already_AddRefed CreatePlatformPrintSettings( + const mozilla::PrintSettingsInitializer& aSettings) { + RefPtr settings = new nsPrintSettingsAndroid(); + settings->InitWithInitializer(aSettings); + return settings.forget(); +} diff --git a/widget/android/nsPrintSettingsServiceAndroid.h b/widget/android/nsPrintSettingsServiceAndroid.h new file mode 100644 index 0000000000..28710feb54 --- /dev/null +++ b/widget/android/nsPrintSettingsServiceAndroid.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsPrintSettingsServiceAndroid_h +#define nsPrintSettingsServiceAndroid_h + +#include "nsPrintSettingsService.h" +#include "nsIPrintSettings.h" + +class nsPrintSettingsServiceAndroid final : public nsPrintSettingsService { + public: + nsPrintSettingsServiceAndroid() {} + + nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override; +}; + +#endif // nsPrintSettingsServiceAndroid_h diff --git a/widget/android/nsUserIdleServiceAndroid.cpp b/widget/android/nsUserIdleServiceAndroid.cpp new file mode 100644 index 0000000000..15f9d7e132 --- /dev/null +++ b/widget/android/nsUserIdleServiceAndroid.cpp @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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 "nsUserIdleServiceAndroid.h" + +bool nsUserIdleServiceAndroid::PollIdleTime(uint32_t* aIdleTime) { + return false; +} + +bool nsUserIdleServiceAndroid::UsePollMode() { return false; } diff --git a/widget/android/nsUserIdleServiceAndroid.h b/widget/android/nsUserIdleServiceAndroid.h new file mode 100644 index 0000000000..2169673088 --- /dev/null +++ b/widget/android/nsUserIdleServiceAndroid.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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 nsUserIdleServiceAndroid_h__ +#define nsUserIdleServiceAndroid_h__ + +#include "nsUserIdleService.h" + +class nsUserIdleServiceAndroid : public nsUserIdleService { + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceAndroid, + nsUserIdleService) + + bool PollIdleTime(uint32_t* aIdleTime) override; + + static already_AddRefed GetInstance() { + RefPtr idleService = nsUserIdleService::GetInstance(); + if (!idleService) { + idleService = new nsUserIdleServiceAndroid(); + } + + return idleService.forget().downcast(); + } + + protected: + nsUserIdleServiceAndroid() {} + virtual ~nsUserIdleServiceAndroid() {} + bool UsePollMode() override; +}; + +#endif // nsUserIdleServiceAndroid_h__ diff --git a/widget/android/nsWidgetFactory.cpp b/widget/android/nsWidgetFactory.cpp new file mode 100644 index 0000000000..00ebd20dd9 --- /dev/null +++ b/widget/android/nsWidgetFactory.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/WidgetUtils.h" + +#include "nsAppShell.h" + +#include "nsLookAndFeel.h" +#include "nsAppShellSingleton.h" + +nsresult nsWidgetAndroidModuleCtor() { return nsAppShellInit(); } + +void nsWidgetAndroidModuleDtor() { + // Shutdown all XP level widget classes. + mozilla::widget::WidgetUtils::Shutdown(); + + nsLookAndFeel::Shutdown(); + nsAppShellShutdown(); +} diff --git a/widget/android/nsWidgetFactory.h b/widget/android/nsWidgetFactory.h new file mode 100644 index 0000000000..57b96c1b3b --- /dev/null +++ b/widget/android/nsWidgetFactory.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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 widget_android_nsWidgetFactory_h +#define widget_android_nsWidgetFactory_h + +#include "nscore.h" +#include "nsID.h" + +class nsISupports; + +nsresult nsAppShellConstructor(nsISupports* outer, const nsIID& iid, + void** result); + +nsresult nsWidgetAndroidModuleCtor(); +void nsWidgetAndroidModuleDtor(); + +#endif // defined widget_android_nsWidgetFactory_h diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp new file mode 100644 index 0000000000..479ce25ef0 --- /dev/null +++ b/widget/android/nsWindow.cpp @@ -0,0 +1,2619 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 4; indent-tabs-mode: nil; -*- + * vim: set sw=2 ts=4 expandtab: + * 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 +#include +#include +#include +#include +#include +#include + +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_android.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/Unused.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy + +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/a11y/SessionAccessibility.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/RenderTrace.h" +#include "mozilla/widget/AndroidVsync.h" +#include + +using mozilla::Unused; +using mozilla::dom::ContentChild; +using mozilla::dom::ContentParent; +using mozilla::gfx::DataSourceSurface; +using mozilla::gfx::IntSize; +using mozilla::gfx::Matrix; +using mozilla::gfx::SurfaceFormat; + +#include "nsWindow.h" + +#include "AndroidGraphics.h" +#include "JavaExceptions.h" + +#include "nsIWidgetListener.h" +#include "nsIWindowWatcher.h" +#include "nsIAppWindow.h" + +#include "nsAppShell.h" +#include "nsFocusManager.h" +#include "nsUserIdleService.h" +#include "nsLayoutUtils.h" +#include "nsViewManager.h" + +#include "WidgetUtils.h" +#include "nsContentUtils.h" + +#include "nsGfxCIID.h" +#include "nsGkAtoms.h" +#include "nsWidgetsCID.h" + +#include "gfxContext.h" + +#include "AndroidContentController.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "Layers.h" +#include "ScopedGLHelpers.h" +#include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/AsyncCompositionManager.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/layers/LayerManagerComposite.h" + +#include "nsTArray.h" + +#include "AndroidBridge.h" +#include "AndroidBridgeUtilities.h" +#include "AndroidUiThread.h" +#include "AndroidView.h" +#include "GeckoEditableSupport.h" +#include "GeckoViewSupport.h" +#include "KeyEvent.h" +#include "MotionEvent.h" +#include "mozilla/java/EventDispatcherWrappers.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/GeckoEditableChildWrappers.h" +#include "mozilla/java/GeckoResultWrappers.h" +#include "mozilla/java/GeckoSessionNatives.h" +#include "mozilla/java/GeckoSystemStateListenerWrappers.h" +#include "mozilla/java/PanZoomControllerNatives.h" +#include "mozilla/java/SessionAccessibilityWrappers.h" +#include "ScreenHelperAndroid.h" +#include "TouchResampler.h" + +#include "GeckoProfiler.h" // For AUTO_PROFILER_LABEL +#include "nsPrintfCString.h" +#include "nsString.h" + +#include "JavaBuiltins.h" + +#include "mozilla/ipc/Shmem.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::ipc; + +using mozilla::java::GeckoSession; + +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorSession.h" +#include "mozilla/layers/LayerTransactionParent.h" +#include "mozilla/layers/UiCompositorControllerChild.h" +#include "nsThreadUtils.h" + +// All the toplevel windows that have been created; these are in +// stacking order, so the window at gTopLevelWindows[0] is the topmost +// one. +static nsTArray gTopLevelWindows; + +static bool sFailedToCreateGLContext = false; + +// Multitouch swipe thresholds in inches +static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4; +static const double SWIPE_MIN_DISTANCE_INCHES = 0.6; + +static const double kTouchResampleVsyncAdjustMs = 5.0; + +static const int32_t INPUT_RESULT_UNHANDLED = + java::PanZoomController::INPUT_RESULT_UNHANDLED; +static const int32_t INPUT_RESULT_HANDLED = + java::PanZoomController::INPUT_RESULT_HANDLED; +static const int32_t INPUT_RESULT_HANDLED_CONTENT = + java::PanZoomController::INPUT_RESULT_HANDLED_CONTENT; +static const int32_t INPUT_RESULT_IGNORED = + java::PanZoomController::INPUT_RESULT_IGNORED; + +namespace { +template +std::enable_if_t::value == + jni::detail::NativePtrType::REFPTR, + void> +CallAttachNative(Instance aInstance, Impl* aImpl) { + Impl::AttachNative(aInstance, RefPtr(aImpl).get()); +} + +template +std::enable_if_t::value == + jni::detail::NativePtrType::OWNING, + void> +CallAttachNative(Instance aInstance, Impl* aImpl) { + Impl::AttachNative(aInstance, UniquePtr(aImpl)); +} + +template +bool DispatchToUiThread(const char* aName, Lambda&& aLambda) { + if (RefPtr uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NS_NewRunnableFunction(aName, std::move(aLambda))); + return true; + } + return false; +} +} // namespace + +namespace mozilla { +namespace widget { + +using WindowPtr = jni::NativeWeakPtr; + +/** + * PanZoomController handles its native calls on the UI thread, so make + * it separate from GeckoViewSupport. + */ +class NPZCSupport final + : public java::PanZoomController::NativeProvider::Natives, + public AndroidVsync::Observer { + WindowPtr mWindow; + java::PanZoomController::NativeProvider::WeakRef mNPZC; + + // Stores the returnResult of each pending motion event between + // HandleMotionEvent and FinishHandlingMotionEvent. + std::queue> + mPendingMotionEventReturnResults; + + RefPtr mAndroidVsync; + TouchResampler mTouchResampler; + int mPreviousButtons = 0; + bool mListeningToVsync = false; + + // Only true if mAndroidVsync is non-null and the resampling pref is set. + bool mTouchResamplingEnabled = false; + + template + class InputEvent final : public nsAppShell::Event { + java::PanZoomController::NativeProvider::GlobalRef mNPZC; + Lambda mLambda; + + public: + InputEvent(const NPZCSupport* aNPZCSupport, Lambda&& aLambda) + : mNPZC(aNPZCSupport->mNPZC), mLambda(std::move(aLambda)) {} + + void Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + JNIEnv* const env = jni::GetGeckoThreadEnv(); + const auto npzcSupportWeak = GetNative( + java::PanZoomController::NativeProvider::LocalRef(env, mNPZC)); + if (!npzcSupportWeak) { + // We already shut down. + env->ExceptionClear(); + return; + } + + auto acc = npzcSupportWeak->Access(); + if (!acc) { + // We already shut down. + env->ExceptionClear(); + return; + } + + auto win = acc->mWindow.Access(); + if (!win) { + // We already shut down. + env->ExceptionClear(); + return; + } + + nsWindow* const window = win->GetNsWindow(); + if (!window) { + // We already shut down. + env->ExceptionClear(); + return; + } + + window->UserActivity(); + return mLambda(window); + } + + bool IsUIEvent() const override { return true; } + }; + + template + void PostInputEvent(Lambda&& aLambda) { + // Use priority queue for input events. + nsAppShell::PostEvent( + MakeUnique>(this, std::move(aLambda))); + } + + public: + typedef java::PanZoomController::NativeProvider::Natives Base; + + NPZCSupport(WindowPtr aWindow, + const java::PanZoomController::NativeProvider::LocalRef& aNPZC) + : mWindow(aWindow), mNPZC(aNPZC) { +#if defined(DEBUG) + auto win(mWindow.Access()); + MOZ_ASSERT(!!win); +#endif // defined(DEBUG) + + // Use vsync for touch resampling on API level 19 and above. + // See gfxAndroidPlatform::CreateHardwareVsyncSource() for comparison. + if (AndroidBridge::Bridge() && + AndroidBridge::Bridge()->GetAPIVersion() >= 19) { + mAndroidVsync = AndroidVsync::GetInstance(); + } + } + + ~NPZCSupport() { + if (mListeningToVsync) { + MOZ_RELEASE_ASSERT(mAndroidVsync); + mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT); + mListeningToVsync = false; + } + } + + using Base::AttachNative; + using Base::DisposeNative; + + void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer) { + RefPtr disposer = aDisposer; + // There are several considerations when shutting down NPZC. 1) The + // Gecko thread may destroy NPZC at any time when nsWindow closes. 2) + // There may be pending events on the Gecko thread when NPZC is + // destroyed. 3) mWindow may not be available when the pending event + // runs. 4) The UI thread may destroy NPZC at any time when GeckoView + // is destroyed. 5) The UI thread may destroy NPZC at the same time as + // Gecko thread trying to destroy NPZC. 6) There may be pending calls + // on the UI thread when NPZC is destroyed. 7) mWindow may have been + // cleared on the Gecko thread when the pending call happens on the UI + // thread. + // + // 1) happens through OnWeakNonIntrusiveDetach, which first notifies the UI + // thread through Destroy; Destroy then calls DisposeNative, which + // finally disposes the native instance back on the Gecko thread. Using + // Destroy to indirectly call DisposeNative here also solves 5), by + // making everything go through the UI thread, avoiding contention. + // + // 2) and 3) are solved by clearing mWindow, which signals to the + // pending event that we had shut down. In that case the event bails + // and does not touch mWindow. + // + // 4) happens through DisposeNative directly. + // + // 6) is solved by keeping a destroyed flag in the Java NPZC instance, + // and only make a pending call if the destroyed flag is not set. + // + // 7) is solved by taking a lock whenever mWindow is modified on the + // Gecko thread or accessed on the UI thread. That way, we don't + // release mWindow until the UI thread is done using it, thus avoiding + // the race condition. + + if (RefPtr uiThread = GetAndroidUiThread()) { + auto npzc = java::PanZoomController::NativeProvider::GlobalRef(mNPZC); + if (!npzc) { + return; + } + + uiThread->Dispatch( + NS_NewRunnableFunction("NPZCSupport::OnWeakNonIntrusiveDetach", + [npzc, disposer = std::move(disposer)] { + npzc->SetAttached(false); + disposer->Run(); + })); + } + } + + const java::PanZoomController::NativeProvider::Ref& GetJavaNPZC() const { + return mNPZC; + } + + public: + void SetIsLongpressEnabled(bool aIsLongpressEnabled) { + RefPtr controller; + + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + controller = gkWindow->mAPZC; + } + } + + if (controller) { + controller->SetLongTapEnabled(aIsLongpressEnabled); + } + } + + int32_t HandleScrollEvent(int64_t aTime, int32_t aMetaState, float aX, + float aY, float aHScroll, float aVScroll) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + RefPtr controller; + + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + controller = gkWindow->mAPZC; + } + } + + if (!controller) { + return INPUT_RESULT_UNHANDLED; + } + + ScreenPoint origin = ScreenPoint(aX, aY); + + if (StaticPrefs::ui_scrolling_negate_wheel_scroll()) { + aHScroll = -aHScroll; + aVScroll = -aVScroll; + } + + ScrollWheelInput input( + aTime, nsWindow::GetEventTimeStamp(aTime), + nsWindow::GetModifiers(aMetaState), ScrollWheelInput::SCROLLMODE_SMOOTH, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, aHScroll, aVScroll, false, + // XXX Do we need to support auto-dir scrolling + // for Android widgets with a wheel device? + // Currently, I just leave it unimplemented. If + // we need to implement it, what's the extra work + // to do? + WheelDeltaAdjustmentStrategy::eNone); + + APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input); + if (result.mStatus == nsEventStatus_eConsumeNoDefault) { + return INPUT_RESULT_IGNORED; + } + + PostInputEvent([input, result](nsWindow* window) { + WidgetWheelEvent wheelEvent = input.ToWidgetEvent(window); + window->ProcessUntransformedAPZEvent(&wheelEvent, result); + }); + + switch (result.mStatus) { + case nsEventStatus_eIgnore: + return INPUT_RESULT_UNHANDLED; + case nsEventStatus_eConsumeDoDefault: + return (result.mHandledResult == Some(APZHandledResult::HandledByRoot)) + ? INPUT_RESULT_HANDLED + : INPUT_RESULT_HANDLED_CONTENT; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus"); + return INPUT_RESULT_UNHANDLED; + } + } + + private: + static MouseInput::ButtonType GetButtonType(int button) { + MouseInput::ButtonType result = MouseInput::NONE; + + switch (button) { + case java::sdk::MotionEvent::BUTTON_PRIMARY: + result = MouseInput::PRIMARY_BUTTON; + break; + case java::sdk::MotionEvent::BUTTON_SECONDARY: + result = MouseInput::SECONDARY_BUTTON; + break; + case java::sdk::MotionEvent::BUTTON_TERTIARY: + result = MouseInput::MIDDLE_BUTTON; + break; + default: + break; + } + + return result; + } + + static int16_t ConvertButtons(int buttons) { + int16_t result = 0; + + if (buttons & java::sdk::MotionEvent::BUTTON_PRIMARY) { + result |= MouseButtonsFlag::ePrimaryFlag; + } + if (buttons & java::sdk::MotionEvent::BUTTON_SECONDARY) { + result |= MouseButtonsFlag::eSecondaryFlag; + } + if (buttons & java::sdk::MotionEvent::BUTTON_TERTIARY) { + result |= MouseButtonsFlag::eMiddleFlag; + } + if (buttons & java::sdk::MotionEvent::BUTTON_BACK) { + result |= MouseButtonsFlag::e4thFlag; + } + if (buttons & java::sdk::MotionEvent::BUTTON_FORWARD) { + result |= MouseButtonsFlag::e5thFlag; + } + + return result; + } + + static int32_t ConvertAPZHandledResult(APZHandledResult aHandledResult) { + switch (aHandledResult) { + case APZHandledResult::Unhandled: + return INPUT_RESULT_UNHANDLED; + case APZHandledResult::HandledByRoot: + return INPUT_RESULT_HANDLED; + case APZHandledResult::HandledByContent: + return INPUT_RESULT_HANDLED_CONTENT; + case APZHandledResult::Invalid: + MOZ_ASSERT_UNREACHABLE("The handled result should NOT be Invalid"); + return INPUT_RESULT_UNHANDLED; + } + MOZ_ASSERT_UNREACHABLE("Unknown handled result"); + return INPUT_RESULT_UNHANDLED; + } + + public: + int32_t HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState, + float aX, float aY, int buttons) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + RefPtr controller; + + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + controller = gkWindow->mAPZC; + } + } + + if (!controller) { + return INPUT_RESULT_UNHANDLED; + } + + MouseInput::MouseType mouseType = MouseInput::MOUSE_NONE; + MouseInput::ButtonType buttonType = MouseInput::NONE; + switch (aAction) { + case java::sdk::MotionEvent::ACTION_DOWN: + mouseType = MouseInput::MOUSE_DOWN; + buttonType = GetButtonType(buttons ^ mPreviousButtons); + mPreviousButtons = buttons; + break; + case java::sdk::MotionEvent::ACTION_UP: + mouseType = MouseInput::MOUSE_UP; + buttonType = GetButtonType(buttons ^ mPreviousButtons); + mPreviousButtons = buttons; + break; + case java::sdk::MotionEvent::ACTION_MOVE: + mouseType = MouseInput::MOUSE_MOVE; + break; + case java::sdk::MotionEvent::ACTION_HOVER_MOVE: + mouseType = MouseInput::MOUSE_MOVE; + break; + case java::sdk::MotionEvent::ACTION_HOVER_ENTER: + mouseType = MouseInput::MOUSE_WIDGET_ENTER; + break; + case java::sdk::MotionEvent::ACTION_HOVER_EXIT: + mouseType = MouseInput::MOUSE_WIDGET_EXIT; + break; + default: + break; + } + + if (mouseType == MouseInput::MOUSE_NONE) { + return INPUT_RESULT_UNHANDLED; + } + + ScreenPoint origin = ScreenPoint(aX, aY); + + MouseInput input( + mouseType, buttonType, MouseEvent_Binding::MOZ_SOURCE_MOUSE, + ConvertButtons(buttons), origin, aTime, + nsWindow::GetEventTimeStamp(aTime), nsWindow::GetModifiers(aMetaState)); + + APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input); + if (result.mStatus == nsEventStatus_eConsumeNoDefault) { + return INPUT_RESULT_IGNORED; + } + + PostInputEvent([input, result](nsWindow* window) { + WidgetMouseEvent mouseEvent = input.ToWidgetEvent(window); + window->ProcessUntransformedAPZEvent(&mouseEvent, result); + }); + + switch (result.mStatus) { + case nsEventStatus_eIgnore: + return INPUT_RESULT_UNHANDLED; + case nsEventStatus_eConsumeDoDefault: + return (result.mHandledResult == Some(APZHandledResult::HandledByRoot)) + ? INPUT_RESULT_HANDLED + : INPUT_RESULT_HANDLED_CONTENT; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus"); + return INPUT_RESULT_UNHANDLED; + } + } + + // Convert MotionEvent touch radius and orientation into the format required + // by w3c touchevents. + // toolMajor and toolMinor span a rectangle that's oriented as per + // aOrientation, centered around the touch point. + static std::pair ConvertOrientationAndRadius( + float aOrientation, float aToolMajor, float aToolMinor) { + float angle = aOrientation * 180.0f / M_PI; + // w3c touchevents spec does not allow orientations == 90 + // this shifts it to -90, which will be shifted to zero below + if (angle >= 90.0) { + angle -= 180.0f; + } + + // w3c touchevent radii are given with an orientation between 0 and + // 90. The radii are found by removing the orientation and + // measuring the x and y radii of the resulting ellipse. For + // Android orientations >= 0 and < 90, use the y radius as the + // major radius, and x as the minor radius. However, for an + // orientation < 0, we have to shift the orientation by adding 90, + // and reverse which radius is major and minor. + ScreenSize radius; + if (angle < 0.0f) { + angle += 90.0f; + radius = + ScreenSize(int32_t(aToolMajor / 2.0f), int32_t(aToolMinor / 2.0f)); + } else { + radius = + ScreenSize(int32_t(aToolMinor / 2.0f), int32_t(aToolMajor / 2.0f)); + } + + return std::make_pair(angle, radius); + } + + void HandleMotionEvent( + const java::PanZoomController::NativeProvider::LocalRef& aInstance, + jni::Object::Param aEventData, float aScreenX, float aScreenY, + jni::Object::Param aResult) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + auto returnResult = java::GeckoResult::Ref::From(aResult); + auto eventData = + java::PanZoomController::MotionEventData::Ref::From(aEventData); + nsTArray pointerId(eventData->PointerId()->GetElements()); + size_t pointerCount = pointerId.Length(); + MultiTouchInput::MultiTouchType type; + size_t startIndex = 0; + size_t endIndex = pointerCount; + + switch (eventData->Action()) { + case java::sdk::MotionEvent::ACTION_DOWN: + case java::sdk::MotionEvent::ACTION_POINTER_DOWN: + type = MultiTouchInput::MULTITOUCH_START; + break; + case java::sdk::MotionEvent::ACTION_MOVE: + type = MultiTouchInput::MULTITOUCH_MOVE; + break; + case java::sdk::MotionEvent::ACTION_UP: + case java::sdk::MotionEvent::ACTION_POINTER_UP: + // for pointer-up events we only want the data from + // the one pointer that went up + type = MultiTouchInput::MULTITOUCH_END; + startIndex = eventData->ActionIndex(); + endIndex = startIndex + 1; + break; + case java::sdk::MotionEvent::ACTION_OUTSIDE: + case java::sdk::MotionEvent::ACTION_CANCEL: + type = MultiTouchInput::MULTITOUCH_CANCEL; + break; + default: + if (returnResult) { + returnResult->Complete( + java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED)); + } + return; + } + + MultiTouchInput input(type, eventData->Time(), + nsWindow::GetEventTimeStamp(eventData->Time()), 0); + input.modifiers = nsWindow::GetModifiers(eventData->MetaState()); + input.mTouches.SetCapacity(endIndex - startIndex); + input.mScreenOffset = + ExternalIntPoint(int32_t(floorf(aScreenX)), int32_t(floorf(aScreenY))); + + size_t historySize = eventData->HistorySize(); + nsTArray historicalTime( + eventData->HistoricalTime()->GetElements()); + MOZ_RELEASE_ASSERT(historicalTime.Length() == historySize); + + // Each of these is |historySize| sets of |pointerCount| values. + size_t historicalDataCount = historySize * pointerCount; + nsTArray historicalX(eventData->HistoricalX()->GetElements()); + nsTArray historicalY(eventData->HistoricalY()->GetElements()); + nsTArray historicalOrientation( + eventData->HistoricalOrientation()->GetElements()); + nsTArray historicalPressure( + eventData->HistoricalPressure()->GetElements()); + nsTArray historicalToolMajor( + eventData->HistoricalToolMajor()->GetElements()); + nsTArray historicalToolMinor( + eventData->HistoricalToolMinor()->GetElements()); + + MOZ_RELEASE_ASSERT(historicalX.Length() == historicalDataCount); + MOZ_RELEASE_ASSERT(historicalY.Length() == historicalDataCount); + MOZ_RELEASE_ASSERT(historicalOrientation.Length() == historicalDataCount); + MOZ_RELEASE_ASSERT(historicalPressure.Length() == historicalDataCount); + MOZ_RELEASE_ASSERT(historicalToolMajor.Length() == historicalDataCount); + MOZ_RELEASE_ASSERT(historicalToolMinor.Length() == historicalDataCount); + + // Each of these is |pointerCount| values. + nsTArray x(eventData->X()->GetElements()); + nsTArray y(eventData->Y()->GetElements()); + nsTArray orientation(eventData->Orientation()->GetElements()); + nsTArray pressure(eventData->Pressure()->GetElements()); + nsTArray toolMajor(eventData->ToolMajor()->GetElements()); + nsTArray toolMinor(eventData->ToolMinor()->GetElements()); + + MOZ_ASSERT(x.Length() == pointerCount); + MOZ_ASSERT(y.Length() == pointerCount); + MOZ_ASSERT(orientation.Length() == pointerCount); + MOZ_ASSERT(pressure.Length() == pointerCount); + MOZ_ASSERT(toolMajor.Length() == pointerCount); + MOZ_ASSERT(toolMinor.Length() == pointerCount); + + for (size_t i = startIndex; i < endIndex; i++) { + float orien; + ScreenSize radius; + std::tie(orien, radius) = ConvertOrientationAndRadius( + orientation[i], toolMajor[i], toolMinor[i]); + + ScreenIntPoint point(int32_t(floorf(x[i])), int32_t(floorf(y[i]))); + SingleTouchData singleTouchData(pointerId[i], point, radius, orien, + pressure[i]); + + for (size_t historyIndex = 0; historyIndex < historySize; + historyIndex++) { + size_t historicalI = historyIndex * pointerCount + i; + float historicalAngle; + ScreenSize historicalRadius; + std::tie(historicalAngle, historicalRadius) = + ConvertOrientationAndRadius(historicalOrientation[historicalI], + historicalToolMajor[historicalI], + historicalToolMinor[historicalI]); + ScreenIntPoint historicalPoint( + int32_t(floorf(historicalX[historicalI])), + int32_t(floorf(historicalY[historicalI]))); + singleTouchData.mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + nsWindow::GetEventTimeStamp(historicalTime[historyIndex]), + historicalPoint, + {}, // mLocalScreenPoint will be computed later by APZ + historicalRadius, + historicalAngle, + historicalPressure[historicalI]}); + } + + input.mTouches.AppendElement(singleTouchData); + } + + if (mAndroidVsync && + eventData->Action() == java::sdk::MotionEvent::ACTION_DOWN) { + // Query pref value at the beginning of a touch gesture so that we don't + // leave events stuck in the resampler after a pref flip. + mTouchResamplingEnabled = StaticPrefs::android_touch_resampling_enabled(); + } + + if (!mTouchResamplingEnabled) { + FinishHandlingMotionEvent(std::move(input), + java::GeckoResult::LocalRef(returnResult)); + return; + } + + uint64_t eventId = mTouchResampler.ProcessEvent(std::move(input)); + mPendingMotionEventReturnResults.push( + {eventId, java::GeckoResult::GlobalRef(returnResult)}); + + RegisterOrUnregisterForVsync(mTouchResampler.InTouchingState()); + ConsumeMotionEventsFromResampler(); + } + + void RegisterOrUnregisterForVsync(bool aNeedVsync) { + MOZ_RELEASE_ASSERT(mAndroidVsync); + if (aNeedVsync && !mListeningToVsync) { + mAndroidVsync->RegisterObserver(this, AndroidVsync::INPUT); + } else if (!aNeedVsync && mListeningToVsync) { + mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT); + } + mListeningToVsync = aNeedVsync; + } + + void OnVsync(const TimeStamp& aTimeStamp) override { + mTouchResampler.NotifyFrame(aTimeStamp - TimeDuration::FromMilliseconds( + kTouchResampleVsyncAdjustMs)); + ConsumeMotionEventsFromResampler(); + } + + void ConsumeMotionEventsFromResampler() { + auto outgoing = mTouchResampler.ConsumeOutgoingEvents(); + while (!outgoing.empty()) { + auto outgoingEvent = std::move(outgoing.front()); + outgoing.pop(); + java::GeckoResult::GlobalRef returnResult; + if (outgoingEvent.mEventId) { + // Look up the GeckoResult for this event. + // The outgoing events from the resampler are in the same order as the + // original events, and no event IDs are skipped. + MOZ_RELEASE_ASSERT(!mPendingMotionEventReturnResults.empty()); + auto pair = mPendingMotionEventReturnResults.front(); + mPendingMotionEventReturnResults.pop(); + MOZ_RELEASE_ASSERT(pair.first == *outgoingEvent.mEventId); + returnResult = pair.second; + } + FinishHandlingMotionEvent(std::move(outgoingEvent.mEvent), + java::GeckoResult::LocalRef(returnResult)); + } + } + + void FinishHandlingMotionEvent(MultiTouchInput&& aInput, + java::GeckoResult::LocalRef&& aReturnResult) { + RefPtr controller; + + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + controller = gkWindow->mAPZC; + } + } + + if (!controller) { + if (aReturnResult) { + aReturnResult->Complete( + java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED)); + } + return; + } + + APZEventResult result = + controller->InputBridge()->ReceiveInputEvent(aInput); + if (result.mStatus == nsEventStatus_eConsumeNoDefault) { + if (aReturnResult) { + aReturnResult->Complete( + java::sdk::Integer::ValueOf(INPUT_RESULT_IGNORED)); + } + return; + } + + // Dispatch APZ input event on Gecko thread. + PostInputEvent([aInput, result](nsWindow* window) { + WidgetTouchEvent touchEvent = aInput.ToWidgetTouchEvent(window); + window->ProcessUntransformedAPZEvent(&touchEvent, result); + window->DispatchHitTest(touchEvent); + }); + + if (!aReturnResult) { + // We don't care how APZ handled the event so we're done here. + return; + } + + if (result.mHandledResult != Nothing()) { + // We know conclusively that the root APZ handled this or not and + // don't need to do any more work. + switch (result.mStatus) { + case nsEventStatus_eIgnore: + aReturnResult->Complete( + java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED)); + break; + case nsEventStatus_eConsumeDoDefault: + aReturnResult->Complete(java::sdk::Integer::ValueOf( + ConvertAPZHandledResult(result.mHandledResult.value()))); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus"); + aReturnResult->Complete( + java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED)); + break; + } + return; + } + + // Wait to see if APZ handled the event or not... + controller->AddInputBlockCallback( + result.mInputBlockId, + [aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)]( + uint64_t aInputBlockId, APZHandledResult aHandledResult) { + aReturnResult->Complete(java::sdk::Integer::ValueOf( + ConvertAPZHandledResult(aHandledResult))); + }); + } +}; + +NS_IMPL_ISUPPORTS(AndroidView, nsIAndroidEventDispatcher, nsIAndroidView) + +nsresult AndroidView::GetInitData(JSContext* aCx, JS::MutableHandleValue aOut) { + if (!mInitData) { + aOut.setNull(); + return NS_OK; + } + + return widget::EventDispatcher::UnboxBundle(aCx, mInitData, aOut); +} + +/** + * Compositor has some unique requirements for its native calls, so make it + * separate from GeckoViewSupport. + */ +class LayerViewSupport final + : public GeckoSession::Compositor::Natives { + WindowPtr mWindow; + GeckoSession::Compositor::WeakRef mCompositor; + Atomic mCompositorPaused; + jni::Object::GlobalRef mSurface; + + struct CaptureRequest { + explicit CaptureRequest() : mResult(nullptr) {} + explicit CaptureRequest(java::GeckoResult::GlobalRef aResult, + java::sdk::Bitmap::GlobalRef aBitmap, + const ScreenRect& aSource, + const IntSize& aOutputSize) + : mResult(aResult), + mBitmap(aBitmap), + mSource(aSource), + mOutputSize(aOutputSize) {} + + // where to send the pixels + java::GeckoResult::GlobalRef mResult; + + // where to store the pixels + java::sdk::Bitmap::GlobalRef mBitmap; + + ScreenRect mSource; + + IntSize mOutputSize; + }; + std::queue mCapturePixelsResults; + + // In order to use Event::HasSameTypeAs in PostTo(), we cannot make + // LayerViewEvent a template because each template instantiation is + // a different type. So implement LayerViewEvent as a ProxyEvent. + class LayerViewEvent final : public nsAppShell::ProxyEvent { + using Event = nsAppShell::Event; + + public: + static UniquePtr MakeEvent(UniquePtr&& event) { + return MakeUnique(std::move(event)); + } + + explicit LayerViewEvent(UniquePtr&& event) + : nsAppShell::ProxyEvent(std::move(event)) {} + + void PostTo(LinkedList& queue) override { + // Give priority to compositor events, but keep in order with + // existing compositor events. + nsAppShell::Event* event = queue.getFirst(); + while (event && event->HasSameTypeAs(this)) { + event = event->getNext(); + } + if (event) { + event->setPrevious(this); + } else { + queue.insertBack(this); + } + } + }; + + public: + typedef GeckoSession::Compositor::Natives Base; + + LayerViewSupport(WindowPtr aWindow, + const GeckoSession::Compositor::LocalRef& aInstance) + : mWindow(aWindow), mCompositor(aInstance), mCompositorPaused(true) { +#if defined(DEBUG) + auto win(mWindow.Access()); + MOZ_ASSERT(!!win); +#endif // defined(DEBUG) + } + + ~LayerViewSupport() {} + + using Base::AttachNative; + using Base::DisposeNative; + + void OnWeakNonIntrusiveDetach(already_AddRefed aDisposer) { + RefPtr disposer = aDisposer; + if (RefPtr uiThread = GetAndroidUiThread()) { + GeckoSession::Compositor::GlobalRef compositor(mCompositor); + if (!compositor) { + return; + } + + uiThread->Dispatch(NS_NewRunnableFunction( + "LayerViewSupport::OnWeakNonIntrusiveDetach", + [compositor, disposer = std::move(disposer), + results = &mCapturePixelsResults, window = mWindow]() mutable { + if (auto accWindow = window.Access()) { + while (!results->empty()) { + auto aResult = + java::GeckoResult::LocalRef(results->front().mResult); + if (aResult) { + aResult->CompleteExceptionally( + java::sdk::IllegalStateException::New( + "The compositor has detached from the session") + .Cast()); + } + results->pop(); + } + compositor->OnCompositorDetached(); + } + + disposer->Run(); + })); + } + } + + const GeckoSession::Compositor::Ref& GetJavaCompositor() const { + return mCompositor; + } + + bool CompositorPaused() const { return mCompositorPaused; } + + jni::Object::Param GetSurface() { return mSurface; } + + private: + already_AddRefed + GetUiCompositorControllerChild() { + RefPtr child; + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + child = gkWindow->GetUiCompositorControllerChild(); + } + } + return child.forget(); + } + + already_AddRefed FlipScreenPixels( + Shmem& aMem, const ScreenIntSize& aInSize, const ScreenRect& aInRegion, + const IntSize& aOutSize) { + RefPtr image = + gfx::Factory::CreateWrappingDataSourceSurface( + aMem.get(), + StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aInSize.width), + IntSize(aInSize.width, aInSize.height), SurfaceFormat::B8G8R8A8); + RefPtr drawTarget = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + aOutSize, SurfaceFormat::B8G8R8A8); + if (!drawTarget) { + return nullptr; + } + + drawTarget->SetTransform(Matrix::Scaling(1.0, -1.0) * + Matrix::Translation(0, aOutSize.height)); + + gfx::Rect srcRect(aInRegion.x, + (aInSize.height - aInRegion.height) - aInRegion.y, + aInRegion.width, aInRegion.height); + gfx::Rect destRect(0, 0, aOutSize.width, aOutSize.height); + drawTarget->DrawSurface(image, destRect, srcRect); + + RefPtr snapshot = drawTarget->Snapshot(); + RefPtr data = snapshot->GetDataSurface(); + return data.forget(); + } + + /** + * Compositor methods + */ + public: + void AttachNPZC(jni::Object::Param aNPZC) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aNPZC); + + auto locked(mWindow.Access()); + if (!locked) { + return; // Already shut down. + } + + nsWindow* gkWindow = locked->GetNsWindow(); + + // We can have this situation if we get two GeckoViewSupport::Transfer() + // called before the first AttachNPZC() gets here. Just detach the current + // instance since that's what happens in GeckoViewSupport::Transfer() as + // well. + gkWindow->mNPZCSupport.Detach(); + + auto npzc = java::PanZoomController::NativeProvider::LocalRef( + jni::GetGeckoThreadEnv(), + java::PanZoomController::NativeProvider::Ref::From(aNPZC)); + gkWindow->mNPZCSupport = + jni::NativeWeakPtrHolder::Attach(npzc, mWindow, npzc); + + DispatchToUiThread( + "LayerViewSupport::AttachNPZC", + [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc)] { + npzc->SetAttached(true); + }); + } + + void OnBoundsChanged(int32_t aLeft, int32_t aTop, int32_t aWidth, + int32_t aHeight) { + MOZ_ASSERT(NS_IsMainThread()); + auto acc = mWindow.Access(); + if (!acc) { + return; // Already shut down. + } + + nsWindow* gkWindow = acc->GetNsWindow(); + if (!gkWindow) { + return; + } + + gkWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false); + } + + void SetDynamicToolbarMaxHeight(int32_t aHeight) { + MOZ_ASSERT(NS_IsMainThread()); + auto acc = mWindow.Access(); + if (!acc) { + return; // Already shut down. + } + + nsWindow* gkWindow = acc->GetNsWindow(); + if (!gkWindow) { + return; + } + + gkWindow->UpdateDynamicToolbarMaxHeight(ScreenIntCoord(aHeight)); + } + + void SyncPauseCompositor() { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + if (RefPtr child = + GetUiCompositorControllerChild()) { + mCompositorPaused = true; + child->Pause(); + } + + if (auto lock{mWindow.Access()}) { + while (!mCapturePixelsResults.empty()) { + auto result = + java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult); + if (result) { + result->CompleteExceptionally( + java::sdk::IllegalStateException::New( + "The compositor has detached from the session") + .Cast()); + } + mCapturePixelsResults.pop(); + } + } + } + + void SyncResumeCompositor() { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + if (RefPtr child = + GetUiCompositorControllerChild()) { + mCompositorPaused = false; + child->Resume(); + } + } + + void SyncResumeResizeCompositor( + const GeckoSession::Compositor::LocalRef& aObj, int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight, jni::Object::Param aSurface) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + mSurface = aSurface; + + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->ResumeAndResize(aX, aY, aWidth, aHeight); + } + + mCompositorPaused = false; + + class OnResumedEvent : public nsAppShell::Event { + GeckoSession::Compositor::GlobalRef mCompositor; + + public: + explicit OnResumedEvent(GeckoSession::Compositor::GlobalRef&& aCompositor) + : mCompositor(std::move(aCompositor)) {} + + void Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + JNIEnv* const env = jni::GetGeckoThreadEnv(); + const auto lvsHolder = + GetNative(GeckoSession::Compositor::LocalRef(env, mCompositor)); + + if (!lvsHolder) { + env->ExceptionClear(); + return; // Already shut down. + } + + auto lvs(lvsHolder->Access()); + if (!lvs) { + env->ExceptionClear(); + return; // Already shut down. + } + + auto win = lvs->mWindow.Access(); + if (!win) { + env->ExceptionClear(); + return; // Already shut down. + } + + // When we get here, the compositor has already been told to + // resume. This means it's now safe for layer updates to occur. + // Since we might have prevented one or more draw events from + // occurring while the compositor was paused, we need to + // schedule a draw event now. + if (!lvs->mCompositorPaused) { + nsWindow* const gkWindow = win->GetNsWindow(); + if (gkWindow) { + gkWindow->RedrawAll(); + } + } + } + }; + + // Use priority queue for timing-sensitive event. + nsAppShell::PostEvent( + MakeUnique(MakeUnique(aObj))); + } + + void SyncInvalidateAndScheduleComposite() { + RefPtr child = + GetUiCompositorControllerChild(); + if (!child) { + return; + } + + if (AndroidBridge::IsJavaUiThread()) { + child->InvalidateAndRender(); + return; + } + + if (RefPtr uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NewRunnableMethod<>( + "LayerViewSupport::InvalidateAndRender", child, + &UiCompositorControllerChild::InvalidateAndRender), + nsIThread::DISPATCH_NORMAL); + } + } + + void SetMaxToolbarHeight(int32_t aHeight) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->SetMaxToolbarHeight(aHeight); + } + } + + void SetFixedBottomOffset(int32_t aOffset) { + if (auto acc{mWindow.Access()}) { + nsWindow* gkWindow = acc->GetNsWindow(); + if (gkWindow) { + gkWindow->UpdateDynamicToolbarOffset(ScreenIntCoord(aOffset)); + } + } + + if (RefPtr uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NS_NewRunnableFunction( + "LayerViewSupport::SetFixedBottomOffset", [this, offset = aOffset] { + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->SetFixedBottomOffset(offset); + } + })); + } + } + + void SendToolbarAnimatorMessage(int32_t aMessage) { + RefPtr child = + GetUiCompositorControllerChild(); + if (!child) { + return; + } + + if (AndroidBridge::IsJavaUiThread()) { + child->ToolbarAnimatorMessageFromUI(aMessage); + return; + } + + if (RefPtr uiThread = GetAndroidUiThread()) { + uiThread->Dispatch( + NewRunnableMethod( + "LayerViewSupport::ToolbarAnimatorMessageFromUI", child, + &UiCompositorControllerChild::ToolbarAnimatorMessageFromUI, + aMessage), + nsIThread::DISPATCH_NORMAL); + } + } + + void RecvToolbarAnimatorMessage(int32_t aMessage) { + auto compositor = GeckoSession::Compositor::LocalRef(mCompositor); + if (compositor) { + compositor->RecvToolbarAnimatorMessage(aMessage); + } + } + + void SetDefaultClearColor(int32_t aColor) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->SetDefaultClearColor((uint32_t)aColor); + } + } + + void RequestScreenPixels(jni::Object::Param aResult, + jni::Object::Param aTarget, int32_t aXOffset, + int32_t aYOffset, int32_t aSrcWidth, + int32_t aSrcHeight, int32_t aOutWidth, + int32_t aOutHeight) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + int size = 0; + if (auto window = mWindow.Access()) { + mCapturePixelsResults.push(CaptureRequest( + java::GeckoResult::GlobalRef(java::GeckoResult::LocalRef(aResult)), + java::sdk::Bitmap::GlobalRef(java::sdk::Bitmap::LocalRef(aTarget)), + ScreenRect(aXOffset, aYOffset, aSrcWidth, aSrcHeight), + IntSize(aOutWidth, aOutHeight))); + size = mCapturePixelsResults.size(); + } + + if (size == 1) { + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->RequestScreenPixels(); + } + } + } + + void RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize, + bool aNeedsYFlip) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + CaptureRequest request; + java::GeckoResult::LocalRef result = nullptr; + java::sdk::Bitmap::LocalRef bitmap = nullptr; + if (auto window = mWindow.Access()) { + // The result might have been already rejected if the compositor was + // detached from the session + if (!mCapturePixelsResults.empty()) { + request = mCapturePixelsResults.front(); + result = java::GeckoResult::LocalRef(request.mResult); + bitmap = java::sdk::Bitmap::LocalRef(request.mBitmap); + mCapturePixelsResults.pop(); + } + } + + if (result) { + if (bitmap) { + RefPtr surf; + if (aNeedsYFlip) { + surf = FlipScreenPixels(aMem, aSize, request.mSource, + request.mOutputSize); + } else { + surf = gfx::Factory::CreateWrappingDataSourceSurface( + aMem.get(), + StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aSize.width), + IntSize(aSize.width, aSize.height), SurfaceFormat::B8G8R8A8); + } + if (surf) { + DataSourceSurface::ScopedMap smap(surf, DataSourceSurface::READ); + auto pixels = mozilla::jni::ByteBuffer::New( + reinterpret_cast(smap.GetData()), + smap.GetStride() * request.mOutputSize.height); + bitmap->CopyPixelsFromBuffer(pixels); + result->Complete(bitmap); + } else { + result->CompleteExceptionally( + java::sdk::IllegalStateException::New( + "Failed to create flipped snapshot surface (probably out of " + "memory)") + .Cast()); + } + } else { + result->CompleteExceptionally(java::sdk::IllegalArgumentException::New( + "No target bitmap argument provided") + .Cast()); + } + } + + // Pixels have been copied, so Dealloc Shmem + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->DeallocPixelBuffer(aMem); + + if (auto window = mWindow.Access()) { + if (!mCapturePixelsResults.empty()) { + child->RequestScreenPixels(); + } + } + } + } + + void EnableLayerUpdateNotifications(bool aEnable) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (RefPtr child = + GetUiCompositorControllerChild()) { + child->EnableLayerUpdateNotifications(aEnable); + } + } + + void OnSafeAreaInsetsChanged(int32_t aTop, int32_t aRight, int32_t aBottom, + int32_t aLeft) { + MOZ_ASSERT(NS_IsMainThread()); + auto win(mWindow.Access()); + if (!win) { + return; // Already shut down. + } + + nsWindow* gkWindow = win->GetNsWindow(); + if (!gkWindow) { + return; + } + + ScreenIntMargin safeAreaInsets(aTop, aRight, aBottom, aLeft); + gkWindow->UpdateSafeAreaInsets(safeAreaInsets); + } +}; + +GeckoViewSupport::~GeckoViewSupport() { + if (mWindow) { + mWindow->DetachNatives(); + } +} + +/* static */ +void GeckoViewSupport::Open( + const jni::Class::LocalRef& aCls, GeckoSession::Window::Param aWindow, + jni::Object::Param aQueue, jni::Object::Param aCompositor, + jni::Object::Param aDispatcher, jni::Object::Param aSessionAccessibility, + jni::Object::Param aInitData, jni::String::Param aId, + jni::String::Param aChromeURI, int32_t aScreenId, bool aPrivateMode) { + MOZ_ASSERT(NS_IsMainThread()); + + AUTO_PROFILER_LABEL("mozilla::widget::GeckoViewSupport::Open", OTHER); + + nsCOMPtr ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID); + MOZ_RELEASE_ASSERT(ww); + + nsAutoCString url; + if (aChromeURI) { + url = aChromeURI->ToCString(); + } else { + nsresult rv = Preferences::GetCString("toolkit.defaultChromeURI", url); + if (NS_FAILED(rv)) { + url = "chrome://geckoview/content/geckoview.xhtml"_ns; + } + } + + // Prepare an nsIAndroidView to pass as argument to the window. + RefPtr androidView = new AndroidView(); + androidView->mEventDispatcher->Attach( + java::EventDispatcher::Ref::From(aDispatcher), nullptr); + androidView->mInitData = java::GeckoBundle::Ref::From(aInitData); + + nsAutoCString chromeFlags("chrome,dialog=0,remote,resizable,scrollbars"); + if (aPrivateMode) { + chromeFlags += ",private"; + } + nsCOMPtr domWindow; + ww->OpenWindow(nullptr, url, nsDependentCString(aId->ToCString().get()), + chromeFlags, androidView, getter_AddRefs(domWindow)); + MOZ_RELEASE_ASSERT(domWindow); + + nsCOMPtr pdomWindow = nsPIDOMWindowOuter::From(domWindow); + const RefPtr window = nsWindow::From(pdomWindow); + MOZ_ASSERT(window); + window->SetScreenId(aScreenId); + + // Attach a new GeckoView support object to the new window. + GeckoSession::Window::LocalRef sessionWindow(aCls.Env(), aWindow); + auto weakGeckoViewSupport = + jni::NativeWeakPtrHolder::Attach( + sessionWindow, window, sessionWindow, pdomWindow); + + window->mGeckoViewSupport = weakGeckoViewSupport; + window->mAndroidView = androidView; + + // Attach other session support objects. + { // Scope for gvsAccess + auto gvsAccess = weakGeckoViewSupport.Access(); + MOZ_ASSERT(gvsAccess); + + gvsAccess->Transfer(sessionWindow, aQueue, aCompositor, aDispatcher, + aSessionAccessibility, aInitData); + } + + if (window->mWidgetListener) { + nsCOMPtr appWindow(window->mWidgetListener->GetAppWindow()); + if (appWindow) { + // Our window is not intrinsically sized, so tell AppWindow to + // not set a size for us. + appWindow->SetIntrinsicallySized(false); + } + } +} + +void GeckoViewSupport::Close() { + if (mWindow) { + if (mWindow->mAndroidView) { + mWindow->mAndroidView->mEventDispatcher->Detach(); + } + mWindow = nullptr; + } + + if (!mDOMWindow) { + return; + } + + mDOMWindow->ForceClose(); + mDOMWindow = nullptr; + mGeckoViewWindow = nullptr; +} + +void GeckoViewSupport::Transfer(const GeckoSession::Window::LocalRef& inst, + jni::Object::Param aQueue, + jni::Object::Param aCompositor, + jni::Object::Param aDispatcher, + jni::Object::Param aSessionAccessibility, + jni::Object::Param aInitData) { + mWindow->mNPZCSupport.Detach(); + + auto compositor = GeckoSession::Compositor::LocalRef( + inst.Env(), GeckoSession::Compositor::Ref::From(aCompositor)); + + bool attachLvs; + { // Scope for lvsAccess + auto lvsAccess{mWindow->mLayerViewSupport.Access()}; + // If we do not yet have mLayerViewSupport, or if the compositor has + // changed, then we must attach a new one. + attachLvs = !lvsAccess || lvsAccess->GetJavaCompositor() != compositor; + } + + if (attachLvs) { + mWindow->mLayerViewSupport = + jni::NativeWeakPtrHolder::Attach( + compositor, mWindow->mGeckoViewSupport, compositor); + } + + MOZ_ASSERT(mWindow->mAndroidView); + mWindow->mAndroidView->mEventDispatcher->Attach( + java::EventDispatcher::Ref::From(aDispatcher), mDOMWindow); + + mWindow->mSessionAccessibility.Detach(); + if (aSessionAccessibility) { + AttachAccessibility(inst, aSessionAccessibility); + } + + if (mIsReady) { + // We're in a transfer; update init-data and notify JS code. + mWindow->mAndroidView->mInitData = java::GeckoBundle::Ref::From(aInitData); + OnReady(aQueue); + mWindow->mAndroidView->mEventDispatcher->Dispatch( + u"GeckoView:UpdateInitData"); + } + + DispatchToUiThread("GeckoViewSupport::Transfer", + [compositor = GeckoSession::Compositor::GlobalRef( + compositor)] { compositor->OnCompositorAttached(); }); +} + +void GeckoViewSupport::AttachEditable( + const GeckoSession::Window::LocalRef& inst, + jni::Object::Param aEditableParent) { + if (auto win{mWindow->mEditableSupport.Access()}) { + win->TransferParent(aEditableParent); + } else { + auto editableChild = java::GeckoEditableChild::New(aEditableParent, + /* default */ true); + mWindow->mEditableSupport = + jni::NativeWeakPtrHolder::Attach( + editableChild, mWindow->mGeckoViewSupport, editableChild); + } + + mWindow->mEditableParent = aEditableParent; +} + +void GeckoViewSupport::AttachAccessibility( + const GeckoSession::Window::LocalRef& inst, + jni::Object::Param aSessionAccessibility) { + java::SessionAccessibility::NativeProvider::LocalRef sessionAccessibility( + inst.Env()); + sessionAccessibility = java::SessionAccessibility::NativeProvider::Ref::From( + aSessionAccessibility); + + mWindow->mSessionAccessibility = + jni::NativeWeakPtrHolder::Attach( + sessionAccessibility, mWindow->mGeckoViewSupport, + sessionAccessibility); +} + +auto GeckoViewSupport::OnLoadRequest(mozilla::jni::String::Param aUri, + int32_t aWindowType, int32_t aFlags, + mozilla::jni::String::Param aTriggeringUri, + bool aHasUserGesture, + bool aIsTopLevel) const + -> java::GeckoResult::LocalRef { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return nullptr; + } + return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri, + aHasUserGesture, aIsTopLevel); +} + +void GeckoViewSupport::OnReady(jni::Object::Param aQueue) { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return; + } + window->OnReady(aQueue); + mIsReady = true; +} + +void GeckoViewSupport::PassExternalResponse( + java::WebResponse::Param aResponse) { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return; + } + + auto response = java::WebResponse::GlobalRef(aResponse); + + DispatchToUiThread("GeckoViewSupport::PassExternalResponse", + [window = java::GeckoSession::Window::GlobalRef(window), + response] { window->PassExternalWebResponse(response); }); +} + +} // namespace widget +} // namespace mozilla + +void nsWindow::InitNatives() { + jni::InitConversionStatics(); + mozilla::widget::GeckoViewSupport::Base::Init(); + mozilla::widget::LayerViewSupport::Init(); + mozilla::widget::NPZCSupport::Init(); + + mozilla::widget::GeckoEditableSupport::Init(); + a11y::SessionAccessibility::Init(); +} + +void nsWindow::DetachNatives() { + MOZ_ASSERT(NS_IsMainThread()); + mEditableSupport.Detach(); + mNPZCSupport.Detach(); + mLayerViewSupport.Detach(); + mSessionAccessibility.Detach(); +} + +/* static */ +already_AddRefed nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) { + nsCOMPtr widget = WidgetUtils::DOMWindowToWidget(aDOMWindow); + return From(widget); +} + +/* static */ +already_AddRefed nsWindow::From(nsIWidget* aWidget) { + // `widget` may be one of several different types in the parent + // process, including the Android nsWindow, PuppetWidget, etc. To + // ensure that the cast to the Android nsWindow is valid, we check that the + // widget is a top-level window and that its NS_NATIVE_WIDGET value is + // non-null, which is not the case for non-native widgets like + // PuppetWidget. + if (aWidget && aWidget->WindowType() == nsWindowType::eWindowType_toplevel && + aWidget->GetNativeData(NS_NATIVE_WIDGET) == aWidget) { + RefPtr window = static_cast(aWidget); + return window.forget(); + } + return nullptr; +} + +nsWindow* nsWindow::TopWindow() { + if (!gTopLevelWindows.IsEmpty()) return gTopLevelWindows[0]; + return nullptr; +} + +void nsWindow::LogWindow(nsWindow* win, int index, int indent) { +#if defined(DEBUG) || defined(FORCE_ALOG) + char spaces[] = " "; + spaces[indent < 20 ? indent : 20] = 0; + ALOG("%s [% 2d] 0x%p [parent 0x%p] [% 3d,% 3dx% 3d,% 3d] vis %d type %d", + spaces, index, win, win->mParent, win->mBounds.x, win->mBounds.y, + win->mBounds.width, win->mBounds.height, win->mIsVisible, + win->mWindowType); +#endif +} + +void nsWindow::DumpWindows() { DumpWindows(gTopLevelWindows); } + +void nsWindow::DumpWindows(const nsTArray& wins, int indent) { + for (uint32_t i = 0; i < wins.Length(); ++i) { + nsWindow* w = wins[i]; + LogWindow(w, i, indent); + DumpWindows(w->mChildren, indent + 1); + } +} + +nsWindow::nsWindow() + : mScreenId(0), // Use 0 (primary screen) as the default value. + mIsVisible(false), + mParent(nullptr), + mDynamicToolbarMaxHeight(0), + mIsFullScreen(false), + mIsDisablingWebRender(false) {} + +nsWindow::~nsWindow() { + gTopLevelWindows.RemoveElement(this); + ALOG("nsWindow %p destructor", (void*)this); + // The mCompositorSession should have been cleaned up in nsWindow::Destroy() + // DestroyLayerManager() will call DestroyCompositor() which will crash if + // called from nsBaseWidget destructor. See Bug 1392705 + MOZ_ASSERT(!mCompositorSession); +} + +bool nsWindow::IsTopLevel() { + return mWindowType == eWindowType_toplevel || + mWindowType == eWindowType_dialog || + mWindowType == eWindowType_invisible; +} + +nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) { + ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent, + aRect.x, aRect.y, aRect.width, aRect.height); + + nsWindow* parent = (nsWindow*)aParent; + if (aNativeParent) { + if (parent) { + ALOG( + "Ignoring native parent on Android window [%p], " + "since parent was specified (%p %p)", + (void*)this, (void*)aNativeParent, (void*)aParent); + } else { + parent = (nsWindow*)aNativeParent; + } + } + + // A default size of 1x1 confuses MobileViewportManager, so + // use 0x0 instead. This is also a little more fitting since + // we don't yet have a surface yet (and therefore a valid size) + // and 0x0 is usually recognized as invalid. + LayoutDeviceIntRect rect = aRect; + if (aRect.width == 1 && aRect.height == 1) { + rect.width = 0; + rect.height = 0; + } + + mBounds = rect; + + BaseCreate(nullptr, aInitData); + + NS_ASSERTION(IsTopLevel() || parent, + "non-top-level window doesn't have a parent!"); + + if (IsTopLevel()) { + gTopLevelWindows.AppendElement(this); + + } else if (parent) { + parent->mChildren.AppendElement(this); + mParent = parent; + } + + CreateLayerManager(); + +#ifdef DEBUG_ANDROID_WIDGET + DumpWindows(); +#endif + + return NS_OK; +} + +void nsWindow::Destroy() { + nsBaseWidget::mOnDestroyCalled = true; + + // Disassociate our native object from GeckoView. + mGeckoViewSupport.Detach(); + + // Stuff below may release the last ref to this + nsCOMPtr kungFuDeathGrip(this); + + while (mChildren.Length()) { + // why do we still have children? + ALOG("### Warning: Destroying window %p and reparenting child %p to null!", + (void*)this, (void*)mChildren[0]); + mChildren[0]->SetParent(nullptr); + } + + // Ensure the compositor has been shutdown before this nsWindow is potentially + // deleted + nsBaseWidget::DestroyCompositor(); + + nsBaseWidget::Destroy(); + + if (IsTopLevel()) gTopLevelWindows.RemoveElement(this); + + SetParent(nullptr); + + nsBaseWidget::OnDestroy(); + +#ifdef DEBUG_ANDROID_WIDGET + DumpWindows(); +#endif +} + +nsresult nsWindow::ConfigureChildren( + const nsTArray& config) { + for (uint32_t i = 0; i < config.Length(); ++i) { + nsWindow* childWin = (nsWindow*)config[i].mChild.get(); + childWin->Resize(config[i].mBounds.x, config[i].mBounds.y, + config[i].mBounds.width, config[i].mBounds.height, false); + } + + return NS_OK; +} + +mozilla::widget::EventDispatcher* nsWindow::GetEventDispatcher() const { + if (mAndroidView) { + return mAndroidView->mEventDispatcher; + } + return nullptr; +} + +void nsWindow::RedrawAll() { + if (mAttachedWidgetListener) { + mAttachedWidgetListener->RequestRepaint(); + } else if (mWidgetListener) { + mWidgetListener->RequestRepaint(); + } +} + +RefPtr nsWindow::GetUiCompositorControllerChild() { + return mCompositorSession + ? mCompositorSession->GetUiCompositorControllerChild() + : nullptr; +} + +mozilla::layers::LayersId nsWindow::GetRootLayerId() const { + return mCompositorSession ? mCompositorSession->RootLayerTreeId() + : mozilla::layers::LayersId{0}; +} + +void nsWindow::OnGeckoViewReady() { + auto acc(mGeckoViewSupport.Access()); + if (!acc) { + return; + } + + acc->OnReady(); +} + +void nsWindow::SetParent(nsIWidget* aNewParent) { + if ((nsIWidget*)mParent == aNewParent) return; + + // If we had a parent before, remove ourselves from its list of + // children. + if (mParent) mParent->mChildren.RemoveElement(this); + + mParent = (nsWindow*)aNewParent; + + if (mParent) mParent->mChildren.AppendElement(this); + + // if we are now in the toplevel window's hierarchy, schedule a redraw + if (FindTopLevel() == nsWindow::TopWindow()) RedrawAll(); +} + +nsIWidget* nsWindow::GetParent() { return mParent; } + +RefPtr> nsWindow::OnLoadRequest( + nsIURI* aUri, int32_t aWindowType, int32_t aFlags, + nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, + bool aIsTopLevel) { + auto geckoViewSupport(mGeckoViewSupport.Access()); + if (!geckoViewSupport) { + return MozPromise::CreateAndResolve(false, __func__); + } + nsAutoCString spec, triggeringSpec; + if (aUri) { + aUri->GetDisplaySpec(spec); + } + + bool isNullPrincipal = false; + if (aTriggeringPrincipal) { + aTriggeringPrincipal->GetIsNullPrincipal(&isNullPrincipal); + + if (!isNullPrincipal) { + nsCOMPtr triggeringUri; + BasePrincipal::Cast(aTriggeringPrincipal) + ->GetURI(getter_AddRefs(triggeringUri)); + if (triggeringUri) { + triggeringUri->GetDisplaySpec(triggeringSpec); + } + } + } + + auto geckoResult = geckoViewSupport->OnLoadRequest( + spec.get(), aWindowType, aFlags, + isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture, + aIsTopLevel); + return geckoResult + ? MozPromise::FromGeckoResult(geckoResult) + : nullptr; +} + +float nsWindow::GetDPI() { + float dpi = 160.0f; + + nsCOMPtr screen = GetWidgetScreen(); + if (screen) { + screen->GetDpi(&dpi); + } + + return dpi; +} + +double nsWindow::GetDefaultScaleInternal() { + double scale = 1.0f; + + nsCOMPtr screen = GetWidgetScreen(); + if (screen) { + screen->GetContentsScaleFactor(&scale); + } + + return scale; +} + +void nsWindow::Show(bool aState) { + ALOG("nsWindow[%p]::Show %d", (void*)this, aState); + + if (mWindowType == eWindowType_invisible) { + ALOG("trying to show invisible window! ignoring.."); + return; + } + + if (aState == mIsVisible) return; + + mIsVisible = aState; + + if (IsTopLevel()) { + // XXX should we bring this to the front when it's shown, + // if it's a toplevel widget? + + // XXX we should synthesize a eMouseExitFromWidget (for old top + // window)/eMouseEnterIntoWidget (for new top window) since we need + // to pretend that the top window always has focus. Not sure + // if Show() is the right place to do this, though. + + if (aState) { + // It just became visible, so bring it to the front. + BringToFront(); + + } else if (nsWindow::TopWindow() == this) { + // find the next visible window to show + unsigned int i; + for (i = 1; i < gTopLevelWindows.Length(); i++) { + nsWindow* win = gTopLevelWindows[i]; + if (!win->mIsVisible) continue; + + win->BringToFront(); + break; + } + } + } else if (FindTopLevel() == nsWindow::TopWindow()) { + RedrawAll(); + } + +#ifdef DEBUG_ANDROID_WIDGET + DumpWindows(); +#endif +} + +bool nsWindow::IsVisible() const { return mIsVisible; } + +void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) { + ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop, + *aX, *aY); + + // constrain toplevel windows; children we don't care about + if (IsTopLevel()) { + *aX = 0; + *aY = 0; + } +} + +void nsWindow::Move(double aX, double aY) { + if (IsTopLevel()) return; + + Resize(aX, aY, mBounds.width, mBounds.height, true); +} + +void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) { + Resize(mBounds.x, mBounds.y, aWidth, aHeight, aRepaint); +} + +void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) { + ALOG("nsWindow[%p]::Resize [%f %f %f %f] (repaint %d)", (void*)this, aX, aY, + aWidth, aHeight, aRepaint); + + bool needPositionDispatch = aX != mBounds.x || aY != mBounds.y; + bool needSizeDispatch = aWidth != mBounds.width || aHeight != mBounds.height; + + mBounds.x = NSToIntRound(aX); + mBounds.y = NSToIntRound(aY); + mBounds.width = NSToIntRound(aWidth); + mBounds.height = NSToIntRound(aHeight); + + if (needSizeDispatch) { + OnSizeChanged(gfx::IntSize::Truncate(aWidth, aHeight)); + } + + if (needPositionDispatch) { + NotifyWindowMoved(mBounds.x, mBounds.y); + } + + // Should we skip honoring aRepaint here? + if (aRepaint && FindTopLevel() == nsWindow::TopWindow()) RedrawAll(); +} + +void nsWindow::SetZIndex(int32_t aZIndex) { + ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex); +} + +void nsWindow::SetSizeMode(nsSizeMode aMode) { + if (aMode == mSizeMode) { + return; + } + + nsBaseWidget::SetSizeMode(aMode); + + switch (aMode) { + case nsSizeMode_Minimized: + java::GeckoAppShell::MoveTaskToBack(); + break; + case nsSizeMode_Fullscreen: + MakeFullScreen(true); + break; + default: + break; + } +} + +void nsWindow::Enable(bool aState) { + ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState); +} + +bool nsWindow::IsEnabled() const { return true; } + +void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {} + +nsWindow* nsWindow::FindTopLevel() { + nsWindow* toplevel = this; + while (toplevel) { + if (toplevel->IsTopLevel()) return toplevel; + + toplevel = toplevel->mParent; + } + + ALOG( + "nsWindow::FindTopLevel(): couldn't find a toplevel or dialog window in " + "this [%p] widget's hierarchy!", + (void*)this); + return this; +} + +void nsWindow::SetFocus(Raise, mozilla::dom::CallerType aCallerType) { + FindTopLevel()->BringToFront(); +} + +void nsWindow::BringToFront() { + MOZ_ASSERT(XRE_IsParentProcess()); + // If the window to be raised is the same as the currently raised one, + // do nothing. We need to check the focus manager as well, as the first + // window that is created will be first in the window list but won't yet + // be focused. + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && fm->GetActiveWindow() && FindTopLevel() == nsWindow::TopWindow()) { + return; + } + + if (!IsTopLevel()) { + FindTopLevel()->BringToFront(); + return; + } + + RefPtr kungFuDeathGrip(this); + + nsWindow* oldTop = nullptr; + if (!gTopLevelWindows.IsEmpty()) { + oldTop = gTopLevelWindows[0]; + } + + gTopLevelWindows.RemoveElement(this); + gTopLevelWindows.InsertElementAt(0, this); + + if (oldTop) { + nsIWidgetListener* listener = oldTop->GetWidgetListener(); + if (listener) { + listener->WindowDeactivated(); + } + } + + if (mWidgetListener) { + mWidgetListener->WindowActivated(); + } + + RedrawAll(); +} + +LayoutDeviceIntRect nsWindow::GetScreenBounds() { + return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size()); +} + +LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() { + LayoutDeviceIntPoint p(0, 0); + + for (nsWindow* w = this; !!w; w = w->mParent) { + p.x += w->mBounds.x; + p.y += w->mBounds.y; + + if (w->IsTopLevel()) { + break; + } + } + return p; +} + +nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent, + nsEventStatus& aStatus) { + aStatus = DispatchEvent(aEvent); + return NS_OK; +} + +nsEventStatus nsWindow::DispatchEvent(WidgetGUIEvent* aEvent) { + if (mAttachedWidgetListener) { + return mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents); + } else if (mWidgetListener) { + return mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents); + } + return nsEventStatus_eIgnore; +} + +nsresult nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*) { + if (!mAndroidView) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsIWidgetListener* listener = GetWidgetListener(); + if (listener) { + listener->FullscreenWillChange(aFullScreen); + } + + mIsFullScreen = aFullScreen; + mAndroidView->mEventDispatcher->Dispatch( + aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit"); + + if (listener) { + mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal; + listener->SizeModeChanged(mSizeMode); + listener->FullscreenChanged(mIsFullScreen); + } + return NS_OK; +} + +mozilla::layers::LayerManager* nsWindow::GetLayerManager( + PLayerTransactionChild*, LayersBackend, LayerManagerPersistence) { + if (mLayerManager) { + return mLayerManager; + } + + if (mIsDisablingWebRender) { + CreateLayerManager(); + mIsDisablingWebRender = false; + return mLayerManager; + } + + return nullptr; +} + +void nsWindow::CreateLayerManager() { + if (mLayerManager) { + return; + } + + nsWindow* topLevelWindow = FindTopLevel(); + if (!topLevelWindow || topLevelWindow->mWindowType == eWindowType_invisible) { + // don't create a layer manager for an invisible top-level window + return; + } + + // Ensure that gfxPlatform is initialized first. + gfxPlatform::GetPlatform(); + + if (ShouldUseOffMainThreadCompositing()) { + LayoutDeviceIntRect rect = GetBounds(); + CreateCompositor(rect.Width(), rect.Height()); + if (mLayerManager) { + return; + } + + // If we get here, then off main thread compositing failed to initialize. + sFailedToCreateGLContext = true; + } + + if (!ComputeShouldAccelerate() || sFailedToCreateGLContext) { + printf_stderr(" -- creating basic, not accelerated\n"); + mLayerManager = CreateBasicLayerManager(); + } +} + +void nsWindow::NotifyDisablingWebRender() { + mIsDisablingWebRender = true; + RedrawAll(); +} + +void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) { + ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width, + aSize.height); + + mBounds.width = aSize.width; + mBounds.height = aSize.height; + + if (mWidgetListener) { + mWidgetListener->WindowResized(this, aSize.width, aSize.height); + } + + if (mAttachedWidgetListener) { + mAttachedWidgetListener->WindowResized(this, aSize.width, aSize.height); + } +} + +void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) { + if (aPoint) { + event.mRefPoint = *aPoint; + } else { + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + } + + event.mTime = PR_Now() / 1000; +} + +void nsWindow::UpdateOverscrollVelocity(const float aX, const float aY) { + if (::mozilla::jni::NativeWeakPtr::Accessor lvs{ + mLayerViewSupport.Access()}) { + const auto& compositor = lvs->GetJavaCompositor(); + if (AndroidBridge::IsJavaUiThread()) { + compositor->UpdateOverscrollVelocity(aX, aY); + return; + } + + DispatchToUiThread( + "nsWindow::UpdateOverscrollVelocity", + [compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] { + compositor->UpdateOverscrollVelocity(aX, aY); + }); + } +} + +void nsWindow::UpdateOverscrollOffset(const float aX, const float aY) { + if (::mozilla::jni::NativeWeakPtr::Accessor lvs{ + mLayerViewSupport.Access()}) { + const auto& compositor = lvs->GetJavaCompositor(); + if (AndroidBridge::IsJavaUiThread()) { + compositor->UpdateOverscrollOffset(aX, aY); + return; + } + + DispatchToUiThread( + "nsWindow::UpdateOverscrollOffset", + [compositor = GeckoSession::Compositor::GlobalRef(compositor), aX, aY] { + compositor->UpdateOverscrollOffset(aX, aY); + }); + } +} + +void* nsWindow::GetNativeData(uint32_t aDataType) { + switch (aDataType) { + // used by GLContextProviderEGL, nullptr is EGL_DEFAULT_DISPLAY + case NS_NATIVE_DISPLAY: + return nullptr; + + case NS_NATIVE_WIDGET: + return (void*)this; + + case NS_RAW_NATIVE_IME_CONTEXT: { + void* pseudoIMEContext = GetPseudoIMEContext(); + if (pseudoIMEContext) { + return pseudoIMEContext; + } + // We assume that there is only one context per process on Android + return NS_ONLY_ONE_NATIVE_IME_CONTEXT; + } + + case NS_JAVA_SURFACE: + if (::mozilla::jni::NativeWeakPtr::Accessor lvs{ + mLayerViewSupport.Access()}) { + return lvs->GetSurface().Get(); + } + return nullptr; + } + + return nullptr; +} + +void nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) { + switch (aDataType) {} +} + +void nsWindow::DispatchHitTest(const WidgetTouchEvent& aEvent) { + if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() == 1) { + // Since touch events don't get retargeted by PositionedEventTargeting.cpp + // code, we dispatch a dummy mouse event that *does* get retargeted. + // Front-end code can use this to activate the highlight element in case + // this touchstart is the start of a tap. + WidgetMouseEvent hittest(true, eMouseHitTest, this, + WidgetMouseEvent::eReal); + hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint; + hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH; + nsEventStatus status; + DispatchEvent(&hittest, status); + } +} + +void nsWindow::PassExternalResponse(java::WebResponse::Param aResponse) { + auto acc(mGeckoViewSupport.Access()); + if (!acc) { + return; + } + + acc->PassExternalResponse(aResponse); +} + +mozilla::Modifiers nsWindow::GetModifiers(int32_t metaState) { + using mozilla::java::sdk::KeyEvent; + return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0) | + (metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0) | + (metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0) | + (metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0) | + (metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0) | + (metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0) | + (metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0) | + (metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0); +} + +TimeStamp nsWindow::GetEventTimeStamp(int64_t aEventTime) { + // Android's event time is SystemClock.uptimeMillis that is counted in ms + // since OS was booted. + // (https://developer.android.com/reference/android/os/SystemClock.html) + // and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC. + // Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC + // too. Due to same implementation, we can use this via FromSystemTime. + int64_t tick = + BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime); + return TimeStamp::FromSystemTime(tick); +} + +void nsWindow::UserActivity() { + if (!mIdleService) { + mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1"); + } + + if (mIdleService) { + mIdleService->ResetIdleTimeOut(0); + } + + if (FindTopLevel() != nsWindow::TopWindow()) { + BringToFront(); + } +} + +RefPtr +nsWindow::GetSessionAccessibility() { + auto acc(mSessionAccessibility.Access()); + if (!acc) { + return nullptr; + } + + return acc.AsRefPtr(); +} + +TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() { + nsWindow* top = FindTopLevel(); + MOZ_ASSERT(top); + + auto acc(top->mEditableSupport.Access()); + if (!acc) { + // Non-GeckoView windows don't support IME operations. + return nullptr; + } + + nsCOMPtr ptr; + if (NS_FAILED(acc->QueryInterface(NS_GET_IID(TextEventDispatcherListener), + getter_AddRefs(ptr)))) { + return nullptr; + } + + return ptr.get(); +} + +void nsWindow::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) { + nsWindow* top = FindTopLevel(); + MOZ_ASSERT(top); + + auto acc(top->mEditableSupport.Access()); + if (!acc) { + // Non-GeckoView windows don't support IME operations. + return; + } + + // We are using an IME event later to notify Java, and the IME event + // will be processed by the top window. Therefore, to ensure the + // IME event uses the correct mInputContext, we need to let the top + // window process SetInputContext + acc->SetInputContext(aContext, aAction); +} + +InputContext nsWindow::GetInputContext() { + nsWindow* top = FindTopLevel(); + MOZ_ASSERT(top); + + auto acc(top->mEditableSupport.Access()); + if (!acc) { + // Non-GeckoView windows don't support IME operations. + return InputContext(); + } + + // We let the top window process SetInputContext, + // so we should let it process GetInputContext as well. + return acc->GetInputContext(); +} + +nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) { + mozilla::widget::AutoObserverNotifier notifier(aObserver, "touchpoint"); + + int eventType; + switch (aPointerState) { + case TOUCH_CONTACT: + // This could be a ACTION_DOWN or ACTION_MOVE depending on the + // existing state; it is mapped to the right thing in Java. + eventType = java::sdk::MotionEvent::ACTION_POINTER_DOWN; + break; + case TOUCH_REMOVE: + // This could be turned into a ACTION_UP in Java + eventType = java::sdk::MotionEvent::ACTION_POINTER_UP; + break; + case TOUCH_CANCEL: + eventType = java::sdk::MotionEvent::ACTION_CANCEL; + break; + case TOUCH_HOVER: // not supported for now + default: + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(mNPZCSupport.IsAttached()); + auto npzcSup(mNPZCSupport.Access()); + MOZ_ASSERT(!!npzcSup); + + const auto& npzc = npzcSup->GetJavaNPZC(); + const auto& bounds = FindTopLevel()->mBounds; + aPoint.x -= bounds.x; + aPoint.y -= bounds.y; + + DispatchToUiThread( + "nsWindow::SynthesizeNativeTouchPoint", + [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc), + aPointerId, eventType, aPoint, aPointerPressure, aPointerOrientation] { + npzc->SynthesizeNativeTouchPoint(aPointerId, eventType, aPoint.x, + aPoint.y, aPointerPressure, + aPointerOrientation); + }); + return NS_OK; +} + +nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) { + mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent"); + + MOZ_ASSERT(mNPZCSupport.IsAttached()); + auto npzcSup(mNPZCSupport.Access()); + MOZ_ASSERT(!!npzcSup); + + const auto& npzc = npzcSup->GetJavaNPZC(); + const auto& bounds = FindTopLevel()->mBounds; + aPoint.x -= bounds.x; + aPoint.y -= bounds.y; + + DispatchToUiThread( + "nsWindow::SynthesizeNativeMouseEvent", + [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc), + aNativeMessage, aPoint] { + npzc->SynthesizeNativeMouseEvent(aNativeMessage, aPoint.x, aPoint.y); + }); + return NS_OK; +} + +nsresult nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + nsIObserver* aObserver) { + mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent"); + + MOZ_ASSERT(mNPZCSupport.IsAttached()); + auto npzcSup(mNPZCSupport.Access()); + MOZ_ASSERT(!!npzcSup); + + const auto& npzc = npzcSup->GetJavaNPZC(); + const auto& bounds = FindTopLevel()->mBounds; + aPoint.x -= bounds.x; + aPoint.y -= bounds.y; + + DispatchToUiThread( + "nsWindow::SynthesizeNativeMouseMove", + [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc), + aPoint] { + npzc->SynthesizeNativeMouseEvent( + java::sdk::MotionEvent::ACTION_HOVER_MOVE, aPoint.x, aPoint.y); + }); + return NS_OK; +} + +bool nsWindow::WidgetPaintsBackground() { + return StaticPrefs::android_widget_paints_background(); +} + +bool nsWindow::NeedsPaint() { + auto lvs(mLayerViewSupport.Access()); + if (!lvs || lvs->CompositorPaused() || !GetLayerManager(nullptr)) { + return false; + } + + return nsIWidget::NeedsPaint(); +} + +void nsWindow::ConfigureAPZControllerThread() { + nsCOMPtr thread = mozilla::GetAndroidUiThread(); + APZThreadUtils::SetControllerThread(thread); +} + +already_AddRefed +nsWindow::CreateRootContentController() { + RefPtr controller = + new AndroidContentController(this, mAPZEventState, mAPZC); + return controller.forget(); +} + +uint32_t nsWindow::GetMaxTouchPoints() const { + return java::GeckoAppShell::GetMaxTouchPoints(); +} + +void nsWindow::UpdateZoomConstraints( + const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId, + const mozilla::Maybe& aConstraints) { + nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints); +} + +CompositorBridgeChild* nsWindow::GetCompositorBridgeChild() const { + return mCompositorSession ? mCompositorSession->GetCompositorBridgeChild() + : nullptr; +} + +already_AddRefed nsWindow::GetWidgetScreen() { + RefPtr screen = + ScreenHelperAndroid::GetSingleton()->ScreenForId(mScreenId); + return screen.forget(); +} + +void nsWindow::SetContentDocumentDisplayed(bool aDisplayed) { + mContentDocumentDisplayed = aDisplayed; +} + +bool nsWindow::IsContentDocumentDisplayed() { + return mContentDocumentDisplayed; +} + +void nsWindow::RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (::mozilla::jni::NativeWeakPtr::Accessor lvs{ + mLayerViewSupport.Access()}) { + lvs->RecvToolbarAnimatorMessage(aMessage); + } +} + +void nsWindow::UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset, + const CSSToScreenScale& aZoom) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (::mozilla::jni::NativeWeakPtr::Accessor lvs{ + mLayerViewSupport.Access()}) { + const auto& compositor = lvs->GetJavaCompositor(); + mContentDocumentDisplayed = true; + compositor->UpdateRootFrameMetrics(aScrollOffset.x, aScrollOffset.y, + aZoom.scale); + } +} + +void nsWindow::RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize, + bool aNeedsYFlip) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (::mozilla::jni::NativeWeakPtr::Accessor lvs{ + mLayerViewSupport.Access()}) { + lvs->RecvScreenPixels(std::move(aMem), aSize, aNeedsYFlip); + } +} + +void nsWindow::UpdateDynamicToolbarMaxHeight(ScreenIntCoord aHeight) { + if (mDynamicToolbarMaxHeight == aHeight) { + return; + } + + mDynamicToolbarMaxHeight = aHeight; + + if (mWidgetListener) { + mWidgetListener->DynamicToolbarMaxHeightChanged(aHeight); + } + + if (mAttachedWidgetListener) { + mAttachedWidgetListener->DynamicToolbarMaxHeightChanged(aHeight); + } +} + +void nsWindow::UpdateDynamicToolbarOffset(ScreenIntCoord aOffset) { + if (mWidgetListener) { + mWidgetListener->DynamicToolbarOffsetChanged(aOffset); + } + + if (mAttachedWidgetListener) { + mAttachedWidgetListener->DynamicToolbarOffsetChanged(aOffset); + } +} + +ScreenIntMargin nsWindow::GetSafeAreaInsets() const { return mSafeAreaInsets; } + +void nsWindow::UpdateSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) { + mSafeAreaInsets = aSafeAreaInsets; + + if (mWidgetListener) { + mWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets); + } + + if (mAttachedWidgetListener) { + mAttachedWidgetListener->SafeAreaInsetsChanged(aSafeAreaInsets); + } +} + +already_AddRefed nsIWidget::CreateTopLevelWindow() { + nsCOMPtr window = new nsWindow(); + return window.forget(); +} + +already_AddRefed nsIWidget::CreateChildWindow() { + nsCOMPtr window = new nsWindow(); + return window.forget(); +} diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h new file mode 100644 index 0000000000..76b3a7b32d --- /dev/null +++ b/widget/android/nsWindow.h @@ -0,0 +1,279 @@ +/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- + * vim: set sw=2 ts=4 expandtab: + * 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 NSWINDOW_H_ +#define NSWINDOW_H_ + +#include "nsBaseWidget.h" +#include "gfxPoint.h" +#include "nsIUserIdleServiceInternal.h" +#include "nsTArray.h" +#include "EventDispatcher.h" +#include "mozilla/EventForwards.h" +#include "mozilla/java/GeckoSessionNatives.h" +#include "mozilla/java/WebResponseWrappers.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextRange.h" +#include "mozilla/UniquePtr.h" + +struct ANPEvent; + +namespace mozilla { +class WidgetTouchEvent; + +namespace layers { +class CompositorBridgeChild; +class LayerManager; +class APZCTreeManager; +class UiCompositorControllerChild; +} // namespace layers + +namespace widget { +class AndroidView; +class GeckoEditableSupport; +class GeckoViewSupport; +class LayerViewSupport; +class NPZCSupport; +} // namespace widget + +namespace ipc { +class Shmem; +} // namespace ipc + +namespace a11y { +class SessionAccessibility; +} // namespace a11y +} // namespace mozilla + +class nsWindow final : public nsBaseWidget { + private: + virtual ~nsWindow(); + + public: + using nsBaseWidget::GetLayerManager; + + nsWindow(); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget) + + static void InitNatives(); + void SetScreenId(uint32_t aScreenId) { mScreenId = aScreenId; } + void OnGeckoViewReady(); + RefPtr> OnLoadRequest( + nsIURI* aUri, int32_t aWindowType, int32_t aFlags, + nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, + bool aIsTopLevel); + + private: + uint32_t mScreenId; + + private: + RefPtr mAndroidView; + + // Object that implements native LayerView calls. + // Owned by the Java Compositor instance. + mozilla::jni::NativeWeakPtr + mLayerViewSupport; + + // Object that implements native NativePanZoomController calls. + // Owned by the Java NativePanZoomController instance. + mozilla::jni::NativeWeakPtr mNPZCSupport; + + // Object that implements native GeckoEditable calls. + // Strong referenced by the Java instance. + mozilla::jni::NativeWeakPtr + mEditableSupport; + mozilla::jni::Object::GlobalRef mEditableParent; + + // Object that implements native SessionAccessibility calls. + // Strong referenced by the Java instance. + mozilla::jni::NativeWeakPtr + mSessionAccessibility; + + // Object that implements native GeckoView calls and associated states. + // nullptr for nsWindows that were not opened from GeckoView. + mozilla::jni::NativeWeakPtr + mGeckoViewSupport; + + mozilla::Atomic mContentDocumentDisplayed; + + public: + static already_AddRefed From(nsPIDOMWindowOuter* aDOMWindow); + static already_AddRefed From(nsIWidget* aWidget); + + static nsWindow* TopWindow(); + + static mozilla::Modifiers GetModifiers(int32_t aMetaState); + static mozilla::TimeStamp GetEventTimeStamp(int64_t aEventTime); + + void OnSizeChanged(const mozilla::gfx::IntSize& aSize); + + void InitEvent(mozilla::WidgetGUIEvent& event, + LayoutDeviceIntPoint* aPoint = 0); + + void UpdateOverscrollVelocity(const float aX, const float aY); + void UpdateOverscrollOffset(const float aX, const float aY); + + mozilla::widget::EventDispatcher* GetEventDispatcher() const; + + void PassExternalResponse(mozilla::java::WebResponse::Param aResponse); + + void NotifyDisablingWebRender(); + + void DetachNatives(); + + // + // nsIWidget + // + + using nsBaseWidget::Create; // for Create signature not overridden here + [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) override; + virtual void Destroy() override; + virtual nsresult ConfigureChildren( + const nsTArray&) override; + virtual void SetParent(nsIWidget* aNewParent) override; + virtual nsIWidget* GetParent(void) override; + virtual float GetDPI() override; + virtual double GetDefaultScaleInternal() override; + virtual void Show(bool aState) override; + virtual bool IsVisible() const override; + virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX, + int32_t* aY) override; + virtual void Move(double aX, double aY) override; + virtual void Resize(double aWidth, double aHeight, bool aRepaint) override; + virtual void Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) override; + void SetZIndex(int32_t aZIndex) override; + virtual void SetSizeMode(nsSizeMode aMode) override; + virtual void Enable(bool aState) override; + virtual bool IsEnabled() const override; + virtual void Invalidate(const LayoutDeviceIntRect& aRect) override; + virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override; + virtual LayoutDeviceIntRect GetScreenBounds() override; + virtual LayoutDeviceIntPoint WidgetToScreenOffset() override; + virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent, + nsEventStatus& aStatus) override; + nsEventStatus DispatchEvent(mozilla::WidgetGUIEvent* aEvent); + virtual already_AddRefed GetWidgetScreen() override; + virtual nsresult MakeFullScreen(bool aFullScreen, + nsIScreen* aTargetScreen = nullptr) override; + void SetCursor(nsCursor aDefaultCursor, imgIContainer* aImageCursor, + uint32_t aHotspotX, uint32_t aHotspotY) override {} + void* GetNativeData(uint32_t aDataType) override; + void SetNativeData(uint32_t aDataType, uintptr_t aVal) override; + virtual nsresult SetTitle(const nsAString& aTitle) override { return NS_OK; } + [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override; + virtual void SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) override; + virtual InputContext GetInputContext() override; + + LayerManager* GetLayerManager( + PLayerTransactionChild* aShadowManager = nullptr, + LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, + LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override; + + virtual bool NeedsPaint() override; + + virtual bool WidgetPaintsBackground() override; + + virtual uint32_t GetMaxTouchPoints() const override; + + void UpdateZoomConstraints( + const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId, + const mozilla::Maybe& aConstraints) override; + + nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) override; + nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + uint32_t aModifierFlags, + nsIObserver* aObserver) override; + nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + nsIObserver* aObserver) override; + + mozilla::layers::CompositorBridgeChild* GetCompositorBridgeChild() const; + + void SetContentDocumentDisplayed(bool aDisplayed); + bool IsContentDocumentDisplayed(); + + // Call this function when the users activity is the direct cause of an + // event (like a keypress or mouse click). + void UserActivity(); + + mozilla::jni::Object::Ref& GetEditableParent() { return mEditableParent; } + + RefPtr GetSessionAccessibility(); + + void RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) override; + void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset, + const CSSToScreenScale& aZoom) override; + void RecvScreenPixels(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize, + bool aNeedsYFlip) override; + void UpdateDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight) override; + mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const override { + return mDynamicToolbarMaxHeight; + } + + void UpdateDynamicToolbarOffset(mozilla::ScreenIntCoord aOffset); + + virtual mozilla::ScreenIntMargin GetSafeAreaInsets() const override; + void UpdateSafeAreaInsets(const mozilla::ScreenIntMargin& aSafeAreaInsets); + + protected: + void BringToFront(); + nsWindow* FindTopLevel(); + bool IsTopLevel(); + + void ConfigureAPZControllerThread() override; + void DispatchHitTest(const mozilla::WidgetTouchEvent& aEvent); + + already_AddRefed CreateRootContentController() + override; + + bool mIsVisible; + nsTArray mChildren; + nsWindow* mParent; + + nsCOMPtr mIdleService; + mozilla::ScreenIntCoord mDynamicToolbarMaxHeight; + mozilla::ScreenIntMargin mSafeAreaInsets; + + bool mIsFullScreen; + bool mIsDisablingWebRender; + + bool UseExternalCompositingSurface() const override { return true; } + + static void DumpWindows(); + static void DumpWindows(const nsTArray& wins, int indent = 0); + static void LogWindow(nsWindow* win, int index, int indent); + + private: + void CreateLayerManager(); + void RedrawAll(); + + mozilla::layers::LayersId GetRootLayerId() const; + RefPtr + GetUiCompositorControllerChild(); + + friend class mozilla::widget::GeckoViewSupport; + friend class mozilla::widget::LayerViewSupport; + friend class mozilla::widget::NPZCSupport; +}; + +#endif /* NSWINDOW_H_ */ -- cgit v1.2.3