summaryrefslogtreecommitdiffstats
path: root/widget/android/jni/Utils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/android/jni/Utils.cpp')
-rw-r--r--widget/android/jni/Utils.cpp348
1 files changed, 348 insertions, 0 deletions
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