diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /widget/android | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/android')
94 files changed, 17872 insertions, 0 deletions
diff --git a/widget/android/AndroidAlerts.cpp b/widget/android/AndroidAlerts.cpp new file mode 100644 index 0000000000..456dc08290 --- /dev/null +++ b/widget/android/AndroidAlerts.cpp @@ -0,0 +1,159 @@ +/* -*- 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::ListenerMap> AndroidAlerts::sListenerMap; +nsTHashMap<nsStringHashKey, java::WebNotification::GlobalRef> + 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<nsIURI> 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); + } + + bool silent; + rv = aAlert->GetSilent(&silent); + NS_ENSURE_SUCCESS(rv, NS_OK); + + bool privateBrowsing; + rv = aAlert->GetInPrivateBrowsing(&privateBrowsing); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsTArray<uint32_t> vibrate; + rv = aAlert->GetVibrate(vibrate); + 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->InsertOrUpdate(name, aAlertListener); + } + + java::WebNotification::LocalRef notification = notification->New( + title, name, cookie, text, imageUrl, dir, lang, requireInteraction, spec, + silent, privateBrowsing, jni::IntArray::From(vibrate)); + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + if (runtime != NULL) { + runtime->NotifyOnShow(notification); + } + mNotificationsMap.InsertOrUpdate(name, notification); + + return NS_OK; +} + +NS_IMETHODIMP +AndroidAlerts::CloseAlert(const nsAString& aAlertName, bool aContextClosed) { + 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<nsIObserver> 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..f4a9822dc1 --- /dev/null +++ b/widget/android/AndroidAlerts.h @@ -0,0 +1,46 @@ +/* -*- 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 "nsTHashMap.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 nsTHashMap<nsStringHashKey, mozilla::java::WebNotification::GlobalRef> + mNotificationsMap; + + protected: + virtual ~AndroidAlerts() { sListenerMap = nullptr; } + + using ListenerMap = nsInterfaceHashtable<nsStringHashKey, nsIObserver>; + static StaticAutoPtr<ListenerMap> 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..024b64036d --- /dev/null +++ b/widget/android/AndroidBridge.cpp @@ -0,0 +1,431 @@ +/* -*- 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 <android/log.h> +#include <dlfcn.h> +#include <math.h> +#include <GLES2/gl2.h> + +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" + +#include "mozilla/Hal.h" +#include "nsXULAppAPI.h" +#include <prthread.h> +#include "AndroidBridge.h" +#include "AndroidBridgeUtilities.h" +#include "nsAlertsUtils.h" +#include "nsAppShell.h" +#include "nsOSHelperAppService.h" +#include "nsWindow.h" +#include "mozilla/Preferences.h" +#include "nsThreadUtils.h" +#include "nsPresContext.h" +#include "nsPIDOMWindow.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Mutex.h" +#include "nsPrintfCString.h" +#include "nsContentUtils.h" + +#include "EventDispatcher.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" + +using namespace mozilla; + +AndroidBridge* AndroidBridge::sBridge = nullptr; +static jobject sGlobalContext = nullptr; +nsTHashMap<nsStringHashKey, nsString> 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<char*>("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;"); +} + +void AndroidBridge::Vibrate(const nsTArray<uint32_t>& 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<uint32_t>(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); +} + +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<nsIThread> mMainThread; +}; +StaticRefPtr<TracerRunnable> 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<widget::EventDispatcher> 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<widget::EventDispatcher> dispatcher = new widget::EventDispatcher(); + dispatcher->Attach(java::EventDispatcher::ByName(aName), + /* window */ nullptr); + dispatcher.forget(aResult); + return NS_OK; +} + +nsAndroidBridge::~nsAndroidBridge() {} + +hal::ScreenOrientation AndroidBridge::GetScreenOrientation() { + ALOG_BRIDGE("AndroidBridge::GetScreenOrientation"); + + int16_t orientation = java::GeckoAppShell::GetScreenOrientation(); + + return hal::ScreenOrientation(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); +} diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h new file mode 100644 index 0000000000..f2070ef31a --- /dev/null +++ b/widget/android/AndroidBridge.h @@ -0,0 +1,275 @@ +/* -*- 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 <unistd.h> // for gettid + +#include "nsCOMPtr.h" + +#include "mozilla/jni/Refs.h" + +#include "nsIMutableArray.h" +#include "nsIMIMEInfo.h" + +#include "nsIAndroidBridge.h" + +#include "mozilla/jni/Utils.h" +#include "nsTHashMap.h" + +// Some debug #defines +// #define DEBUG_ANDROID_EVENTS +// #define DEBUG_ANDROID_WIDGET + +namespace mozilla { + +class AutoLocalJNIFrame; + +namespace hal { +class BatteryInformation; +class NetworkInformation; +enum class ScreenOrientation : uint32_t; +} // namespace hal + +class AndroidBridge final { + public: + static bool IsJavaUiThread() { + return mozilla::jni::GetUIThreadId() == gettid(); + } + + static void ConstructBridge(); + static void DeconstructBridge(); + + static AndroidBridge* Bridge() { return sBridge; } + + 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); + + void GetMimeTypeFromExtensions(const nsACString& aFileExt, + nsCString& aMimeType); + void GetExtensionFromMimeType(const nsACString& aMimeType, + nsACString& aFileExt); + + void Vibrate(const nsTArray<uint32_t>& aPattern); + + void GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, + uint8_t* const aBuf); + + // 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); + + hal::ScreenOrientation GetScreenOrientation(); + uint16_t GetScreenAngle(); + + 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 nsTHashMap<nsStringHashKey, nsString> sStoragePaths; + + static AndroidBridge* sBridge; + + AndroidBridge(); + ~AndroidBridge(); + + 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<jclass>(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 <typename ReturnType = jobject> + ReturnType Pop(ReturnType aResult = nullptr) { + MOZ_ASSERT(mHasFrameBeenPushed); + mHasFrameBeenPushed = false; + return static_cast<ReturnType>( + mJNIEnv->PopLocalFrame(static_cast<jobject>(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<nsIAndroidEventDispatcher> 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/AndroidCompositorWidget.cpp b/widget/android/AndroidCompositorWidget.cpp new file mode 100644 index 0000000000..a7eeb665e1 --- /dev/null +++ b/widget/android/AndroidCompositorWidget.cpp @@ -0,0 +1,105 @@ +/* -*- 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 "mozilla/gfx/Logging.h" +#include "mozilla/widget/PlatformWidgetTypes.h" +#include "nsWindow.h" + +namespace mozilla { +namespace widget { + +AndroidCompositorWidget::AndroidCompositorWidget( + const AndroidCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions) + : CompositorWidget(aOptions), + mWidgetId(aInitData.widgetId()), + mNativeWindow(nullptr), + mFormat(WINDOW_FORMAT_RGBA_8888), + mClientSize(aInitData.clientSize()) {} + +AndroidCompositorWidget::~AndroidCompositorWidget() { + if (mNativeWindow) { + ANativeWindow_release(mNativeWindow); + } +} + +already_AddRefed<gfx::DrawTarget> +AndroidCompositorWidget::StartRemoteDrawingInRegion( + const LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode) { + if (!mNativeWindow) { + EGLNativeWindowType window = GetEGLNativeWindow(); + JNIEnv* const env = jni::GetEnvForThread(); + mNativeWindow = + ANativeWindow_fromSurface(env, reinterpret_cast<jobject>(window)); + if (mNativeWindow) { + mFormat = ANativeWindow_getFormat(mNativeWindow); + ANativeWindow_acquire(mNativeWindow); + } else { + return nullptr; + } + } + + if (mFormat != WINDOW_FORMAT_RGBA_8888 && + mFormat != WINDOW_FORMAT_RGBX_8888) { + gfxCriticalNoteOnce << "Non supported format: " << mFormat; + return nullptr; + } + + // XXX Handle inOutDirtyBounds + if (ANativeWindow_lock(mNativeWindow, &mBuffer, nullptr) != 0) { + return nullptr; + } + + const int bpp = 4; + gfx::SurfaceFormat format = gfx::SurfaceFormat::R8G8B8A8; + if (mFormat == WINDOW_FORMAT_RGBX_8888) { + format = gfx::SurfaceFormat::R8G8B8X8; + } + + RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::SKIA, static_cast<unsigned char*>(mBuffer.bits), + gfx::IntSize(mBuffer.width, mBuffer.height), mBuffer.stride * bpp, format, + true); + + return dt.forget(); +} + +void AndroidCompositorWidget::EndRemoteDrawingInRegion( + gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) { + ANativeWindow_unlockAndPost(mNativeWindow); +} + +bool AndroidCompositorWidget::OnResumeComposition() { + OnCompositorSurfaceChanged(); + + if (!mSurface) { + gfxCriticalError() << "OnResumeComposition called with null Surface"; + return false; + } + + return true; +} + +EGLNativeWindowType AndroidCompositorWidget::GetEGLNativeWindow() { + return (EGLNativeWindowType)mSurface.Get(); +} + +LayoutDeviceIntSize AndroidCompositorWidget::GetClientSize() { + return mClientSize; +} + +void AndroidCompositorWidget::NotifyClientSizeChanged( + const LayoutDeviceIntSize& aClientSize) { + mClientSize = + LayoutDeviceIntSize(std::min(aClientSize.width, MOZ_WIDGET_MAX_SIZE), + std::min(aClientSize.height, MOZ_WIDGET_MAX_SIZE)); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/AndroidCompositorWidget.h b/widget/android/AndroidCompositorWidget.h new file mode 100644 index 0000000000..c478477b5f --- /dev/null +++ b/widget/android/AndroidCompositorWidget.h @@ -0,0 +1,70 @@ +/* -*- 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 "CompositorWidget.h" +#include "AndroidNativeWindow.h" +#include "GLDefs.h" + +namespace mozilla { +namespace widget { + +class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate { + public: + virtual void NotifyClientSizeChanged( + const LayoutDeviceIntSize& aClientSize) = 0; + + // CompositorWidgetDelegate Overrides + PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() override { + return this; + } +}; + +class AndroidCompositorWidgetInitData; + +class AndroidCompositorWidget : public CompositorWidget { + public: + AndroidCompositorWidget(const AndroidCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions); + ~AndroidCompositorWidget() override; + + EGLNativeWindowType GetEGLNativeWindow(); + + // CompositorWidget overrides + + already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion( + const LayoutDeviceIntRegion& aInvalidRegion, + layers::BufferMode* aBufferMode) override; + void EndRemoteDrawingInRegion( + gfx::DrawTarget* aDrawTarget, + const LayoutDeviceIntRegion& aInvalidRegion) override; + + bool OnResumeComposition() override; + + AndroidCompositorWidget* AsAndroid() override { return this; } + + LayoutDeviceIntSize GetClientSize() override; + void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize); + + protected: + int32_t mWidgetId; + java::sdk::Surface::GlobalRef mSurface; + ANativeWindow* mNativeWindow; + ANativeWindow_Buffer mBuffer; + int32_t mFormat; + LayoutDeviceIntSize mClientSize; + + private: + // Called whenever the compositor surface may have changed. The derived class + // should update mSurface to the new compositor surface. + virtual void OnCompositorSurfaceChanged() = 0; +}; + +} // 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..c1029fd4a2 --- /dev/null +++ b/widget/android/AndroidContentController.cpp @@ -0,0 +1,70 @@ +/* -*- 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, + Maybe<uint64_t> aInputBlockId) { + // 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, + aInputBlockId); + if (NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> 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..04640deb4e --- /dev/null +++ b/widget/android/AndroidContentController.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 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, + Maybe<uint64_t> aInputBlockId) 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..dc8d56b163 --- /dev/null +++ b/widget/android/AndroidUiThread.cpp @@ -0,0 +1,377 @@ +/* -*- 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/ScopeExit.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" + +#include <android/api-level.h> +#include <pthread.h> + +using namespace mozilla; + +namespace { + +class AndroidUiThread; +class AndroidUiTask; + +StaticAutoPtr<LinkedList<AndroidUiTask> > sTaskQueue; +StaticAutoPtr<mozilla::Mutex> sTaskQueueLock; +StaticRefPtr<AndroidUiThread> sThread; +static bool sThreadDestroyed; +static MessageLoop* sMessageLoop; +static Atomic<Monitor*> sMessageLoopAccessMonitor; + +void EnqueueTask(already_AddRefed<nsIRunnable> 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<ThreadEventQueue*>(MakeUnique<mozilla::EventQueue>()), + nsThread::NOT_MAIN_THREAD, {.stackSize = 0}) {} + + nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) override; + nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aDelayMs) override; + + private: + ~AndroidUiThread() {} +}; + +NS_IMETHODIMP +AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, + uint32_t aFlags) { + EnqueueTask(std::move(aEvent), 0); + return NS_OK; +} + +NS_IMETHODIMP +AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> 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<AndroidUiTask> { + using TimeStamp = mozilla::TimeStamp; + using TimeDuration = mozilla::TimeDuration; + + public: + explicit AndroidUiTask(already_AddRefed<nsIRunnable> aTask) + : mTask(aTask), + mRunTime() // Null timestamp representing no delay. + {} + + AndroidUiTask(already_AddRefed<nsIRunnable> 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<nsIRunnable> TakeTask() { return mTask.forget(); } + + private: + nsCOMPtr<nsIRunnable> 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()); + RegisterThreadWithProfiler(); + sMessageLoop = + new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get()); + lock.NotifyAll(); + return NS_OK; + } + + private: + static void RegisterThreadWithProfiler() { +#if defined(MOZ_GECKO_PROFILER) + // We don't use the PROFILER_REGISTER_THREAD macro here because by this + // point the Android UI thread is already quite a ways into its stack; + // the profiler's sampler thread will ignore a lot of frames if we do not + // provide a better value for the stack top. We'll manually obtain that + // info via pthreads. + + // Fallback address if any pthread calls fail + char fallback; + char* stackTop = &fallback; + + auto regOnExit = MakeScopeExit( + [&stackTop]() { profiler_register_thread("AndroidUI", stackTop); }); + + // Bionic does not properly support pthread_attr_getstack for the UI thread + // until Lollipop (API 21). +# if __ANDROID_API__ >= __ANDROID_API_L__ + pthread_attr_t attrs; + if (pthread_getattr_np(pthread_self(), &attrs)) { + return; + } + + void* stackBase; + size_t stackSize; + if (pthread_attr_getstack(&attrs, &stackBase, &stackSize)) { + return; + } + + stackTop = static_cast<char*>(stackBase) + stackSize - 1; +# endif // __ANDROID_API__ >= __ANDROID_API_L__ +#endif // defined(MOZ_GECKO_PROFILER) + } +}; + +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<nsIRunnable> 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<AndroidUiTask>(); + sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock"); + sMessageLoopAccessMonitor = + new Monitor("AndroidUiThreadMessageLoopAccessMonitor"); + sThreadDestroyed = false; + RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread; + EnqueueTask(do_AddRef(runnable), 0); +} + +void DestroyAndroidUiThread() { + MOZ_ASSERT(sThread); + RefPtr<DestroyOnUiThread> 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<nsThread> 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<nsIRunnable> 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 <mozilla/RefPtr.h> +#include <nsThread.h> + +class MessageLoop; + +namespace mozilla { + +void CreateAndroidUiThread(); +void DestroyAndroidUiThread(); +int64_t RunAndroidUiTasks(); + +MessageLoop* GetAndroidUiThreadMessageLoop(); +RefPtr<nsThread> 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<mozilla::widget::EventDispatcher> 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..6aed5f1e53 --- /dev/null +++ b/widget/android/AndroidVsync.cpp @@ -0,0 +1,150 @@ +/* -*- 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 "AndroidBridge.h" +#include "nsTArray.h" + +/** + * Implementation for the AndroidVsync class. + */ + +namespace mozilla { +namespace widget { + +StaticDataMutex<ThreadSafeWeakPtr<AndroidVsync>> AndroidVsync::sInstance( + "AndroidVsync::sInstance"); + +/* static */ RefPtr<AndroidVsync> AndroidVsync::GetInstance() { + auto weakInstance = sInstance.Lock(); + RefPtr<AndroidVsync> instance(*weakInstance); + if (!instance) { + instance = new AndroidVsync(); + *weakInstance = instance; + } + return instance; +} + +/** + * Owned by the Java AndroidVsync instance. + */ +class AndroidVsyncSupport final + : public java::AndroidVsync::Natives<AndroidVsyncSupport> { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidVsyncSupport) + + using Base = java::AndroidVsync::Natives<AndroidVsyncSupport>; + 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<AndroidVsync*> 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); +} + +AndroidVsync::~AndroidVsync() { + auto impl = mImpl.Lock(); + impl->mInputObservers.Clear(); + impl->mRenderObservers.Clear(); + impl->UpdateObservingVsync(); + impl->mSupport->Unlink(); +} + +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); + } + aObserver->Dispose(); + 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) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + // 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<Observer*> observers; + { + auto impl = mImpl.Lock(); + observers.AppendElements(impl->mInputObservers); + observers.AppendElements(impl->mRenderObservers); + } + for (Observer* observer : observers) { + observer->OnVsync(timeStamp); + } +} + +void AndroidVsync::OnMaybeUpdateRefreshRate() { + MOZ_ASSERT(NS_IsMainThread()); + + auto impl = mImpl.Lock(); + + nsTArray<Observer*> observers; + observers.AppendElements(impl->mRenderObservers); + + for (Observer* observer : observers) { + observer->OnMaybeUpdateRefreshRate(); + } +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/AndroidVsync.h b/widget/android/AndroidVsync.h new file mode 100644 index 0000000000..d50e1ec71f --- /dev/null +++ b/widget/android/AndroidVsync.h @@ -0,0 +1,79 @@ +/* -*- 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<AndroidVsync> { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(AndroidVsync) + + static RefPtr<AndroidVsync> GetInstance(); + + ~AndroidVsync(); + + class Observer { + public: + // Will be called on the Java UI thread. + virtual void OnVsync(const TimeStamp& aTimeStamp) = 0; + // Will be called on the Java UI thread. + virtual void OnMaybeUpdateRefreshRate() {} + // Called when the observer is unregistered, in case it wants to + // manage its own lifetime. + virtual void Dispose() {} + virtual ~Observer() = default; + }; + + // INPUT observers are called before RENDER observers. + enum ObserverType { INPUT, RENDER }; + void RegisterObserver(Observer* aObserver, ObserverType aType); + void UnregisterObserver(Observer* aObserver, ObserverType aType); + + void OnMaybeUpdateRefreshRate(); + + private: + friend class AndroidVsyncSupport; + + AndroidVsync(); + + // Called by Java, via AndroidVsyncSupport + void NotifyVsync(int64_t aFrameTimeNanos); + + struct Impl { + void UpdateObservingVsync(); + + nsTArray<Observer*> mInputObservers; + nsTArray<Observer*> mRenderObservers; + RefPtr<AndroidVsyncSupport> mSupport; + java::AndroidVsync::GlobalRef mSupportJava; + bool mObservingVsync = false; + }; + + DataMutex<Impl> mImpl; + + static StaticDataMutex<ThreadSafeWeakPtr<AndroidVsync>> 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<Base64UtilsSupport> { + public: + static jni::ByteArray::LocalRef Decode(jni::String::Param data) { + if (!data) { + return nullptr; + } + + FallibleTArray<uint8_t> 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<int8_t> 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/CompositorWidgetChild.cpp b/widget/android/CompositorWidgetChild.cpp new file mode 100644 index 0000000000..ba51dda7a5 --- /dev/null +++ b/widget/android/CompositorWidgetChild.cpp @@ -0,0 +1,42 @@ +/* -*- 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 "CompositorWidgetChild.h" +#include "mozilla/Unused.h" +#include "gfxPlatform.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetChild::CompositorWidgetChild( + RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver, + const CompositorWidgetInitData&) + : mVsyncDispatcher(aVsyncDispatcher), mVsyncObserver(aVsyncObserver) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!gfxPlatform::IsHeadless()); +} + +CompositorWidgetChild::~CompositorWidgetChild() = default; + +bool CompositorWidgetChild::Initialize() { return true; } + +mozilla::ipc::IPCResult CompositorWidgetChild::RecvObserveVsync() { + mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorWidgetChild::RecvUnobserveVsync() { + mVsyncDispatcher->SetCompositorVsyncObserver(nullptr); + return IPC_OK(); +} + +void CompositorWidgetChild::NotifyClientSizeChanged( + const LayoutDeviceIntSize& aClientSize) { + Unused << SendNotifyClientSizeChanged(aClientSize); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/CompositorWidgetChild.h b/widget/android/CompositorWidgetChild.h new file mode 100644 index 0000000000..88cb2913e9 --- /dev/null +++ b/widget/android/CompositorWidgetChild.h @@ -0,0 +1,41 @@ +/* -*- 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 widget_android_CompositorWidgetChild_h +#define widget_android_CompositorWidgetChild_h + +#include "AndroidCompositorWidget.h" +#include "mozilla/widget/PCompositorWidgetChild.h" +#include "mozilla/widget/CompositorWidgetVsyncObserver.h" + +namespace mozilla { +namespace widget { + +class CompositorWidgetChild final : public PCompositorWidgetChild, + public PlatformCompositorWidgetDelegate { + public: + CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher, + RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver, + const CompositorWidgetInitData&); + ~CompositorWidgetChild() override; + + bool Initialize(); + + mozilla::ipc::IPCResult RecvObserveVsync() override; + mozilla::ipc::IPCResult RecvUnobserveVsync() override; + + // PlatformCompositorWidgetDelegate overrides + + void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override; + + private: + RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher; + RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_android_CompositorWidgetChild_h diff --git a/widget/android/CompositorWidgetParent.cpp b/widget/android/CompositorWidgetParent.cpp new file mode 100644 index 0000000000..93b87f3cd3 --- /dev/null +++ b/widget/android/CompositorWidgetParent.cpp @@ -0,0 +1,54 @@ +/* -*- 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 "CompositorWidgetParent.h" +#include "mozilla/Unused.h" +#include "mozilla/java/GeckoServiceGpuProcessWrappers.h" +#include "mozilla/widget/PlatformWidgetTypes.h" + +namespace mozilla { +namespace widget { + +CompositorWidgetParent::CompositorWidgetParent( + const CompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions) + : AndroidCompositorWidget(aInitData.get_AndroidCompositorWidgetInitData(), + aOptions) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); +} + +CompositorWidgetParent::~CompositorWidgetParent() = default; + +nsIWidget* CompositorWidgetParent::RealWidget() { return nullptr; } + +void CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) { + if (aObserver) { + Unused << SendObserveVsync(); + } else { + Unused << SendUnobserveVsync(); + } + mVsyncObserver = aObserver; +} + +RefPtr<VsyncObserver> CompositorWidgetParent::GetVsyncObserver() const { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + return mVsyncObserver; +} + +mozilla::ipc::IPCResult CompositorWidgetParent::RecvNotifyClientSizeChanged( + const LayoutDeviceIntSize& aClientSize) { + NotifyClientSizeChanged(aClientSize); + return IPC_OK(); +} + +void CompositorWidgetParent::OnCompositorSurfaceChanged() { + java::GeckoServiceGpuProcess::RemoteCompositorSurfaceManager::LocalRef + manager = java::GeckoServiceGpuProcess::RemoteCompositorSurfaceManager:: + GetInstance(); + mSurface = manager->GetCompositorSurface(mWidgetId); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/CompositorWidgetParent.h b/widget/android/CompositorWidgetParent.h new file mode 100644 index 0000000000..cd6e4241ca --- /dev/null +++ b/widget/android/CompositorWidgetParent.h @@ -0,0 +1,42 @@ +/* -*- 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 widget_android_CompositorWidgetParent_h +#define widget_android_CompositorWidgetParent_h + +#include "AndroidCompositorWidget.h" +#include "mozilla/VsyncDispatcher.h" +#include "mozilla/widget/PCompositorWidgetParent.h" + +namespace mozilla { +namespace widget { + +class CompositorWidgetParent final : public PCompositorWidgetParent, + public AndroidCompositorWidget { + public: + explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions); + ~CompositorWidgetParent() override; + + // CompositorWidget overrides + + nsIWidget* RealWidget() override; + void ObserveVsync(VsyncObserver* aObserver) override; + RefPtr<VsyncObserver> GetVsyncObserver() const override; + + mozilla::ipc::IPCResult RecvNotifyClientSizeChanged( + const LayoutDeviceIntSize& aClientSize) override; + + private: + // AndroidCompositorWidget overrides + void OnCompositorSurfaceChanged() override; + + RefPtr<VsyncObserver> mVsyncObserver; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_android_CompositorWidgetParent_h diff --git a/widget/android/EventDispatcher.cpp b/widget/android/EventDispatcher.cpp new file mode 100644 index 0000000000..ab876de136 --- /dev/null +++ b/widget/android/EventDispatcher.cpp @@ -0,0 +1,776 @@ +/* -*- 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/PropertyAndElement.h" // JS_Enumerate, JS_GetElement, JS_GetProperty, JS_GetPropertyById, JS_SetElement, JS_SetUCProperty +#include "js/String.h" // JS::StringHasLatin1Chars +#include "js/Warnings.h" // JS::WarnUTF8 +#include "xpcpublic.h" + +#include "mozilla/fallible.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/java/EventCallbackWrappers.h" +#include "mozilla/jni/GeckoBundleUtils.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 BoxData(const nsAString& aEvent, JSContext* aCx, + JS::Handle<JS::Value> aData, jni::Object::LocalRef& aOut, + bool aObjectOnly) { + nsresult rv = jni::BoxData(aCx, aData, aOut, aObjectOnly); + 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::MutableHandle<JS::Value> aOut) { + if (!aData) { + aOut.setNull(); + return NS_OK; + } + + MOZ_ASSERT(aData.IsInstanceOf<jni::String>()); + + 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::Rooted<JSString*> str( + aCx, + JS_NewUCStringCopyN(aCx, reinterpret_cast<const char16_t*>(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::MutableHandle<JS::Value> aOut); + +nsresult UnboxBundle(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandle<JS::Value> aOut) { + if (!aData) { + aOut.setNull(); + return NS_OK; + } + + MOZ_ASSERT(aData.IsInstanceOf<java::GeckoBundle>()); + + 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::Rooted<JSObject*> 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::Rooted<JS::Value> 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<const char16_t*>(keyChars), keyLen)) + .get()); + } + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE( + CheckJS(aCx, JS_SetUCProperty( + aCx, obj, reinterpret_cast<const char16_t*>(keyChars), + keyLen, value)), + NS_ERROR_FAILURE); + } + + aOut.setObject(*obj); + return NS_OK; +} + +template <typename Type, typename JNIType, typename ArrayType, + JNIType* (JNIEnv::*GetElements)(ArrayType, jboolean*), + void (JNIEnv::*ReleaseElements)(ArrayType, JNIType*, jint), + JS::Value (*ToValue)(Type)> +nsresult UnboxArrayPrimitive(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandle<JS::Value> aOut) { + JNIEnv* const env = aData.Env(); + const ArrayType jarray = ArrayType(aData.Get()); + JNIType* const array = (env->*GetElements)(jarray, nullptr); + JS::RootedVector<JS::Value> 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::Rooted<JSObject*> 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<StringArray> { + static const char name[]; +}; + +struct GeckoBundleArray : jni::ObjectBase<GeckoBundleArray> { + static const char name[]; +}; + +const char StringArray::name[] = "[Ljava/lang/String;"; +const char GeckoBundleArray::name[] = "[Lorg/mozilla/gecko/util/GeckoBundle;"; + +template <nsresult (*Unbox)(JSContext*, const jni::Object::LocalRef&, + JS::MutableHandle<JS::Value>)> +nsresult UnboxArrayObject(JSContext* aCx, const jni::Object::LocalRef& aData, + JS::MutableHandle<JS::Value> aOut) { + jni::ObjectArray::LocalRef array(aData.Env(), + jni::ObjectArray::Ref::From(aData)); + const size_t len = array->Length(); + JS::Rooted<JSObject*> 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::Rooted<JS::Value> 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::MutableHandle<JS::Value> aOut) { + using jni::Java2Native; + + if (!aData) { + aOut.setNull(); + } else if (aData.IsInstanceOf<jni::Boolean>()) { + aOut.setBoolean(Java2Native<bool>(aData, aData.Env())); + } else if (aData.IsInstanceOf<jni::Integer>()) { + aOut.setInt32(Java2Native<int>(aData, aData.Env())); + } else if (aData.IsInstanceOf<jni::Byte>() || + aData.IsInstanceOf<jni::Short>()) { + aOut.setInt32(java::sdk::Number::Ref::From(aData)->IntValue()); + } else if (aData.IsInstanceOf<jni::Double>()) { + aOut.setNumber(Java2Native<double>(aData, aData.Env())); + } else if (aData.IsInstanceOf<jni::Float>() || + aData.IsInstanceOf<jni::Long>()) { + aOut.setNumber(java::sdk::Number::Ref::From(aData)->DoubleValue()); + } else if (aData.IsInstanceOf<jni::String>()) { + return UnboxString(aCx, aData, aOut); + } else if (aData.IsInstanceOf<jni::Character>()) { + return UnboxString(aCx, java::sdk::String::ValueOf(aData), aOut); + } else if (aData.IsInstanceOf<java::GeckoBundle>()) { + return UnboxBundle(aCx, aData, aOut); + + } else if (aData.IsInstanceOf<jni::BooleanArray>()) { + return UnboxArrayPrimitive< + bool, jboolean, jbooleanArray, &JNIEnv::GetBooleanArrayElements, + &JNIEnv::ReleaseBooleanArrayElements, &JS::BooleanValue>(aCx, aData, + aOut); + + } else if (aData.IsInstanceOf<jni::IntArray>()) { + return UnboxArrayPrimitive< + int32_t, jint, jintArray, &JNIEnv::GetIntArrayElements, + &JNIEnv::ReleaseIntArrayElements, &JS::Int32Value>(aCx, aData, aOut); + + } else if (aData.IsInstanceOf<jni::DoubleArray>()) { + return UnboxArrayPrimitive< + double, jdouble, jdoubleArray, &JNIEnv::GetDoubleArrayElements, + &JNIEnv::ReleaseDoubleArrayElements, &JS::DoubleValue>(aCx, aData, + aOut); + + } else if (aData.IsInstanceOf<StringArray>()) { + return UnboxArrayObject<&UnboxString>(aCx, aData, aOut); + } else if (aData.IsInstanceOf<GeckoBundleArray>()) { + 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::MutableHandle<JS::Value> 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<java::GeckoBundle>()) { + 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::Handle<JS::Value> 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::Handle<JS::Value> aData, JSContext* aCx) override { + return Call(aCx, aData, &java::EventCallback::SendSuccess); + } + + NS_IMETHOD OnError(JS::Handle<JS::Value> 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<NativeCallbackDelegateSupport>; + + const nsCOMPtr<nsIAndroidEventCallback> mCallback; + const nsCOMPtr<nsIAndroidEventFinalizer> mFinalizer; + const nsCOMPtr<nsIGlobalObject> mGlobalObject; + + void Call(jni::Object::Param aData, + nsresult (nsIAndroidEventCallback::*aCall)(JS::Handle<JS::Value>, + 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::Rooted<JS::Value> 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 <typename Functor> + 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<nsIAndroidEventCallback> mCallback; + const nsCOMPtr<nsIAndroidEventFinalizer> 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::Handle<JS::Value> 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<NativeCallbackDelegateSupport>(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::Handle<JS::Value> aEvent, + JS::Handle<JS::Value> 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<nsIAndroidEventCallback> 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::Rooted<JS::Value> 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::Handle<JS::Value> aEvents, + IterateEventsCallback aCallback, + nsIAndroidEventListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + auto processEvent = [this, aCx, aCallback, + aListener](JS::Handle<JS::Value> 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::Rooted<JSObject*> 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::Rooted<JS::Value> 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.GetOrInsertNew(aEvent); + +#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::Handle<JS::Value> 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<bool> 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::Handle<JS::Value> 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::Rooted<JS::Value> data(jsapi.cx()); + nsresult rv = UnboxData(aEvent, jsapi.cx(), aData, &data, + /* BundleOnly */ true); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIAndroidEventCallback> 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::MutableHandle<JS::Value> 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..a7daa18d7d --- /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<EventDispatcher> { + using NativesBase = java::EventDispatcher::Natives<EventDispatcher>; + + 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::MutableHandle<JS::Value> aOut); + + nsIGlobalObject* GetGlobalObject(); + + using NativesBase::DisposeNative; + + private: + friend class java::EventDispatcher::Natives<EventDispatcher>; + + java::EventDispatcher::WeakRef mDispatcher; + nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow; + + virtual ~EventDispatcher() {} + + void Shutdown(); + + struct ListenersList { + nsCOMArray<nsIAndroidEventListener> 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<nsStringHashKey, ListenersList>; + + Mutex mLock MOZ_UNANNOTATED{"mozilla::widget::EventDispatcher"}; + ListenersMap mListenersMap; + + using IterateEventsCallback = + nsresult (EventDispatcher::*)(const nsAString&, nsIAndroidEventListener*); + + nsresult IterateEvents(JSContext* aCx, JS::Handle<JS::Value> aEvents, + IterateEventsCallback aCallback, + nsIAndroidEventListener* aListener); + nsresult RegisterEventLocked(const nsAString&, nsIAndroidEventListener*); + nsresult UnregisterEventLocked(const nsAString&, nsIAndroidEventListener*); + + nsresult DispatchOnGecko(ListenersList* list, const nsAString& aEvent, + JS::Handle<JS::Value> 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<GeckoBatteryManager> { + 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..fcac5d5399 --- /dev/null +++ b/widget/android/GeckoEditableSupport.cpp @@ -0,0 +1,1684 @@ +/* -*- 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 "nsITransferable.h" +#include "nsStringStream.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/java/GeckoEditableChildWrappers.h" +#include "mozilla/java/GeckoServiceChildProcessWrappers.h" +#include "mozilla/jni/NativesInlines.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 <android/api-level.h> +#include <android/input.h> +#include <android/log.h> + +#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.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<LayoutDeviceIntRect>& aRects) { + const size_t length = aRects.Length(); + auto rects = jni::ObjectArray::New<java::sdk::RectF>(length); + + for (size_t i = 0; i < length; i++) { + const LayoutDeviceIntRect& tmp = aRects[i]; + + auto rect = java::sdk::RectF::New(tmp.x, tmp.y, tmp.XMost(), tmp.YMost()); + 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<GeckoEditableSupport> mGeckoEditable; +}; + +RefPtr<TextComposition> GeckoEditableSupport::GetComposition() const { + nsCOMPtr<nsIWidget> 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<nsIWidget> widget = GetWidget(); + RefPtr<TextEventDispatcher> 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<WidgetEvent>(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<WidgetEvent>(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); + // 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. + if (nsIWidget::UsePuppetWidgets()) { + event.PreventNativeKeyBindings(); + } + NS_ENSURE_SUCCESS_VOID(BeginInputTransaction(mDispatcher)); + mDispatcher->DispatchKeyboardEvent(msg, event, status); +} + +void GeckoEditableSupport::AddIMETextChange( + const IMENotification::TextChangeDataBase& aChange) { + mIMEPendingTextChange.MergeWith(aChange); + + // We may not be in the middle of flushing, + // in which case this flag is meaningless. + mIMETextChangedDuringFlush = true; +} + +void GeckoEditableSupport::PostFlushIMEChanges() { + if (mIMEPendingTextChange.IsValid() || mIMESelectionChanged) { + // Already posted + return; + } + + RefPtr<GeckoEditableSupport> self(this); + + nsAppShell::PostEvent([this, self] { + nsCOMPtr<nsIWidget> 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<nsIWidget> widget = GetWidget(); + NS_ENSURE_TRUE_VOID(widget); + + struct TextRecord { + TextRecord() : start(-1), oldEnd(-1), newEnd(-1) {} + + bool IsValid() const { return start >= 0; } + + nsString text; + int32_t start; + int32_t oldEnd; + int32_t newEnd; + }; + TextRecord textTransaction; + + nsEventStatus status = nsEventStatus_eIgnore; + bool causedOnlyByComposition = mIMEPendingTextChange.IsValid() && + mIMEPendingTextChange.mCausedOnlyByComposition; + 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; + }; + + if (mIMEPendingTextChange.IsValid() && + (mIMEPendingTextChange.mStartOffset != + mIMEPendingTextChange.mRemovedEndOffset || + mIMEPendingTextChange.mStartOffset != + mIMEPendingTextChange.mAddedEndOffset)) { + WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent, + widget); + + if (mIMEPendingTextChange.mAddedEndOffset != + mIMEPendingTextChange.mStartOffset) { + queryTextContentEvent.InitForQueryTextContent( + mIMEPendingTextChange.mStartOffset, + mIMEPendingTextChange.mAddedEndOffset - + mIMEPendingTextChange.mStartOffset); + widget->DispatchEvent(&queryTextContentEvent, status); + + if (shouldAbort(NS_WARN_IF(queryTextContentEvent.Failed()))) { + return; + } + + textTransaction.text = queryTextContentEvent.mReply->DataRef(); + } + + textTransaction.start = + static_cast<int32_t>(mIMEPendingTextChange.mStartOffset); + textTransaction.oldEnd = + static_cast<int32_t>(mIMEPendingTextChange.mRemovedEndOffset); + textTransaction.newEnd = + static_cast<int32_t>(mIMEPendingTextChange.mAddedEndOffset); + } + + int32_t selStart = -1; + int32_t selEnd = -1; + + if (mIMESelectionChanged) { + if (mCachedSelection.IsValid()) { + selStart = mCachedSelection.mStartOffset; + selEnd = mCachedSelection.mEndOffset; + } else { + // XXX Unfortunately we don't know current selection via selection + // change notification. + // eQuerySelectedText might be newer data than text change data. + // It means that GeckoEditableChild.onSelectionChange may throw + // IllegalArgumentException since we don't merge with newer text + // change. + WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText, + widget); + widget->DispatchEvent(&querySelectedTextEvent, status); + + if (shouldAbort( + NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection()))) { + return; + } + + selStart = + static_cast<int32_t>(querySelectedTextEvent.mReply->AnchorOffset()); + selEnd = + static_cast<int32_t>(querySelectedTextEvent.mReply->FocusOffset()); + } + + if (aFlags == FLUSH_FLAG_RECOVER && textTransaction.IsValid()) { + // Sometimes we get out-of-bounds selection during recovery. + // Limit the offsets so we don't crash. + const int32_t end = textTransaction.start + textTransaction.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. + mIMEPendingTextChange.Clear(); + + if (textTransaction.IsValid()) { + mEditable->OnTextChange(textTransaction.text, textTransaction.start, + textTransaction.oldEnd, textTransaction.newEnd, + causedOnlyByComposition); + if (flushOnException()) { + return; + } + } + + while (mIMEDelaySynchronizeReply && mIMEActiveSynchronizeCount) { + mIMEActiveSynchronizeCount--; + mEditable->NotifyIME(EditableListener::NOTIFY_IME_REPLY_EVENT); + } + mIMEDelaySynchronizeReply = false; + mIMEActiveSynchronizeCount = 0; + mIMEActiveCompositionCount = 0; + + if (mIMESelectionChanged) { + mIMESelectionChanged = false; + if (mDispatcher) { + // mCausedOnlyByComposition may be true on committing text. + // So even if true, there is no composition. + causedOnlyByComposition &= mDispatcher->IsComposing(); + } + mEditable->OnSelectionChange(selStart, selEnd, causedOnlyByComposition); + 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 + mIMEPendingTextChange.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<nsIWidget> widget = GetWidget(); + RefPtr<TextComposition> composition(GetComposition()); + NS_ENSURE_TRUE_VOID(mDispatcher && widget); + + jni::ObjectArray::LocalRef rects; + if (composition) { + nsEventStatus status = nsEventStatus_eIgnore; + uint32_t offset = composition->NativeOffsetOfStartComposition(); + WidgetQueryContentEvent queryTextRectsEvent(true, eQueryTextRectArray, + widget); + queryTextRectsEvent.InitForQueryTextRectArray( + offset, composition->String().Length()); + widget->DispatchEvent(&queryTextRectsEvent, status); + rects = ConvertRectArrayToJavaRectFArray( + queryTextRectsEvent.Succeeded() + ? queryTextRectsEvent.mReply->mRectArray + : CopyableTArray<mozilla::LayoutDeviceIntRect>()); + } else { + rects = ConvertRectArrayToJavaRectFArray( + CopyableTArray<mozilla::LayoutDeviceIntRect>()); + } + + WidgetQueryContentEvent queryCaretRectEvent(true, eQueryCaretRect, widget); + WidgetQueryContentEvent::Options options; + options.mRelativeToInsertionPoint = true; + queryCaretRectEvent.InitForQueryCaretRect(0, options); + + nsEventStatus status = nsEventStatus_eIgnore; + widget->DispatchEvent(&queryCaretRectEvent, status); + auto caretRect = + queryCaretRectEvent.Succeeded() + ? java::sdk::RectF::New(queryCaretRectEvent.mReply->mRect.x, + queryCaretRectEvent.mReply->mRect.y, + queryCaretRectEvent.mReply->mRect.XMost(), + queryCaretRectEvent.mReply->mRect.YMost()) + : java::sdk::RectF::New(); + + mEditable->UpdateCompositionRects(rects, caretRect); +} + +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<nsIWidget> widget = GetWidget(); + NS_ENSURE_TRUE(mDispatcher && widget, false); + NS_ENSURE_SUCCESS(BeginInputTransaction(mDispatcher), false); + + RefPtr<TextComposition> composition(GetComposition()); + MOZ_ASSERT(!composition || !composition->IsEditorHandlingEvent()); + + nsString string(aText->ToString()); + const bool composing = !mIMERanges->IsEmpty(); + nsEventStatus status = nsEventStatus_eIgnore; + bool textChanged = composing; + // Whether deleting content before setting or committing composition text. + bool performDeletion = false; + // Dispatch composition start to set current composition. + bool needDispatchCompositionStart = false; + + 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) { + if (composing) { + // Actually Gecko doesn't start composition, so it is unnecessary to + // delete content before setting composition string. + needDispatchCompositionStart = true; + } else { + // 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. + // + // Also, since this is IME change, we have to set mCausedOnlyByComposition. + IMENotification::TextChangeData dummyChange(aStart, aEnd, aEnd, true, + false); + PostFlushIMEChanges(); + mIMESelectionChanged = true; + AddIMETextChange(dummyChange); + textChanged = true; + } + + SendIMEDummyKeyEvent(widget, eKeyDown); + if (!mDispatcher || widget->Destroyed()) { + return false; + } + + if (needDispatchCompositionStart) { + // StartComposition sets composition string from selected string. + nsEventStatus status = nsEventStatus_eIgnore; + mDispatcher->StartComposition(status); + if (!mDispatcher || widget->Destroyed()) { + return false; + } + } else if (performDeletion) { + WidgetContentCommandEvent event(true, eContentCommandDelete, widget); + 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; + } + + 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<nsIWidget> 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<TextComposition> 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=%zu, 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<nsIWidget> mWidget; + RefPtr<TextEventDispatcher> mDispatcher; + uint32_t mOffset; + uint32_t mLength; +}; + +void GeckoEditableSupport::OnImeRequestCommit() { + AutoGeckoEditableBlocker blocker(this); + + if (mIMEMaskEventsCount > 0) { + // Not focused. + return; + } + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + return; + } + + AutoSelectionRestore restore(widget, mDispatcher); + + RemoveComposition(COMMIT_IME_COMPOSITION); +} + +void GeckoEditableSupport::AsyncNotifyIME(int32_t aNotification) { + RefPtr<GeckoEditableSupport> 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<GeckoEditableSupport> self(this); + RefPtr<TextEventDispatcher> 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<nsIWidget> 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<GeckoEditableSupport>::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(); + + mCachedSelection.Reset(); + + 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<GeckoEditableSupport> 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()); + + if (aNotification.mSelectionChangeData.HasRange()) { + mCachedSelection.mStartOffset = static_cast<int32_t>( + aNotification.mSelectionChangeData.AnchorOffset()); + mCachedSelection.mEndOffset = static_cast<int32_t>( + aNotification.mSelectionChangeData.FocusOffset()); + } else { + mCachedSelection.Reset(); + } + + 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(aNotification.mTextChangeData); + 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<GeckoEditableSupport> 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); +} + +static bool ShouldKeyboardDismiss(const nsAString& aInputType, + const nsAString& aInputMode) { + // Some input type uses the prompt to input value. So it is unnecessary to + // show software keyboard. + return aInputMode.EqualsLiteral("none") || aInputType.EqualsLiteral("date") || + aInputType.EqualsLiteral("time") || + aInputType.EqualsLiteral("month") || + aInputType.EqualsLiteral("week") || + aInputType.EqualsLiteral("datetime-local"); +} + +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 && + !ShouldKeyboardDismiss(mInputContext.mHTMLInputType, + mInputContext.mHTMLInputMode) && + 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<GeckoEditableSupport>(this), + context = mInputContext, action = aAction] { + nsCOMPtr<nsIWidget> 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) | + (aAction.mFocusChange == InputContextAction::FOCUS_NOT_CHANGED + ? EditableListener::IME_FOCUS_NOT_CHANGED + : 0); + + mEditable->NotifyIMEContext(static_cast<int32_t>(aContext.mIMEState.mEnabled), + aContext.mHTMLInputType, aContext.mHTMLInputMode, + 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<GeckoEditableSupport> 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::PuppetWidget> 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<widget::TextEventDispatcherListener> listener = + widget->GetNativeTextEventDispatcherListener(); + + if (!listener || + listener.get() == + static_cast<widget::TextEventDispatcherListener*>(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<GeckoEditableSupport>::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<widget::GeckoEditableSupport> dummy = + new widget::GeckoEditableSupport(/* child */ nullptr); + NS_ENSURE_TRUE_VOID(*reinterpret_cast<const uintptr_t*>(listener.get()) == + *reinterpret_cast<const uintptr_t*>(dummy.get())); + + const auto support = + static_cast<widget::GeckoEditableSupport*>(listener.get()); + if (!support->mEditableAttached) { + // Temporarily attach so we can receive the initial editable parent. + jni::NativeWeakPtrHolder<GeckoEditableSupport>::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(); +} + +void GeckoEditableSupport::OnImeInsertImage(jni::ByteArray::Param aData, + jni::String::Param aMimeType) { + AutoGeckoEditableBlocker blocker(this); + + nsCString mimeType(aMimeType->ToCString()); + nsCOMPtr<nsIInputStream> byteStream; + + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(byteStream), + mozilla::Span( + reinterpret_cast<const char*>(aData->GetElements().Elements()), + aData->Length()), + NS_ASSIGNMENT_COPY); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsITransferable> trans = + do_CreateInstance("@mozilla.org/widget/transferable;1"); + if (NS_WARN_IF(!trans)) { + return; + } + trans->Init(nullptr); + rv = trans->SetTransferData(mimeType.get(), byteStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget) || NS_WARN_IF(widget->Destroyed())) { + return; + } + + WidgetContentCommandEvent command(true, eContentCommandPasteTransferable, + widget); + command.mTransferable = trans.forget(); + nsEventStatus status; + widget->DispatchEvent(&command, status); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/GeckoEditableSupport.h b/widget/android/GeckoEditableSupport.h new file mode 100644 index 0000000000..90cb2710d3 --- /dev/null +++ b/widget/android/GeckoEditableSupport.h @@ -0,0 +1,286 @@ +/* -*- 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<GeckoEditableSupport> { + /* + 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<GeckoEditableSupport>; + using EditableClient = java::SessionTextInput::EditableClient; + using EditableListener = java::SessionTextInput::EditableListener; + + 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<GeckoViewSupport> mWindow; // Parent only + RefPtr<TextEventDispatcher> mDispatcher; + java::GeckoEditableChild::GlobalRef mEditable; + bool mEditableAttached; + InputContext mInputContext; + AutoTArray<UniquePtr<mozilla::WidgetEvent>, 4> mIMEKeyEvents; + IMENotification::TextChangeData mIMEPendingTextChange; + RefPtr<TextRangeArray> mIMERanges; + RefPtr<Runnable> 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; + + // The cached selection data + struct Selection { + Selection() : mStartOffset(-1), mEndOffset(-1) {} + + void Reset() { + mStartOffset = -1; + mEndOffset = -1; + } + + bool IsValid() const { return mStartOffset >= 0 && mEndOffset >= 0; } + + int32_t mStartOffset; + int32_t mEndOffset; + } mCachedSelection; + + nsIWidget* GetWidget() const; + nsWindow* GetNsWindow() const; + + nsresult BeginInputTransaction(TextEventDispatcher* aDispatcher) { + if (mIsRemote) { + return aDispatcher->BeginInputTransaction(this); + } else { + return aDispatcher->BeginNativeInputTransaction(); + } + } + + virtual ~GeckoEditableSupport() {} + + RefPtr<TextComposition> GetComposition() const; + bool RemoveComposition(RemoveCompositionFlag aFlag = COMMIT_IME_COMPOSITION); + void SendIMEDummyKeyEvent(nsIWidget* aWidget, EventMessage msg); + void AddIMETextChange(const IMENotification::TextChangeDataBase& 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 <typename Functor> + static void OnNativeCall(Functor&& aCall) { + struct IMEEvent : nsAppShell::LambdaEvent<Functor> { + explicit IMEEvent(Functor&& l) + : nsAppShell::LambdaEvent<Functor>(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<Functor>::Run(); + } + }; + nsAppShell::PostEvent(mozilla::MakeUnique<IMEEvent>(std::move(aCall))); + } + + static void SetOnBrowserChild(dom::BrowserChild* aBrowserChild); + + // Constructor for main process GeckoEditableChild. + GeckoEditableSupport(jni::NativeWeakPtr<GeckoViewSupport> 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<GeckoEditableSupport> self(this); + RefPtr<Runnable> 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<Runnable> aDisposer) { + RefPtr<GeckoEditableSupport> self(this); + nsAppShell::PostEvent( + [self = std::move(self), disposer = RefPtr<Runnable>(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(); + + // Insert image from software keyboard. + void OnImeInsertImage(jni::ByteArray::Param aData, + jni::String::Param aMimeType); +}; + +} // 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> { + 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<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC, + aSubType->ToString().get()); + } + } + + static void OnStatusChanged(jni::String::Param aStatus) { + nsCOMPtr<nsIObserverService> 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<ConnectionManager> native(new ConnectionManager()); + BaseNatives::AttachNative(aInstance, native); + + native->mJavaConnMgr = aInstance; + + nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService()); + obsServ->AddObserver(native, "application-background", false); + obsServ->AddObserver(native, "application-foreground", false); +} + +void GeckoProcessManager::ConnectionManager::ObserveNetworkNotifications() { + nsCOMPtr<nsIObserverService> 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<GeckoProcessManager> { + using BaseNatives = java::GeckoProcessManager::Natives<GeckoProcessManager>; + + GeckoProcessManager() = delete; + + static already_AddRefed<nsIWidget> 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<BrowserParent> tab = cpm->GetTopLevelBrowserParentByProcessAndTabId( + ContentParentId(aContentId), TabId(aTabId)); + NS_ENSURE_TRUE(tab, nullptr); + + nsCOMPtr<nsPIDOMWindowOuter> 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<nsIWidget> widget = GetWidget(aContentId, aTabId); + if (RefPtr<nsWindow> window = nsWindow::From(widget)) { + java::GeckoProcessManager::SetEditableChildParent( + aEditableChild, window->GetEditableParent()); + } + } +}; + +} // namespace mozilla + +#endif // GeckoProcessManager_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> { + 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..43b20d4ad6 --- /dev/null +++ b/widget/android/GeckoTelemetryDelegate.h @@ -0,0 +1,100 @@ +/* -*- 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 <jni.h> + +#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<uint32_t>& 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<int64_t> samples(aSamples.Length()); + for (size_t i = 0, l = aSamples.Length(); i < l; ++i) { + samples.AppendElement(static_cast<int64_t>(aSamples[i])); + } + + // LongArray::From *copies* the elements + mProxy->DispatchHistogram(aIsCategorical, aName, + mozilla::jni::LongArray::From(samples)); + } + + // Implement StreamingTelemetryDelegate. + void ReceiveHistogramSamples(const nsCString& aName, + const nsTArray<uint32_t>& aSamples) override { + DispatchHistogram(/* isCategorical */ false, aName, aSamples); + } + + void ReceiveCategoricalHistogramSamples( + const nsCString& aName, const nsTArray<uint32_t>& 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<int64_t>(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<void*>( + 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..c784552824 --- /dev/null +++ b/widget/android/GeckoViewSupport.h @@ -0,0 +1,126 @@ +/* -*- 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/dom/CanonicalBrowsingContext.h" +#include "mozilla/widget/WindowEvent.h" + +class nsPIDOMWindowOuter; +class nsWindow; + +namespace mozilla { +namespace widget { + +class GeckoViewSupport final + : public java::GeckoSession::Window::Natives<GeckoViewSupport> { + RefPtr<nsWindow> 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<GeckoViewSupport> Base; + + template <typename Functor> + static void OnNativeCall(Functor&& aCall) { + NS_DispatchToMainThread(new WindowEvent<Functor>(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<nsPIDOMWindowOuter> mDOMWindow; + bool mIsReady{false}; + RefPtr<dom::CanonicalBrowsingContext> GetContentCanonicalBrowsingContext(); + MOZ_CAN_RUN_SCRIPT void CreatePdf( + jni::LocalRef<mozilla::java::GeckoResult> aGeckoResult, + RefPtr<dom::CanonicalBrowsingContext> aCbc); + + 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, 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 OnShowDynamicToolbar() const; + + void OnUpdateSessionStore(mozilla::jni::Object::Param aBundle); + + 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<Runnable> aDisposer) { + RefPtr<Runnable> disposer(aDisposer); + disposer->Run(); + } + + MOZ_CAN_RUN_SCRIPT void PrintToPdf( + const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aStream, int64_t aBcId); + + MOZ_CAN_RUN_SCRIPT void PrintToPdf( + const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aStream); +}; + +} // 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..e4f8d5d88b --- /dev/null +++ b/widget/android/GfxInfo.cpp @@ -0,0 +1,866 @@ +/* -*- 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 "AndroidBuild.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "nsUnicharUtils.h" +#include "prenv.h" +#include "nsExceptionHandler.h" +#include "nsHashKeys.h" +#include "nsVersionComparator.h" +#include "nsServiceManagerUtils.h" + +#include "mozilla/Preferences.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/HardwareCodecCapabilityUtilsWrappers.h" + +namespace mozilla { +namespace widget { + +class GfxInfo::GLStrings { + nsCString mVendor; + nsCString mRenderer; + nsCString mVersion; + nsTArray<nsCString> mExtensions; + 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; } + + const nsTArray<nsCString>& Extensions() { + EnsureInitialized(); + return mExtensions; + } + + void EnsureInitialized() { + if (mReady) { + return; + } + + RefPtr<gl::GLContext> 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)); + } + } + + if (mExtensions.IsEmpty()) { + nsCString rawExtensions; + rawExtensions.Assign((const char*)gl->fGetString(LOCAL_GL_EXTENSIONS)); + rawExtensions.Trim(" "); + + for (auto extension : rawExtensions.Split(' ')) { + mExtensions.AppendElement(extension); + } + } + + 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::GetTestType(nsAString& aTestType) { return NS_ERROR_NOT_IMPLEMENTED; } + +void GfxInfo::EnsureInitialized() { + if (mInitialized) return; + + if (!jni::IsAvailable()) { + gfxWarning() << "JNI missing during initialization"; + return; + } + + jni::String::LocalRef model = java::sdk::Build::MODEL(); + mModel = model->ToString(); + mAdapterDescription.AppendPrintf("Model: %s", + NS_LossyConvertUTF16toASCII(mModel).get()); + + jni::String::LocalRef product = java::sdk::Build::PRODUCT(); + mProduct = product->ToString(); + mAdapterDescription.AppendPrintf(", Product: %s", + NS_LossyConvertUTF16toASCII(mProduct).get()); + + jni::String::LocalRef manufacturer = + mozilla::java::sdk::Build::MANUFACTURER(); + mManufacturer = manufacturer->ToString(); + mAdapterDescription.AppendPrintf( + ", Manufacturer: %s", NS_LossyConvertUTF16toASCII(mManufacturer).get()); + + mSDKVersion = java::sdk::Build::VERSION::SDK_INT(); + // the HARDWARE field isn't available on Android SDK < 8, but we require 9+ + // anyway. + MOZ_ASSERT(mSDKVersion >= 8); + jni::String::LocalRef hardware = java::sdk::Build::HARDWARE(); + mHardware = hardware->ToString(); + mAdapterDescription.AppendPrintf( + ", Hardware: %s", NS_LossyConvertUTF16toASCII(mHardware).get()); + + jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); + mOSVersion = release->ToCString(); + + 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(); + 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::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<GfxDriverInfo>& 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<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId, + OperatingSystem* aOS /* = nullptr */) { + NS_ENSURE_ARG_POINTER(aStatus); + aSuggestedDriverVersion.SetIsVoid(true); + *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + OperatingSystem os = OperatingSystem::Android; + 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.LowerCaseFindASCII("sgh-i717") != -1 || + cModel.LowerCaseFindASCII("sgh-i727") != -1 || + cModel.LowerCaseFindASCII("sgh-i757") != -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.LowerCaseFindASCII("gt-p3100") != -1 || + cModel.LowerCaseFindASCII("gt-p3110") != -1 || + cModel.LowerCaseFindASCII("gt-p3113") != -1 || + cModel.LowerCaseFindASCII("gt-p5100") != -1 || + cModel.LowerCaseFindASCII("gt-p5110") != -1 || + cModel.LowerCaseFindASCII("gt-p5113") != -1 || + cModel.LowerCaseFindASCII("xt890") != -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.LowerCaseFindASCII("sony") != -1) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_4_3_SONY"; + return NS_OK; + } + } + } + + if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_ENCODE) { + if (jni::IsAvailable()) { + *aStatus = WebRtcHwVp8EncodeSupported(); + aFailureId = "FEATURE_FAILURE_WEBRTC_ENCODE"; + return NS_OK; + } + } + if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_DECODE) { + if (jni::IsAvailable()) { + *aStatus = WebRtcHwVp8DecodeSupported(); + aFailureId = "FEATURE_FAILURE_WEBRTC_DECODE"; + return NS_OK; + } + } + if (aFeature == FEATURE_WEBRTC_HW_ACCELERATION_H264) { + if (jni::IsAvailable()) { + *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) { + const bool isMali4xx = + mGLStrings->Renderer().LowerCaseFindASCII("mali-4") >= 0; + + const bool isPowerVrG6110 = + mGLStrings->Renderer().LowerCaseFindASCII("powervr rogue g6110") >= 0; + + const bool isVivanteGC7000UL = + mGLStrings->Renderer().LowerCaseFindASCII("vivante gc7000ul") >= 0; + + const bool isPowerVrFenceSyncCrash = + (mGLStrings->Renderer().LowerCaseFindASCII("powervr rogue g6200") >= + 0 || + mGLStrings->Renderer().LowerCaseFindASCII("powervr rogue g6430") >= + 0 || + mGLStrings->Renderer().LowerCaseFindASCII("powervr rogue gx6250") >= + 0) && + (mGLStrings->Version().Find("3283119") >= 0 || + mGLStrings->Version().Find("3443629") >= 0 || + mGLStrings->Version().Find("3573678") >= 0 || + mGLStrings->Version().Find("3830101") >= 0); + + if (isMali4xx) { + // Mali 4xx does not support GLES 3. + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_NO_GLES_3"; + } else if (isPowerVrG6110) { + // Blocked on PowerVR Rogue G6110 due to bug 1742986 and bug 1717863. + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_POWERVR_G6110"; + } else if (isVivanteGC7000UL) { + // Blocked on Vivante GC7000UL due to bug 1719327. + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_VIVANTE_GC7000UL"; + } else if (isPowerVrFenceSyncCrash) { + // Blocked on various PowerVR GPUs due to bug 1773128. + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_POWERVR_FENCE_SYNC_CRASH"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + 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; + } + + if (aFeature == FEATURE_WEBRENDER_SHADER_CACHE) { + // Program binaries are known to be buggy on Adreno 3xx. While we haven't + // encountered any correctness or stability issues with them, loading them + // fails more often than not, so is a waste of time. Better to just not + // even attempt to cache them. See bug 1615574. + const bool isAdreno3xx = + mGLStrings->Renderer().LowerCaseFindASCII("adreno (tm) 3") >= 0; + if (isAdreno3xx) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_ADRENO_3XX"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + } + + if (aFeature == FEATURE_WEBRENDER_OPTIMIZED_SHADERS) { + // Optimized shaders result in completely broken rendering on some Mali-T + // devices. We have seen this on T6xx, T7xx, and T8xx on android versions + // up to 5.1, and on T6xx on versions up to android 7.1. As a precaution + // disable for all Mali-T regardless of version. See bug 1689064 and bug + // 1707283 for details. + const bool isMaliT = + mGLStrings->Renderer().LowerCaseFindASCII("mali-t") >= 0; + if (isMaliT) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_BUG_1689064"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + return NS_OK; + } + + if (aFeature == FEATURE_WEBRENDER_PARTIAL_PRESENT) { + // Block partial present on some devices due to rendering issues. + // On Mali-Txxx due to bug 1680087 and bug 1707815. + // On Adreno 3xx GPUs due to bug 1695771. + const bool isMaliT = + mGLStrings->Renderer().LowerCaseFindASCII("mali-t") >= 0; + const bool isAdreno3xx = + mGLStrings->Renderer().LowerCaseFindASCII("adreno (tm) 3") >= 0; + if (isMaliT || isAdreno3xx) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_BUG_1680087_1695771_1707815"; + } else { + *aStatus = nsIGfxInfo::FEATURE_STATUS_OK; + } + return NS_OK; + } + } + + if (aFeature == FEATURE_GL_SWIZZLE) { + // Swizzling appears to be buggy on PowerVR Rogue devices with webrender. + // See bug 1704783. + const bool isPowerVRRogue = + mGLStrings->Renderer().LowerCaseFindASCII("powervr rogue") >= 0; + if (isPowerVRRogue) { + *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + aFailureId = "FEATURE_FAILURE_POWERVR_ROGUE"; + } 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 FeatureCacheAppVerPrefName(int32_t aFeature) { + nsCString osPrefName; + osPrefName.AppendASCII("gfxinfo.cache."); + osPrefName.AppendInt(aFeature); + osPrefName.AppendASCII(".appver"); + 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, + const nsCString& aCurrentAppVer, + int32_t& aOutStatus) { + uint32_t osVer = 0; + nsresult rv = + Preferences::GetUint(FeatureCacheOsVerPrefName(aFeature).get(), &osVer); + if (NS_FAILED(rv) || osVer != aExpectedOsVer) { + return false; + } + // Bug 1804287 requires we invalidate cached values for new builds to allow + // for code changes to modify the features support. + nsAutoCString cachedAppVersion; + rv = Preferences::GetCString(FeatureCacheAppVerPrefName(aFeature).get(), + cachedAppVersion); + if (NS_FAILED(rv) || !aCurrentAppVer.Equals(cachedAppVersion)) { + 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, + const nsCString& aCurrentAppVer, + int32_t aStatus) { + // Ignore failures; not much we can do anyway. + Preferences::SetUint(FeatureCacheOsVerPrefName(aFeature).get(), aOsVer); + Preferences::SetCString(FeatureCacheAppVerPrefName(aFeature).get(), + aCurrentAppVer); + Preferences::SetInt(FeatureCacheValuePrefName(aFeature).get(), aStatus); +} + +int32_t GfxInfo::WebRtcHwVp8EncodeSupported() { + MOZ_ASSERT(jni::IsAvailable()); + + // 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; + const auto& currentAppVersion = GfxInfoBase::GetApplicationVersion(); + if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, + mOSVersionInteger, currentAppVersion, status)) { + return status; + } + + status = java::GeckoAppShell::HasHWVP8Encoder() + ? nsIGfxInfo::FEATURE_STATUS_OK + : nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + + SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, mOSVersionInteger, + currentAppVersion, status); + + return status; +} + +int32_t GfxInfo::WebRtcHwVp8DecodeSupported() { + MOZ_ASSERT(jni::IsAvailable()); + + // 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; + const auto& appVersion = GfxInfoBase::GetApplicationVersion(); + if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_DECODE, + mOSVersionInteger, appVersion, status)) { + return status; + } + + status = java::GeckoAppShell::HasHWVP8Decoder() + ? nsIGfxInfo::FEATURE_STATUS_OK + : nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + + SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_DECODE, mOSVersionInteger, + appVersion, status); + + return status; +} + +int32_t GfxInfo::WebRtcHwH264Supported() { + MOZ_ASSERT(jni::IsAvailable()); + + // 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; + const auto& currentAppVersion = GfxInfoBase::GetApplicationVersion(); + if (GetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_H264, + mOSVersionInteger, currentAppVersion, status)) { + return status; + } + + status = java::HardwareCodecCapabilityUtils::HasHWH264() + ? nsIGfxInfo::FEATURE_STATUS_OK + : nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + + SetCachedFeatureVal(FEATURE_WEBRTC_HW_ACCELERATION_H264, mOSVersionInteger, + currentAppVersion, 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; +} + +#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..7a963b0275 --- /dev/null +++ b/widget/android/GfxInfo.h @@ -0,0 +1,109 @@ +/* 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 GetTestType(nsAString& aTestType) 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 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: + OperatingSystem GetOperatingSystem() override { + return OperatingSystem::Android; + } + virtual nsresult GetFeatureStatusImpl( + int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion, + const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId, + OperatingSystem* aOS = nullptr) override; + virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override; + + private: + void AddCrashReportAnnotations(); + int32_t WebRtcHwVp8EncodeSupported(); + int32_t WebRtcHwVp8DecodeSupported(); + int32_t WebRtcHwH264Supported(); + + bool mInitialized; + + class GLStrings; + UniquePtr<GLStrings> mGLStrings; + + nsCString mAdapterDescription; + + nsString mModel, mHardware, mManufacturer, mProduct; + nsCString mOSVersion; + uint32_t mOSVersionInteger; + int32_t mSDKVersion; +}; + +} // 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..f3e1c3e444 --- /dev/null +++ b/widget/android/ImageDecoderSupport.cpp @@ -0,0 +1,185 @@ +/* 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 "mozilla/java/ImageWrappers.h" +#include "nsNetUtil.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace widget { + +namespace { + +class ImageCallbackHelper; + +HashSet<RefPtr<ImageCallbackHelper>, PointerHasher<ImageCallbackHelper*>> + gDecodeRequests; + +class ImageCallbackHelper : public imgIContainerCallback, + public imgINotificationObserver { + public: + NS_DECL_ISUPPORTS + + void CompleteExceptionally(nsresult aRv) { + nsPrintfCString error("Could not process image: 0x%08X", aRv); + mResult->CompleteExceptionally( + java::Image::ImageProcessingException::New(error.get()) + .Cast<jni::Throwable>()); + gDecodeRequests.remove(this); + } + + void Complete(DataSourceSurface::ScopedMap& aSourceSurface, int32_t width, + int32_t height) { + auto pixels = mozilla::jni::ByteBuffer::New( + reinterpret_cast<int8_t*>(aSourceSurface.GetData()), + aSourceSurface.GetStride() * height); + auto bitmap = java::sdk::Bitmap::CreateBitmap( + width, height, java::sdk::Bitmap::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(aStatus); + 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<gfx::SourceSurface> surface; + + NS_ENSURE_TRUE(mImage, NS_ERROR_FAILURE); + 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); + } + + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); + RefPtr<DataSourceSurface> 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<DataSourceSurface> 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) { + nsresult status = SendBitmap(); + if (NS_FAILED(status)) { + CompleteExceptionally(status); + } + + // Breack the cyclic reference between `ImageDecoderListener` (which is a + // `imgIContainer`) and `ImageCallbackHelper`. + mImage = nullptr; + } + } + + private: + const java::GeckoResult::GlobalRef mResult; + int32_t mDesiredLength; + nsCOMPtr<imgIContainer> 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<ImageCallbackHelper> 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<imgITools> imgTools = do_GetService("@mozilla.org/image/tools;1"); + if (NS_WARN_IF(!imgTools)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + + nsCOMPtr<nsIChannel> 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<ImageDecoderSupport> { + 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/InProcessAndroidCompositorWidget.cpp b/widget/android/InProcessAndroidCompositorWidget.cpp new file mode 100644 index 0000000000..1ae221acb9 --- /dev/null +++ b/widget/android/InProcessAndroidCompositorWidget.cpp @@ -0,0 +1,57 @@ +/* -*- 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 "HeadlessCompositorWidget.h" +#include "HeadlessWidget.h" +#include "mozilla/widget/PlatformWidgetTypes.h" + +#include "InProcessAndroidCompositorWidget.h" +#include "nsWindow.h" + +namespace mozilla { +namespace widget { + +/* static */ +RefPtr<CompositorWidget> CompositorWidget::CreateLocal( + const CompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions, nsIWidget* aWidget) { + if (aInitData.type() == + CompositorWidgetInitData::THeadlessCompositorWidgetInitData) { + return new HeadlessCompositorWidget( + aInitData.get_HeadlessCompositorWidgetInitData(), aOptions, + static_cast<HeadlessWidget*>(aWidget)); + } else { + return new InProcessAndroidCompositorWidget( + aInitData.get_AndroidCompositorWidgetInitData(), aOptions, + static_cast<nsWindow*>(aWidget)); + } +} + +InProcessAndroidCompositorWidget::InProcessAndroidCompositorWidget( + const AndroidCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions, nsWindow* aWindow) + : AndroidCompositorWidget(aInitData, aOptions), mWindow(aWindow) {} + +void InProcessAndroidCompositorWidget::ObserveVsync(VsyncObserver* aObserver) { + if (RefPtr<CompositorVsyncDispatcher> cvd = + mWindow->GetCompositorVsyncDispatcher()) { + cvd->SetCompositorVsyncObserver(aObserver); + } +} + +nsIWidget* InProcessAndroidCompositorWidget::RealWidget() { return mWindow; } + +void InProcessAndroidCompositorWidget::OnCompositorSurfaceChanged() { + mSurface = java::sdk::Surface::Ref::From( + static_cast<jobject>(mWindow->GetNativeData(NS_JAVA_SURFACE))); +} + +void InProcessAndroidCompositorWidget::NotifyClientSizeChanged( + const LayoutDeviceIntSize& aClientSize) { + AndroidCompositorWidget::NotifyClientSizeChanged(aClientSize); +} + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/InProcessAndroidCompositorWidget.h b/widget/android/InProcessAndroidCompositorWidget.h new file mode 100644 index 0000000000..b7ba280d5c --- /dev/null +++ b/widget/android/InProcessAndroidCompositorWidget.h @@ -0,0 +1,45 @@ +/* -*- 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 widget_android_InProcessAndroidCompositorWidget_h +#define widget_android_InProcessAndroidCompositorWidget_h + +#include "AndroidCompositorWidget.h" +#include "CompositorWidget.h" + +class nsWindow; + +namespace mozilla { +namespace widget { + +class InProcessAndroidCompositorWidget final + : public AndroidCompositorWidget, + public PlatformCompositorWidgetDelegate { + public: + InProcessAndroidCompositorWidget( + const AndroidCompositorWidgetInitData& aInitData, + const layers::CompositorOptions& aOptions, nsWindow* aWidget); + + // CompositorWidget overrides + + void ObserveVsync(VsyncObserver* aObserver) override; + nsIWidget* RealWidget() override; + CompositorWidgetDelegate* AsDelegate() override { return this; } + + // PlatformCompositorWidgetDelegate overrides + + void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override; + + private: + // AndroidCompositorWidget overrides + void OnCompositorSurfaceChanged() override; + + nsWindow* mWindow; +}; + +} // namespace widget +} // namespace mozilla + +#endif // widget_android_InProcessAndroidCompositorWidget_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/PCompositorWidget.ipdl b/widget/android/PCompositorWidget.ipdl new file mode 100644 index 0000000000..35a150cbba --- /dev/null +++ b/widget/android/PCompositorWidget.ipdl @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* 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 protocol PCompositorBridge; + +using mozilla::LayoutDeviceIntSize from "Units.h"; + +namespace mozilla { +namespace widget { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PCompositorWidget +{ + manager PCompositorBridge; + +parent: + async __delete__(); + + async NotifyClientSizeChanged(LayoutDeviceIntSize aClientSize); + +child: + async ObserveVsync(); + async UnobserveVsync(); +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/PlatformWidgetTypes.ipdlh b/widget/android/PlatformWidgetTypes.ipdlh new file mode 100644 index 0000000000..62ac0df9ff --- /dev/null +++ b/widget/android/PlatformWidgetTypes.ipdlh @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=99: */ +/* 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 HeadlessWidgetTypes; + +include "mozilla/GfxMessageUtils.h"; + +using mozilla::LayoutDeviceIntSize from "Units.h"; + +namespace mozilla { +namespace widget { + +struct AndroidCompositorWidgetInitData +{ + int32_t widgetId; + LayoutDeviceIntSize clientSize; +}; + +union CompositorWidgetInitData +{ + AndroidCompositorWidgetInitData; + HeadlessCompositorWidgetInitData; +}; + +} // namespace widget +} // namespace mozilla diff --git a/widget/android/ScreenHelperAndroid.cpp b/widget/android/ScreenHelperAndroid.cpp new file mode 100644 index 0000000000..33c91fe6e0 --- /dev/null +++ b/widget/android/ScreenHelperAndroid.cpp @@ -0,0 +1,70 @@ +/* -*- 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 <mozilla/jni/Refs.h> + +#include "AndroidVsync.h" +#include "mozilla/Atomics.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/ScreenManagerHelperNatives.h" +#include "mozilla/widget/ScreenManager.h" +#include "nsXULAppAPI.h" + +using namespace mozilla; +using namespace mozilla::widget; + +static ScreenHelperAndroid* gHelper = nullptr; + +class ScreenHelperAndroid::ScreenHelperSupport final + : public java::ScreenManagerHelper::Natives<ScreenHelperSupport> { + public: + typedef java::ScreenManagerHelper::Natives<ScreenHelperSupport> Base; + + static void RefreshScreenInfo() { gHelper->Refresh(); } +}; + +static already_AddRefed<Screen> 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(); + auto orientation = + hal::ScreenOrientation(java::GeckoAppShell::GetScreenOrientation()); + uint16_t angle = java::GeckoAppShell::GetScreenAngle(); + float refreshRate = java::GeckoAppShell::GetScreenRefreshRate(); + return MakeAndAddRef<Screen>(bounds, bounds, depth, depth, refreshRate, + DesktopToLayoutDeviceScale(density), + CSSToLayoutDeviceScale(1.0f), dpi, + Screen::IsPseudoDisplay::No, orientation, angle); +} + +ScreenHelperAndroid::ScreenHelperAndroid() { + MOZ_ASSERT(!gHelper); + gHelper = this; + + ScreenHelperSupport::Base::Init(); + + Refresh(); +} + +ScreenHelperAndroid::~ScreenHelperAndroid() { gHelper = nullptr; } + +void ScreenHelperAndroid::Refresh() { + AutoTArray<RefPtr<Screen>, 1> screens; + screens.AppendElement(MakePrimaryScreen()); + ScreenManager::Refresh(std::move(screens)); + + if (RefPtr<AndroidVsync> vsync = AndroidVsync::GetInstance()) { + vsync->OnMaybeUpdateRefreshRate(); + } +} diff --git a/widget/android/ScreenHelperAndroid.h b/widget/android/ScreenHelperAndroid.h new file mode 100644 index 0000000000..c7015ee873 --- /dev/null +++ b/widget/android/ScreenHelperAndroid.h @@ -0,0 +1,29 @@ +/* -*- 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 "nsTHashMap.h" + +namespace mozilla { +namespace widget { + +class ScreenHelperAndroid final : public ScreenManager::Helper { + public: + class ScreenHelperSupport; + + ScreenHelperAndroid(); + ~ScreenHelperAndroid(); + + void Refresh(); +}; + +} // 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..9936b13367 --- /dev/null +++ b/widget/android/Telemetry.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 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> { + Telemetry() = delete; + + public: + static void AddHistogram(jni::String::Param aName, int32_t aValue) { + MOZ_ASSERT(aName); + mozilla::Telemetry::Accumulate(aName->ToCString().get(), aValue); + } +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_Telemetry_h__ diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp new file mode 100644 index 0000000000..eca9790aad --- /dev/null +++ b/widget/android/WebExecutorSupport.cpp @@ -0,0 +1,469 @@ +/* -*- 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 <algorithm> + +#include "GeckoViewStreamListener.h" +#include "InetAddress.h" // for java::sdk::InetAddress and java::sdk::UnknownHostException +#include "ReferrerInfo.h" +#include "WebExecutorSupport.h" + +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIDNSService.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsINSSErrorsService.h" +#include "nsNetUtil.h" // for NS_NewURI, NS_NewChannel, NS_NewStreamLoader +#include "nsIPrivateBrowsingChannel.h" +#include "nsIUploadChannel2.h" +#include "nsIX509Cert.h" + +#include "mozilla/Preferences.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/net/DNS.h" // for NetAddr +#include "mozilla/java/GeckoWebExecutorWrappers.h" +#include "mozilla/java/WebMessageWrappers.h" +#include "mozilla/java/WebRequestErrorWrappers.h" +#include "mozilla/java/WebResponseWrappers.h" + +namespace mozilla { +using namespace net; + +namespace widget { + +static void CompleteWithError(java::GeckoResult::Param aResult, + nsresult aStatus, nsIChannel* aChannel) { + nsCOMPtr<nsINSSErrorsService> 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<jni::Throwable>()); +} + +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 + StreamStatus() override { return mClosed ? NS_BASE_STREAM_CLOSED : 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<jni::Throwable>()); + } + + private: + nsresult CompleteWithRecord(nsIDNSRecord* aRecord) { + nsTArray<NetAddr> addrs; + nsCOMPtr<nsIDNSAddrRecord> 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<java::sdk::InetAddress>(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<const int8_t*>(&addr.inet.ip), 4); + } else if (addr.raw.family == AF_INET6) { + bytes = jni::ByteArray::New( + reinterpret_cast<const int8_t*>(&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<java::WebMessage>()); + + // 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<nsIInputStream> stream = new ByteBufferStream(body); + + nsCOMPtr<nsIUploadChannel2> 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<nsIURI> 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<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrerUri); + rv = aHttpChannel->SetReferrerInfoWithoutClone(referrerInfo); + NS_ENSURE_SUCCESS(rv, rv); + + // Cache mode + nsCOMPtr<nsIHttpChannelInternal> 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); + + if (req->BeConservative()) { + rv = internalChannel->SetBeConservative(true); + 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<java::WebMessage>()); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), reqBase->Uri()->ToString()); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + + nsCOMPtr<nsIChannel> 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); + } + + bool shouldResistFingerprinting = + nsContentUtils::ShouldResistFingerprinting(channel); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + if (aFlags & java::GeckoWebExecutor::FETCH_FLAGS_PRIVATE) { + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel); + NS_ENSURE_TRUE(pbChannel, NS_ERROR_FAILURE); + pbChannel->SetPrivate(true); + cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::ePrivate, + shouldResistFingerprinting); + } else { + cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular, + shouldResistFingerprinting); + } + MOZ_ASSERT(cookieJarSettings); + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + loadInfo->SetCookieJarSettings(cookieJarSettings); + + // setup http/https specific things + nsCOMPtr<nsIHttpChannel> 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<LoaderListener> listener = + new LoaderListener(aResult, allowRedirects, testStreamFailure); + + rv = channel->SetNotificationCallbacks(listener); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally, open the channel + return channel->AsyncOpen(listener); +} + +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<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsICancelable> cancelable; + RefPtr<DNSListener> listener = new DNSListener(host, result); + rv = dns->AsyncResolveNative(host, nsIDNSService::RESOLVE_TYPE_DEFAULT, + nsIDNSService::RESOLVE_DEFAULT_FLAGS, 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<jni::Throwable>()); + } +} + +} // 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<WebExecutorSupport> { + 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 <typename Lambda, bool IsStatic = Lambda::isStatic, + typename InstanceType = typename Lambda::ThisArgType, + class Impl = typename Lambda::TargetClass> +class WindowEvent : public Runnable { + bool IsStaleCall() { + if (IsStatic) { + // Static calls are never stale. + return false; + } + + return jni::NativePtrTraits<Impl>::IsStale(mInstance); + } + + Lambda mLambda; + const InstanceType mInstance; + + public: + WindowEvent(Lambda&& aLambda, InstanceType&& aInstance) + : Runnable("mozilla::widget::WindowEvent"), + mLambda(std::move(aLambda)), + mInstance(std::forward<InstanceType>(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] +<field> = 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] +<field> = noLiteral:true + +[android.os.Build$VERSION] +<field> = 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] +<field> = skip:false diff --git a/widget/android/bindings/AndroidProcess-classes.txt b/widget/android/bindings/AndroidProcess-classes.txt new file mode 100644 index 0000000000..a6929ac5bc --- /dev/null +++ b/widget/android/bindings/AndroidProcess-classes.txt @@ -0,0 +1,5 @@ +[android.os.Process = skip:true] +setThreadPriority = +getThreadPriority = +myTid = +THREAD_PRIORITY_URGENT_AUDIO = 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] +<init>()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] +<init>(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..ebaff375d5 --- /dev/null +++ b/widget/android/bindings/JavaExceptions-classes.txt @@ -0,0 +1,8 @@ +[java.lang.IllegalStateException = skip:true] +<init>(Ljava/lang/String;)V = + +[java.lang.IllegalArgumentException = skip:true] +<init>(Ljava/lang/String;)V = + +[java.lang.Throwable = skip:true] +getMessage()Ljava/lang/String; = 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] +<field> = skip:false diff --git a/widget/android/bindings/MediaCodec-classes.txt b/widget/android/bindings/MediaCodec-classes.txt new file mode 100644 index 0000000000..8830c11ccf --- /dev/null +++ b/widget/android/bindings/MediaCodec-classes.txt @@ -0,0 +1,13 @@ +[android.media.MediaCodec = exceptionMode:nsresult] +[android.media.MediaCodec$BufferInfo = exceptionMode:nsresult] +[android.media.MediaCodec$CryptoInfo = exceptionMode:nsresult] + +# We only use constants from CodecCapabilities +[android.media.MediaCodecInfo$CodecCapabilities = skip:true] +<field> = skip:false + +# We only use constants from KeyStatus +[android.media.MediaDrm$KeyStatus = skip:true] +<field> = 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] +<field> = skip:false diff --git a/widget/android/bindings/SurfaceTexture-classes.txt b/widget/android/bindings/SurfaceTexture-classes.txt new file mode 100644 index 0000000000..554c8e7c3f --- /dev/null +++ b/widget/android/bindings/SurfaceTexture-classes.txt @@ -0,0 +1,5 @@ +[android.graphics.SurfaceTexture = exceptionMode:nsresult] +[android.view.Surface = exceptionMode:nsresult] +<init>(Landroid/view/SurfaceControl;)V = stubName:FromSurfaceControl, exceptionMode:abort +[android.view.SurfaceControl = exceptionMode:nsresult] +isValid()Z = exceptionMode:abort 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..49a7dbd9e0 --- /dev/null +++ b/widget/android/bindings/moz.build @@ -0,0 +1,54 @@ +# -*- 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", + "AndroidProcess", + "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..3b926771f3 --- /dev/null +++ b/widget/android/components.conf @@ -0,0 +1,100 @@ +# -*- 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_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{d594094c-28b6-466b-97d7-66c039c3dea9}', + 'contract_ids': ['@mozilla.org/gfx/parent/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'], + }, + { + 'cid': '{9d5adbb9-1da4-4162-acba-b373fe3ae837}', + 'contract_ids': ['@mozilla.org/widget/parent/clipboard;1'], + 'interfaces': ['nsIClipboard'], + 'type': 'nsClipboard', + 'headers': ['/widget/android/nsClipboard.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + { + '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': '{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'], + }, + { + 'name': 'GfxInfo', + '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': '{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 <jni.h> + +#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 <class Traits> + 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 Traits, typename ReturnType = typename Traits::ReturnType> +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<Traits>(ctx, rv); + } + + public: + template <typename... Args> + static ReturnType Call(const Context& ctx, nsresult* rv, + const Args&... args) { + JNIEnv* const env = ctx.Env(); + BeginAccess(ctx); + + jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...}; + + auto result = TypeAdapter<ReturnType>::ToNative( + env, Traits::isStatic ? (env->*TypeAdapter<ReturnType>::StaticCall)( + ctx.ClassRef(), sID, jargs) + : (env->*TypeAdapter<ReturnType>::Call)( + ctx.Get(), sID, jargs)); + + EndAccess(ctx, rv); + return result; + } +}; + +// Define sID member. +template <class T, typename R> +jmethodID Method<T, R>::sID; + +// Specialize void because C++ forbids us from +// using a "void" temporary result variable. +template <class Traits> +class Method<Traits, void> : public Method<Traits, bool> { + typedef Method<Traits, bool> Base; + typedef typename Traits::Owner::Context Context; + + public: + template <typename... Args> + static void Call(const Context& ctx, nsresult* rv, const Args&... args) { + JNIEnv* const env = ctx.Env(); + Base::BeginAccess(ctx); + + jvalue jargs[] = {Value(TypeAdapter<Args>::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 Traits> +class Constructor : protected Method<Traits, typename Traits::ReturnType> { + typedef typename Traits::Owner::Context Context; + typedef typename Traits::ReturnType ReturnType; + typedef Method<Traits, ReturnType> Base; + + public: + template <typename... Args> + static ReturnType Call(const Context& ctx, nsresult* rv, + const Args&... args) { + JNIEnv* const env = ctx.Env(); + Base::BeginAccess(ctx); + + jvalue jargs[] = {Value(TypeAdapter<Args>::FromNative(env, args)).val...}; + + auto result = TypeAdapter<ReturnType>::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 Traits> +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<Traits>(ctx, rv); + } + + public: + static GetterType Get(const Context& ctx, nsresult* rv) { + JNIEnv* const env = ctx.Env(); + BeginAccess(ctx); + + auto result = TypeAdapter<GetterType>::ToNative( + env, Traits::isStatic + ? + + (env->*TypeAdapter<GetterType>::StaticGet)(ctx.ClassRef(), sID) + : + + (env->*TypeAdapter<GetterType>::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<SetterType>::StaticSet)( + ctx.ClassRef(), sID, TypeAdapter<SetterType>::FromNative(env, val)); + } else { + (env->*TypeAdapter<SetterType>::Set)( + ctx.Get(), sID, TypeAdapter<SetterType>::FromNative(env, val)); + } + + EndAccess(ctx, rv); + } +}; + +// Define sID member. +template <class T> +jfieldID Field<T>::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..f7742b9535 --- /dev/null +++ b/widget/android/jni/Conversions.cpp @@ -0,0 +1,115 @@ +/* -*- 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 <class T> +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<java::sdk::Boolean>(env, "Z"); + gIntValueField = GetValueFieldID<java::sdk::Integer>(env, "I"); + gDoubleValueField = GetValueFieldID<java::sdk::Double>(env, "D"); +} + +template <> +bool Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + MOZ_ASSERT(aData.IsInstanceOf<jni::Boolean>()); + + 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<jni::Integer>()); + + 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<jni::Double>()); + + 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) { + // Bug 1819311: there is not much we can really catch due to how Android + // services are started, so for now we just expose it this way. + return ipc::LaunchError("Java2Native"); +} + +template <> +nsString Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + nsString result; + if (aData != NULL && aData.IsInstanceOf<jni::String>()) { + result = jni::String::Ref::From(aData)->ToString(); + } + return result; +} + +template <> +nsresult Java2Native(mozilla::jni::Object::Param aData, JNIEnv* aEnv) { + MOZ_ASSERT(aData.IsInstanceOf<jni::Throwable>()); + return NS_ERROR_FAILURE; +} + +} // 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 <typename ArgType> +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.cpp b/widget/android/jni/GeckoBundleUtils.cpp new file mode 100644 index 0000000000..310f284093 --- /dev/null +++ b/widget/android/jni/GeckoBundleUtils.cpp @@ -0,0 +1,309 @@ +/* -*- 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 "mozilla/jni/GeckoBundleUtils.h" + +#include "JavaBuiltins.h" +#include "js/Warnings.h" +#include "nsJSUtils.h" + +#include "js/Array.h" +#include "js/experimental/TypedData.h" + +namespace mozilla::jni { +namespace detail { +bool CheckJS(JSContext* aCx, bool aResult) { + if (!aResult) { + JS_ClearPendingException(aCx); + } + return aResult; +} + +nsresult BoxString(JSContext* aCx, JS::Handle<JS::Value> aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + return NS_OK; + } + + MOZ_ASSERT(aData.isString()); + + JS::Rooted<JSString*> 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(), fallible); + if (!aOut) { + return NS_ERROR_FAILURE; + } + 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<const jchar*>(chars), len)); + } + } + if (NS_WARN_IF(!CheckJS(aCx, !!chars) || !aOut)) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult BoxObject(JSContext* aCx, JS::Handle<JS::Value> aData, + jni::Object::LocalRef& aOut); + +template <typename Type, bool (JS::Value::*IsType)() const, + Type (JS::Value::*ToType)() const, class ArrayType, + typename ArrayType::LocalRef (*NewArray)(const Type*, size_t)> +nsresult BoxArrayPrimitive(JSContext* aCx, JS::Handle<JSObject*> aData, + jni::Object::LocalRef& aOut, size_t aLength, + JS::Handle<JS::Value> aElement) { + JS::Rooted<JS::Value> element(aCx); + auto data = MakeUnique<Type[]>(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; +} + +nsresult BoxByteArray(JSContext* aCx, JS::Handle<JSObject*> aData, + jni::Object::LocalRef& aOut) { + JS::AutoCheckCannotGC nogc; + bool isShared = false; + const void* data = JS_GetArrayBufferViewData(aData, &isShared, nogc); + size_t length = JS_GetArrayBufferViewByteLength(aData); + + aOut = jni::ByteArray::New(reinterpret_cast<const int8_t*>(data), length); + if (!aOut) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +template <class Type, + nsresult (*Box)(JSContext*, JS::Handle<JS::Value>, + jni::Object::LocalRef&), + typename IsType> +nsresult BoxArrayObject(JSContext* aCx, JS::Handle<JSObject*> aData, + jni::Object::LocalRef& aOut, size_t aLength, + JS::Handle<JS::Value> aElement, IsType&& aIsType) { + auto out = jni::ObjectArray::New<Type>(aLength); + JS::Rooted<JS::Value> 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::Handle<JSObject*> 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::Rooted<JS::Value> element(aCx); + NS_ENSURE_TRUE(CheckJS(aCx, JS_GetElement(aCx, aData, 0, &element)), + NS_ERROR_FAILURE); + + if (element.isBoolean()) { + return BoxArrayPrimitive<bool, &JS::Value::isBoolean, &JS::Value::toBoolean, + jni::BooleanArray, &jni::BooleanArray::New>( + aCx, aData, aOut, length, element); + } + + if (element.isInt32()) { + nsresult rv = + BoxArrayPrimitive<int32_t, &JS::Value::isInt32, &JS::Value::toInt32, + jni::IntArray, &jni::IntArray::New>(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<double, &JS::Value::isNumber, &JS::Value::toNumber, + jni::DoubleArray, &jni::DoubleArray::New>( + aCx, aData, aOut, length, element); + } + + if (element.isNullOrUndefined() || element.isString()) { + const auto isString = [](JS::Handle<JS::Value> val) -> bool { + return val.isString(); + }; + nsresult rv = BoxArrayObject<jni::String, &BoxString>( + 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::Handle<JS::Value> val) -> bool { + if (!val.isObject()) { + return false; + } + bool array = false; + JS::Rooted<JSObject*> 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<java::GeckoBundle, &BoxObject>( + aCx, aData, aOut, length, element, isObject); + } + + NS_WARNING("Unknown type"); + return NS_ERROR_INVALID_ARG; +} + +nsresult BoxValue(JSContext* aCx, JS::Handle<JS::Value> aData, + jni::Object::LocalRef& aOut); + +nsresult BoxObject(JSContext* aCx, JS::Handle<JS::Value> aData, + jni::Object::LocalRef& aOut) { + if (aData.isNullOrUndefined()) { + aOut = nullptr; + return NS_OK; + } + + MOZ_ASSERT(aData.isObject()); + + JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx)); + JS::Rooted<JSObject*> obj(aCx, &aData.toObject()); + + bool isArray = false; + if (CheckJS(aCx, JS::IsArrayObject(aCx, obj, &isArray)) && isArray) { + return BoxArray(aCx, obj, aOut); + } + + if (JS_IsTypedArrayObject(obj)) { + return BoxByteArray(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<jni::String>(length); + auto values = jni::ObjectArray::New<jni::Object>(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::Rooted<JS::Value> idVal(aCx); + JS::Rooted<JS::Value> 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::Rooted<JSString*> 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, "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::Handle<JS::Value> 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; +} + +} // namespace detail + +nsresult BoxData(JSContext* aCx, JS::Handle<JS::Value> aData, + jni::Object::LocalRef& aOut, bool aObjectOnly) { + nsresult rv = NS_ERROR_INVALID_ARG; + + if (!aObjectOnly) { + rv = detail::BoxValue(aCx, aData, aOut); + } else if (aData.isObject() || aData.isNullOrUndefined()) { + rv = detail::BoxObject(aCx, aData, aOut); + } + + return rv; +} +} // namespace mozilla::jni diff --git a/widget/android/jni/GeckoBundleUtils.h b/widget/android/jni/GeckoBundleUtils.h new file mode 100644 index 0000000000..a92a27abc3 --- /dev/null +++ b/widget/android/jni/GeckoBundleUtils.h @@ -0,0 +1,46 @@ +/* -*- 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" + +#include "jsapi.h" + +namespace mozilla { +namespace jni { + +#define GECKOBUNDLE_START(name) \ + nsTArray<jni::String::LocalRef> _##name##_keys; \ + nsTArray<jni::Object::LocalRef> _##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<jni::String>(_##name##_keys.Length()); \ + auto _##name##_jvalues = \ + jni::ObjectArray::New<jni::Object>(_##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); + +nsresult BoxData(JSContext* aCx, JS::Handle<JS::Value> aData, + jni::Object::LocalRef& aOut, bool aObjectOnly); + +} // 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..da103f35ee --- /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<GeckoResultCallback> { + public: + typedef java::GeckoResult::GeckoCallback::Natives<GeckoResultCallback> Base; + typedef std::function<void(mozilla::jni::Object::Param)> OuterCallback; + + void Call(mozilla::jni::Object::Param aArg) { mCallback(aArg); } + + template <typename ArgType> + static java::GeckoResult::GeckoCallback::LocalRef CreateAndAttach( + std::function<void(ArgType)>&& aInnerCallback) { + auto java = java::GeckoResult::GeckoCallback::New(); + OuterCallback outerCallback = + [inner{std::move(aInnerCallback)}](mozilla::jni::Object::Param aParam) { + ArgType converted = Java2Native<ArgType>(aParam); + inner(std::move(converted)); + }; + auto native = MakeUnique<GeckoResultCallback>(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..45b351ba21 --- /dev/null +++ b/widget/android/jni/Natives.h @@ -0,0 +1,1540 @@ +/* -*- 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 <jni.h> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "mozilla/RefPtr.h" +#include "mozilla/RWLock.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" + +#if defined(_MSC_VER) // MSVC +# define FUNCTION_SIGNATURE __FUNCSIG__ +#elif defined(__GNUC__) // GCC, Clang +# define FUNCTION_SIGNATURE __PRETTY_FUNCTION__ +#endif + +struct NativeException { + const char* str; +}; + +template <class T> +static NativeException NullHandle() { + return {FUNCTION_SIGNATURE}; +} + +template <class T> +static NativeException NullWeakPtr() { + return {FUNCTION_SIGNATURE}; +} + +namespace mozilla { + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise; + +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<MyClass> + * { + * // ... + * + * public: + * using MyJavaClass::Natives<MyClass>::DisposeNative; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives<MyClass>::AttachNative( + * instance, static_cast<SupportsWeakPtr*>(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<MyClass> + * , public MyJavaClass::Natives<MyClass> + * { + * // ... + * + * public: + * using MyJavaClass::Natives<MyClass>::DisposeNative; + * + * void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives<MyClass>::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<MyClass>). + * + * class MyClass : public MyJavaClass::Natives<MyClass> + * { + * // ... + * + * public: + * using MyJavaClass::Natives<MyClass>::DisposeNative; + * + * static void AttachTo(const MyJavaClass::LocalRef& instance) + * { + * MyJavaClass::Natives<MyClass>::AttachNative( + * instance, mozilla::MakeUnique<MyClass>()); + * + * // "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<nsIRunnable> aRunnable); + * }; + * + * constexpr bool foo = HasWeakNonIntrusiveDetach<Foo>::value; // Expect false + * constexpr bool bar = HasWeakNonIntrusiveDetach<Bar>::value; // Expect true + */ +template <typename, typename = std::void_t<>> +struct HasWeakNonIntrusiveDetach : std::false_type {}; + +template <typename T> +struct HasWeakNonIntrusiveDetach< + T, std::void_t<decltype(std::declval<T>().OnWeakNonIntrusiveDetach( + std::declval<already_AddRefed<nsIRunnable>>()))>> : 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<Foo>::value; // Expect false + * constexpr bool bar = IsRefCounted<Bar>::value; // Expect true + */ +template <typename, typename = std::void_t<>> +struct IsRefCounted : std::false_type {}; + +template <typename T> +struct IsRefCounted<T, std::void_t<decltype(std::declval<T>().AddRef(), + std::declval<T>().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 Impl> +class NativePtrInternalPicker { + // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK + template <class I> + static std::enable_if_t< + std::is_base_of<SupportsWeakPtr, I>::value, + char (&)[static_cast<size_t>(NativePtrInternalType::WEAK)]> + Test(char); + + // Enable if Impl implements AddRef and Release, yielding type REFPTR + template <class I, typename = decltype(&I::AddRef, &I::Release)> + static char (&Test(int))[static_cast<size_t>(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 <class> + static char (&Test(...))[static_cast<size_t>(NativePtrInternalType::OWNING)]; + + public: + // Given a hypothetical function call Test<Impl>, convert the size of its + // resulting array back into a NativePtrInternalType enum value. + static const NativePtrInternalType value = static_cast<NativePtrInternalType>( + sizeof(Test<Impl>('\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 Impl> +class NativePtrPicker { + // Just shorthand for each overload's return type + template <NativePtrType PtrType> + using ResultTypeT = char (&)[static_cast<size_t>(PtrType)]; + + // Enable if Impl derives from SupportsWeakPtr, yielding type WEAK_INTRUSIVE + template <typename I> + static auto Test(void*) + -> std::enable_if_t<std::is_base_of<SupportsWeakPtr, I>::value, + ResultTypeT<NativePtrType::WEAK_INTRUSIVE>>; + + // Enable if Impl implements OnWeakNonIntrusiveDetach, yielding type + // WEAK_NON_INTRUSIVE + template <typename I> + static auto Test(void*) + -> std::enable_if_t<HasWeakNonIntrusiveDetach<I>::value, + ResultTypeT<NativePtrType::WEAK_NON_INTRUSIVE>>; + + // 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 <typename I> + static auto Test(void*) -> std::enable_if_t< + std::conjunction_v<IsRefCounted<I>, + std::negation<HasWeakNonIntrusiveDetach<I>>>, + ResultTypeT<NativePtrType::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 <typename> + static char (&Test(...))[static_cast<size_t>(NativePtrType::OWNING)]; + + public: + // Given a hypothetical function call Test<Impl>, convert the size of its + // resulting array back into a NativePtrType enum value. + static const NativePtrType value = + static_cast<NativePtrType>(sizeof(Test<Impl>(nullptr))); +}; + +template <class Impl> +inline uintptr_t CheckNativeHandle(JNIEnv* env, uintptr_t handle) { + if (!handle) { + if (!env->ExceptionCheck()) { + ThrowException(env, "java/lang/NullPointerException", + NullHandle<Impl>().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 <class Impl, NativePtrType Type = NativePtrPicker<Impl>::value> +struct NativePtrTraits; + +template <class Impl> +struct NativePtrTraits<Impl, /* Type = */ NativePtrType::OWNING> { + 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<HandleType, RefType>::value, + "HandleType and RefType must be identical for owning pointers"); + return reinterpret_cast<HandleType>( + CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance))); + } + + /** + * Returns a RefType to the native implementation belonging to + * the given Java object. + */ + template <class LocalRef> + 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<AccessorType, RefType>::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 <class LocalRef> + static void Set(const LocalRef& instance, UniquePtr<Impl>&& ptr) { + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), + reinterpret_cast<uintptr_t>(ptr.release())); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + /** + * Clear the JNIObject's handle. + */ + template <class LocalRef> + static void Clear(const LocalRef& instance) { + UniquePtr<Impl> ptr(reinterpret_cast<RefType>( + 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 <class Impl> +struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_INTRUSIVE> { + using AccessorType = Impl*; + using HandleType = WeakPtr<Impl>*; + using RefType = WeakPtr<Impl>; + + static RefType Get(JNIEnv* env, jobject instance) { + const auto ptr = reinterpret_cast<HandleType>( + CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance))); + return *ptr; + } + + template <class LocalRef> + 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<Impl>().str); + } + + return impl; + } + + template <class LocalRef> + 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<uintptr_t>(new WeakPtr<Impl>(ptr)); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template <class LocalRef> + static void Clear(const LocalRef& instance) { + const auto ptr = reinterpret_cast<HandleType>( + 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 <class Impl> +struct NativePtrTraits<Impl, /* Type = */ NativePtrType::REFPTR> { + using AccessorType = Impl*; + using HandleType = RefPtr<Impl>*; + using RefType = Impl*; + + static RefType Get(JNIEnv* env, jobject instance) { + const auto ptr = reinterpret_cast<HandleType>( + CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance))); + if (!ptr) { + return nullptr; + } + + MOZ_ASSERT(*ptr); + return *ptr; + } + + template <class LocalRef> + 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<AccessorType, RefType>::value, + "AccessorType and RefType must be identical for refpointers"); + return aImpl; + } + + template <class LocalRef> + 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<uintptr_t>(new RefPtr<Impl>(ptr)); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template <class LocalRef> + static void Clear(const LocalRef& instance) { + const auto ptr = reinterpret_cast<HandleType>( + 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 <typename NativeImpl> +class NativeWeakPtr; +template <typename NativeImpl> +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<NativeImpl>::value> +struct NativeWeakPtrControlBlockStorageTraits; + +template <typename NativeImpl> +struct NativeWeakPtrControlBlockStorageTraits< + NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::OWNING> { + using Type = UniquePtr<NativeImpl>; + + static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } +}; + +template <typename NativeImpl> +struct NativeWeakPtrControlBlockStorageTraits< + NativeImpl, ::mozilla::jni::detail::NativePtrInternalType::REFPTR> { + using Type = RefPtr<NativeImpl>; + + static NativeImpl* AsRaw(const Type& aStorage) { return aStorage.get(); } +}; + +// Forward Declaration +template <typename NativeImpl> +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 <typename NativeImpl> +class MOZ_HEAP_CLASS NativeWeakPtrControlBlock final { + public: + using StorageTraits = NativeWeakPtrControlBlockStorageTraits<NativeImpl>; + 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; + } + + MOZ_PUSH_IGNORE_THREAD_SAFETY + void Lock() const { mLock.ReadLock(); } + + void Unlock() const { mLock.ReadUnlock(); } + MOZ_POP_THREAD_SAFETY + +#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<NativeImpl>; + friend class NativeWeakPtr<NativeImpl>; + friend class NativeWeakPtrHolder<NativeImpl>; + + private: + const mozilla::jni::Object::WeakRef mJavaOwner; + mutable RWLock mLock MOZ_UNANNOTATED; // Protects mNativeImpl + StorageType mNativeImpl; +}; + +/** + * 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 <typename NativeImpl> +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<NativeImpl>::AsRaw( + mCtlBlock->mNativeImpl); + } + + // This allows us to support calling a pointer to a member function + template <typename Member> + auto operator->*(Member aMember) const { + NativeImpl* impl = + NativeWeakPtrControlBlockStorageTraits<NativeImpl>::AsRaw( + mCtlBlock->mNativeImpl); + return [impl, member = aMember](auto&&... aArgs) { + return (impl->*member)(std::forward<decltype(aArgs)>(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 <typename I = NativeImpl> + auto AsRefPtr() const -> std::enable_if_t<IsRefCounted<I>::value, RefPtr<I>> { + 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<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock) + : mCtlBlock(aCtlBlock) { + if (aCtlBlock) { + aCtlBlock->Lock(); + } + } + + private: + friend class NativeWeakPtr<NativeImpl>; + friend class NativeWeakPtrHolder<NativeImpl>; + + private: + const RefPtr<NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock; +}; + +} // namespace detail + +using DetachPromise = mozilla::MozPromise<bool, nsresult, true>; + +/** + * 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 <typename NativeImpl> +class NativeWeakPtr { + public: + using Accessor = detail::Accessor<NativeImpl>; + + /** + * 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> 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. + */ + RefPtr<DetachPromise> Detach(); + + /** + * 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<detail::NativeWeakPtrControlBlock<NativeImpl>>& + 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<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock) + : mCtlBlock(aCtlBlock) {} + + private: + // Construction of subsequent NativeWeakPtrs for aCtlBlock + explicit NativeWeakPtr( + const RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>>& aCtlBlock) + : mCtlBlock(aCtlBlock) {} + + friend class NativeWeakPtrHolder<NativeImpl>; + + protected: + RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> 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<Runnable> aDisposer); + * }; + * + * java::Object::LocalRef javaObj(...); + * + * // Create a new Foo that is attached to javaObj + * auto weakFoo = NativeWeakPtrHolder<NativeFoo>::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 <typename NativeImpl> +class MOZ_HEAP_CLASS NativeWeakPtrHolder final + : public NativeWeakPtr<NativeImpl> { + using Base = NativeWeakPtr<NativeImpl>; + + public: + using Accessor = typename Base::Accessor; + using StorageTraits = + typename detail::NativeWeakPtrControlBlock<NativeImpl>::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 <typename Cls, typename JNIType, typename... Args> + static NativeWeakPtr<NativeImpl> Attach(const Ref<Cls, JNIType>& aJavaObject, + Args&&... aArgs) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + StorageType nativeImpl(new NativeImpl(std::forward<Args>(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 <typename Cls, typename JNIType> + static NativeWeakPtr<NativeImpl> AttachExisting( + const Ref<Cls, JNIType>& aJavaObject, + already_AddRefed<NativeImpl> 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 <typename Cls> + NativeWeakPtrHolder(const LocalRef<Cls>& aJavaObject, + StorageType&& aNativeImpl) + : NativeWeakPtr<NativeImpl>( + do_AddRef(new NativeWeakPtrControlBlock<NativeImpl>( + aJavaObject, std::move(aNativeImpl)))) {} + + /** + * Internal function that actually wraps the native pointer, binds it to the + * JNIObject, and then returns the NativeWeakPtr result. + */ + template <typename Cls, typename JNIType> + static NativeWeakPtr<NativeImpl> AttachInternal( + const Ref<Cls, JNIType>& aJavaObject, StorageType&& aPtr) { + auto localJavaObject = ToLocalRef(aJavaObject); + NativeWeakPtrHolder<NativeImpl>* holder = + new NativeWeakPtrHolder<NativeImpl>(localJavaObject, std::move(aPtr)); + static_assert( + NativePtrPicker<NativeImpl>::value == NativePtrType::WEAK_NON_INTRUSIVE, + "This type is not compatible with mozilla::jni::NativeWeakPtr"); + NativePtrTraits<NativeImpl>::Set(localJavaObject, holder); + return NativeWeakPtr<NativeImpl>(holder->mCtlBlock); + } +}; + +namespace detail { + +/** + * NativePtrTraits for the WEAK_NON_INTRUSIVE pointer type. + */ +template <class Impl> +struct NativePtrTraits<Impl, /* Type = */ NativePtrType::WEAK_NON_INTRUSIVE> { + using AccessorType = typename NativeWeakPtrHolder<Impl>::Accessor; + using HandleType = NativeWeakPtrHolder<Impl>*; + using RefType = NativeWeakPtrHolder<Impl>* const; + + static RefType Get(JNIEnv* env, jobject instance) { + return GetHandle(env, instance); + } + + template <typename Cls> + static RefType Get(const LocalRef<Cls>& instance) { + return GetHandle(instance.Env(), instance.Get()); + } + + static AccessorType Access(RefType aPtr) { return aPtr->Access(); } + + template <typename Cls> + static void Set(const LocalRef<Cls>& instance, HandleType ptr) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + const uintptr_t handle = reinterpret_cast<uintptr_t>(ptr); + Clear(instance); + SetNativeHandle(instance.Env(), instance.Get(), handle); + MOZ_CATCH_JNI_EXCEPTION(instance.Env()); + } + + template <typename Cls> + static void Clear(const LocalRef<Cls>& instance) { + auto ptr = reinterpret_cast<HandleType>( + 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 <typename Cls> + static void ClearFinish(const LocalRef<Cls>& instance) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + JNIEnv* const env = instance.Env(); + auto ptr = + reinterpret_cast<HandleType>(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 <class LocalRef> + 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<HandleType>(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<HandleType>( + CheckNativeHandle<Impl>(env, GetNativeHandle(env, instance))); + } + + template <typename Cls> + static HandleType GetHandle(const LocalRef<Cls>& instance) { + return GetHandle(instance.Env(), instance.Get()); + } + + friend class NativeWeakPtrHolder<Impl>; +}; + +} // 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<MyClass> + * { + * // ... + * + * template<class Functor> + * class ProxyRunnable final : public Runnable + * { + * Functor mCall; + * public: + * ProxyRunnable(Functor&& call) : mCall(std::move(call)) {} + * virtual void run() override { mCall(); } + * }; + * + * public: + * template<class Functor> + * 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 <typename T> +struct ProxyArg { + static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>, + "T must be primitive type"); + + // Primitive types can be saved by value. + typedef T Type; + typedef typename TypeAdapter<T>::JNIType JNIType; + + static void Clear(JNIEnv* env, Type&) {} + + static Type From(JNIEnv* env, JNIType val) { + return TypeAdapter<T>::ToNative(env, val); + } +}; + +template <class C, typename T> +struct ProxyArg<Ref<C, T>> { + // Ref types need to be saved by global ref. + typedef typename C::GlobalRef Type; + typedef typename TypeAdapter<Ref<C, T>>::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 <typename C> +struct ProxyArg<const C&> : ProxyArg<C> {}; +template <> +struct ProxyArg<StringParam> : ProxyArg<String::Ref> {}; +template <class C> +struct ProxyArg<LocalRef<C>> : ProxyArg<typename C::Ref> {}; + +// ProxyNativeCall implements the functor object that is passed to OnNativeCall +template <class Impl, class Owner, bool IsStatic, + bool HasThisArg /* has instance/class local ref in the call */, + typename... Args> +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<IsStatic, Class, Owner>; + using ThisArgJNIType = std::conditional_t<IsStatic, jclass, jobject>; + + // 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<HasThisArg, void (*)(const Class::LocalRef&, Args...), + void (*)(Args...)>, + 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. + std::tuple<typename ProxyArg<Args>::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 <bool Static, bool ThisArg, size_t... Indices> + std::enable_if_t<Static && ThisArg, void> Call( + const Class::LocalRef& cls, std::index_sequence<Indices...>) const { + (*mNativeCall)(cls, std::get<Indices>(mArgs)...); + } + + template <bool Static, bool ThisArg, size_t... Indices> + std::enable_if_t<Static && !ThisArg, void> Call( + const Class::LocalRef& cls, std::index_sequence<Indices...>) const { + (*mNativeCall)(std::get<Indices>(mArgs)...); + } + + template <bool Static, bool ThisArg, size_t... Indices> + std::enable_if_t<!Static && ThisArg, void> Call( + const typename Owner::LocalRef& inst, + std::index_sequence<Indices...>) const { + auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst)); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(inst, std::get<Indices>(mArgs)...); + } + + template <bool Static, bool ThisArg, size_t... Indices> + std::enable_if_t<!Static && !ThisArg, void> Call( + const typename Owner::LocalRef& inst, + std::index_sequence<Indices...>) const { + auto impl = NativePtrTraits<Impl>::Access(NativePtrTraits<Impl>::Get(inst)); + MOZ_CATCH_JNI_EXCEPTION(inst.Env()); + (impl->*mNativeCall)(std::get<Indices>(mArgs)...); + } + + template <size_t... Indices> + void Clear(JNIEnv* env, std::index_sequence<Indices...>) { + int dummy[] = { + (ProxyArg<Args>::Clear(env, std::get<Indices>(mArgs)), 0)...}; + mozilla::Unused << dummy; + } + + static decltype(auto) GetNativeObject(Class::Param thisArg) { + return nullptr; + } + + static decltype(auto) GetNativeObject(typename Owner::Param thisArg) { + return NativePtrTraits<Impl>::Access( + NativePtrTraits<Impl>::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<Args>::JNIType... args) + : mNativeCall(nativeCall), + mThisArg(env, ThisArgClass::Ref::From(thisArg)), + mArgs(ProxyArg<Args>::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 <typename T> + 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 <typename T> + void SetTarget(T&&) const { + MOZ_CRASH(); + } + + void operator()() { + JNIEnv* const env = GetEnvForThread(); + typename ThisArgClass::LocalRef thisArg(env, mThisArg); + Call<IsStatic, HasThisArg>(thisArg, std::index_sequence_for<Args...>{}); + + // 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<Args...>{}); + mThisArg.Clear(env); + } +}; + +template <class Impl, bool HasThisArg, typename... Args> +struct Dispatcher { + template <class Traits, bool IsStatic = Traits::isStatic, + typename... ProxyArgs> + static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::PROXY, void> + Run(ProxyArgs&&... args) { + Impl::OnNativeCall( + ProxyNativeCall<Impl, typename Traits::Owner, IsStatic, HasThisArg, + Args...>(std::forward<ProxyArgs>(args)...)); + } + + template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg, + typename... ProxyArgs> + 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<Impl, typename Traits::Owner, IsStatic, HasThisArg, + Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr, + std::forward<ProxyArgs>(args)...); + DispatchToGeckoPriorityQueue( + NS_NewRunnableFunction("PriorityNativeCall", std::move(proxy))); + } + + template <class Traits, bool IsStatic = Traits::isStatic, typename ThisArg, + typename... ProxyArgs> + static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::GECKO, 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<Impl, typename Traits::Owner, IsStatic, HasThisArg, + Args...>((HasThisArg || !IsStatic) ? thisArg : nullptr, + std::forward<ProxyArgs>(args)...); + NS_DispatchToMainThread( + NS_NewRunnableFunction("GeckoNativeCall", std::move(proxy))); + } + + template <class Traits, bool IsStatic = false, typename... ProxyArgs> + static std::enable_if_t<Traits::dispatchTarget == DispatchTarget::CURRENT, + void> + 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 Traits, class Impl, class Args = typename Traits::Args> +class NativeStub; + +template <class Traits, class Impl, typename... Args> +class NativeStub<Traits, Impl, jni::Args<Args...>> { + using Owner = typename Traits::Owner; + using ReturnType = typename Traits::ReturnType; + + static constexpr bool isStatic = Traits::isStatic; + static constexpr bool isVoid = std::is_void_v<ReturnType>; + + struct VoidType { + using JNIType = void; + }; + using ReturnJNIType = + typename std::conditional_t<isVoid, VoidType, + TypeAdapter<ReturnType>>::JNIType; + + using ReturnTypeForNonVoidInstance = + std::conditional_t<!isStatic && !isVoid, ReturnType, VoidType>; + using ReturnTypeForVoidInstance = + std::conditional_t<!isStatic && isVoid, ReturnType, VoidType&>; + using ReturnTypeForNonVoidStatic = + std::conditional_t<isStatic && !isVoid, ReturnType, VoidType>; + using ReturnTypeForVoidStatic = + std::conditional_t<isStatic && isVoid, ReturnType, VoidType&>; + + static_assert(Traits::dispatchTarget == DispatchTarget::CURRENT || isVoid, + "Dispatched calls must have void return type"); + + public: + // Non-void instance method + template <ReturnTypeForNonVoidInstance (Impl::*Method)(Args...)> + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto impl = NativePtrTraits<Impl>::Access( + NativePtrTraits<Impl>::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return ReturnJNIType(); + } + return TypeAdapter<ReturnType>::FromNative( + env, (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...)); + } + + // Non-void instance method with instance reference + template <ReturnTypeForNonVoidInstance (Impl::*Method)( + const typename Owner::LocalRef&, Args...)> + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto impl = NativePtrTraits<Impl>::Access( + NativePtrTraits<Impl>::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<ReturnType>::FromNative( + env, (impl->*Method)(self, TypeAdapter<Args>::ToNative(env, args)...)); + self.Forget(); + return res; + } + + // Void instance method + template <ReturnTypeForVoidInstance (Impl::*Method)(Args...)> + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>( + instance, Method, env, args...); + return; + } + + auto impl = NativePtrTraits<Impl>::Access( + NativePtrTraits<Impl>::Get(env, instance)); + if (!impl) { + // There is a pending JNI exception at this point. + return; + } + (impl->*Method)(TypeAdapter<Args>::ToNative(env, args)...); + } + + // Void instance method with instance reference + template <ReturnTypeForVoidInstance (Impl::*Method)( + const typename Owner::LocalRef&, Args...)> + static MOZ_JNICALL void Wrap(JNIEnv* env, jobject instance, + typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>( + instance, Method, env, args...); + return; + } + + auto impl = NativePtrTraits<Impl>::Access( + NativePtrTraits<Impl>::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<Args>::ToNative(env, args)...); + self.Forget(); + } + + // Overload for DisposeNative + template <ReturnTypeForVoidInstance (*DisposeNative)( + const typename Owner::LocalRef&)> + 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<Impl, /* HasThisArg */ false, const LocalRef&>::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 <ReturnTypeForNonVoidStatic (*Method)(Args...)> + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jclass, typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + return TypeAdapter<ReturnType>::FromNative( + env, (*Method)(TypeAdapter<Args>::ToNative(env, args)...)); + } + + // Non-void static method with class reference + template <ReturnTypeForNonVoidStatic (*Method)(const Class::LocalRef&, + Args...)> + static MOZ_JNICALL ReturnJNIType + Wrap(JNIEnv* env, jclass cls, typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + auto clazz = Class::LocalRef::Adopt(env, cls); + const auto res = TypeAdapter<ReturnType>::FromNative( + env, (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...)); + clazz.Forget(); + return res; + } + + // Void static method + template <ReturnTypeForVoidStatic (*Method)(Args...)> + static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, + typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher<Impl, /* HasThisArg */ false, Args...>::template Run<Traits>( + cls, Method, env, args...); + return; + } + + (*Method)(TypeAdapter<Args>::ToNative(env, args)...); + } + + // Void static method with class reference + template <ReturnTypeForVoidStatic (*Method)(const Class::LocalRef&, Args...)> + static MOZ_JNICALL void Wrap(JNIEnv* env, jclass cls, + typename TypeAdapter<Args>::JNIType... args) { + MOZ_ASSERT_JNI_THREAD(Traits::callingThread); + + if (Traits::dispatchTarget != DispatchTarget::CURRENT) { + Dispatcher<Impl, /* HasThisArg */ true, Args...>::template Run<Traits>( + cls, Method, env, args...); + return; + } + + auto clazz = Class::LocalRef::Adopt(env, cls); + (*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...); + clazz.Forget(); + } +}; + +// Generate a JNINativeMethod from a native +// method's traits class and a wrapped stub. +template <class Traits, typename Ret, typename... Args> +constexpr JNINativeMethod MakeNativeMethod(MOZ_JNICALL Ret (*stub)(JNIEnv*, + Args...)) { + return {Traits::name, Traits::signature, reinterpret_cast<void*>(stub)}; +} + +// Class inherited by implementing class. +template <class Cls, class Impl> +class NativeImpl { + typedef typename Cls::template Natives<Impl> 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<Impl>::value == NativePtrType::WEAK_INTRUSIVE, + "Use another AttachNative for non-WeakPtr usage"); + return NativePtrTraits<Impl>::Set(instance, static_cast<Impl*>(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, + UniquePtr<Impl>&& ptr) { + static_assert(NativePtrPicker<Impl>::value == NativePtrType::OWNING, + "Use another AttachNative for WeakPtr or RefPtr usage"); + return NativePtrTraits<Impl>::Set(instance, std::move(ptr)); + } + + static void AttachNative(const typename Cls::LocalRef& instance, Impl* ptr) { + static_assert(NativePtrPicker<Impl>::value == NativePtrType::REFPTR, + "Use another AttachNative for non-RefPtr usage"); + return NativePtrTraits<Impl>::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<Impl>::Get(instance); + } + + static void DisposeNative(const typename Cls::LocalRef& instance) { + NativePtrTraits<Impl>::Clear(instance); + } + + NativeImpl() { + // Initialize on creation if not already initialized. + Init(); + } +}; + +// Define static member. +template <class C, class I> +bool NativeImpl<C, I>::sInited; + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Natives_h__ diff --git a/widget/android/jni/NativesInlines.h b/widget/android/jni/NativesInlines.h new file mode 100644 index 0000000000..858b677715 --- /dev/null +++ b/widget/android/jni/NativesInlines.h @@ -0,0 +1,116 @@ +/* -*- 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 "Natives.h" + +#include "mozilla/MozPromise.h" + +namespace mozilla::jni { + +namespace details { + +/** + * 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 <typename NativeImpl> +class NativeWeakPtrDetachRunnable final : public Runnable { + public: + NativeWeakPtrDetachRunnable( + already_AddRefed<detail::NativeWeakPtrControlBlock<NativeImpl>> aCtlBlock, + const Object::LocalRef& aOwner, + typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::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<NativeImpl>::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<NativeImpl>::ClearFinish(owner); + } + + // Now we destroy that native object. + mNativeImpl = nullptr; + mHolder.Resolve(true, __func__); + return NS_OK; + } + + RefPtr<DetachPromise> GetPromise() { return mHolder.Ensure(__func__); } + + private: + ~NativeWeakPtrDetachRunnable() { + // Guard against somebody forgetting to call this runnable. + MOZ_RELEASE_ASSERT(mHasRun, "You must run/dispatch this runnable!"); + } + + private: + RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock; + Object::GlobalRef mOwner; + MozPromiseHolder<DetachPromise> mHolder; + typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::Type mNativeImpl; + bool mHasRun; +}; + +} // namespace details + +template <typename NativeImpl> +RefPtr<DetachPromise> NativeWeakPtr<NativeImpl>::Detach() { + if (!IsAttached()) { + // Never attached to begin with; no-op + return DetachPromise::CreateAndResolve(true, __func__); + } + + auto native = mCtlBlock->Clear(); + if (!native) { + // Detach already in progress + return DetachPromise::CreateAndResolve(true, __func__); + } + + 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<NativeImpl>::StorageTraits::AsRaw( + native); + RefPtr<details::NativeWeakPtrDetachRunnable<NativeImpl>> runnable = + new details::NativeWeakPtrDetachRunnable<NativeImpl>( + mCtlBlock.forget(), owner, std::move(native)); + RefPtr<DetachPromise> promise = runnable->GetPromise(); + rawImpl->OnWeakNonIntrusiveDetach(runnable.forget()); + return promise; +} + +} // namespace mozilla::jni diff --git a/widget/android/jni/Refs.h b/widget/android/jni/Refs.h new file mode 100644 index 0000000000..9b50703491 --- /dev/null +++ b/widget/android/jni/Refs.h @@ -0,0 +1,1117 @@ +/* -*- 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 <jni.h> + +#include <utility> + +#include "mozilla/fallible.h" +#include "mozilla/jni/Utils.h" +#include "mozilla/jni/TypeAdapter.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 Cls, typename JNIType> +class Ref; +// Represents a calling context for JNI methods. +template <class Cls, typename JNIType> +class Context; +// Wrapped local reference that inherits from Ref. +template <class Cls> +class LocalRef; +// Wrapped global reference that inherits from Ref. +template <class Cls> +class GlobalRef; +// Wrapped weak reference that inherits from Ref. +template <class Cls> +class WeakRef; +// Wrapped dangling reference that's owned by someone else. +template <class Cls> +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<int32_t, jni::String::Param> +template <typename...> +struct Args {}; + +class Object; + +// Base class for Ref and its specializations. +template <class Cls, typename Type> +class Ref { + template <class C, typename T> + friend class Ref; + + using Self = Ref<Cls, Type>; + 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<Cls, Type> { + CopyableCtx(JNIEnv* env, Type instance) + : Context<Cls, Type>(env, instance) {} + + CopyableCtx(const CopyableCtx& cls) + : Context<Cls, Type>(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<Cls, Type>; + + 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<Cls, Type> From(JNIType obj) { return Ref<Cls, Type>(obj); } + + // Construct a Ref form a generic object reference. + static Ref<Cls, Type> From(const Ref<Object, jobject>& obj) { + return Ref<Cls, Type>(JNIType(obj.Get())); + } + + MOZ_IMPLICIT Ref(decltype(nullptr)) : mInstance(nullptr) {} + + // Get the raw JNI reference. + JNIType Get() const { return mInstance; } + + template <class T> + bool IsInstanceOf() const { + return FindEnv()->IsInstanceOf(mInstance, typename T::Context().ClassRef()); + } + + template <class T> + typename T::Ref Cast() const { +#ifdef MOZ_CHECK_JNI + MOZ_RELEASE_ASSERT(FindEnv()->IsAssignableFrom( + Context<Cls, Type>().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<Object, jobject>() const { + return Ref<Object, jobject>(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 Cls, typename Type> +class Context : public Ref<Cls, Type> { + using Ref = jni::Ref<Cls, Type>; + + 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 <class T> + 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<Cls, Type>& operator*() const { return *this; } +}; + +template <class C, typename T> +jclass Context<C, T>::sClassRef; + +template <class Cls, typename Type = jobject> +class ObjectBase { + protected: + const jni::Context<Cls, Type>& mCtx; + + jclass ClassRef() const { return mCtx.ClassRef(); } + JNIEnv* Env() const { return mCtx.Env(); } + Type Instance() const { return mCtx.Get(); } + + public: + using Ref = jni::Ref<Cls, Type>; + using Context = jni::Context<Cls, Type>; + using LocalRef = jni::LocalRef<Cls>; + using GlobalRef = jni::GlobalRef<Cls>; + using WeakRef = jni::WeakRef<Cls>; + 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<Cls*>(this); } +}; + +// Binding for a plain jobject. +class Object : public ObjectBase<Object, jobject> { + public: + explicit Object(const Context& ctx) : ObjectBase<Object, jobject>(ctx) {} +}; + +// Binding for a built-in object reference other than jobject. +template <typename T> +class TypedObject : public ObjectBase<TypedObject<T>, T> { + public: + explicit TypedObject(const Context<TypedObject<T>, T>& ctx) + : ObjectBase<TypedObject<T>, T>(ctx) {} +}; + +// Binding for a boxed primitive object. +template <typename T> +class BoxedObject : public ObjectBase<BoxedObject<T>, jobject> { + public: + explicit BoxedObject(const Context<BoxedObject<T>, jobject>& ctx) + : ObjectBase<BoxedObject<T>, jobject>(ctx) {} +}; + +template <> +const char ObjectBase<Object, jobject>::name[]; +template <> +const char ObjectBase<TypedObject<jstring>, jstring>::name[]; +template <> +const char ObjectBase<TypedObject<jclass>, jclass>::name[]; +template <> +const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[]; +template <> +const char ObjectBase<BoxedObject<jboolean>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jbyte>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jchar>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jshort>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jint>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jlong>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jfloat>, jobject>::name[]; +template <> +const char ObjectBase<BoxedObject<jdouble>, jobject>::name[]; +template <> +const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[]; +template <> +const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[]; +template <> +const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[]; +template <> +const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[]; +template <> +const char ObjectBase<TypedObject<jintArray>, jintArray>::name[]; +template <> +const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[]; +template <> +const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[]; +template <> +const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[]; +template <> +const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[]; + +// Define bindings for built-in types. +using String = TypedObject<jstring>; +using Class = TypedObject<jclass>; +using Throwable = TypedObject<jthrowable>; + +using Boolean = BoxedObject<jboolean>; +using Byte = BoxedObject<jbyte>; +using Character = BoxedObject<jchar>; +using Short = BoxedObject<jshort>; +using Integer = BoxedObject<jint>; +using Long = BoxedObject<jlong>; +using Float = BoxedObject<jfloat>; +using Double = BoxedObject<jdouble>; + +using BooleanArray = TypedObject<jbooleanArray>; +using ByteArray = TypedObject<jbyteArray>; +using CharArray = TypedObject<jcharArray>; +using ShortArray = TypedObject<jshortArray>; +using IntArray = TypedObject<jintArray>; +using LongArray = TypedObject<jlongArray>; +using FloatArray = TypedObject<jfloatArray>; +using DoubleArray = TypedObject<jdoubleArray>; +using ObjectArray = TypedObject<jobjectArray>; + +namespace detail { + +// See explanation in LocalRef. +template <class Cls> +struct GenericObject { + using Type = Object; +}; +template <> +struct GenericObject<Object> { + struct Type { + using Ref = jni::Ref<Type, jobject>; + using Context = jni::Context<Type, jobject>; + }; +}; +template <class Cls> +struct GenericLocalRef { + template <class C> + struct Type : jni::Object {}; +}; +template <> +struct GenericLocalRef<Object> { + template <class C> + using Type = jni::LocalRef<C>; +}; + +} // namespace detail + +template <class Cls> +class LocalRef : public Cls::Context { + template <class C> + 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<Object> to LocalRef<Cls>, we + // need constructors and copy assignment operators that take in a + // LocalRef<Object> argument. However, if Cls *is* Object, we would have + // duplicated constructors and operators with LocalRef<Object> arguments. To + // avoid this conflict, we use GenericObject, which is defined as Object for + // LocalRef<non-Object> and defined as a dummy class for LocalRef<Object>. + using GenericObject = typename detail::GenericObject<Cls>::Type; + + // Similarly, GenericLocalRef is useed to convert LocalRef<Cls> to, + // LocalRef<Object>. It's defined as LocalRef<C> for Cls == Object, + // and defined as a dummy template class for Cls != Object. + template <class C> + using GenericLocalRef = + typename detail::GenericLocalRef<Cls>::template Type<C>; + + 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<Cls>& ref) + : Ctx(ref.mEnv, NewLocalRef(ref.mEnv, ref.mInstance)) {} + + // Move constructor. + LocalRef(LocalRef<Cls>&& 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<Object> into a LocalRef<Cls> without + // creating/deleting local references. + MOZ_IMPLICIT LocalRef(LocalRef<GenericObject>&& ref) + : Ctx(ref.mEnv, JNIType(ref.mInstance)) { + ref.mInstance = nullptr; + } + + template <class C> + MOZ_IMPLICIT LocalRef(GenericLocalRef<C>&& 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<Cls>& operator=(LocalRef<Cls> ref) & { return swap(ref); } + + LocalRef<Cls>& operator=(const Ref& ref) & { + LocalRef<Cls> newRef(Ctx::mEnv, ref); + return swap(newRef); + } + + LocalRef<Cls>& operator=(LocalRef<GenericObject>&& ref) & { + LocalRef<Cls> newRef(std::move(ref)); + return swap(newRef); + } + + template <class C> + LocalRef<Cls>& operator=(GenericLocalRef<C>&& ref) & { + LocalRef<Cls> newRef(std::move(ref)); + return swap(newRef); + } + + LocalRef<Cls>& operator=(decltype(nullptr)) & { + LocalRef<Cls> newRef(Ctx::mEnv, nullptr); + return swap(newRef); + } +}; + +template <class Cls> +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<Cls>& 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<Cls>& operator=(GlobalRef<Cls> ref) & { return swap(ref); } + + GlobalRef<Cls>& operator=(const Ref& ref) & { + GlobalRef<Cls> newRef(ref); + return swap(newRef); + } + + GlobalRef<Cls>& operator=(const LocalRef<Cls>& ref) & { + GlobalRef<Cls> newRef(ref); + return swap(newRef); + } + + GlobalRef<Cls>& operator=(decltype(nullptr)) & { + GlobalRef<Cls> newRef(nullptr); + return swap(newRef); + } +}; + +template <class Cls> +class WeakRef : public Ref<Cls, jweak> { + using Ref = Ref<Cls, jweak>; + 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<Cls>& 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<Cls>& operator=(WeakRef<Cls> ref) & { return swap(ref); } + + WeakRef<Cls>& operator=(const Ref& ref) & { + WeakRef<Cls> newRef(ref); + return swap(newRef); + } + + WeakRef<Cls>& operator=(const LocalRef<Cls>& ref) & { + WeakRef<Cls> newRef(ref); + return swap(newRef); + } + + WeakRef<Cls>& operator=(decltype(nullptr)) & { + WeakRef<Cls> newRef(nullptr); + return swap(newRef); + } + + void operator->() const = delete; + void operator*() const = delete; +}; + +template <class Cls> +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<jstring> : public ObjectBase<TypedObject<jstring>, jstring> { + using Base = ObjectBase<TypedObject<jstring>, 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<const char16_t*>(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<const jchar*>(str.BeginReading()), str.Length()); + if (!result) { + NS_ABORT_OOM(str.Length() * sizeof(char16_t)); + } + MOZ_CATCH_JNI_EXCEPTION(env); + return result; + } + + static jstring GetString(JNIEnv* env, const nsAString& str, + const fallible_t&) { + const jstring result = env->NewString( + reinterpret_cast<const jchar*>(str.BeginReading()), str.Length()); + if (env->ExceptionCheck()) { +#ifdef MOZ_CHECK_JNI + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } + return result; + } + + static jstring GetString(JNIEnv* env, const nsACString& str, + const fallible_t& aFallible) { + nsAutoString utf16; + if (!CopyUTF8toUTF16(str, utf16, aFallible)) { + return nullptr; + } + return GetString(env, utf16, aFallible); + } + + 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 nsAString& str, JNIEnv* env, + const fallible_t& aFallible) + : Ref(GetString(env, str, aFallible)), 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 nsACString& str, JNIEnv* env, + const fallible_t& aFallible) + : Ref(GetString(env, str, aFallible)), 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 <typename T> +struct TypeAdapter; +} + +// Ref specialization for arrays. +template <typename JNIType, class ElementType> +class ArrayRefBase : public ObjectBase<TypedObject<JNIType>, JNIType> { + protected: + using Base = ObjectBase<TypedObject<JNIType>, JNIType>; + + public: + explicit ArrayRefBase(const Context<TypedObject<JNIType>, JNIType>& ctx) + : Base(ctx) {} + + static typename Base::LocalRef New(const ElementType* data, size_t length) { + using JNIElemType = typename detail::TypeAdapter<ElementType>::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<ElementType>::NewArray)(length); + MOZ_CATCH_JNI_EXCEPTION(jenv); + (jenv->*detail::TypeAdapter<ElementType>::SetArray)( + result, jsize(0), length, reinterpret_cast<const JNIElemType*>(data)); + MOZ_CATCH_JNI_EXCEPTION(jenv); + return Base::LocalRef::Adopt(jenv, result); + } + + static typename Base::LocalRef New(const ElementType* data, size_t length, + const fallible_t&) { + using JNIElemType = typename detail::TypeAdapter<ElementType>::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<ElementType>::NewArray)(length); + if (jenv->ExceptionCheck()) { + if (!IsOOMException(jenv)) { + // This exception isn't excepted due not to OOM. This is unrecoverable + // error. + MOZ_CATCH_JNI_EXCEPTION(jenv); + } +#ifdef MOZ_CHECK_JNI + jenv->ExceptionDescribe(); +#endif + jenv->ExceptionClear(); + return Base::LocalRef::Adopt(jenv, nullptr); + } + (jenv->*detail::TypeAdapter<ElementType>::SetArray)( + result, jsize(0), length, reinterpret_cast<const JNIElemType*>(data)); + if (jenv->ExceptionCheck()) { + if (!IsOOMException(jenv)) { + // This exception isn't excepted due not to OOM. This is unrecoverable + // error. + MOZ_CATCH_JNI_EXCEPTION(jenv); + } +#ifdef MOZ_CHECK_JNI + jenv->ExceptionDescribe(); +#endif + jenv->ExceptionClear(); + jenv->DeleteLocalRef(result); + return Base::LocalRef::Adopt(jenv, nullptr); + } + 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<ElementType>::JNIType; + static_assert(sizeof(ElementType) == sizeof(JNIElemType), + "Size of native type must match size of JNI type"); + + ElementType ret; + (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)( + Base::Instance(), jsize(index), 1, + reinterpret_cast<JNIElemType*>(&ret)); + MOZ_CATCH_JNI_EXCEPTION(Base::Env()); + return ret; + } + + nsTArray<ElementType> GetElements() const { + using JNIElemType = typename detail::TypeAdapter<ElementType>::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<ElementType> 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<ElementType>::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<ElementType>::GetArray)( + Base::Instance(), 0, jsize(amountToCopy), + reinterpret_cast<JNIElemType*>(buffer)); + return amountToCopy; + } + + ElementType operator[](size_t index) const { return GetElement(index); } + + operator nsTArray<ElementType>() const { return GetElements(); } +}; + +#define DEFINE_PRIMITIVE_ARRAY_REF_HEADER(JNIType, ElementType) \ + template <> \ + class TypedObject<JNIType> : public ArrayRefBase<JNIType, ElementType> { \ + public: \ + explicit TypedObject(const Context& ctx) \ + : ArrayRefBase<JNIType, ElementType>(ctx) {} \ + static typename Base::LocalRef From(const nsTArray<ElementType>& aArray) { \ + return New(aArray.Elements(), aArray.Length()); \ + } + +#define DEFINE_PRIMITIVE_ARRAY_REF_FOOTER } + +#define DEFINE_PRIMITIVE_ARRAY_REF_FROM_IMPLICIT_CONVERSION(ElementType, \ + ConvertFromType) \ + static typename Base::LocalRef From( \ + const nsTArray<ConvertFromType>& aArray) { \ + return New(reinterpret_cast<const ElementType*>(aArray.Elements()), \ + aArray.Length()); \ + } + +#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType) \ + DEFINE_PRIMITIVE_ARRAY_REF_HEADER(JNIType, ElementType) \ + DEFINE_PRIMITIVE_ARRAY_REF_FOOTER + +#define DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION( \ + JNIType, ElementType, ConvertFromType) \ + DEFINE_PRIMITIVE_ARRAY_REF_HEADER(JNIType, ElementType) \ + DEFINE_PRIMITIVE_ARRAY_REF_FROM_IMPLICIT_CONVERSION(ElementType, \ + ConvertFromType) \ + DEFINE_PRIMITIVE_ARRAY_REF_FOOTER + +DEFINE_PRIMITIVE_ARRAY_REF(jbooleanArray, bool); +DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jbyteArray, int8_t, + uint8_t); +DEFINE_PRIMITIVE_ARRAY_REF(jcharArray, char16_t); +DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jshortArray, int16_t, + uint16_t); +DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jintArray, int32_t, + uint32_t); +DEFINE_PRIMITIVE_ARRAY_REF(jfloatArray, float); +DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION(jlongArray, int64_t, + uint64_t); +DEFINE_PRIMITIVE_ARRAY_REF(jdoubleArray, double); + +#undef DEFINE_PRIMITIVE_ARRAY_REF +#undef DEFINE_PRIMITIVE_ARRAY_REF_WITH_IMPLICIT_CONVERSION +#undef DEFINE_PRIMITIVE_ARRAY_HEADER +#undef DEFINE_PRIMITIVE_ARRAY_FROM_IMPLICIT_CONVERSION +#undef DEFINE_PRIMITIVE_ARRAY_FOOTER + +class ByteBuffer : public ObjectBase<ByteBuffer, jobject> { + public: + explicit ByteBuffer(const Context& ctx) + : ObjectBase<ByteBuffer, jobject>(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<ByteBuffer, jobject>::name[]; + +template <> +class TypedObject<jobjectArray> + : public ObjectBase<TypedObject<jobjectArray>, jobjectArray> { + using Base = ObjectBase<TypedObject<jobjectArray>, jobjectArray>; + + public: + template <class Cls = Object> + 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<Object::LocalRef> GetElements() const { + const jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance())); + + nsTArray<Object::LocalRef> 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<Object::LocalRef>() 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<T>* to LocalRef<Object>*: +// LocalRef<Foo> foo; +// Foo::GetFoo(&foo); // error because parameter type is LocalRef<Object>*. +// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument. +template <class Cls> +class ReturnToLocal { + private: + LocalRef<Cls>* const localRef; + LocalRef<Object> objRef; + + public: + explicit ReturnToLocal(LocalRef<Cls>* ref) : localRef(ref) {} + operator LocalRef<Object>*() { return &objRef; } + + ~ReturnToLocal() { + if (objRef) { + *localRef = std::move(objRef); + } + } +}; + +template <class Cls> +ReturnToLocal<Cls> ReturnTo(LocalRef<Cls>* ref) { + return ReturnToLocal<Cls>(ref); +} + +// Support conversion from GlobalRef<T>* to LocalRef<Object/T>*: +// GlobalRef<Foo> foo; +// Foo::GetFoo(&foo); // error because parameter type is LocalRef<Foo>*. +// Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument. +template <class Cls> +class ReturnToGlobal { + private: + GlobalRef<Cls>* const globalRef; + LocalRef<Object> objRef; + LocalRef<Cls> clsRef; + + public: + explicit ReturnToGlobal(GlobalRef<Cls>* ref) : globalRef(ref) {} + operator LocalRef<Object>*() { return &objRef; } + operator LocalRef<Cls>*() { return &clsRef; } + + ~ReturnToGlobal() { + if (objRef) { + *globalRef = (clsRef = std::move(objRef)); + } else if (clsRef) { + *globalRef = clsRef; + } + } +}; + +template <class Cls> +ReturnToGlobal<Cls> ReturnTo(GlobalRef<Cls>* ref) { + return ReturnToGlobal<Cls>(ref); +} + +// Make a LocalRef<T> from any other Ref<T> +template <typename Cls, typename JNIType> +LocalRef<Cls> ToLocalRef(const Ref<Cls, JNIType>& aRef) { + return LocalRef<Cls>(aRef); +} + +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Refs_h__ diff --git a/widget/android/jni/TypeAdapter.h b/widget/android/jni/TypeAdapter.h new file mode 100644 index 0000000000..5ab1e14bf5 --- /dev/null +++ b/widget/android/jni/TypeAdapter.h @@ -0,0 +1,71 @@ +/* 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_TypeAdapter_h__ +#define mozilla_jni_TypeAdapter_h__ + +#include <jni.h> + +#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 <typename T> +struct TypeAdapter; + +#define DEFINE_PRIMITIVE_TYPE_ADAPTER(NativeType, JNIType, JNIName) \ + \ + template <> \ + struct TypeAdapter<NativeType> { \ + 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<JNIType>(val); \ + } \ + static NativeType ToNative(JNIEnv*, JNIType val) { \ + return static_cast<NativeType>(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 +} // namespace jni +} // namespace mozilla + +#endif // mozilla_jni_Types_h__ diff --git a/widget/android/jni/Types.h b/widget/android/jni/Types.h new file mode 100644 index 0000000000..1fe5fa4400 --- /dev/null +++ b/widget/android/jni/Types.h @@ -0,0 +1,123 @@ +/* -*- 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 <jni.h> + +#include "mozilla/jni/Refs.h" +#include "mozilla/jni/TypeAdapter.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. + +// TypeAdapter<LocalRef<Cls>> applies when jobject is a return value. +template <class Cls> +struct TypeAdapter<LocalRef<Cls>> { + 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<Cls> ToNative(JNIEnv* env, jobject instance) { + return LocalRef<Cls>::Adopt(env, JNIType(instance)); + } + + static JNIType FromNative(JNIEnv*, LocalRef<Cls>&& 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 <class Cls> +constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Call)( + jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; +template <class Cls> +constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticCall)( + jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; +template <class Cls> +constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::Get)(jobject, jfieldID); +template <class Cls> +constexpr jobject (JNIEnv::*TypeAdapter<LocalRef<Cls>>::StaticGet)(jclass, + jfieldID); + +// TypeAdapter<Ref<Cls>> applies when jobject is a parameter value. +template <class Cls, typename T> +struct TypeAdapter<Ref<Cls, T>> { + using JNIType = typename Ref<Cls, T>::JNIType; + + static constexpr auto Set = &JNIEnv::SetObjectField; + static constexpr auto StaticSet = &JNIEnv::SetStaticObjectField; + + static DependentRef<Cls> ToNative(JNIEnv* env, JNIType instance) { + return DependentRef<Cls>(instance); + } + + static JNIType FromNative(JNIEnv*, const Ref<Cls, T>& instance) { + return instance.Get(); + } +}; + +template <class Cls, typename T> +constexpr void (JNIEnv::*TypeAdapter<Ref<Cls, T>>::Set)(jobject, jfieldID, + jobject); +template <class Cls, typename T> +constexpr void (JNIEnv::*TypeAdapter<Ref<Cls, T>>::StaticSet)(jclass, jfieldID, + jobject); + +// jstring has its own Param type. +template <> +struct TypeAdapter<StringParam> : public TypeAdapter<String::Ref> {}; + +template <class Cls> +struct TypeAdapter<const Cls&> : public TypeAdapter<Cls> {}; + +} // 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..08164e4950 --- /dev/null +++ b/widget/android/jni/Utils.cpp @@ -0,0 +1,348 @@ +/* -*- 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 <android/log.h> +#include <pthread.h> + +#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<NativeType>::Call)( \ + jobject, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \ + constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticCall)( \ + jclass, jmethodID, CallArgs::JValueType) MOZ_JNICALL_ABI; \ + constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::Get)(jobject, jfieldID) \ + ABIName; \ + constexpr JNIType (JNIEnv::*TypeAdapter<NativeType>::StaticGet)( \ + jclass, jfieldID) ABIName; \ + constexpr void (JNIEnv::*TypeAdapter<NativeType>::Set)(jobject, jfieldID, \ + JNIType) ABIName; \ + constexpr void (JNIEnv::*TypeAdapter<NativeType>::StaticSet)( \ + jclass, jfieldID, JNIType) ABIName; \ + constexpr void (JNIEnv::*TypeAdapter<NativeType>::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<Object, jobject>::name[] = "java/lang/Object"; +template <> +const char ObjectBase<TypedObject<jstring>, jstring>::name[] = + "java/lang/String"; +template <> +const char ObjectBase<TypedObject<jclass>, jclass>::name[] = "java/lang/Class"; +template <> +const char ObjectBase<TypedObject<jthrowable>, jthrowable>::name[] = + "java/lang/Throwable"; +template <> +const char ObjectBase<BoxedObject<jboolean>, jobject>::name[] = + "java/lang/Boolean"; +template <> +const char ObjectBase<BoxedObject<jbyte>, jobject>::name[] = "java/lang/Byte"; +template <> +const char ObjectBase<BoxedObject<jchar>, jobject>::name[] = + "java/lang/Character"; +template <> +const char ObjectBase<BoxedObject<jshort>, jobject>::name[] = "java/lang/Short"; +template <> +const char ObjectBase<BoxedObject<jint>, jobject>::name[] = "java/lang/Integer"; +template <> +const char ObjectBase<BoxedObject<jlong>, jobject>::name[] = "java/lang/Long"; +template <> +const char ObjectBase<BoxedObject<jfloat>, jobject>::name[] = "java/lang/Float"; +template <> +const char ObjectBase<BoxedObject<jdouble>, jobject>::name[] = + "java/lang/Double"; +template <> +const char ObjectBase<TypedObject<jbooleanArray>, jbooleanArray>::name[] = "[Z"; +template <> +const char ObjectBase<TypedObject<jbyteArray>, jbyteArray>::name[] = "[B"; +template <> +const char ObjectBase<TypedObject<jcharArray>, jcharArray>::name[] = "[C"; +template <> +const char ObjectBase<TypedObject<jshortArray>, jshortArray>::name[] = "[S"; +template <> +const char ObjectBase<TypedObject<jintArray>, jintArray>::name[] = "[I"; +template <> +const char ObjectBase<TypedObject<jlongArray>, jlongArray>::name[] = "[J"; +template <> +const char ObjectBase<TypedObject<jfloatArray>, jfloatArray>::name[] = "[F"; +template <> +const char ObjectBase<TypedObject<jdoubleArray>, jdoubleArray>::name[] = "[D"; +template <> +const char ObjectBase<TypedObject<jobjectArray>, jobjectArray>::name[] = + "[Ljava/lang/Object;"; +template <> +const char ObjectBase<ByteBuffer, jobject>::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<JNIEnv*>(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<uintptr_t>( + env->GetLongField(instance, sJNIObjectHandleField)); +} + +void SetNativeHandle(JNIEnv* env, jobject instance, uintptr_t handle) { + if (!EnsureJNIObject(env, instance)) { + return; + } + + env->SetLongField(instance, sJNIObjectHandleField, + static_cast<jlong>(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<nsIRunnable> aCall) { + class RunnableEvent : public nsAppShell::Event { + nsCOMPtr<nsIRunnable> mCall; + + public: + explicit RunnableEvent(already_AddRefed<nsIRunnable> aCall) + : mCall(aCall) {} + void Run() override { NS_ENSURE_SUCCESS_VOID(mCall->Run()); } + }; + + nsAppShell::PostEvent(MakeUnique<RunnableEvent>(std::move(aCall))); +} + +int GetAPIVersion() { + static int32_t apiVersion = 0; + if (!apiVersion && IsAvailable()) { + apiVersion = java::sdk::Build::VERSION::SDK_INT(); + } + return apiVersion; +} + +pid_t GetUIThreadId() { + static pid_t uiThreadId; + if (!uiThreadId) { + uiThreadId = pid_t(java::GeckoThread::UiThreadId()); + } + return uiThreadId; +} + +bool IsOOMException(JNIEnv* aEnv) { + MOZ_ASSERT(aEnv->ExceptionCheck()); + Throwable::LocalRef e = + Throwable::LocalRef::Adopt(aEnv, aEnv->ExceptionOccurred()); + return sOOMErrorClass && aEnv->IsInstanceOf(e.Get(), sOOMErrorClass); +} + +} // namespace jni +} // namespace mozilla diff --git a/widget/android/jni/Utils.h b/widget/android/jni/Utils.h new file mode 100644 index 0000000000..adcafeb44e --- /dev/null +++ b/widget/android/jni/Utils.h @@ -0,0 +1,150 @@ +/* -*- 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 <jni.h> + +#include "nsIRunnable.h" + +#include "mozilla/UniquePtr.h" + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) +# define MOZ_CHECK_JNI +#endif + +#ifdef MOZ_CHECK_JNI +# include <unistd.h> +# 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<nsIRunnable> aCall); + +int GetAPIVersion(); + +pid_t GetUIThreadId(); + +bool IsOOMException(JNIEnv* aEnv); + +} // 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..53bef6663f --- /dev/null +++ b/widget/android/jni/moz.build @@ -0,0 +1,36 @@ +# -*- 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", + "NativesInlines.h", + "Refs.h", + "TypeAdapter.h", + "Types.h", + "Utils.h", +] + +UNIFIED_SOURCES += [ + "Conversions.cpp", + "GeckoBundleUtils.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..ca05de848b --- /dev/null +++ b/widget/android/moz.build @@ -0,0 +1,201 @@ +# -*- 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") + 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", + "CompositorSurfaceManager", + "EnterpriseRoots", + "EventCallback", + "EventDispatcher", + "GeckoAppShell", + "GeckoAudioInfo", + "GeckoBatteryManager", + "GeckoBundle", + "GeckoEditableChild", + "GeckoHLSDemuxerWrapper", + "GeckoHLSResourceWrapper", + "GeckoHLSSample", + "GeckoInputStream", + "GeckoJavaSampler", + "GeckoNetworkManager", + "GeckoProcessManager", + "GeckoProcessType", + "GeckoResult", + "GeckoRuntime", + "GeckoServiceChildProcess", + "GeckoServiceGpuProcess", + "GeckoSession", + "GeckoSurface", + "GeckoSurfaceTexture", + "GeckoSystemStateListener", + "GeckoThread", + "GeckoVRManager", + "GeckoVideoInfo", + "GeckoWebExecutor", + "HardwareCodecCapabilityUtils", + "Image", + "ImageDecoder", + "MediaDrmProxy", + "PanZoomController", + "RuntimeTelemetry", + "Sample", + "SampleBuffer", + "ScreenManagerHelper", + "ServiceAllocator", + "SessionAccessibility", + "SessionKeyInfo", + "SessionTextInput", + "SpeechSynthesisService", + "SurfaceAllocator", + "SurfaceControlManager", + "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", + "CompositorWidgetChild.h", + "CompositorWidgetParent.h", + "EventDispatcher.h", + "GeckoViewSupport.h", + "InProcessAndroidCompositorWidget.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", + "CompositorWidgetChild.cpp", + "CompositorWidgetParent.cpp", + "EventDispatcher.cpp", + "GeckoEditableSupport.cpp", + "GeckoProcessManager.cpp", + "GfxInfo.cpp", + "ImageDecoderSupport.cpp", + "InProcessAndroidCompositorWidget.cpp", + "nsAppShell.cpp", + "nsClipboard.cpp", + "nsDeviceContextAndroid.cpp", + "nsLookAndFeel.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", + "/toolkit/components/telemetry", + "/widget", + "/widget/headless", + "/xpcom/threads", +] + +OS_LIBS += ["android"] + +if CONFIG["MOZ_NATIVE_DEVICES"]: + DEFINES["MOZ_NATIVE_DEVICES"] = True + +# DEFINES['DEBUG_WIDGETS'] = True diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp new file mode 100644 index 0000000000..28389184a0 --- /dev/null +++ b/widget/android/nsAppShell.cpp @@ -0,0 +1,753 @@ +/* -*- 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 "gfxConfig.h" +#include "nsExceptionHandler.h" +#include "nsIScreen.h" +#include "nsWindow.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsIAppStartup.h" +#include "nsIGeolocationProvider.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/AppShutdown.h" +#include "mozilla/Components.h" +#include "mozilla/Services.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.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/GeckoResultWrappers.h" +#include "mozilla/java/GeckoThreadNatives.h" +#include "mozilla/java/XPCOMEventTargetNatives.h" +#include "mozilla/widget/ScreenManager.h" +#include "prenv.h" +#include "prtime.h" + +#include "AndroidBridge.h" +#include "AndroidBridgeUtilities.h" +#include "AndroidSurfaceTexture.h" +#include <android/log.h> +#include <pthread.h> +#include <wchar.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 "GeckoSystemStateListener.h" +#include "GeckoTelemetryDelegate.h" +#include "GeckoVRManager.h" +#include "ImageDecoderSupport.h" +#include "JavaBuiltins.h" +#include "ScreenHelperAndroid.h" +#include "Telemetry.h" +#include "WebExecutorSupport.h" +#include "Base64UtilsSupport.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<Mutex> nsAppShell::sAppShellLock; + +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<nsIPowerManagerService> sPowerManagerService = nullptr; +StaticRefPtr<WakeLockListener> sWakeLockListener; + +class GeckoThreadSupport final + : public java::GeckoThread::Natives<GeckoThreadSupport> { + // 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<nsIIOService> ioServ = do_GetIOService(); + nsCOMPtr<nsISpeculativeConnect> specConn = do_QueryInterface(ioServ); + if (!specConn) { + return; + } + + nsCOMPtr<nsIURI> uri = nsAppShell::ResolveURI(aUriStr->ToCString()); + if (!uri) { + return; + } + + OriginAttributes attrs; + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(uri, attrs); + specConn->SpeculativeConnect(uri, principal, nullptr, false); + } + + 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<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + obsServ->NotifyObservers(nullptr, "application-background", nullptr); + + obsServ->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); + + // 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*>(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; + } + + // We didn't return from one of our own activities, so restore + // to foreground status + nsCOMPtr<nsIObserverService> 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<nsIAppStartup> 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<GeckoAppShellSupport> { + 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<nsIObserverService> 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<float, 4> 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: + 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) { + if (!gLocationCallback) { + return; + } + + static constexpr float kEpsilon = 0.0001f; + double heading = (aHeading >= kEpsilon && aHeading < (360.0f - kEpsilon) && + aSpeed > kEpsilon) + ? aHeading + : UnspecifiedNaN<double>(); + + RefPtr<nsIDOMGeoPosition> geoPosition(new nsGeoPosition( + aLatitude, aLongitude, aAltitude, aAccuracy, aAltitudeAccuracy, heading, + aSpeed, PR_Now() / PR_USEC_PER_MSEC)); + 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()); + } + + static bool IsParentProcess() { return XRE_IsParentProcess(); } + + static jni::Object::LocalRef IsGpuProcessEnabled() { + java::GeckoResult::GlobalRef result = java::GeckoResult::New(); + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "GeckoAppShellSupport::IsGpuProcessEnabled", [result]() { + result->Complete(gfx::gfxConfig::IsEnabled(gfx::Feature::GPU_PROCESS) + ? java::sdk::Boolean::TRUE() + : java::sdk::Boolean::FALSE()); + })); + + return jni::Object::Ref::From(result); + } +}; + +class XPCOMEventTargetWrapper final + : public java::XPCOMEventTarget::Natives<XPCOMEventTargetWrapper> { + public: + // Wraps a java runnable into an XPCOM runnable and dispatches it to mTarget. + void DispatchNative(mozilla::jni::Object::Param aJavaRunnable) { + if (AppShutdown::GetCurrentShutdownPhase() >= + ShutdownPhase::XPCOMShutdownThreads) { + // No point in trying to dispatch this if we're already shutting down. + return; + } + 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<XPCOMEventTargetWrapper>::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<nsIEventTarget> aTarget) { + auto java = java::XPCOMEventTarget::New(); + auto native = MakeUnique<XPCOMEventTargetWrapper>(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<nsIEventTarget> aTarget) + : mTarget(aTarget) {} + + private: + nsCOMPtr<nsIEventTarget> 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::widget::Telemetry::Init(); + mozilla::widget::GeckoTelemetryDelegate::Init(); + + if (XRE_IsGPUProcess()) { + mozilla::gl::AndroidSurfaceTexture::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<ScreenHelperAndroid>()); + + // 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::GeckoSystemStateListener::Init(); + mozilla::widget::Telemetry::Init(); + mozilla::widget::ImageDecoderSupport::Init(); + mozilla::widget::WebExecutorSupport::Init(); + mozilla::widget::Base64UtilsSupport::Init(); + nsWindow::InitNatives(); + mozilla::gl::AndroidSurfaceTexture::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(); } + +nsresult nsAppShell::Init() { + nsresult rv = nsBaseAppShell::Init(); + nsCOMPtr<nsIObserverService> 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); + + 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::Exit(void) { + { + // 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::Exit(); +} + +NS_IMETHODIMP +nsAppShell::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + bool removeObserver = false; + + 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<nsIAppStartup> 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<dom::Document> doc = do_QueryInterface(aSubject); + MOZ_ASSERT(doc); + if (const RefPtr<nsWindow> 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<nsIAppStartup> appStartup = components::AppStartup::Service(); + if (appStartup) { + appStartup->ExitLastWindowClosingSurvivalArea(); + } + } + removeObserver = true; + + } 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<mozIDOMWindowProxy> domWindow = do_QueryInterface(aSubject); + MOZ_ASSERT(domWindow); + nsCOMPtr<nsIWidget> 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<nsIDocShell> docShell = do_QueryInterface(aSubject); + widget::GeckoEditableSupport::SetOnBrowserChild( + dom::BrowserChild::GetFrom(docShell)); + } else { + return nsBaseAppShell::Observe(aSubject, aTopic, aData); + } + + if (removeObserver) { + nsCOMPtr<nsIObserverService> 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<Event> 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<Event> (*eventFactory)(UniquePtr<Event>&&), + 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<Event> runAndNotifyEvent = + mozilla::MakeUnique<LambdaEvent<decltype(runAndNotify)>>( + 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<nsIURI> nsAppShell::ResolveURI(const nsCString& aUriStr) { + nsCOMPtr<nsIIOService> ioServ = do_GetIOService(); + nsCOMPtr<nsIURI> uri; + + if (NS_SUCCEEDED( + ioServ->NewURI(aUriStr, nullptr, nullptr, getter_AddRefs(uri)))) { + return uri.forget(); + } + + nsCOMPtr<nsIURIFixup> fixup = components::URIFixup::Service(); + nsCOMPtr<nsIURIFixupInfo> 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.InsertOrUpdate(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..9b1dc5ca14 --- /dev/null +++ b/widget/android/nsAppShell.h @@ -0,0 +1,217 @@ +/* -*- 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 <time.h> + +#include <type_traits> +#include <utility> + +#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<Event> { + 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<const uintptr_t*>(this) == + *reinterpret_cast<const uintptr_t*>(other); + } + + virtual ~Event() {} + virtual void Run() = 0; + + virtual void PostTo(mozilla::LinkedList<Event>& queue) { + queue.insertBack(this); + } + + virtual bool IsUIEvent() const { return false; } + }; + + template <typename T> + 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<Event> baseEvent; + + public: + explicit ProxyEvent(mozilla::UniquePtr<Event>&& event) + : baseEvent(std::move(event)) {} + + void PostTo(mozilla::LinkedList<Event>& 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<MyEvent>()); + template <typename T, typename D> + static void PostEvent(mozilla::UniquePtr<T, D>&& 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 <typename T> + static void PostEvent(T&& lambda) { + mozilla::MutexAutoLock lock(*sAppShellLock); + if (!sAppShell) { + return; + } + sAppShell->mEventQueue.Post( + mozilla::MakeUnique<LambdaEvent<T>>(std::move(lambda))); + } + + // Post a event and wait for it to finish running on the Gecko thread. + static bool SyncRunEvent( + Event&& event, + mozilla::UniquePtr<Event> (*eventFactory)(mozilla::UniquePtr<Event>&&) = + nullptr, + const mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever()); + + template <typename T> + static std::enable_if_t<!std::is_base_of<Event, T>::value, void> SyncRunEvent( + T&& lambda) { + SyncRunEvent(LambdaEvent<T>(std::forward<T>(lambda))); + } + + static already_AddRefed<nsIURI> ResolveURI(const nsCString& aUriStr); + + protected: + static nsAppShell* sAppShell; + static mozilla::StaticAutoPtr<mozilla::Mutex> sAppShellLock; + + virtual ~nsAppShell(); + + NS_IMETHOD Exit() override; + 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<NativeCallbackEvent>(this)); + } + + class Queue { + private: + mozilla::Monitor mMonitor MOZ_UNANNOTATED; + mozilla::LinkedList<Event> mQueue; + + public: + enum { LATENCY_UI, LATENCY_OTHER, LATENCY_COUNT }; + Queue() : mMonitor("nsAppShell.Queue") {} + + void Signal() { + mozilla::MonitorAutoLock lock(mMonitor); + lock.NotifyAll(); + } + + void Post(mozilla::UniquePtr<Event>&& 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<Event> Pop(bool mayWait) { + mozilla::MonitorAutoLock lock(mMonitor); + + if (mayWait && mQueue.isEmpty()) { + lock.Wait(); + } + + // Ownership of event object transfers to the return value. + mozilla::UniquePtr<Event> event(mQueue.popFirst()); + if (!event || !event->mPostTime) { + return event; + } + + return event; + } + + } mEventQueue; + + private: + mozilla::CondVar mSyncRunFinished; + bool mSyncRunQuit; + + nsInterfaceHashtable<nsStringHashKey, nsIObserver> mObserversHash; +}; + +#endif // nsAppShell_h__ diff --git a/widget/android/nsClipboard.cpp b/widget/android/nsClipboard.cpp new file mode 100644 index 0000000000..37bec159d0 --- /dev/null +++ b/widget/android/nsClipboard.cpp @@ -0,0 +1,182 @@ +/* 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_INHERITED0(nsClipboard, ClipboardSetDataHelper) + +/* The Android clipboard only supports text and doesn't support mime types + * so we assume all clipboard data is text/plain for now. Documentation + * indicates that support for other data types is planned for future + * releases. + */ + +NS_IMETHODIMP +nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable, + nsIClipboardOwner* aOwner, + int32_t aWhichClipboard) { + if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED; + + if (!jni::IsAvailable()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsTArray<nsCString> flavors; + aTransferable->FlavorsTransferableCanImport(flavors); + + nsAutoString html; + nsAutoString text; + + for (auto& flavorStr : flavors) { + if (flavorStr.EqualsLiteral(kTextMime)) { + nsCOMPtr<nsISupports> item; + nsresult rv = + aTransferable->GetTransferData(kTextMime, getter_AddRefs(item)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(item); + if (supportsString) { + supportsString->GetData(text); + } + } else if (flavorStr.EqualsLiteral(kHTMLMime)) { + nsCOMPtr<nsISupports> item; + nsresult rv = + aTransferable->GetTransferData(kHTMLMime, getter_AddRefs(item)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + nsCOMPtr<nsISupportsString> 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<nsCString> flavors; + aTransferable->FlavorsTransferableCanImport(flavors); + + for (auto& flavorStr : flavors) { + if (flavorStr.EqualsLiteral(kTextMime) || + flavorStr.EqualsLiteral(kHTMLMime)) { + auto text = java::Clipboard::GetData( + java::GeckoAppShell::GetApplicationContext(), flavorStr); + if (!text) { + continue; + } + nsString buffer = text->ToString(); + if (buffer.IsEmpty()) { + continue; + } + nsCOMPtr<nsISupports> 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; +} + +RefPtr<GenericPromise> nsClipboard::AsyncGetData(nsITransferable* aTransferable, + int32_t aWhichClipboard) { + nsresult rv = GetData(aTransferable, aWhichClipboard); + if (NS_FAILED(rv)) { + return GenericPromise::CreateAndReject(rv, __func__); + } + + return GenericPromise::CreateAndResolve(true, __func__); +} + +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<nsCString>& 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; +} + +RefPtr<DataFlavorsPromise> nsClipboard::AsyncHasDataMatchingFlavors( + const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) { + nsTArray<nsCString> results; + for (const auto& flavor : aFlavorList) { + bool hasMatchingFlavor = false; + nsresult rv = HasDataMatchingFlavors(AutoTArray<nsCString, 1>{flavor}, + aWhichClipboard, &hasMatchingFlavor); + if (NS_SUCCEEDED(rv) && hasMatchingFlavor) { + results.AppendElement(flavor); + } + } + + return DataFlavorsPromise::CreateAndResolve(std::move(results), __func__); +} + +NS_IMETHODIMP +nsClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard, bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = kGlobalClipboard == aWhichClipboard; + return NS_OK; +} diff --git a/widget/android/nsClipboard.h b/widget/android/nsClipboard.h new file mode 100644 index 0000000000..365a7f85f1 --- /dev/null +++ b/widget/android/nsClipboard.h @@ -0,0 +1,41 @@ +/* -*- 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 "nsBaseClipboard.h" + +class nsClipboard final : public ClipboardSetDataHelper { + private: + ~nsClipboard() = default; + + public: + nsClipboard() = default; + + NS_DECL_ISUPPORTS_INHERITED + + // nsIClipboard + NS_IMETHOD GetData(nsITransferable* aTransferable, + int32_t aWhichClipboard) override; + NS_IMETHOD EmptyClipboard(int32_t aWhichClipboard) override; + NS_IMETHOD HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList, + int32_t aWhichClipboard, + bool* _retval) override; + NS_IMETHOD IsClipboardTypeSupported(int32_t aWhichClipboard, + bool* _retval) override; + RefPtr<mozilla::GenericPromise> AsyncGetData( + nsITransferable* aTransferable, int32_t aWhichClipboard) override; + RefPtr<DataFlavorsPromise> AsyncHasDataMatchingFlavors( + const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) override; + + protected: + // Implement the native clipboard behavior. + NS_IMETHOD SetNativeClipboardData(nsITransferable* aTransferable, + nsIClipboardOwner* aOwner, + int32_t aWhichClipboard) override; +}; + +#endif diff --git a/widget/android/nsDeviceContextAndroid.cpp b/widget/android/nsDeviceContextAndroid.cpp new file mode 100644 index 0000000000..e6643ef9b3 --- /dev/null +++ b/widget/android/nsDeviceContextAndroid.cpp @@ -0,0 +1,100 @@ +/* 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 "nsIFileStreams.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAnonymousTemporaryFile.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +NS_IMPL_ISUPPORTS(nsDeviceContextSpecAndroid, nsIDeviceContextSpec) + +nsDeviceContextSpecAndroid::~nsDeviceContextSpecAndroid() { + if (mTempFile) { + mTempFile->Remove(false); + } +} + +already_AddRefed<PrintTarget> nsDeviceContextSpecAndroid::MakePrintTarget() { + double width, height; + mPrintSettings->GetEffectiveSheetSize(&width, &height); + + // convert twips to points + width /= TWIPS_PER_POINT_FLOAT; + height /= TWIPS_PER_POINT_FLOAT; + + auto stream = [&]() -> nsCOMPtr<nsIOutputStream> { + if (mPrintSettings->GetOutputDestination() == + nsIPrintSettings::kOutputDestinationStream) { + nsCOMPtr<nsIOutputStream> out; + mPrintSettings->GetOutputStream(getter_AddRefs(out)); + return out; + } + if (NS_FAILED( + NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(mTempFile)))) { + return nullptr; + } + // Print to printer not supported... + nsCOMPtr<nsIFileOutputStream> s = + do_CreateInstance("@mozilla.org/network/file-output-stream;1"); + if (NS_FAILED(s->Init(mTempFile, -1, -1, 0))) { + return nullptr; + } + return s; + }(); + + return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height)); +} + +NS_IMETHODIMP +nsDeviceContextSpecAndroid::Init(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; +} + +RefPtr<PrintEndDocumentPromise> nsDeviceContextSpecAndroid::EndDocument() { + return nsIDeviceContextSpec::EndDocumentPromiseFromResult(DoEndDocument(), + __func__); +} + +NS_IMETHODIMP +nsDeviceContextSpecAndroid::DoEndDocument() { + if (mPrintSettings->GetOutputDestination() == + nsIPrintSettings::kOutputDestinationFile && + mTempFile) { + nsAutoString targetPath; + mPrintSettings->GetToFileName(targetPath); + nsCOMPtr<nsIFile> destFile; + MOZ_TRY(NS_NewLocalFile(targetPath, false, getter_AddRefs(destFile))); + nsAutoString destLeafName; + MOZ_TRY(destFile->GetLeafName(destLeafName)); + + nsCOMPtr<nsIFile> destDir; + MOZ_TRY(destFile->GetParent(getter_AddRefs(destDir))); + + MOZ_TRY(mTempFile->MoveTo(destDir, destLeafName)); + destFile->SetPermissions(0666); + + mTempFile = nullptr; + } + return NS_OK; +} diff --git a/widget/android/nsDeviceContextAndroid.h b/widget/android/nsDeviceContextAndroid.h new file mode 100644 index 0000000000..1ab3c72c1c --- /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" +#include "mozilla/gfx/PrintPromise.h" + +class nsDeviceContextSpecAndroid final : public nsIDeviceContextSpec { + private: + virtual ~nsDeviceContextSpecAndroid(); + + public: + NS_DECL_ISUPPORTS + + already_AddRefed<PrintTarget> MakePrintTarget() final; + + NS_IMETHOD Init(nsIPrintSettings* aPS, bool aIsPrintPreview) override; + NS_IMETHOD BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) override; + RefPtr<mozilla::gfx::PrintEndDocumentPromise> EndDocument() override; + NS_IMETHOD BeginPage() override { return NS_OK; } + NS_IMETHOD EndPage() override { return NS_OK; } + + private: + nsresult DoEndDocument(); + nsCOMPtr<nsIFile> mTempFile; +}; +#endif // nsDeviceContextAndroid_h__ diff --git a/widget/android/nsIAndroidBridge.idl b/widget/android/nsIAndroidBridge.idl new file mode 100644 index 0000000000..b30ed60d77 --- /dev/null +++ b/widget/android/nsIAndroidBridge.idl @@ -0,0 +1,58 @@ +/* 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(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 +{ + nsIAndroidEventDispatcher getDispatcherByName(in string name); +}; diff --git a/widget/android/nsLookAndFeel.cpp b/widget/android/nsLookAndFeel.cpp new file mode 100644 index 0000000000..84558862d7 --- /dev/null +++ b/widget/android/nsLookAndFeel.cpp @@ -0,0 +1,457 @@ +/* -*- 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 "nsStyleConsts.h" +#include "nsXULAppAPI.h" +#include "nsLookAndFeel.h" +#include "Theme.h" +#include "gfxFont.h" +#include "gfxFontConstants.h" +#include "mozilla/FontPropertyTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/GeckoRuntimeWrappers.h" +#include "mozilla/java/GeckoSystemStateListenerWrappers.h" +#include "ThemeColors.h" + +using namespace mozilla; +using namespace mozilla::widget; + +static const char16_t UNICODE_BULLET = 0x2022; + +nsLookAndFeel::nsLookAndFeel() = default; + +nsLookAndFeel::~nsLookAndFeel() = default; + +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<uint32_t>(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<uint32_t>(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(); +} + +void nsLookAndFeel::RefreshImpl() { + mInitializedSystemColors = false; + mInitializedShowPassword = false; + nsXPLookAndFeel::RefreshImpl(); +} + +nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aColorScheme, + nscolor& aColor) { + EnsureInitSystemColors(); + if (!mInitializedSystemColors) { + // Failure to initialize colors is an error condition. Return black. + aColor = 0; + return NS_ERROR_FAILURE; + } + + // Highlight/Highlighttext have native equivalents that we can map to (on + // Android) which should work fine, regardless of the color-scheme. + switch (aID) { + case ColorID::Highlight: { + // Matched to action_accent in java codebase. This works fine with both + // light and dark color scheme. + nscolor accent = + Color(ColorID::Accentcolor, aColorScheme, UseStandins::No); + aColor = + NS_RGBA(NS_GET_R(accent), NS_GET_G(accent), NS_GET_B(accent), 78); + return NS_OK; + } + case ColorID::Highlighttext: + // Selection background is transparent enough that any foreground color + // will do. + aColor = NS_SAME_AS_FOREGROUND_COLOR; + return NS_OK; + default: + break; + } + + if (aColorScheme == ColorScheme::Dark) { + if (auto darkColor = GenericDarkColor(aID)) { + aColor = *darkColor; + return NS_OK; + } + } + + // XXX we'll want to use context.obtainStyledAttributes on the java side to + // get all of these; see TextView.java for a good example. + auto UseNativeAccent = [this] { + return mSystemColors.colorAccent && + StaticPrefs::widget_non_native_theme_use_theme_accent(); + }; + + switch (aID) { + // These colors don't seem to be used for anything anymore in Mozilla + // The CSS2 colors below are used. + case ColorID::MozMenubartext: + aColor = mSystemColors.colorForeground; + break; + + case ColorID::ThemedScrollbarThumbInactive: + case ColorID::ThemedScrollbarThumb: + // We don't need to care about the Active and Hover colors because Android + // scrollbars can't be hovered (they always have pointer-events: none). + aColor = NS_RGBA(119, 119, 119, 102); + break; + + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedConvertedTextBackground: + aColor = mSystemColors.textColorHighlight; + break; + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedConvertedTextForeground: + aColor = mSystemColors.textColorPrimaryInverse; + break; + case ColorID::IMERawInputBackground: + case ColorID::IMEConvertedTextBackground: + aColor = NS_TRANSPARENT; + break; + case ColorID::IMERawInputForeground: + case ColorID::IMEConvertedTextForeground: + case ColorID::IMERawInputUnderline: + case ColorID::IMEConvertedTextUnderline: + aColor = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::IMESelectedRawTextUnderline: + case ColorID::IMESelectedConvertedTextUnderline: + aColor = NS_TRANSPARENT; + break; + + // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors + case ColorID::Activeborder: // active window border + case ColorID::Appworkspace: // MDI background color + case ColorID::Activecaption: // active window caption background + case ColorID::Background: // desktop background + case ColorID::Inactiveborder: // inactive window border + case ColorID::Inactivecaption: // inactive window caption + case ColorID::Scrollbar: // scrollbar gray area + aColor = mSystemColors.colorBackground; + break; + case ColorID::Graytext: // disabled text in windows, menus, etc. + aColor = NS_RGB(0xb1, 0xa5, 0x98); + break; + // FIXME: -moz-cellhighlight should show some kind of unfocused state. + case ColorID::MozCellhighlight: + case ColorID::Selecteditem: + case ColorID::Accentcolor: + aColor = UseNativeAccent() ? mSystemColors.colorAccent + : GetStandinForNativeColor( + ColorID::Accentcolor, aColorScheme); + break; + case ColorID::MozCellhighlighttext: + case ColorID::Selecteditemtext: + case ColorID::Accentcolortext: + aColor = UseNativeAccent() ? ThemeColors::ComputeCustomAccentForeground( + mSystemColors.colorAccent) + : GetStandinForNativeColor( + ColorID::Accentcolortext, aColorScheme); + break; + case ColorID::Fieldtext: + aColor = NS_RGB(0x1a, 0x1a, 0x1a); + 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: + case ColorID::Threeddarkshadow: // 3-D shadow outer edge color + case ColorID::MozButtondefault: // default button border color + aColor = NS_RGB(0x00, 0x00, 0x00); + break; + case ColorID::Menu: + aColor = NS_RGB(0xf7, 0xf5, 0xf3); + break; + + case ColorID::Buttonface: + case ColorID::MozButtondisabledface: + case ColorID::Threedface: + case ColorID::Threedlightshadow: + case ColorID::Buttonborder: + case ColorID::MozDisabledfield: + 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::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::MozButtonhoverface: + case ColorID::MozButtonactiveface: + aColor = NS_RGB(0xf3, 0xf0, 0xed); + break; + case ColorID::MozMenuhover: + aColor = NS_RGB(0xee, 0xee, 0xee); + break; + case ColorID::MozMenubarhovertext: + case ColorID::MozMenuhovertext: + aColor = NS_RGB(0x77, 0x77, 0x77); + break; + case ColorID::MozOddtreerow: + aColor = NS_TRANSPARENT; + break; + case ColorID::MozNativehyperlinktext: + aColor = NS_RGB(0, 0, 0xee); + break; + case ColorID::Marktext: + case ColorID::Mark: + case ColorID::SpellCheckerUnderline: + aColor = GetStandinForNativeColor(aID, aColorScheme); + break; + default: + /* default color is BLACK */ + aColor = 0; + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +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::CaretBlinkCount: + aResult = 10; + 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::UseOverlayScrollbars: + aResult = 1; + break; + + case IntID::SpellCheckerUnderlineStyle: + aResult = int32_t(StyleTextDecorationStyle::Wavy); + break; + + case IntID::ScrollbarButtonAutoRepeatBehavior: + aResult = 0; + break; + + case IntID::ContextMenuOffsetVertical: + case IntID::ContextMenuOffsetHorizontal: + aResult = 2; + break; + + case IntID::PrefersReducedMotion: + aResult = java::GeckoSystemStateListener::PrefersReducedMotion(); + break; + + case IntID::PrefersReducedTransparency: + aResult = 0; + break; + + case IntID::InvertedColors: + aResult = java::GeckoSystemStateListener::IsInvertedColors(); + break; + + case IntID::PrimaryPointerCapabilities: + aResult = java::GeckoAppShell::GetAllPointerCapabilities(); + + // We cannot assume what is primary device, so we use Blink's way for web + // compatibility (https://crbug.com/136119#c6). If having coarse + // capability in any devices, return it. + if (aResult & static_cast<int32_t>(PointerCapabilities::Coarse)) { + aResult = static_cast<int32_t>(PointerCapabilities::Coarse); + } + break; + + case IntID::AllPointerCapabilities: + aResult = java::GeckoAppShell::GetAllPointerCapabilities(); + break; + + case IntID::SystemUsesDarkTheme: { + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + aResult = runtime && runtime->UsesDarkTheme(); + break; + } + + case IntID::DragThresholdX: + case IntID::DragThresholdY: + // Threshold where a tap becomes a drag, in 1/240" reference pixels. + aResult = 25; + break; + + case IntID::TouchDeviceSupportPresent: + // Touch support is always enabled on android. + aResult = 1; + 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; + case FloatID::TextScaleFactor: { + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + aResult = runtime ? runtime->TextScaleFactor() : 1.0f; + break; + } + default: + aResult = -1.0; + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +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; +} + +bool nsLookAndFeel::GetEchoPasswordImpl() { + EnsureInitShowPassword(); + return mShowPassword; +} + +uint32_t nsLookAndFeel::GetPasswordMaskDelayImpl() { + // This value is hard-coded in Android OS's PasswordTransformationMethod.java + return 1500; +} + +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; + } +} diff --git a/widget/android/nsLookAndFeel.h b/widget/android/nsLookAndFeel.h new file mode 100644 index 0000000000..128b871a08 --- /dev/null +++ b/widget/android/nsLookAndFeel.h @@ -0,0 +1,57 @@ +/* -*- 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" + +namespace mozilla { +// The order and number of the members in this structure must correspond +// to the attrsAppearance array in GeckoAppShell.getSystemColors() +struct AndroidSystemColors { + nscolor textColorPrimary; + nscolor textColorPrimaryInverse; + nscolor textColorSecondary; + nscolor textColorSecondaryInverse; + nscolor textColorTertiary; + nscolor textColorTertiaryInverse; + nscolor textColorHighlight; + nscolor colorForeground; + nscolor colorBackground; + nscolor panelColorForeground; + nscolor panelColorBackground; + nscolor colorAccent; +}; +} // namespace mozilla + +class nsLookAndFeel final : public nsXPLookAndFeel { + public: + explicit nsLookAndFeel(); + virtual ~nsLookAndFeel(); + + void NativeInit() final; + virtual void RefreshImpl() override; + nsresult NativeGetInt(IntID, int32_t& aResult) override; + nsresult NativeGetFloat(FloatID, float& aResult) override; + nsresult NativeGetColor(ColorID, ColorScheme, nscolor& aResult) override; + bool NativeGetFont(FontID aID, nsString& aName, + gfxFontStyle& aStyle) override; + bool GetEchoPasswordImpl() override; + uint32_t GetPasswordMaskDelayImpl() override; + char16_t GetPasswordCharacterImpl() override; + + protected: + bool mInitializedSystemColors = false; + mozilla::AndroidSystemColors mSystemColors; + bool mInitializedShowPassword = false; + bool mShowPassword = false; + + nsresult GetSystemColors(); + + void EnsureInitSystemColors(); + void EnsureInitShowPassword(); +}; + +#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<nsIPrintSettings> CreatePlatformPrintSettings( + const mozilla::PrintSettingsInitializer& aSettings) { + RefPtr<nsPrintSettings> 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..670aebe82e --- /dev/null +++ b/widget/android/nsUserIdleServiceAndroid.cpp @@ -0,0 +1,12 @@ +/* -*- 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; +} diff --git a/widget/android/nsUserIdleServiceAndroid.h b/widget/android/nsUserIdleServiceAndroid.h new file mode 100644 index 0000000000..81ab790d9d --- /dev/null +++ b/widget/android/nsUserIdleServiceAndroid.h @@ -0,0 +1,34 @@ +/* -*- 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<nsUserIdleServiceAndroid> GetInstance() { + RefPtr<nsUserIdleService> idleService = nsUserIdleService::GetInstance(); + if (!idleService) { + idleService = new nsUserIdleServiceAndroid(); + } + + return idleService.forget().downcast<nsUserIdleServiceAndroid>(); + } + + protected: + nsUserIdleServiceAndroid() {} + virtual ~nsUserIdleServiceAndroid() {} +}; + +#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..934d75d8be --- /dev/null +++ b/widget/android/nsWidgetFactory.h @@ -0,0 +1,21 @@ +/* -*- 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(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..0c9fa562a4 --- /dev/null +++ b/widget/android/nsWindow.cpp @@ -0,0 +1,3252 @@ +/* -*- 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 <algorithm> +#include <atomic> +#include <android/log.h> +#include <android/native_window.h> +#include <android/native_window_jni.h> +#include <math.h> +#include <queue> +#include <type_traits> +#include <unistd.h> + +#include "AndroidGraphics.h" +#include "AndroidBridge.h" +#include "AndroidBridgeUtilities.h" +#include "AndroidCompositorWidget.h" +#include "AndroidContentController.h" +#include "AndroidUiThread.h" +#include "AndroidView.h" +#include "gfxContext.h" +#include "GeckoEditableSupport.h" +#include "GeckoViewOutputStream.h" +#include "GeckoViewSupport.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "JavaBuiltins.h" +#include "JavaExceptions.h" +#include "KeyEvent.h" +#include "MotionEvent.h" +#include "ScopedGLHelpers.h" +#include "ScreenHelperAndroid.h" +#include "TouchResampler.h" +#include "WidgetUtils.h" +#include "WindowRenderer.h" + +#include "mozilla/EventForwards.h" +#include "nsAppShell.h" +#include "nsContentUtils.h" +#include "nsFocusManager.h" +#include "nsGkAtoms.h" +#include "nsGfxCIID.h" +#include "nsIDocShellTreeOwner.h" +#include "nsLayoutUtils.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsUserIdleService.h" +#include "nsViewManager.h" +#include "nsWidgetsCID.h" +#include "nsWindow.h" + +#include "nsIWidgetListener.h" +#include "nsIWindowWatcher.h" +#include "nsIAppWindow.h" + +#include "mozilla/Logging.h" +#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/WeakPtr.h" +#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaAdjustmentStrategy +#include "mozilla/a11y/SessionAccessibility.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/BrowserHost.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/Logging.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/ipc/Shmem.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 "mozilla/java/SurfaceControlManagerWrappers.h" +#include "mozilla/jni/NativesInlines.h" +#include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/CompositorSession.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/UiCompositorControllerChild.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/widget/AndroidVsync.h" +#include "mozilla/widget/Screen.h" + +#define GVS_LOG(...) MOZ_LOG(sGVSupportLog, LogLevel::Warning, (__VA_ARGS__)) + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::layers; +using namespace mozilla::widget; +using namespace mozilla::ipc; + +using mozilla::dom::ContentChild; +using mozilla::dom::ContentParent; +using mozilla::gfx::DataSourceSurface; +using mozilla::gfx::IntSize; +using mozilla::gfx::Matrix; +using mozilla::gfx::SurfaceFormat; +using mozilla::java::GeckoSession; +using mozilla::java::sdk::IllegalStateException; +using GeckoPrintException = GeckoSession::GeckoPrintException; +static mozilla::LazyLogModule sGVSupportLog("GeckoViewSupport"); + +// 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<nsWindow*> 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; + +static const nsCString::size_type MAX_TOPLEVEL_DATA_URI_LEN = 2 * 1024 * 1024; + +// Unique ID given to each widget, to identify it for the +// CompositorSurfaceManager. +static std::atomic<int32_t> sWidgetId{0}; + +namespace { +template <class Instance, class Impl> +std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value == + jni::detail::NativePtrType::REFPTR, + void> +CallAttachNative(Instance aInstance, Impl* aImpl) { + Impl::AttachNative(aInstance, RefPtr<Impl>(aImpl).get()); +} + +template <class Instance, class Impl> +std::enable_if_t<jni::detail::NativePtrPicker<Impl>::value == + jni::detail::NativePtrType::OWNING, + void> +CallAttachNative(Instance aInstance, Impl* aImpl) { + Impl::AttachNative(aInstance, UniquePtr<Impl>(aImpl)); +} + +template <class Lambda> +bool DispatchToUiThread(const char* aName, Lambda&& aLambda) { + if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NS_NewRunnableFunction(aName, std::move(aLambda))); + return true; + } + return false; +} +} // namespace + +namespace mozilla { +namespace widget { + +using WindowPtr = jni::NativeWeakPtr<GeckoViewSupport>; + +/** + * PanZoomController handles its native calls on the UI thread, so make + * it separate from GeckoViewSupport. + */ +class NPZCSupport final + : public java::PanZoomController::NativeProvider::Natives<NPZCSupport> { + WindowPtr mWindow; + java::PanZoomController::NativeProvider::WeakRef mNPZC; + + // Stores the returnResult of each pending motion event between + // HandleMotionEvent and FinishHandlingMotionEvent. + std::queue<std::pair<uint64_t, java::GeckoResult::GlobalRef>> + mPendingMotionEventReturnResults; + + RefPtr<AndroidVsync> 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 <typename Lambda> + 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; } + }; + + class MOZ_HEAP_CLASS Observer final : public AndroidVsync::Observer { + public: + static Observer* Create(jni::NativeWeakPtr<NPZCSupport>&& aNPZCSupport) { + return new Observer(std::move(aNPZCSupport)); + } + + private: + // Private constructor, part of a strategy to make sure + // we're only able to create these on the heap. + explicit Observer(jni::NativeWeakPtr<NPZCSupport>&& aNPZCSupport) + : mNPZCSupport(std::move(aNPZCSupport)) {} + + void OnVsync(const TimeStamp& aTimeStamp) override { + auto accessor = mNPZCSupport.Access(); + + if (!accessor) { + return; + } + + accessor->mTouchResampler.NotifyFrame( + aTimeStamp - + TimeDuration::FromMilliseconds(kTouchResampleVsyncAdjustMs)); + accessor->ConsumeMotionEventsFromResampler(); + } + + void Dispose() override { delete this; } + + jni::NativeWeakPtr<NPZCSupport> mNPZCSupport; + }; + + Observer* mObserver = nullptr; + + template <typename Lambda> + void PostInputEvent(Lambda&& aLambda) { + // Use priority queue for input events. + nsAppShell::PostEvent( + MakeUnique<InputEvent<Lambda>>(this, std::move(aLambda))); + } + + public: + typedef java::PanZoomController::NativeProvider::Natives<NPZCSupport> 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::CreateGlobalHardwareVsyncSource() for comparison. + if (jni::GetAPIVersion() >= 19) { + mAndroidVsync = AndroidVsync::GetInstance(); + } + } + + ~NPZCSupport() { + if (mListeningToVsync) { + MOZ_RELEASE_ASSERT(mAndroidVsync); + mAndroidVsync->UnregisterObserver(mObserver, AndroidVsync::INPUT); + mListeningToVsync = false; + } + } + + using Base::AttachNative; + using Base::DisposeNative; + + void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) { + RefPtr<Runnable> 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<nsThread> 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<IAPZCTreeManager> 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<IAPZCTreeManager> 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( + 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.GetStatus() == nsEventStatus_eConsumeNoDefault) { + return INPUT_RESULT_IGNORED; + } + + PostInputEvent([input = std::move(input), result](nsWindow* window) { + WidgetWheelEvent wheelEvent = input.ToWidgetEvent(window); + window->ProcessUntransformedAPZEvent(&wheelEvent, result); + }); + + switch (result.GetStatus()) { + case nsEventStatus_eIgnore: + return INPUT_RESULT_UNHANDLED; + case nsEventStatus_eConsumeDoDefault: + return result.GetHandledResult()->IsHandledByRoot() + ? 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 ConvertAPZHandledPlace(APZHandledPlace aHandledPlace) { + switch (aHandledPlace) { + case APZHandledPlace::Unhandled: + return INPUT_RESULT_UNHANDLED; + case APZHandledPlace::HandledByRoot: + return INPUT_RESULT_HANDLED; + case APZHandledPlace::HandledByContent: + return INPUT_RESULT_HANDLED_CONTENT; + case APZHandledPlace::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; + } + + static int32_t ConvertSideBits(SideBits aSideBits) { + int32_t ret = java::PanZoomController::SCROLLABLE_FLAG_NONE; + if (aSideBits & SideBits::eTop) { + ret |= java::PanZoomController::SCROLLABLE_FLAG_TOP; + } + if (aSideBits & SideBits::eRight) { + ret |= java::PanZoomController::SCROLLABLE_FLAG_RIGHT; + } + if (aSideBits & SideBits::eBottom) { + ret |= java::PanZoomController::SCROLLABLE_FLAG_BOTTOM; + } + if (aSideBits & SideBits::eLeft) { + ret |= java::PanZoomController::SCROLLABLE_FLAG_LEFT; + } + return ret; + } + + static int32_t ConvertScrollDirections( + layers::ScrollDirections aScrollDirections) { + int32_t ret = java::PanZoomController::OVERSCROLL_FLAG_NONE; + if (aScrollDirections.contains(layers::HorizontalScrollDirection)) { + ret |= java::PanZoomController::OVERSCROLL_FLAG_HORIZONTAL; + } + if (aScrollDirections.contains(layers::VerticalScrollDirection)) { + ret |= java::PanZoomController::OVERSCROLL_FLAG_VERTICAL; + } + return ret; + } + + static java::PanZoomController::InputResultDetail::LocalRef + ConvertAPZHandledResult(const APZHandledResult& aHandledResult) { + return java::PanZoomController::InputResultDetail::New( + ConvertAPZHandledPlace(aHandledResult.mPlace), + ConvertSideBits(aHandledResult.mScrollableDirections), + ConvertScrollDirections(aHandledResult.mOverscrollDirections)); + } + + public: + int32_t HandleMouseEvent(int32_t aAction, int64_t aTime, int32_t aMetaState, + float aX, float aY, int buttons) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + RefPtr<IAPZCTreeManager> 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, nsWindow::GetEventTimeStamp(aTime), + nsWindow::GetModifiers(aMetaState)); + + APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input); + if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) { + return INPUT_RESULT_IGNORED; + } + + PostInputEvent([input = std::move(input), result](nsWindow* window) { + WidgetMouseEvent mouseEvent = input.ToWidgetEvent(window); + window->ProcessUntransformedAPZEvent(&mouseEvent, result); + }); + + switch (result.GetStatus()) { + case nsEventStatus_eIgnore: + return INPUT_RESULT_UNHANDLED; + case nsEventStatus_eConsumeDoDefault: + return result.GetHandledResult()->IsHandledByRoot() + ? 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<float, ScreenSize> 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<int32_t> 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<int64_t> 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<float> historicalX(eventData->HistoricalX()->GetElements()); + nsTArray<float> historicalY(eventData->HistoricalY()->GetElements()); + nsTArray<float> historicalOrientation( + eventData->HistoricalOrientation()->GetElements()); + nsTArray<float> historicalPressure( + eventData->HistoricalPressure()->GetElements()); + nsTArray<float> historicalToolMajor( + eventData->HistoricalToolMajor()->GetElements()); + nsTArray<float> 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<float> x(eventData->X()->GetElements()); + nsTArray<float> y(eventData->Y()->GetElements()); + nsTArray<float> orientation(eventData->Orientation()->GetElements()); + nsTArray<float> pressure(eventData->Pressure()->GetElements()); + nsTArray<float> toolMajor(eventData->ToolMajor()->GetElements()); + nsTArray<float> 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++) { + auto [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; + auto [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) { + MOZ_ASSERT(!mObserver); + auto win = mWindow.Access(); + if (!win) { + return; + } + RefPtr<nsWindow> gkWindow = win->GetNsWindow(); + if (!gkWindow) { + return; + } + MutexAutoLock lock(gkWindow->GetDestroyMutex()); + if (gkWindow->Destroyed()) { + return; + } + jni::NativeWeakPtr<NPZCSupport> weakPtrToThis = + gkWindow->GetNPZCSupportWeakPtr(); + mObserver = Observer::Create(std::move(weakPtrToThis)); + mAndroidVsync->RegisterObserver(mObserver, AndroidVsync::INPUT); + } else if (!aNeedVsync && mListeningToVsync) { + mAndroidVsync->UnregisterObserver(mObserver, AndroidVsync::INPUT); + mObserver = nullptr; + } + mListeningToVsync = aNeedVsync; + } + + 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<IAPZCTreeManager> controller; + + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + controller = gkWindow->mAPZC; + } + } + + if (!controller) { + if (aReturnResult) { + aReturnResult->Complete(java::PanZoomController::InputResultDetail::New( + INPUT_RESULT_UNHANDLED, + java::PanZoomController::SCROLLABLE_FLAG_NONE, + java::PanZoomController::OVERSCROLL_FLAG_NONE)); + } + return; + } + + APZInputBridge::InputBlockCallback callback; + if (aReturnResult) { + callback = [aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)]( + uint64_t aInputBlockId, + const APZHandledResult& aHandledResult) { + aReturnResult->Complete(ConvertAPZHandledResult(aHandledResult)); + }; + } + APZEventResult result = controller->InputBridge()->ReceiveInputEvent( + aInput, std::move(callback)); + + if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) { + if (aReturnResult) { + if (result.GetHandledResult() != Nothing()) { + aReturnResult->Complete( + ConvertAPZHandledResult(result.GetHandledResult().value())); + } else { + MOZ_ASSERT_UNREACHABLE( + "nsEventStatus_eConsumeNoDefault should involve a valid " + "APZHandledResult"); + aReturnResult->Complete( + java::PanZoomController::InputResultDetail::New( + INPUT_RESULT_IGNORED, + java::PanZoomController::SCROLLABLE_FLAG_NONE, + java::PanZoomController::OVERSCROLL_FLAG_NONE)); + } + } + return; + } + + // Dispatch APZ input event on Gecko thread. + PostInputEvent([input = std::move(aInput), result](nsWindow* window) { + WidgetTouchEvent touchEvent = input.ToWidgetEvent(window); + window->ProcessUntransformedAPZEvent(&touchEvent, result); + window->DispatchHitTest(touchEvent); + }); + + if (aReturnResult && result.GetHandledResult() != Nothing()) { + MOZ_ASSERT(result.GetStatus() == nsEventStatus_eConsumeDoDefault || + result.GetStatus() == nsEventStatus_eIgnore); + aReturnResult->Complete( + ConvertAPZHandledResult(result.GetHandledResult().value())); + } + } +}; + +NS_IMPL_ISUPPORTS(AndroidView, nsIAndroidEventDispatcher, nsIAndroidView) + +nsresult AndroidView::GetInitData(JSContext* aCx, + JS::MutableHandle<JS::Value> 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<LayerViewSupport> { + WindowPtr mWindow; + GeckoSession::Compositor::WeakRef mCompositor; + Atomic<bool, ReleaseAcquire> mCompositorPaused; + java::sdk::Surface::GlobalRef mSurface; + java::sdk::SurfaceControl::GlobalRef mSurfaceControl; + int32_t mX; + int32_t mY; + int32_t mWidth; + int32_t mHeight; + // Used to communicate with the gecko compositor from the UI thread. + // Set in NotifyCompositorCreated and cleared in + // NotifyCompositorSessionLost. + RefPtr<UiCompositorControllerChild> mUiCompositorControllerChild; + // Whether we have requested a new Surface from the GeckoSession. + bool mRequestedNewSurface = false; + + Maybe<uint32_t> mDefaultClearColor; + + 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<CaptureRequest> 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<Event> MakeEvent(UniquePtr<Event>&& event) { + return MakeUnique<LayerViewEvent>(std::move(event)); + } + + explicit LayerViewEvent(UniquePtr<Event>&& event) + : nsAppShell::ProxyEvent(std::move(event)) {} + + void PostTo(LinkedList<Event>& 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<LayerViewSupport> 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<Runnable> aDisposer) { + RefPtr<Runnable> disposer = aDisposer; + if (RefPtr<nsThread> 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<jni::Throwable>()); + } + results->pop(); + } + } + + compositor->OnCompositorDetached(); + disposer->Run(); + })); + } + } + + const GeckoSession::Compositor::Ref& GetJavaCompositor() const { + return mCompositor; + } + + bool CompositorPaused() const { return mCompositorPaused; } + + /// Called from the main thread whenever the compositor has been + /// (re)initialized. + void NotifyCompositorCreated( + RefPtr<UiCompositorControllerChild> aUiCompositorControllerChild) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + mUiCompositorControllerChild = aUiCompositorControllerChild; + + if (mDefaultClearColor) { + mUiCompositorControllerChild->SetDefaultClearColor(*mDefaultClearColor); + } + + if (!mCompositorPaused) { + // If we are using SurfaceControl but mSurface is null, that means the + // previous surface was destroyed along with the the previous + // compositor, and we need to create a new one. + if (mSurfaceControl && !mSurface) { + mSurface = java::SurfaceControlManager::GetInstance()->GetChildSurface( + mSurfaceControl, mWidth, mHeight); + } + + if (auto window{mWindow.Access()}) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + mUiCompositorControllerChild->OnCompositorSurfaceChanged( + gkWindow->mWidgetId, mSurface); + } + } + + bool resumed = mUiCompositorControllerChild->ResumeAndResize( + mX, mY, mWidth, mHeight); + if (!resumed) { + gfxCriticalNote + << "Failed to resume compositor from NotifyCompositorCreated"; + RequestNewSurface(); + } + } + } + + /// Called from the main thread whenever the compositor has been destroyed. + void NotifyCompositorSessionLost() { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + mUiCompositorControllerChild = nullptr; + + if (mSurfaceControl) { + // If we are using SurfaceControl then we must set the Surface to null + // here to ensure we create a new one when the new compositor is + // created. + mSurface = nullptr; + } + + if (auto window = mWindow.Access()) { + while (!mCapturePixelsResults.empty()) { + auto result = + java::GeckoResult::LocalRef(mCapturePixelsResults.front().mResult); + if (result) { + result->CompleteExceptionally( + java::sdk::IllegalStateException::New( + "Compositor session lost during screen pixels request") + .Cast<jni::Throwable>()); + } + mCapturePixelsResults.pop(); + } + } + } + + java::sdk::Surface::Param GetSurface() { return mSurface; } + + private: + already_AddRefed<DataSourceSurface> FlipScreenPixels( + Shmem& aMem, const ScreenIntSize& aInSize, const ScreenRect& aInRegion, + const IntSize& aOutSize) { + RefPtr<gfx::DataSourceSurface> image = + gfx::Factory::CreateWrappingDataSourceSurface( + aMem.get<uint8_t>(), + StrideForFormatAndWidth(SurfaceFormat::B8G8R8A8, aInSize.width), + IntSize(aInSize.width, aInSize.height), SurfaceFormat::B8G8R8A8); + RefPtr<gfx::DrawTarget> 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<gfx::SourceSurface> snapshot = drawTarget->Snapshot(); + RefPtr<gfx::DataSourceSurface> 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<NPZCSupport>::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 NotifyMemoryPressure() { + MOZ_ASSERT(NS_IsMainThread()); + auto acc = mWindow.Access(); + if (!acc) { + return; // Already shut down. + } + + nsWindow* gkWindow = acc->GetNsWindow(); + if (!gkWindow || !gkWindow->mCompositorBridgeChild) { + return; + } + + gkWindow->mCompositorBridgeChild->SendNotifyMemoryPressure(); + } + + 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()); + + mCompositorPaused = true; + + if (mUiCompositorControllerChild) { + mUiCompositorControllerChild->Pause(); + + mSurface = nullptr; + mSurfaceControl = nullptr; + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + mUiCompositorControllerChild->OnCompositorSurfaceChanged( + gkWindow->mWidgetId, nullptr); + } + } + } + + 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<jni::Throwable>()); + } + mCapturePixelsResults.pop(); + } + } + } + + void SyncResumeCompositor() { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + if (mUiCompositorControllerChild) { + mCompositorPaused = false; + bool resumed = mUiCompositorControllerChild->Resume(); + if (!resumed) { + gfxCriticalNote + << "Failed to resume compositor from SyncResumeCompositor"; + RequestNewSurface(); + } + } + } + + void SyncResumeResizeCompositor( + const GeckoSession::Compositor::LocalRef& aObj, int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight, jni::Object::Param aSurface, + jni::Object::Param aSurfaceControl) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + mX = aX; + mY = aY; + mWidth = aWidth; + mHeight = aHeight; + mSurfaceControl = + java::sdk::SurfaceControl::GlobalRef::From(aSurfaceControl); + if (mSurfaceControl) { + // When using SurfaceControl, we create a child Surface to render in to + // rather than rendering directly in to the Surface provided by the + // application. This allows us to work around a bug on some versions of + // Android when recovering from a GPU process crash. + mSurface = java::SurfaceControlManager::GetInstance()->GetChildSurface( + mSurfaceControl, mWidth, mHeight); + } else { + mSurface = java::sdk::Surface::GlobalRef::From(aSurface); + } + + if (mUiCompositorControllerChild) { + if (auto window = mWindow.Access()) { + nsWindow* gkWindow = window->GetNsWindow(); + if (gkWindow) { + // Send new Surface to GPU process, if one exists. + mUiCompositorControllerChild->OnCompositorSurfaceChanged( + gkWindow->mWidgetId, mSurface); + } + } + + bool resumed = mUiCompositorControllerChild->ResumeAndResize( + aX, aY, aWidth, aHeight); + if (!resumed) { + gfxCriticalNote + << "Failed to resume compositor from SyncResumeResizeCompositor"; + // Only request a new Surface if this SyncResumeAndResize call is not + // response to a previous request, otherwise we will get stuck in an + // infinite loop. + if (!mRequestedNewSurface) { + RequestNewSurface(); + } + return; + } + } + + mRequestedNewSurface = false; + + 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<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj))); + } + + void RequestNewSurface() { + if (const auto& compositor = GetJavaCompositor()) { + mRequestedNewSurface = true; + if (mSurfaceControl) { + java::SurfaceControlManager::GetInstance()->RemoveSurface( + mSurfaceControl); + } + compositor->RequestNewSurface(); + } + } + + mozilla::jni::Object::LocalRef GetMagnifiableSurface() { + return mozilla::jni::Object::LocalRef::From(GetSurface()); + } + + void SyncInvalidateAndScheduleComposite() { + if (!mUiCompositorControllerChild) { + return; + } + + if (AndroidBridge::IsJavaUiThread()) { + mUiCompositorControllerChild->InvalidateAndRender(); + return; + } + + if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NewRunnableMethod<>( + "LayerViewSupport::InvalidateAndRender", + mUiCompositorControllerChild, + &UiCompositorControllerChild::InvalidateAndRender), + nsIThread::DISPATCH_NORMAL); + } + } + + void SetMaxToolbarHeight(int32_t aHeight) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + + if (mUiCompositorControllerChild) { + mUiCompositorControllerChild->SetMaxToolbarHeight(aHeight); + } + } + + void SetFixedBottomOffset(int32_t aOffset) { + if (auto acc{mWindow.Access()}) { + nsWindow* gkWindow = acc->GetNsWindow(); + if (gkWindow) { + gkWindow->UpdateDynamicToolbarOffset(ScreenIntCoord(aOffset)); + } + } + + if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NS_NewRunnableFunction( + "LayerViewSupport::SetFixedBottomOffset", [this, offset = aOffset] { + if (mUiCompositorControllerChild) { + mUiCompositorControllerChild->SetFixedBottomOffset(offset); + } + })); + } + } + + void SendToolbarAnimatorMessage(int32_t aMessage) { + if (!mUiCompositorControllerChild) { + return; + } + + if (AndroidBridge::IsJavaUiThread()) { + mUiCompositorControllerChild->ToolbarAnimatorMessageFromUI(aMessage); + return; + } + + if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) { + uiThread->Dispatch( + NewRunnableMethod<int32_t>( + "LayerViewSupport::ToolbarAnimatorMessageFromUI", + mUiCompositorControllerChild, + &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()); + mDefaultClearColor = Some((uint32_t)aColor); + if (mUiCompositorControllerChild) { + mUiCompositorControllerChild->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()); + auto result = java::GeckoResult::LocalRef(aResult); + + if (!mUiCompositorControllerChild) { + if (result) { + if (auto window = mWindow.Access()) { + result->CompleteExceptionally( + java::sdk::IllegalStateException::New( + "Compositor session lost prior to screen pixels request") + .Cast<jni::Throwable>()); + } + } + return; + } + + int size = 0; + if (auto window = mWindow.Access()) { + mCapturePixelsResults.push(CaptureRequest( + java::GeckoResult::GlobalRef(result), + java::sdk::Bitmap::GlobalRef(java::sdk::Bitmap::LocalRef(aTarget)), + ScreenRect(aXOffset, aYOffset, aSrcWidth, aSrcHeight), + IntSize(aOutWidth, aOutHeight))); + size = mCapturePixelsResults.size(); + } + + if (size == 1) { + mUiCompositorControllerChild->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<DataSourceSurface> surf; + if (aNeedsYFlip) { + surf = FlipScreenPixels(aMem, aSize, request.mSource, + request.mOutputSize); + } else { + surf = gfx::Factory::CreateWrappingDataSourceSurface( + aMem.get<uint8_t>(), + 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<int8_t*>(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<jni::Throwable>()); + } + } else { + result->CompleteExceptionally(java::sdk::IllegalArgumentException::New( + "No target bitmap argument provided") + .Cast<jni::Throwable>()); + } + } + + // Pixels have been copied, so Dealloc Shmem + if (mUiCompositorControllerChild) { + mUiCompositorControllerChild->DeallocPixelBuffer(aMem); + + if (auto window = mWindow.Access()) { + if (!mCapturePixelsResults.empty()) { + mUiCompositorControllerChild->RequestScreenPixels(); + } + } + } + } + + void EnableLayerUpdateNotifications(bool aEnable) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (mUiCompositorControllerChild) { + mUiCompositorControllerChild->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, bool aPrivateMode) { + MOZ_ASSERT(NS_IsMainThread()); + + AUTO_PROFILER_LABEL("mozilla::widget::GeckoViewSupport::Open", OTHER); + + // We'll need gfxPlatform to be initialized to create a compositor later. + // Might as well do that now so that the GPU process launch can get a head + // start. + gfxPlatform::GetPlatform(); + + nsCOMPtr<nsIWindowWatcher> 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> 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<mozIDOMWindowProxy> domWindow; + ww->OpenWindow(nullptr, url, nsDependentCString(aId->ToCString().get()), + chromeFlags, androidView, getter_AddRefs(domWindow)); + MOZ_RELEASE_ASSERT(domWindow); + + nsCOMPtr<nsPIDOMWindowOuter> pdomWindow = nsPIDOMWindowOuter::From(domWindow); + const RefPtr<nsWindow> window = nsWindow::From(pdomWindow); + MOZ_ASSERT(window); + + // Attach a new GeckoView support object to the new window. + GeckoSession::Window::LocalRef sessionWindow(aCls.Env(), aWindow); + auto weakGeckoViewSupport = + jni::NativeWeakPtrHolder<GeckoViewSupport>::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<nsIAppWindow> 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<LayerViewSupport>::Attach( + compositor, mWindow->mGeckoViewSupport, compositor); + + if (RefPtr<UiCompositorControllerChild> uiCompositorController = + mWindow->GetUiCompositorControllerChild()) { + DispatchToUiThread( + "LayerViewSupport::NotifyCompositorCreated", + [lvs = mWindow->mLayerViewSupport, uiCompositorController] { + if (auto lvsAccess{lvs.Access()}) { + lvsAccess->NotifyCompositorCreated(uiCompositorController); + } + }); + } + } + + MOZ_ASSERT(mWindow->mAndroidView); + mWindow->mAndroidView->mEventDispatcher->Attach( + java::EventDispatcher::Ref::From(aDispatcher), mDOMWindow); + + RefPtr<jni::DetachPromise> promise = mWindow->mSessionAccessibility.Detach(); + if (aSessionAccessibility) { + // SessionAccessibility's JNI object isn't released immediately, it uses + // recycled object, we have to wait for released object completely. + auto sa = java::SessionAccessibility::NativeProvider::LocalRef( + aSessionAccessibility); + promise->Then( + GetMainThreadSerialEventTarget(), + "GeckoViewSupprt::Transfer::SessionAccessibility", + [inst = GeckoSession::Window::GlobalRef(inst), + sa = java::SessionAccessibility::NativeProvider::GlobalRef(sa), + window = mWindow, gvs = mWindow->mGeckoViewSupport]( + const mozilla::jni::DetachPromise::ResolveOrRejectValue& aValue) { + MOZ_ASSERT(aValue.IsResolve()); + if (window->Destroyed()) { + return; + } + + MOZ_ASSERT(!window->mSessionAccessibility.IsAttached()); + if (auto gvsAccess{gvs.Access()}) { + gvsAccess->AttachAccessibility(inst, sa); + } + }); + } + + 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<GeckoEditableSupport>::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<a11y::SessionAccessibility>::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::OnShowDynamicToolbar() const { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return; + } + + window->OnShowDynamicToolbar(); +} + +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); }); +} + +RefPtr<CanonicalBrowsingContext> +GeckoViewSupport::GetContentCanonicalBrowsingContext() { + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = mDOMWindow->GetTreeOwner(); + if (!treeOwner) { + return nullptr; + } + RefPtr<BrowsingContext> bc; + nsresult rv = treeOwner->GetPrimaryContentBrowsingContext(getter_AddRefs(bc)); + if (NS_WARN_IF(NS_FAILED(rv)) || !bc) { + return nullptr; + } + return bc->Canonical(); +} + +void GeckoViewSupport::CreatePdf( + jni::LocalRef<mozilla::java::GeckoResult> aGeckoResult, + RefPtr<dom::CanonicalBrowsingContext> aCbc) { + MOZ_ASSERT(NS_IsMainThread()); + const auto pdfErrorMsg = "Could not save this page as PDF."; + auto stream = java::GeckoInputStream::New(nullptr); + RefPtr<GeckoViewOutputStream> streamListener = + new GeckoViewOutputStream(stream); + + nsCOMPtr<nsIPrintSettingsService> printSettingsService = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (!printSettingsService) { + aGeckoResult->CompleteExceptionally( + GeckoPrintException::New( + GeckoPrintException::ERROR_PRINT_SETTINGS_SERVICE_NOT_AVAILABLE) + .Cast<jni::Throwable>()); + GVS_LOG("Could not create print settings service."); + return; + } + + nsCOMPtr<nsIPrintSettings> printSettings; + nsresult rv = printSettingsService->CreateNewPrintSettings( + getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + aGeckoResult->CompleteExceptionally( + GeckoPrintException::New( + GeckoPrintException::ERROR_UNABLE_TO_CREATE_PRINT_SETTINGS) + .Cast<jni::Throwable>()); + GVS_LOG("Could not create print settings."); + return; + } + + printSettings->SetPrinterName(u"Mozilla Save to PDF"_ns); + printSettings->SetOutputDestination( + nsIPrintSettings::kOutputDestinationStream); + printSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF); + printSettings->SetOutputStream(streamListener); + printSettings->SetPrintSilent(true); + + RefPtr<CanonicalBrowsingContext::PrintPromise> print = + aCbc->Print(printSettings); + + aGeckoResult->Complete(stream); + print->Then( + mozilla::GetCurrentSerialEventTarget(), __func__, + [result = java::GeckoResult::GlobalRef(aGeckoResult), stream, + pdfErrorMsg]( + const CanonicalBrowsingContext::PrintPromise::ResolveOrRejectValue& + aValue) { + if (aValue.IsReject()) { + GVS_LOG("Could not print. %s", pdfErrorMsg); + stream->WriteError(); + } + }); +} + +void GeckoViewSupport::PrintToPdf( + const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aResult) { + auto geckoResult = java::GeckoResult::Ref::From(aResult); + RefPtr<CanonicalBrowsingContext> cbc = GetContentCanonicalBrowsingContext(); + if (!cbc) { + geckoResult->CompleteExceptionally( + GeckoPrintException::New( + GeckoPrintException:: + ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT) + .Cast<jni::Throwable>()); + GVS_LOG("Could not retrieve content canonical browsing context."); + return; + } + CreatePdf(geckoResult, cbc); +} + +void GeckoViewSupport::PrintToPdf( + const java::GeckoSession::Window::LocalRef& inst, + jni::Object::Param aResult, int64_t aBcId) { + auto geckoResult = java::GeckoResult::Ref::From(aResult); + + RefPtr<CanonicalBrowsingContext> cbc = CanonicalBrowsingContext::Get(aBcId); + if (!cbc) { + geckoResult->CompleteExceptionally( + GeckoPrintException::New( + GeckoPrintException:: + ERROR_UNABLE_TO_RETRIEVE_CANONICAL_BROWSING_CONTEXT) + .Cast<jni::Throwable>()); + GVS_LOG("Could not retrieve content canonical browsing context by ID."); + return; + } + CreatePdf(geckoResult, cbc); +} +} // 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> nsWindow::From(nsPIDOMWindowOuter* aDOMWindow) { + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aDOMWindow); + return From(widget); +} + +/* static */ +already_AddRefed<nsWindow> 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->GetWindowType() == WindowType::TopLevel && + aWidget->GetNativeData(NS_NATIVE_WIDGET) == aWidget) { + RefPtr<nsWindow> window = static_cast<nsWindow*>(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, + int(win->mWindowType)); +#endif +} + +void nsWindow::DumpWindows() { DumpWindows(gTopLevelWindows); } + +void nsWindow::DumpWindows(const nsTArray<nsWindow*>& 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() + : mWidgetId(++sWidgetId), + mIsVisible(false), + mParent(nullptr), + mDynamicToolbarMaxHeight(0), + mSizeMode(nsSizeMode_Normal), + mIsFullScreen(false), + mCompositorWidgetDelegate(nullptr), + mDestroyMutex("nsWindow::mDestroyMutex") {} + +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 == WindowType::TopLevel || + mWindowType == WindowType::Dialog || + mWindowType == WindowType::Invisible; +} + +nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + InitData* 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; + SetSizeConstraints(SizeConstraints()); + + 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; + } + +#ifdef DEBUG_ANDROID_WIDGET + DumpWindows(); +#endif + + return NS_OK; +} + +void nsWindow::Destroy() { + MutexAutoLock lock(mDestroyMutex); + + nsBaseWidget::mOnDestroyCalled = true; + + // Disassociate our native object from GeckoView. + mGeckoViewSupport.Detach(); + + // Stuff below may release the last ref to this + nsCOMPtr<nsIWidget> 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 +} + +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<UiCompositorControllerChild> 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<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest( + nsIURI* aUri, int32_t aWindowType, int32_t aFlags, + nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, + bool aIsTopLevel) { + auto geckoViewSupport(mGeckoViewSupport.Access()); + if (!geckoViewSupport) { + return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__); + } + + nsAutoCString spec, triggeringSpec; + if (aUri) { + aUri->GetDisplaySpec(spec); + if (aIsTopLevel && mozilla::net::SchemeIsData(aUri) && + spec.Length() > MAX_TOPLEVEL_DATA_URI_LEN) { + return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__); + } + } + + bool isNullPrincipal = false; + if (aTriggeringPrincipal) { + aTriggeringPrincipal->GetIsNullPrincipal(&isNullPrincipal); + + if (!isNullPrincipal) { + nsCOMPtr<nsIURI> 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<bool, bool, false>::FromGeckoResult(geckoResult) + : nullptr; +} + +void nsWindow::OnUpdateSessionStore(mozilla::jni::Object::Param aBundle) { + auto geckoViewSupport(mGeckoViewSupport.Access()); + if (!geckoViewSupport) { + return; + } + + geckoViewSupport->OnUpdateSessionStore(aBundle); +} + +float nsWindow::GetDPI() { + float dpi = 160.0f; + + nsCOMPtr<nsIScreen> screen = GetWidgetScreen(); + if (screen) { + screen->GetDpi(&dpi); + } + + return dpi; +} + +double nsWindow::GetDefaultScaleInternal() { + double scale = 1.0f; + + nsCOMPtr<nsIScreen> screen = GetWidgetScreen(); + if (screen) { + screen->GetContentsScaleFactor(&scale); + } + + return scale; +} + +void nsWindow::Show(bool aState) { + ALOG("nsWindow[%p]::Show %d", (void*)this, aState); + + if (mWindowType == WindowType::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(DesktopIntPoint& aPoint) { + ALOG("nsWindow[%p]::ConstrainPosition [%d %d]", (void*)this, aPoint.x.value, + aPoint.y.value); + + // Constrain toplevel windows; children we don't care about + if (IsTopLevel()) { + aPoint = DesktopIntPoint(); + } +} + +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); + + LayoutDeviceIntRect oldBounds = mBounds; + + mBounds.x = NSToIntRound(aX); + mBounds.y = NSToIntRound(aY); + mBounds.width = NSToIntRound(aWidth); + mBounds.height = NSToIntRound(aHeight); + + ConstrainSize(&mBounds.width, &mBounds.height); + + bool needPositionDispatch = mBounds.TopLeft() != oldBounds.TopLeft(); + bool needSizeDispatch = mBounds.Size() != oldBounds.Size(); + + if (needSizeDispatch) { + OnSizeChanged(mBounds.Size().ToUnknownSize()); + } + + 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; + } + + mSizeMode = 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<nsWindow> 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) { + if (!mAndroidView) { + return NS_ERROR_NOT_AVAILABLE; + } + + mIsFullScreen = aFullScreen; + mAndroidView->mEventDispatcher->Dispatch( + aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit"); + + nsIWidgetListener* listener = GetWidgetListener(); + if (listener) { + mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal; + listener->SizeModeChanged(mSizeMode); + } + return NS_OK; +} + +mozilla::WindowRenderer* nsWindow::GetWindowRenderer() { + if (!mWindowRenderer) { + CreateLayerManager(); + } + + return mWindowRenderer; +} + +void nsWindow::CreateLayerManager() { + if (mWindowRenderer) { + return; + } + + nsWindow* topLevelWindow = FindTopLevel(); + if (!topLevelWindow || topLevelWindow->mWindowType == WindowType::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 (mWindowRenderer) { + if (mLayerViewSupport.IsAttached()) { + DispatchToUiThread( + "LayerViewSupport::NotifyCompositorCreated", + [lvs = mLayerViewSupport, + uiCompositorController = GetUiCompositorControllerChild()] { + if (auto lvsAccess{lvs.Access()}) { + lvsAccess->NotifyCompositorCreated(uiCompositorController); + } + }); + } + + 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"); + mWindowRenderer = CreateFallbackRenderer(); + } +} + +void nsWindow::NotifyCompositorSessionLost( + mozilla::layers::CompositorSession* aSession) { + nsBaseWidget::NotifyCompositorSessionLost(aSession); + + DispatchToUiThread("nsWindow::NotifyCompositorSessionLost", + [lvs = mLayerViewSupport] { + if (auto lvsAccess{lvs.Access()}) { + lvsAccess->NotifyCompositorSessionLost(); + } + }); + + RedrawAll(); +} + +void nsWindow::ShowDynamicToolbar() { + auto acc(mGeckoViewSupport.Access()); + if (!acc) { + return; + } + + acc->OnShowDynamicToolbar(); +} + +void GeckoViewSupport::OnUpdateSessionStore( + mozilla::jni::Object::Param aBundle) { + GeckoSession::Window::LocalRef window(mGeckoViewWindow); + if (!window) { + return; + } + + window->OnUpdateSessionStore(aBundle); +} + +void nsWindow::OnSizeChanged(const gfx::IntSize& aSize) { + ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, aSize.width, + aSize.height); + + if (mWidgetListener) { + mWidgetListener->WindowResized(this, aSize.width, aSize.height); + } + + if (mAttachedWidgetListener) { + mAttachedWidgetListener->WindowResized(this, aSize.width, aSize.height); + } + + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->NotifyClientSizeChanged( + LayoutDeviceIntSize::FromUnknownSize(aSize)); + } +} + +void nsWindow::InitEvent(WidgetGUIEvent& event, LayoutDeviceIntPoint* aPoint) { + if (aPoint) { + event.mRefPoint = *aPoint; + } else { + event.mRefPoint = LayoutDeviceIntPoint(0, 0); + } +} + +void nsWindow::UpdateOverscrollVelocity(const float aX, const float aY) { + if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::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<LayerViewSupport>::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_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<LayerViewSupport>::Accessor lvs{ + mLayerViewSupport.Access()}) { + return lvs->GetSurface().Get(); + } + return nullptr; + } + + return nullptr; +} + +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<mozilla::a11y::SessionAccessibility> +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<TextEventDispatcherListener> 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, NativeMouseMessage aNativeMessage, + MouseButton aButton, nsIWidget::Modifiers 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; + + int32_t nativeMessage; + switch (aNativeMessage) { + case NativeMouseMessage::ButtonDown: + nativeMessage = java::sdk::MotionEvent::ACTION_POINTER_DOWN; + break; + case NativeMouseMessage::ButtonUp: + nativeMessage = java::sdk::MotionEvent::ACTION_POINTER_UP; + break; + case NativeMouseMessage::Move: + nativeMessage = java::sdk::MotionEvent::ACTION_HOVER_MOVE; + break; + default: + MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Android"); + return NS_ERROR_INVALID_ARG; + } + int32_t button = 0; + if (aNativeMessage != NativeMouseMessage::ButtonUp) { + switch (aButton) { + case MouseButton::ePrimary: + button = java::sdk::MotionEvent::BUTTON_PRIMARY; + break; + case MouseButton::eMiddle: + button = java::sdk::MotionEvent::BUTTON_TERTIARY; + break; + case MouseButton::eSecondary: + button = java::sdk::MotionEvent::BUTTON_SECONDARY; + break; + case MouseButton::eX1: + button = java::sdk::MotionEvent::BUTTON_BACK; + break; + case MouseButton::eX2: + button = java::sdk::MotionEvent::BUTTON_FORWARD; + break; + default: + if (aNativeMessage == NativeMouseMessage::ButtonDown) { + MOZ_ASSERT_UNREACHABLE("Non supported mouse button type on Android"); + return NS_ERROR_INVALID_ARG; + } + break; + } + } + + // TODO (bug 1693237): Handle aModifierFlags. + DispatchToUiThread( + "nsWindow::SynthesizeNativeMouseEvent", + [npzc = java::PanZoomController::NativeProvider::GlobalRef(npzc), + nativeMessage, aPoint, button] { + npzc->SynthesizeNativeMouseEvent(nativeMessage, aPoint.x, aPoint.y, + button); + }); + return NS_OK; +} + +nsresult nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + nsIObserver* aObserver) { + return SynthesizeNativeMouseEvent( + aPoint, NativeMouseMessage::Move, MouseButton::eNotPressed, + nsIWidget::Modifiers::NO_MODIFIERS, aObserver); +} + +void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) { + if (delegate) { + mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate(); + MOZ_ASSERT(mCompositorWidgetDelegate, + "nsWindow::SetCompositorWidgetDelegate called with a " + "non-PlatformCompositorWidgetDelegate"); + } else { + mCompositorWidgetDelegate = nullptr; + } +} + +void nsWindow::GetCompositorWidgetInitData( + mozilla::widget::CompositorWidgetInitData* aInitData) { + *aInitData = mozilla::widget::AndroidCompositorWidgetInitData( + mWidgetId, GetClientSize()); +} + +bool nsWindow::WidgetPaintsBackground() { + return StaticPrefs::android_widget_paints_background(); +} + +bool nsWindow::NeedsPaint() { + auto lvs(mLayerViewSupport.Access()); + if (!lvs || lvs->CompositorPaused() || !GetWindowRenderer()) { + return false; + } + + return nsIWidget::NeedsPaint(); +} + +void nsWindow::ConfigureAPZControllerThread() { + nsCOMPtr<nsISerialEventTarget> thread = mozilla::GetAndroidUiThread(); + APZThreadUtils::SetControllerThread(thread); +} + +already_AddRefed<GeckoContentController> +nsWindow::CreateRootContentController() { + RefPtr<GeckoContentController> 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<ZoomConstraints>& aConstraints) { + nsBaseWidget::UpdateZoomConstraints(aPresShellId, aViewId, aConstraints); +} + +CompositorBridgeChild* nsWindow::GetCompositorBridgeChild() const { + return mCompositorSession ? mCompositorSession->GetCompositorBridgeChild() + : nullptr; +} + +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<LayerViewSupport>::Accessor lvs{ + mLayerViewSupport.Access()}) { + lvs->RecvToolbarAnimatorMessage(aMessage); + } +} + +void nsWindow::UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset, + const CSSToScreenScale& aZoom) { + MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); + if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::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<LayerViewSupport>::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); + } +} + +jni::NativeWeakPtr<NPZCSupport> nsWindow::GetNPZCSupportWeakPtr() { + return mNPZCSupport; +} + +already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() { + nsCOMPtr<nsIWidget> window = new nsWindow(); + return window.forget(); +} + +already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() { + nsCOMPtr<nsIWidget> window = new nsWindow(); + return window.forget(); +} + +static already_AddRefed<DataSourceSurface> GetCursorImage( + const nsIWidget::Cursor& aCursor, mozilla::CSSToLayoutDeviceScale aScale) { + if (!aCursor.IsCustom()) { + return nullptr; + } + + RefPtr<DataSourceSurface> destDataSurface; + + nsIntSize size = nsIWidget::CustomCursorSize(aCursor); + // prevent DoS attacks + if (size.width > 128 || size.height > 128) { + return nullptr; + } + + RefPtr<gfx::SourceSurface> surface = aCursor.mContainer->GetFrameAtSize( + size * aScale.scale, imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + RefPtr<DataSourceSurface> srcDataSurface = surface->GetDataSurface(); + if (NS_WARN_IF(!srcDataSurface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap sourceMap(srcDataSurface, + DataSourceSurface::READ); + + destDataSurface = gfx::Factory::CreateDataSourceSurfaceWithStride( + srcDataSurface->GetSize(), SurfaceFormat::R8G8B8A8, + sourceMap.GetStride()); + if (NS_WARN_IF(!destDataSurface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap destMap(destDataSurface, + DataSourceSurface::READ_WRITE); + + SwizzleData(sourceMap.GetData(), sourceMap.GetStride(), surface->GetFormat(), + destMap.GetData(), destMap.GetStride(), SurfaceFormat::R8G8B8A8, + destDataSurface->GetSize()); + + return destDataSurface.forget(); +} + +static int32_t GetCursorType(nsCursor aCursor) { + // When our minimal requirement of SDK version is 25+, + // we should replace with JNI auto-generator. + switch (aCursor) { + case eCursor_standard: + // android.view.PointerIcon.TYPE_ARROW + return 0x3e8; + case eCursor_wait: + // android.view.PointerIcon.TYPE_WAIT + return 0x3ec; + case eCursor_select: + // android.view.PointerIcon.TYPE_TEXT; + return 0x3f0; + case eCursor_hyperlink: + // android.view.PointerIcon.TYPE_HAND + return 0x3ea; + case eCursor_n_resize: + case eCursor_s_resize: + case eCursor_ns_resize: + case eCursor_row_resize: + // android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW + return 0x3f7; + case eCursor_w_resize: + case eCursor_e_resize: + case eCursor_ew_resize: + case eCursor_col_resize: + // android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW + return 0x3f6; + case eCursor_nw_resize: + case eCursor_se_resize: + case eCursor_nwse_resize: + // android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW + return 0x3f9; + case eCursor_ne_resize: + case eCursor_sw_resize: + case eCursor_nesw_resize: + // android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW + return 0x3f8; + case eCursor_crosshair: + // android.view.PointerIcon.TYPE_CROSSHAIR + return 0x3ef; + case eCursor_move: + // android.view.PointerIcon.TYPE_ARROW + return 0x3e8; + case eCursor_help: + // android.view.PointerIcon.TYPE_HELP + return 0x3eb; + case eCursor_copy: + // android.view.PointerIcon.TYPE_COPY + return 0x3f3; + case eCursor_alias: + // android.view.PointerIcon.TYPE_ALIAS + return 0x3f2; + case eCursor_context_menu: + // android.view.PointerIcon.TYPE_CONTEXT_MENU + return 0x3e9; + case eCursor_cell: + // android.view.PointerIcon.TYPE_CELL + return 0x3ee; + case eCursor_grab: + // android.view.PointerIcon.TYPE_GRAB + return 0x3fc; + case eCursor_grabbing: + // android.view.PointerIcon.TYPE_GRABBING + return 0x3fd; + case eCursor_spinning: + // android.view.PointerIcon.TYPE_WAIT + return 0x3ec; + case eCursor_zoom_in: + // android.view.PointerIcon.TYPE_ZOOM_IN + return 0x3fa; + case eCursor_zoom_out: + // android.view.PointerIcon.TYPE_ZOOM_OUT + return 0x3fb; + case eCursor_not_allowed: + // android.view.PointerIcon.TYPE_NO_DROP: + return 0x3f4; + case eCursor_no_drop: + // android.view.PointerIcon.TYPE_NO_DROP: + return 0x3f4; + case eCursor_vertical_text: + // android.view.PointerIcon.TYPE_VERTICAL_TEXT + return 0x3f1; + case eCursor_all_scroll: + // android.view.PointerIcon.TYPE_ALL_SCROLL + return 0x3f5; + case eCursor_none: + // android.view.PointerIcon.TYPE_NULL + return 0; + default: + NS_WARNING_ASSERTION(aCursor, "Invalid cursor type"); + // android.view.PointerIcon.TYPE_ARROW + return 0x3e8; + } +} + +void nsWindow::SetCursor(const Cursor& aCursor) { + if (mozilla::jni::GetAPIVersion() < 24) { + return; + } + + // Only change cursor if it's actually been changed + if (!mUpdateCursor && mCursor == aCursor) { + return; + } + + mUpdateCursor = false; + mCursor = aCursor; + + int32_t type = 0; + RefPtr<DataSourceSurface> destDataSurface = + GetCursorImage(aCursor, GetDefaultScale()); + if (!destDataSurface) { + type = GetCursorType(aCursor.mDefaultCursor); + } + + if (mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{ + mLayerViewSupport.Access()}) { + const auto& compositor = lvs->GetJavaCompositor(); + + DispatchToUiThread( + "nsWindow::SetCursor", + [compositor = GeckoSession::Compositor::GlobalRef(compositor), type, + destDataSurface = std::move(destDataSurface), + hotspotX = aCursor.mHotspotX, hotspotY = aCursor.mHotspotY] { + java::sdk::Bitmap::LocalRef bitmap; + if (destDataSurface) { + DataSourceSurface::ScopedMap destMap(destDataSurface, + DataSourceSurface::READ); + auto pixels = mozilla::jni::ByteBuffer::New( + reinterpret_cast<int8_t*>(destMap.GetData()), + destMap.GetStride() * destDataSurface->GetSize().height); + bitmap = java::sdk::Bitmap::CreateBitmap( + destDataSurface->GetSize().width, + destDataSurface->GetSize().height, + java::sdk::Bitmap::Config::ARGB_8888()); + bitmap->CopyPixelsFromBuffer(pixels); + } + compositor->SetPointerIcon(type, bitmap, hotspotX, hotspotY); + }); + } +} diff --git a/widget/android/nsWindow.h b/widget/android/nsWindow.h new file mode 100644 index 0000000000..7c6f8ba9bb --- /dev/null +++ b/widget/android/nsWindow.h @@ -0,0 +1,292 @@ +/* -*- 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; +class PlatformCompositorWidgetDelegate; +} // 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::GetWindowRenderer; + + nsWindow(); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget) + + static void InitNatives(); + void OnGeckoViewReady(); + RefPtr<mozilla::MozPromise<bool, bool, false>> OnLoadRequest( + nsIURI* aUri, int32_t aWindowType, int32_t aFlags, + nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture, + bool aIsTopLevel); + + void OnUpdateSessionStore(mozilla::jni::Object::Param aBundle); + + private: + // Unique ID given to each widget, used to map Surfaces to widgets + // in the CompositorSurfaceManager. + int32_t mWidgetId; + + private: + RefPtr<mozilla::widget::AndroidView> mAndroidView; + + // Object that implements native LayerView calls. + // Owned by the Java Compositor instance. + mozilla::jni::NativeWeakPtr<mozilla::widget::LayerViewSupport> + mLayerViewSupport; + + // Object that implements native NativePanZoomController calls. + // Owned by the Java NativePanZoomController instance. + mozilla::jni::NativeWeakPtr<mozilla::widget::NPZCSupport> mNPZCSupport; + + // Object that implements native GeckoEditable calls. + // Strong referenced by the Java instance. + mozilla::jni::NativeWeakPtr<mozilla::widget::GeckoEditableSupport> + mEditableSupport; + mozilla::jni::Object::GlobalRef mEditableParent; + + // Object that implements native SessionAccessibility calls. + // Strong referenced by the Java instance. + mozilla::jni::NativeWeakPtr<mozilla::a11y::SessionAccessibility> + mSessionAccessibility; + + // Object that implements native GeckoView calls and associated states. + // nullptr for nsWindows that were not opened from GeckoView. + mozilla::jni::NativeWeakPtr<mozilla::widget::GeckoViewSupport> + mGeckoViewSupport; + + mozilla::Atomic<bool, mozilla::ReleaseAcquire> mContentDocumentDisplayed; + + public: + static already_AddRefed<nsWindow> From(nsPIDOMWindowOuter* aDOMWindow); + static already_AddRefed<nsWindow> From(nsIWidget* aWidget); + + static nsWindow* TopWindow(); + + static mozilla::Modifiers GetModifiers(int32_t aMetaState); + static mozilla::TimeStamp GetEventTimeStamp(int64_t aEventTime); + + 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 ShowDynamicToolbar(); + + void DetachNatives(); + + mozilla::Mutex& GetDestroyMutex() { return mDestroyMutex; } + + // + // nsIWidget + // + + using nsBaseWidget::Create; // for Create signature not overridden here + [[nodiscard]] virtual nsresult Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + InitData* aInitData) override; + virtual void Destroy() 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(DesktopIntPoint&) 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 nsSizeMode SizeMode() override { return mSizeMode; } + 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 nsresult MakeFullScreen(bool aFullScreen) override; + void SetCursor(const Cursor& aDefaultCursor) override; + void* GetNativeData(uint32_t aDataType) 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; + + WindowRenderer* GetWindowRenderer() override; + + void NotifyCompositorSessionLost( + mozilla::layers::CompositorSession* aSession) 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<ZoomConstraints>& aConstraints) override; + + nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + nsIObserver* aObserver) override; + nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + NativeMouseMessage aNativeMessage, + mozilla::MouseButton aButton, + nsIWidget::Modifiers aModifierFlags, + nsIObserver* aObserver) override; + nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + nsIObserver* aObserver) override; + + void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override; + + virtual void GetCompositorWidgetInitData( + mozilla::widget::CompositorWidgetInitData* aInitData) 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<mozilla::a11y::SessionAccessibility> 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); + + mozilla::jni::NativeWeakPtr<mozilla::widget::NPZCSupport> + GetNPZCSupportWeakPtr(); + + protected: + void BringToFront(); + nsWindow* FindTopLevel(); + bool IsTopLevel(); + + void ConfigureAPZControllerThread() override; + void DispatchHitTest(const mozilla::WidgetTouchEvent& aEvent); + + already_AddRefed<GeckoContentController> CreateRootContentController() + override; + + bool mIsVisible; + nsTArray<nsWindow*> mChildren; + nsWindow* mParent; + + nsCOMPtr<nsIUserIdleServiceInternal> mIdleService; + mozilla::ScreenIntCoord mDynamicToolbarMaxHeight; + mozilla::ScreenIntMargin mSafeAreaInsets; + + nsSizeMode mSizeMode; + bool mIsFullScreen; + + bool UseExternalCompositingSurface() const override { return true; } + + static void DumpWindows(); + static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0); + static void LogWindow(nsWindow* win, int index, int indent); + + private: + void CreateLayerManager(); + void RedrawAll(); + + void OnSizeChanged(const mozilla::gfx::IntSize& aSize); + + mozilla::layers::LayersId GetRootLayerId() const; + RefPtr<mozilla::layers::UiCompositorControllerChild> + GetUiCompositorControllerChild(); + + mozilla::widget::PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate; + + mozilla::Mutex mDestroyMutex; + + friend class mozilla::widget::GeckoViewSupport; + friend class mozilla::widget::LayerViewSupport; + friend class mozilla::widget::NPZCSupport; +}; + +#endif /* NSWINDOW_H_ */ |