summaryrefslogtreecommitdiffstats
path: root/widget/android
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/android/AndroidAlerts.cpp159
-rw-r--r--widget/android/AndroidAlerts.h46
-rw-r--r--widget/android/AndroidBridge.cpp431
-rw-r--r--widget/android/AndroidBridge.h275
-rw-r--r--widget/android/AndroidBridgeUtilities.h19
-rw-r--r--widget/android/AndroidCompositorWidget.cpp113
-rw-r--r--widget/android/AndroidCompositorWidget.h70
-rw-r--r--widget/android/AndroidContentController.cpp68
-rw-r--r--widget/android/AndroidContentController.h51
-rw-r--r--widget/android/AndroidUiThread.cpp381
-rw-r--r--widget/android/AndroidUiThread.h25
-rw-r--r--widget/android/AndroidView.h35
-rw-r--r--widget/android/AndroidVsync.cpp142
-rw-r--r--widget/android/AndroidVsync.h78
-rw-r--r--widget/android/Base64UtilsSupport.h52
-rw-r--r--widget/android/CompositorWidgetChild.cpp42
-rw-r--r--widget/android/CompositorWidgetChild.h41
-rw-r--r--widget/android/CompositorWidgetParent.cpp54
-rw-r--r--widget/android/CompositorWidgetParent.h42
-rw-r--r--widget/android/EventDispatcher.cpp776
-rw-r--r--widget/android/EventDispatcher.h104
-rw-r--r--widget/android/GeckoBatteryManager.h28
-rw-r--r--widget/android/GeckoEditableSupport.cpp1690
-rw-r--r--widget/android/GeckoEditableSupport.h286
-rw-r--r--widget/android/GeckoNetworkManager.h45
-rw-r--r--widget/android/GeckoProcessManager.cpp77
-rw-r--r--widget/android/GeckoProcessManager.h84
-rw-r--r--widget/android/GeckoSystemStateListener.h31
-rw-r--r--widget/android/GeckoTelemetryDelegate.h100
-rw-r--r--widget/android/GeckoVRManager.h24
-rw-r--r--widget/android/GeckoViewSupport.h119
-rw-r--r--widget/android/GfxInfo.cpp873
-rw-r--r--widget/android/GfxInfo.h110
-rw-r--r--widget/android/ImageDecoderSupport.cpp185
-rw-r--r--widget/android/ImageDecoderSupport.h30
-rw-r--r--widget/android/InProcessAndroidCompositorWidget.cpp57
-rw-r--r--widget/android/InProcessAndroidCompositorWidget.h45
-rw-r--r--widget/android/MediaKeysEventSourceFactory.cpp17
-rw-r--r--widget/android/PCompositorWidget.ipdl30
-rw-r--r--widget/android/PlatformWidgetTypes.ipdlh29
-rw-r--r--widget/android/ScreenHelperAndroid.cpp65
-rw-r--r--widget/android/ScreenHelperAndroid.h29
-rw-r--r--widget/android/SurfaceViewWrapperSupport.h39
-rw-r--r--widget/android/Telemetry.h31
-rw-r--r--widget/android/WebExecutorSupport.cpp466
-rw-r--r--widget/android/WebExecutorSupport.h32
-rw-r--r--widget/android/WindowEvent.h57
-rw-r--r--widget/android/bindings/AccessibilityEvent-classes.txt3
-rw-r--r--widget/android/bindings/AndroidBuild-classes.txt5
-rw-r--r--widget/android/bindings/AndroidGraphics-classes.txt10
-rw-r--r--widget/android/bindings/AndroidInputType-classes.txt3
-rw-r--r--widget/android/bindings/AndroidProcess-classes.txt5
-rw-r--r--widget/android/bindings/AndroidRect-classes.txt2
-rw-r--r--widget/android/bindings/InetAddress-classes.txt6
-rw-r--r--widget/android/bindings/JavaBuiltins-classes.txt25
-rw-r--r--widget/android/bindings/JavaExceptions-classes.txt8
-rw-r--r--widget/android/bindings/KeyEvent-classes.txt3
-rw-r--r--widget/android/bindings/MediaCodec-classes.txt13
-rw-r--r--widget/android/bindings/MotionEvent-classes.txt3
-rw-r--r--widget/android/bindings/SurfaceTexture-classes.txt5
-rw-r--r--widget/android/bindings/ViewConfiguration-classes.txt1
-rw-r--r--widget/android/bindings/moz.build54
-rw-r--r--widget/android/components.conf100
-rw-r--r--widget/android/jni/Accessors.h251
-rw-r--r--widget/android/jni/Conversions.cpp113
-rw-r--r--widget/android/jni/Conversions.h23
-rw-r--r--widget/android/jni/GeckoBundleUtils.cpp290
-rw-r--r--widget/android/jni/GeckoBundleUtils.h46
-rw-r--r--widget/android/jni/GeckoResultUtils.h54
-rw-r--r--widget/android/jni/Natives.h1623
-rw-r--r--widget/android/jni/Refs.h1117
-rw-r--r--widget/android/jni/TypeAdapter.h71
-rw-r--r--widget/android/jni/Types.h123
-rw-r--r--widget/android/jni/Utils.cpp348
-rw-r--r--widget/android/jni/Utils.h150
-rw-r--r--widget/android/jni/moz.build37
-rw-r--r--widget/android/moz.build205
-rw-r--r--widget/android/nsAppShell.cpp755
-rw-r--r--widget/android/nsAppShell.h217
-rw-r--r--widget/android/nsClipboard.cpp188
-rw-r--r--widget/android/nsClipboard.h22
-rw-r--r--widget/android/nsDeviceContextAndroid.cpp100
-rw-r--r--widget/android/nsDeviceContextAndroid.h33
-rw-r--r--widget/android/nsIAndroidBridge.idl58
-rw-r--r--widget/android/nsLookAndFeel.cpp446
-rw-r--r--widget/android/nsLookAndFeel.h57
-rw-r--r--widget/android/nsPrintSettingsServiceAndroid.cpp33
-rw-r--r--widget/android/nsPrintSettingsServiceAndroid.h18
-rw-r--r--widget/android/nsUserIdleServiceAndroid.cpp14
-rw-r--r--widget/android/nsUserIdleServiceAndroid.h35
-rw-r--r--widget/android/nsWidgetFactory.cpp21
-rw-r--r--widget/android/nsWidgetFactory.h21
-rw-r--r--widget/android/nsWindow.cpp3166
-rw-r--r--widget/android/nsWindow.h290
94 files changed, 17754 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..edbdad6bb6
--- /dev/null
+++ b/widget/android/AndroidCompositorWidget.cpp
@@ -0,0 +1,113 @@
+/* -*- 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"
+#include "SurfaceViewWrapperSupport.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;
+ }
+
+ // If our Surface is in an abandoned state then we will never succesfully
+ // create an EGL Surface, and will eventually crash. Better to explicitly
+ // crash now.
+ if (SurfaceViewWrapperSupport::IsSurfaceAbandoned(mSurface)) {
+ MOZ_CRASH("Compositor resumed with abandoned Surface");
+ }
+
+ 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..c42370e26c
--- /dev/null
+++ b/widget/android/AndroidContentController.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AndroidContentController.h"
+
+#include "AndroidBridge.h"
+#include "base/message_loop.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "nsIObserverService.h"
+#include "nsLayoutUtils.h"
+#include "nsWindow.h"
+
+using mozilla::layers::IAPZCTreeManager;
+
+namespace mozilla {
+namespace widget {
+
+void AndroidContentController::Destroy() {
+ mAndroidWindow = nullptr;
+ ChromeProcessController::Destroy();
+}
+
+void AndroidContentController::UpdateOverscrollVelocity(
+ const ScrollableLayerGuid& aGuid, const float aX, const float aY,
+ const bool aIsRootContent) {
+ if (aIsRootContent && mAndroidWindow) {
+ mAndroidWindow->UpdateOverscrollVelocity(aX, aY);
+ }
+}
+
+void AndroidContentController::UpdateOverscrollOffset(
+ const ScrollableLayerGuid& aGuid, const float aX, const float aY,
+ const bool aIsRootContent) {
+ if (aIsRootContent && mAndroidWindow) {
+ mAndroidWindow->UpdateOverscrollOffset(aX, aY);
+ }
+}
+
+void AndroidContentController::NotifyAPZStateChange(
+ const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) {
+ // This function may get invoked twice, if the first invocation is not on
+ // the main thread then the ChromeProcessController version of this function
+ // will redispatch to the main thread. We want to make sure that our handling
+ // only happens on the main thread.
+ ChromeProcessController::NotifyAPZStateChange(aGuid, aChange, aArg);
+ if (NS_IsMainThread()) {
+ nsCOMPtr<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..d3535c78cf
--- /dev/null
+++ b/widget/android/AndroidContentController.h
@@ -0,0 +1,51 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AndroidContentController_h__
+#define AndroidContentController_h__
+
+#include "mozilla/layers/ChromeProcessController.h"
+#include "mozilla/EventForwards.h" // for Modifiers
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace layers {
+class APZEventState;
+class IAPZCTreeManager;
+} // namespace layers
+namespace widget {
+
+class AndroidContentController final
+ : public mozilla::layers::ChromeProcessController {
+ public:
+ AndroidContentController(nsWindow* aWindow,
+ mozilla::layers::APZEventState* aAPZEventState,
+ mozilla::layers::IAPZCTreeManager* aAPZCTreeManager)
+ : mozilla::layers::ChromeProcessController(aWindow, aAPZEventState,
+ aAPZCTreeManager),
+ mAndroidWindow(aWindow) {}
+
+ // ChromeProcessController methods
+ virtual void Destroy() override;
+ void UpdateOverscrollVelocity(const ScrollableLayerGuid& aGuid,
+ const float aX, const float aY,
+ const bool aIsRootContent) override;
+ void UpdateOverscrollOffset(const ScrollableLayerGuid& aGuid, const float aX,
+ const float aY,
+ const bool aIsRootContent) override;
+ void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
+ APZStateChange aChange, int aArg) override;
+
+ private:
+ nsWindow* mAndroidWindow;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/android/AndroidUiThread.cpp b/widget/android/AndroidUiThread.cpp
new file mode 100644
index 0000000000..b51dab3f42
--- /dev/null
+++ b/widget/android/AndroidUiThread.cpp
@@ -0,0 +1,381 @@
+/* -*- 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) {
+ if (aFlags & NS_DISPATCH_SYNC) {
+ return nsThread::Dispatch(std::move(aEvent), aFlags);
+ } else {
+ EnqueueTask(std::move(aEvent), 0);
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+AndroidUiThread::DelayedDispatch(already_AddRefed<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..b168154810
--- /dev/null
+++ b/widget/android/AndroidVsync.cpp
@@ -0,0 +1,142 @@
+/* -*- 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 "mozilla/java/GeckoAppShellWrappers.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);
+ float fps = java::GeckoAppShell::GetScreenRefreshRate();
+ impl->mVsyncDuration = TimeDuration::FromMilliseconds(1000.0 / fps);
+}
+
+AndroidVsync::~AndroidVsync() {
+ auto impl = mImpl.Lock();
+ impl->mInputObservers.Clear();
+ impl->mRenderObservers.Clear();
+ impl->UpdateObservingVsync();
+ impl->mSupport->Unlink();
+}
+
+TimeDuration AndroidVsync::GetVsyncRate() {
+ auto impl = mImpl.Lock();
+ return impl->mVsyncDuration;
+}
+
+void AndroidVsync::RegisterObserver(Observer* aObserver, ObserverType aType) {
+ auto impl = mImpl.Lock();
+ if (aType == AndroidVsync::INPUT) {
+ impl->mInputObservers.AppendElement(aObserver);
+ } else {
+ impl->mRenderObservers.AppendElement(aObserver);
+ }
+ impl->UpdateObservingVsync();
+}
+
+void AndroidVsync::UnregisterObserver(Observer* aObserver, ObserverType aType) {
+ auto impl = mImpl.Lock();
+ if (aType == AndroidVsync::INPUT) {
+ impl->mInputObservers.RemoveElement(aObserver);
+ } else {
+ impl->mRenderObservers.RemoveElement(aObserver);
+ }
+ 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) {
+ // 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);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/AndroidVsync.h b/widget/android/AndroidVsync.h
new file mode 100644
index 0000000000..9c090b8f0a
--- /dev/null
+++ b/widget/android/AndroidVsync.h
@@ -0,0 +1,78 @@
+/* -*- 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;
+ // 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);
+
+ TimeDuration GetVsyncRate();
+
+ private:
+ friend class AndroidVsyncSupport;
+
+ AndroidVsync();
+
+ // Called by Java, via AndroidVsyncSupport
+ void NotifyVsync(int64_t aFrameTimeNanos);
+
+ struct Impl {
+ void UpdateObservingVsync();
+
+ TimeDuration mVsyncDuration;
+ 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..8c21a0ff73
--- /dev/null
+++ b/widget/android/GeckoEditableSupport.cpp
@@ -0,0 +1,1690 @@
+/* -*- 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/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;
+ }
+
+ if (StaticPrefs::
+ intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
+ mInputContext.mMayBeIMEUnaware) {
+ SendIMEDummyKeyEvent(widget, eKeyDown);
+ if (!mDispatcher || widget->Destroyed()) {
+ return false;
+ }
+ }
+
+ if (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;
+ }
+
+ if (StaticPrefs::
+ intl_ime_hack_on_any_apps_fire_key_events_for_composition() ||
+ mInputContext.mMayBeIMEUnaware) {
+ SendIMEDummyKeyEvent(widget, eKeyUp);
+ // Widget may be destroyed after dispatching the above event.
+ }
+ return textChanged;
+}
+
+void GeckoEditableSupport::OnImeAddCompositionRange(
+ int32_t aStart, int32_t aEnd, int32_t aRangeType, int32_t aRangeStyle,
+ int32_t aRangeLineStyle, bool aRangeBoldLine, int32_t aRangeForeColor,
+ int32_t aRangeBackColor, int32_t aRangeLineColor) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return;
+ }
+
+ TextRange range;
+ range.mStartOffset = aStart;
+ range.mEndOffset = aEnd;
+ range.mRangeType = ToTextRangeType(aRangeType);
+ range.mRangeStyle.mDefinedStyles = aRangeStyle;
+ range.mRangeStyle.mLineStyle = TextRangeStyle::ToLineStyle(aRangeLineStyle);
+ range.mRangeStyle.mIsBoldLine = aRangeBoldLine;
+ range.mRangeStyle.mForegroundColor =
+ ConvertAndroidColor(uint32_t(aRangeForeColor));
+ range.mRangeStyle.mBackgroundColor =
+ ConvertAndroidColor(uint32_t(aRangeBackColor));
+ range.mRangeStyle.mUnderlineColor =
+ ConvertAndroidColor(uint32_t(aRangeLineColor));
+ mIMERanges->AppendElement(range);
+}
+
+void GeckoEditableSupport::OnImeUpdateComposition(int32_t aStart, int32_t aEnd,
+ int32_t aFlags) {
+ AutoGeckoEditableBlocker blocker(this);
+
+ if (DoUpdateComposition(aStart, aEnd, aFlags)) {
+ mIMEDelaySynchronizeReply = true;
+ }
+}
+
+bool GeckoEditableSupport::DoUpdateComposition(int32_t aStart, int32_t aEnd,
+ int32_t aFlags) {
+ if (mIMEMaskEventsCount > 0) {
+ // Not focused.
+ return false;
+ }
+
+ nsCOMPtr<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..18df7909cf
--- /dev/null
+++ b/widget/android/GeckoViewSupport.h
@@ -0,0 +1,119 @@
+/* -*- 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();
+
+ 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);
+};
+
+} // 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..6c976b6556
--- /dev/null
+++ b/widget/android/GfxInfo.cpp
@@ -0,0 +1,873 @@
+/* -*- 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::GetDesktopEnvironment(nsAString& aDesktopEnvironment) {
+ 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;
+}
+
+NS_IMETHODIMP GfxInfo::FireTestProcess() { return NS_OK; }
+
+#endif
+
+nsString GfxInfo::Model() {
+ EnsureInitialized();
+ return mModel;
+}
+
+nsString GfxInfo::Hardware() {
+ EnsureInitialized();
+ return mHardware;
+}
+
+nsString GfxInfo::Product() {
+ EnsureInitialized();
+ return mProduct;
+}
+
+nsString GfxInfo::Manufacturer() {
+ EnsureInitialized();
+ return mManufacturer;
+}
+
+uint32_t GfxInfo::OperatingSystemVersion() {
+ EnsureInitialized();
+ return mOSVersionInteger;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/android/GfxInfo.h b/widget/android/GfxInfo.h
new file mode 100644
index 0000000000..3a30d0b11e
--- /dev/null
+++ b/widget/android/GfxInfo.h
@@ -0,0 +1,110 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+#include "GfxDriverInfo.h"
+
+#include "nsString.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+namespace widget {
+
+class GfxInfo : public GfxInfoBase {
+ private:
+ ~GfxInfo();
+
+ public:
+ GfxInfo();
+
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetDesktopEnvironment(nsAString& aDesktopEnvironment) override;
+ NS_IMETHOD 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..0cc1cd26cd
--- /dev/null
+++ b/widget/android/ScreenHelperAndroid.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "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));
+}
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/SurfaceViewWrapperSupport.h b/widget/android/SurfaceViewWrapperSupport.h
new file mode 100644
index 0000000000..fde98dd957
--- /dev/null
+++ b/widget/android/SurfaceViewWrapperSupport.h
@@ -0,0 +1,39 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SurfaceViewWrapperSupport_h__
+#define SurfaceViewWrapperSupport_h__
+
+#include "mozilla/java/SurfaceViewWrapperNatives.h"
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+
+namespace mozilla {
+namespace widget {
+
+class SurfaceViewWrapperSupport final
+ : public java::SurfaceViewWrapper::Natives<SurfaceViewWrapperSupport> {
+ public:
+ static bool IsSurfaceAbandoned(jni::Object::Param aSurface) {
+ ANativeWindow* win =
+ ANativeWindow_fromSurface(jni::GetEnvForThread(), aSurface.Get());
+ if (!win) {
+ return true;
+ }
+
+ // If the Surface's underlying BufferQueue has been abandoned, then
+ // ANativeWindow_getWidth (or height) will return a negative value
+ // to indicate an error.
+ int32_t width = ANativeWindow_getWidth(win);
+
+ ANativeWindow_release(win);
+ return width < 0;
+ }
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // SurfaceViewWrapperSupport_h__
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..33f7cc5c1b
--- /dev/null
+++ b/widget/android/WebExecutorSupport.cpp
@@ -0,0 +1,466 @@
+/* -*- 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
+ 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..ff4517daf2
--- /dev/null
+++ b/widget/android/jni/Conversions.cpp
@@ -0,0 +1,113 @@
+/* -*- 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) {
+ return ipc::LaunchError{};
+}
+
+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..7bb0507279
--- /dev/null
+++ b/widget/android/jni/GeckoBundleUtils.cpp
@@ -0,0 +1,290 @@
+/* -*- 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"
+
+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;
+}
+
+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);
+ }
+
+ 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..72e02d9db0
--- /dev/null
+++ b/widget/android/jni/Natives.h
@@ -0,0 +1,1623 @@
+/* -*- 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/TypeTraits.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/jni/Accessors.h"
+#include "mozilla/jni/Refs.h"
+#include "mozilla/jni/Types.h"
+#include "mozilla/jni/Utils.h"
+#include "nsThreadUtils.h"
+
+#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 {
+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;
+};
+
+/**
+ * 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;
+ return NS_OK;
+ }
+
+ private:
+ ~NativeWeakPtrDetachRunnable() {
+ // Guard against somebody forgetting to call this runnable.
+ MOZ_RELEASE_ASSERT(mHasRun, "You must run/dispatch this runnable!");
+ }
+
+ private:
+ RefPtr<detail::NativeWeakPtrControlBlock<NativeImpl>> mCtlBlock;
+ Object::GlobalRef mOwner;
+ typename NativeWeakPtrControlBlockStorageTraits<NativeImpl>::Type mNativeImpl;
+ bool mHasRun;
+};
+
+/**
+ * If you want to temporarily access the object held by a NativeWeakPtr, you
+ * must obtain one of these Accessor objects from the pointer. Access must
+ * be done _exclusively_ using once of these objects!
+ */
+template <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
+
+/**
+ * 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.
+ */
+ void Detach() {
+ if (!IsAttached()) {
+ // Never attached to begin with; no-op
+ return;
+ }
+
+ auto native = mCtlBlock->Clear();
+ if (!native) {
+ // Detach already in progress
+ return;
+ }
+
+ Object::LocalRef owner(mCtlBlock->GetJavaOwner());
+ MOZ_RELEASE_ASSERT(!!owner);
+
+ // Save the raw pointer before we move native into the runnable so that we
+ // may call OnWeakNonIntrusiveDetach on it even after moving native into
+ // the runnable.
+ NativeImpl* rawImpl =
+ detail::NativeWeakPtrControlBlock<NativeImpl>::StorageTraits::AsRaw(
+ native);
+ rawImpl->OnWeakNonIntrusiveDetach(
+ do_AddRef(new NativeWeakPtrDetachRunnable<NativeImpl>(
+ mCtlBlock.forget(), owner, std::move(native))));
+ }
+
+ /**
+ * This method does not indicate whether or not the weak pointer is still
+ * valid; it only indicates whether we're actually attached to one.
+ */
+ bool IsAttached() const { return !!mCtlBlock; }
+
+ /**
+ * Does this pointer reference the same object as the one referenced by the
+ * provided Accessor?
+ */
+ bool IsSame(const Accessor& aAccessor) const {
+ return mCtlBlock == aAccessor.mCtlBlock;
+ }
+
+ /**
+ * Does this pointer reference the same object as the one referenced by the
+ * provided Control Block?
+ */
+ bool IsSame(const RefPtr<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(mozilla::IsPod<T>::value, "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/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..b3db8f730a
--- /dev/null
+++ b/widget/android/jni/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("GeckoView", "General")
+
+EXPORTS.mozilla.jni += [
+ "Accessors.h",
+ "Conversions.h",
+ "GeckoBundleUtils.h",
+ "GeckoResultUtils.h",
+ "Natives.h",
+ "Refs.h",
+ "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",
+]
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/widget/android/moz.build b/widget/android/moz.build
new file mode 100644
index 0000000000..ac22221e27
--- /dev/null
+++ b/widget/android/moz.build
@@ -0,0 +1,205 @@
+# -*- 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",
+ "SurfaceViewWrapper",
+ "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",
+ "/netwerk/cache",
+ "/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
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp
new file mode 100644
index 0000000000..b529ade115
--- /dev/null
+++ b/widget/android/nsAppShell.cpp
@@ -0,0 +1,755 @@
+/* -*- 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"
+#include "SurfaceViewWrapperSupport.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);
+ }
+
+ 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();
+ mozilla::widget::SurfaceViewWrapperSupport::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..a77ca56eae
--- /dev/null
+++ b/widget/android/nsClipboard.cpp
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/java/ClipboardWrappers.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "nsClipboard.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPrimitiveHelpers.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+/* The Android clipboard only supports text and doesn't support mime types
+ * so we assume all clipboard data is text/unicode for now. Documentation
+ * indicates that support for other data types is planned for future
+ * releases.
+ */
+
+nsClipboard::nsClipboard() {}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard) {
+ if (aWhichClipboard != kGlobalClipboard) return NS_ERROR_NOT_IMPLEMENTED;
+
+ if (!jni::IsAvailable()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<nsCString> flavors;
+ aTransferable->FlavorsTransferableCanImport(flavors);
+
+ nsAutoString html;
+ nsAutoString text;
+
+ for (auto& flavorStr : flavors) {
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ nsCOMPtr<nsISupports> item;
+ nsresult rv =
+ aTransferable->GetTransferData(kUnicodeMime, 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(kUnicodeMime) ||
+ flavorStr.EqualsLiteral(kHTMLMime)) {
+ auto text = java::Clipboard::GetData(
+ java::GeckoAppShell::GetApplicationContext(), flavorStr);
+ if (!text) {
+ continue;
+ }
+ nsString buffer = text->ToString();
+ if (buffer.IsEmpty()) {
+ continue;
+ }
+ nsCOMPtr<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::SupportsSelectionClipboard(bool* aIsSupported) {
+ *aIsSupported = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
diff --git a/widget/android/nsClipboard.h b/widget/android/nsClipboard.h
new file mode 100644
index 0000000000..f714effaed
--- /dev/null
+++ b/widget/android/nsClipboard.h
@@ -0,0 +1,22 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_CLIPBOARD_H
+#define NS_CLIPBOARD_H
+
+#include "nsIClipboard.h"
+
+class nsClipboard final : public nsIClipboard {
+ private:
+ ~nsClipboard() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ nsClipboard();
+};
+
+#endif
diff --git a/widget/android/nsDeviceContextAndroid.cpp b/widget/android/nsDeviceContextAndroid.cpp
new file mode 100644
index 0000000000..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..3829df564c
--- /dev/null
+++ b/widget/android/nsLookAndFeel.cpp
@@ -0,0 +1,446 @@
+/* -*- 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::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;
+
+ 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..15f9d7e132
--- /dev/null
+++ b/widget/android/nsUserIdleServiceAndroid.cpp
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUserIdleServiceAndroid.h"
+
+bool nsUserIdleServiceAndroid::PollIdleTime(uint32_t* aIdleTime) {
+ return false;
+}
+
+bool nsUserIdleServiceAndroid::UsePollMode() { return false; }
diff --git a/widget/android/nsUserIdleServiceAndroid.h b/widget/android/nsUserIdleServiceAndroid.h
new file mode 100644
index 0000000000..2169673088
--- /dev/null
+++ b/widget/android/nsUserIdleServiceAndroid.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUserIdleServiceAndroid_h__
+#define nsUserIdleServiceAndroid_h__
+
+#include "nsUserIdleService.h"
+
+class nsUserIdleServiceAndroid : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceAndroid,
+ nsUserIdleService)
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceAndroid> GetInstance() {
+ RefPtr<nsUserIdleService> idleService = nsUserIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsUserIdleServiceAndroid();
+ }
+
+ return idleService.forget().downcast<nsUserIdleServiceAndroid>();
+ }
+
+ protected:
+ nsUserIdleServiceAndroid() {}
+ virtual ~nsUserIdleServiceAndroid() {}
+ bool UsePollMode() override;
+};
+
+#endif // nsUserIdleServiceAndroid_h__
diff --git a/widget/android/nsWidgetFactory.cpp b/widget/android/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..00ebd20dd9
--- /dev/null
+++ b/widget/android/nsWidgetFactory.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/WidgetUtils.h"
+
+#include "nsAppShell.h"
+
+#include "nsLookAndFeel.h"
+#include "nsAppShellSingleton.h"
+
+nsresult nsWidgetAndroidModuleCtor() { return nsAppShellInit(); }
+
+void nsWidgetAndroidModuleDtor() {
+ // Shutdown all XP level widget classes.
+ mozilla::widget::WidgetUtils::Shutdown();
+
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
diff --git a/widget/android/nsWidgetFactory.h b/widget/android/nsWidgetFactory.h
new file mode 100644
index 0000000000..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..dd7eb4390e
--- /dev/null
+++ b/widget/android/nsWindow.cpp
@@ -0,0 +1,3166 @@
+/* -*- 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 "SurfaceViewWrapperSupport.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/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/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"
+
+#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;
+ }
+ nsWindow* gkWindow = win->GetNsWindow();
+ 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) {
+ 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;
+
+ 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);
+ }
+ }
+
+ mUiCompositorControllerChild->ResumeAndResize(mX, mY, mWidth, mHeight);
+ }
+ }
+
+ /// 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;
+ mUiCompositorControllerChild->Resume();
+ }
+ }
+
+ 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());
+
+ // If our Surface is in an abandoned state then we will never succesfully
+ // create an EGL Surface, and will eventually crash. Better to explicitly
+ // crash now.
+ if (SurfaceViewWrapperSupport::IsSurfaceAbandoned(aSurface)) {
+ MOZ_CRASH("Compositor resumed with abandoned Surface");
+ }
+
+ 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);
+ }
+ }
+
+ mUiCompositorControllerChild->ResumeAndResize(aX, aY, aWidth, aHeight);
+ }
+
+ mCompositorPaused = false;
+
+ class OnResumedEvent : public nsAppShell::Event {
+ GeckoSession::Compositor::GlobalRef mCompositor;
+
+ public:
+ explicit OnResumedEvent(GeckoSession::Compositor::GlobalRef&& aCompositor)
+ : mCompositor(std::move(aCompositor)) {}
+
+ void Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ JNIEnv* const env = jni::GetGeckoThreadEnv();
+ const auto lvsHolder =
+ GetNative(GeckoSession::Compositor::LocalRef(env, mCompositor));
+
+ if (!lvsHolder) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ auto lvs(lvsHolder->Access());
+ if (!lvs) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ auto win = lvs->mWindow.Access();
+ if (!win) {
+ env->ExceptionClear();
+ return; // Already shut down.
+ }
+
+ // When we get here, the compositor has already been told to
+ // resume. This means it's now safe for layer updates to occur.
+ // Since we might have prevented one or more draw events from
+ // occurring while the compositor was paused, we need to
+ // schedule a draw event now.
+ if (!lvs->mCompositorPaused) {
+ nsWindow* const gkWindow = win->GetNsWindow();
+ if (gkWindow) {
+ gkWindow->RedrawAll();
+ }
+ }
+ }
+ };
+
+ // Use priority queue for timing-sensitive event.
+ nsAppShell::PostEvent(
+ MakeUnique<LayerViewEvent>(MakeUnique<OnResumedEvent>(aObj)));
+ }
+
+ 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);
+
+ mWindow->mSessionAccessibility.Detach();
+ if (aSessionAccessibility) {
+ AttachAccessibility(inst, aSessionAccessibility);
+ }
+
+ if (mIsReady) {
+ // We're in a transfer; update init-data and notify JS code.
+ mWindow->mAndroidView->mInitData = java::GeckoBundle::Ref::From(aInitData);
+ OnReady(aQueue);
+ mWindow->mAndroidView->mEventDispatcher->Dispatch(
+ u"GeckoView:UpdateInitData");
+ }
+
+ DispatchToUiThread("GeckoViewSupport::Transfer",
+ [compositor = GeckoSession::Compositor::GlobalRef(
+ compositor)] { compositor->OnCompositorAttached(); });
+}
+
+void GeckoViewSupport::AttachEditable(
+ const GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aEditableParent) {
+ if (auto win{mWindow->mEditableSupport.Access()}) {
+ win->TransferParent(aEditableParent);
+ } else {
+ auto editableChild = java::GeckoEditableChild::New(aEditableParent,
+ /* default */ true);
+ mWindow->mEditableSupport =
+ jni::NativeWeakPtrHolder<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::PrintToPdf(
+ const java::GeckoSession::Window::LocalRef& inst,
+ jni::Object::Param aResult) {
+ auto stream = java::GeckoInputStream::New(nullptr);
+ auto geckoResult = java::GeckoResult::Ref::From(aResult);
+ const auto pdfErrorMsg = "Could not save this page as PDF.";
+ RefPtr<GeckoViewOutputStream> streamListener =
+ new GeckoViewOutputStream(stream);
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (!printSettingsService) {
+ geckoResult->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))) {
+ geckoResult->CompleteExceptionally(
+ GeckoPrintException::New(
+ GeckoPrintException::ERROR_UNABLE_TO_CREATE_PRINT_SETTINGS)
+ .Cast<jni::Throwable>());
+ GVS_LOG("Could not create print settings.");
+ }
+
+ printSettings->SetPrinterName(u"Mozilla Save to PDF"_ns);
+ printSettings->SetOutputDestination(
+ nsIPrintSettings::kOutputDestinationStream);
+ printSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
+ printSettings->SetOutputStream(streamListener);
+ printSettings->SetPrintSilent(true);
+
+ 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;
+ }
+
+ RefPtr<CanonicalBrowsingContext::PrintPromise> print =
+ cbc->Print(printSettings);
+
+ geckoResult->Complete(stream);
+ print->Then(
+ mozilla::GetCurrentSerialEventTarget(), __func__,
+ [result = java::GeckoResult::GlobalRef(geckoResult), stream, pdfErrorMsg](
+ const CanonicalBrowsingContext::PrintPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ GVS_LOG("Could not print. %s", pdfErrorMsg);
+ stream->SendError();
+ }
+ });
+}
+} // 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->WindowType() == nsWindowType::eWindowType_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,
+ 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) {}
+
+nsWindow::~nsWindow() {
+ gTopLevelWindows.RemoveElement(this);
+ ALOG("nsWindow %p destructor", (void*)this);
+ // The mCompositorSession should have been cleaned up in nsWindow::Destroy()
+ // DestroyLayerManager() will call DestroyCompositor() which will crash if
+ // called from nsBaseWidget destructor. See Bug 1392705
+ MOZ_ASSERT(!mCompositorSession);
+}
+
+bool nsWindow::IsTopLevel() {
+ return mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_invisible;
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+ ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent,
+ aRect.x, aRect.y, aRect.width, aRect.height);
+
+ nsWindow* parent = (nsWindow*)aParent;
+ if (aNativeParent) {
+ if (parent) {
+ ALOG(
+ "Ignoring native parent on Android window [%p], "
+ "since parent was specified (%p %p)",
+ (void*)this, (void*)aNativeParent, (void*)aParent);
+ } else {
+ parent = (nsWindow*)aNativeParent;
+ }
+ }
+
+ // A default size of 1x1 confuses MobileViewportManager, so
+ // use 0x0 instead. This is also a little more fitting since
+ // we don't yet have a surface yet (and therefore a valid size)
+ // and 0x0 is usually recognized as invalid.
+ LayoutDeviceIntRect rect = aRect;
+ if (aRect.width == 1 && aRect.height == 1) {
+ rect.width = 0;
+ rect.height = 0;
+ }
+
+ mBounds = rect;
+ 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() {
+ 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 == eWindowType_invisible) {
+ ALOG("trying to show invisible window! ignoring..");
+ return;
+ }
+
+ if (aState == mIsVisible) return;
+
+ mIsVisible = aState;
+
+ if (IsTopLevel()) {
+ // XXX should we bring this to the front when it's shown,
+ // if it's a toplevel widget?
+
+ // XXX we should synthesize a eMouseExitFromWidget (for old top
+ // window)/eMouseEnterIntoWidget (for new top window) since we need
+ // to pretend that the top window always has focus. Not sure
+ // if Show() is the right place to do this, though.
+
+ if (aState) {
+ // It just became visible, so bring it to the front.
+ BringToFront();
+
+ } else if (nsWindow::TopWindow() == this) {
+ // find the next visible window to show
+ unsigned int i;
+ for (i = 1; i < gTopLevelWindows.Length(); i++) {
+ nsWindow* win = gTopLevelWindows[i];
+ if (!win->mIsVisible) continue;
+
+ win->BringToFront();
+ break;
+ }
+ }
+ } else if (FindTopLevel() == nsWindow::TopWindow()) {
+ RedrawAll();
+ }
+
+#ifdef DEBUG_ANDROID_WIDGET
+ DumpWindows();
+#endif
+}
+
+bool nsWindow::IsVisible() const { return mIsVisible; }
+
+void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
+ ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop,
+ *aX, *aY);
+
+ // constrain toplevel windows; children we don't care about
+ if (IsTopLevel()) {
+ *aX = 0;
+ *aY = 0;
+ }
+}
+
+void nsWindow::Move(double aX, double aY) {
+ if (IsTopLevel()) return;
+
+ Resize(aX, aY, mBounds.width, mBounds.height, true);
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ Resize(mBounds.x, mBounds.y, aWidth, aHeight, aRepaint);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ ALOG("nsWindow[%p]::Resize [%f %f %f %f] (repaint %d)", (void*)this, aX, aY,
+ aWidth, aHeight, aRepaint);
+
+ 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;
+ }
+
+ nsIWidgetListener* listener = GetWidgetListener();
+ if (listener) {
+ listener->FullscreenWillChange(aFullScreen);
+ }
+
+ mIsFullScreen = aFullScreen;
+ mAndroidView->mEventDispatcher->Dispatch(
+ aFullScreen ? u"GeckoView:FullScreenEnter" : u"GeckoView:FullScreenExit");
+
+ if (listener) {
+ mSizeMode = mIsFullScreen ? nsSizeMode_Fullscreen : nsSizeMode_Normal;
+ listener->SizeModeChanged(mSizeMode);
+ listener->FullscreenChanged(mIsFullScreen);
+ }
+ return NS_OK;
+}
+
+mozilla::WindowRenderer* nsWindow::GetWindowRenderer() {
+ if (!mWindowRenderer) {
+ CreateLayerManager();
+ }
+
+ return mWindowRenderer;
+}
+
+void nsWindow::CreateLayerManager() {
+ if (mWindowRenderer) {
+ return;
+ }
+
+ nsWindow* topLevelWindow = FindTopLevel();
+ if (!topLevelWindow || topLevelWindow->mWindowType == eWindowType_invisible) {
+ // don't create a layer manager for an invisible top-level window
+ return;
+ }
+
+ // Ensure that gfxPlatform is initialized first.
+ gfxPlatform::GetPlatform();
+
+ if (ShouldUseOffMainThreadCompositing()) {
+ LayoutDeviceIntRect rect = GetBounds();
+ CreateCompositor(rect.Width(), rect.Height());
+ if (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_DISPLAY:
+ return nullptr;
+
+ case NS_NATIVE_WIDGET:
+ return (void*)this;
+
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // We assume that there is only one context per process on Android
+ return NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ }
+
+ case NS_JAVA_SURFACE:
+ if (::mozilla::jni::NativeWeakPtr<LayerViewSupport>::Accessor lvs{
+ mLayerViewSupport.Access()}) {
+ return lvs->GetSurface().Get();
+ }
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+void nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) {
+ switch (aDataType) {}
+}
+
+void nsWindow::DispatchHitTest(const WidgetTouchEvent& aEvent) {
+ if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() == 1) {
+ // Since touch events don't get retargeted by PositionedEventTargeting.cpp
+ // code, we dispatch a dummy mouse event that *does* get retargeted.
+ // Front-end code can use this to activate the highlight element in case
+ // this touchstart is the start of a tap.
+ WidgetMouseEvent hittest(true, eMouseHitTest, this,
+ WidgetMouseEvent::eReal);
+ hittest.mRefPoint = aEvent.mTouches[0]->mRefPoint;
+ hittest.mInputSource = MouseEvent_Binding::MOZ_SOURCE_TOUCH;
+ nsEventStatus status;
+ DispatchEvent(&hittest, status);
+ }
+}
+
+void nsWindow::PassExternalResponse(java::WebResponse::Param aResponse) {
+ auto acc(mGeckoViewSupport.Access());
+ if (!acc) {
+ return;
+ }
+
+ acc->PassExternalResponse(aResponse);
+}
+
+mozilla::Modifiers nsWindow::GetModifiers(int32_t metaState) {
+ using mozilla::java::sdk::KeyEvent;
+ return (metaState & KeyEvent::META_ALT_MASK ? MODIFIER_ALT : 0) |
+ (metaState & KeyEvent::META_SHIFT_MASK ? MODIFIER_SHIFT : 0) |
+ (metaState & KeyEvent::META_CTRL_MASK ? MODIFIER_CONTROL : 0) |
+ (metaState & KeyEvent::META_META_MASK ? MODIFIER_META : 0) |
+ (metaState & KeyEvent::META_FUNCTION_ON ? MODIFIER_FN : 0) |
+ (metaState & KeyEvent::META_CAPS_LOCK_ON ? MODIFIER_CAPSLOCK : 0) |
+ (metaState & KeyEvent::META_NUM_LOCK_ON ? MODIFIER_NUMLOCK : 0) |
+ (metaState & KeyEvent::META_SCROLL_LOCK_ON ? MODIFIER_SCROLLLOCK : 0);
+}
+
+TimeStamp nsWindow::GetEventTimeStamp(int64_t aEventTime) {
+ // Android's event time is SystemClock.uptimeMillis that is counted in ms
+ // since OS was booted.
+ // (https://developer.android.com/reference/android/os/SystemClock.html)
+ // and this SystemClock.uptimeMillis uses SYSTEM_TIME_MONOTONIC.
+ // Our posix implemententaion of TimeStamp::Now uses SYSTEM_TIME_MONOTONIC
+ // too. Due to same implementation, we can use this via FromSystemTime.
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime);
+ return TimeStamp::FromSystemTime(tick);
+}
+
+void nsWindow::UserActivity() {
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+
+ if (FindTopLevel() != nsWindow::TopWindow()) {
+ BringToFront();
+ }
+}
+
+RefPtr<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..efd7dde556
--- /dev/null
+++ b/widget/android/nsWindow.h
@@ -0,0 +1,290 @@
+/* -*- 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();
+
+ //
+ // nsIWidget
+ //
+
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ [[nodiscard]] virtual nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy() override;
+ virtual void SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+ virtual void Show(bool aState) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX,
+ int32_t* aY) override;
+ virtual void Move(double aX, double aY) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ void SetZIndex(int32_t aZIndex) override;
+ virtual 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;
+ void SetNativeData(uint32_t aDataType, uintptr_t aVal) override;
+ virtual nsresult SetTitle(const nsAString& aTitle) override { return NS_OK; }
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+
+ 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;
+
+ friend class mozilla::widget::GeckoViewSupport;
+ friend class mozilla::widget::LayerViewSupport;
+ friend class mozilla::widget::NPZCSupport;
+};
+
+#endif /* NSWINDOW_H_ */