summaryrefslogtreecommitdiffstats
path: root/widget/android/AndroidBridge.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/android/AndroidBridge.cpp741
1 files changed, 741 insertions, 0 deletions
diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp
new file mode 100644
index 0000000000..71215e41b0
--- /dev/null
+++ b/widget/android/AndroidBridge.cpp
@@ -0,0 +1,741 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <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 "AndroidRect.h"
+#include "nsAlertsUtils.h"
+#include "nsAppShell.h"
+#include "nsOSHelperAppService.h"
+#include "nsWindow.h"
+#include "mozilla/Preferences.h"
+#include "nsThreadUtils.h"
+#include "gfxPlatform.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxUtils.h"
+#include "nsPresContext.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsPrintfCString.h"
+#include "nsContentUtils.h"
+
+#include "EventDispatcher.h"
+#include "MediaCodec.h"
+#include "SurfaceTexture.h"
+#include "GLContextProvider.h"
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "WidgetUtils.h"
+
+#include "mozilla/java/EventDispatcherWrappers.h"
+#include "mozilla/java/GeckoAppShellWrappers.h"
+#include "mozilla/java/GeckoThreadWrappers.h"
+#include "mozilla/java/HardwareCodecCapabilityUtilsWrappers.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+AndroidBridge* AndroidBridge::sBridge = nullptr;
+static jobject sGlobalContext = nullptr;
+nsDataHashtable<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;");
+
+ AutoJNIClass string(jEnv, "java/lang/String");
+ jStringClass = string.getGlobalRef();
+
+ mAPIVersion = jni::GetAPIVersion();
+
+ AutoJNIClass channels(jEnv, "java/nio/channels/Channels");
+ jChannels = channels.getGlobalRef();
+ jChannelCreate = channels.getStaticMethod(
+ "newChannel",
+ "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
+
+ AutoJNIClass readableByteChannel(jEnv,
+ "java/nio/channels/ReadableByteChannel");
+ jReadableByteChannel = readableByteChannel.getGlobalRef();
+ jByteBufferRead =
+ readableByteChannel.getMethod("read", "(Ljava/nio/ByteBuffer;)I");
+
+ AutoJNIClass inputStream(jEnv, "java/io/InputStream");
+ jInputStream = inputStream.getGlobalRef();
+ jClose = inputStream.getMethod("close", "()V");
+ jAvailable = inputStream.getMethod("available", "()I");
+}
+
+static void getHandlersFromStringArray(
+ JNIEnv* aJNIEnv, jni::ObjectArray::Param aArr, size_t aLen,
+ nsIMutableArray* aHandlersArray, nsIHandlerApp** aDefaultApp,
+ const nsAString& aAction = u""_ns, const nsACString& aMimeType = ""_ns) {
+ auto getNormalizedString = [](jni::Object::Param obj) -> nsString {
+ nsString out;
+ if (!obj) {
+ out.SetIsVoid(true);
+ } else {
+ out.Assign(jni::String::Ref::From(obj)->ToString());
+ }
+ return out;
+ };
+
+ for (size_t i = 0; i < aLen; i += 4) {
+ nsString name(getNormalizedString(aArr->GetElement(i)));
+ nsString isDefault(getNormalizedString(aArr->GetElement(i + 1)));
+ nsString packageName(getNormalizedString(aArr->GetElement(i + 2)));
+ nsString className(getNormalizedString(aArr->GetElement(i + 3)));
+
+ nsIHandlerApp* app = nsOSHelperAppService::CreateAndroidHandlerApp(
+ name, className, packageName, className, aMimeType, aAction);
+
+ aHandlersArray->AppendElement(app);
+ if (aDefaultApp && isDefault.Length() > 0) *aDefaultApp = app;
+ }
+}
+
+bool AndroidBridge::GetHandlersForMimeType(const nsAString& aMimeType,
+ nsIMutableArray* aHandlersArray,
+ nsIHandlerApp** aDefaultApp,
+ const nsAString& aAction) {
+ ALOG_BRIDGE("AndroidBridge::GetHandlersForMimeType");
+
+ auto arr = java::GeckoAppShell::GetHandlersForMimeType(aMimeType, aAction);
+ if (!arr) return false;
+
+ JNIEnv* const env = arr.Env();
+ size_t len = arr->Length();
+
+ if (!aHandlersArray) return len > 0;
+
+ getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp,
+ aAction, NS_ConvertUTF16toUTF8(aMimeType));
+ return true;
+}
+
+bool AndroidBridge::HasHWVP8Encoder() {
+ ALOG_BRIDGE("AndroidBridge::HasHWVP8Encoder");
+
+ bool value = java::GeckoAppShell::HasHWVP8Encoder();
+
+ return value;
+}
+
+bool AndroidBridge::HasHWVP8Decoder() {
+ ALOG_BRIDGE("AndroidBridge::HasHWVP8Decoder");
+
+ bool value = java::GeckoAppShell::HasHWVP8Decoder();
+
+ return value;
+}
+
+bool AndroidBridge::HasHWH264() {
+ ALOG_BRIDGE("AndroidBridge::HasHWH264");
+
+ return java::HardwareCodecCapabilityUtils::HasHWH264();
+}
+
+bool AndroidBridge::GetHandlersForURL(const nsAString& aURL,
+ nsIMutableArray* aHandlersArray,
+ nsIHandlerApp** aDefaultApp,
+ const nsAString& aAction) {
+ ALOG_BRIDGE("AndroidBridge::GetHandlersForURL");
+
+ auto arr = java::GeckoAppShell::GetHandlersForURL(aURL, aAction);
+ if (!arr) return false;
+
+ JNIEnv* const env = arr.Env();
+ size_t len = arr->Length();
+
+ if (!aHandlersArray) return len > 0;
+
+ getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp,
+ aAction);
+ return true;
+}
+
+void AndroidBridge::GetMimeTypeFromExtensions(const nsACString& aFileExt,
+ nsCString& aMimeType) {
+ ALOG_BRIDGE("AndroidBridge::GetMimeTypeFromExtensions");
+
+ auto jstrType = java::GeckoAppShell::GetMimeTypeFromExtensions(aFileExt);
+
+ if (jstrType) {
+ aMimeType = jstrType->ToCString();
+ }
+}
+
+void AndroidBridge::GetExtensionFromMimeType(const nsACString& aMimeType,
+ nsACString& aFileExt) {
+ ALOG_BRIDGE("AndroidBridge::GetExtensionFromMimeType");
+
+ auto jstrExt = java::GeckoAppShell::GetExtensionFromMimeType(aMimeType);
+
+ if (jstrExt) {
+ aFileExt = jstrExt->ToCString();
+ }
+}
+
+gfx::Rect AndroidBridge::getScreenSize() {
+ ALOG_BRIDGE("AndroidBridge::getScreenSize");
+
+ java::sdk::Rect::LocalRef screenrect = java::GeckoAppShell::GetScreenSize();
+ gfx::Rect screensize(screenrect->Left(), screenrect->Top(),
+ screenrect->Width(), screenrect->Height());
+
+ return screensize;
+}
+
+int AndroidBridge::GetScreenDepth() {
+ ALOG_BRIDGE("%s", __PRETTY_FUNCTION__);
+
+ static int sDepth = 0;
+ if (sDepth) return sDepth;
+
+ const int DEFAULT_DEPTH = 16;
+
+ if (jni::IsAvailable()) {
+ sDepth = java::GeckoAppShell::GetScreenDepth();
+ }
+ if (!sDepth) return DEFAULT_DEPTH;
+
+ return sDepth;
+}
+
+void AndroidBridge::Vibrate(const nsTArray<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);
+}
+
+bool AndroidBridge::GetStaticIntField(const char* className,
+ const char* fieldName, int32_t* aInt,
+ JNIEnv* jEnv /* = nullptr */) {
+ ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName);
+
+ if (!jEnv) {
+ if (!jni::IsAvailable()) {
+ return false;
+ }
+ jEnv = jni::GetGeckoThreadEnv();
+ }
+
+ AutoJNIClass cls(jEnv, className);
+ jfieldID field = cls.getStaticField(fieldName, "I");
+
+ if (!field) {
+ return false;
+ }
+
+ *aInt = static_cast<int32_t>(jEnv->GetStaticIntField(cls.getRawRef(), field));
+ return true;
+}
+
+bool AndroidBridge::GetStaticStringField(const char* className,
+ const char* fieldName,
+ nsAString& result,
+ JNIEnv* jEnv /* = nullptr */) {
+ ALOG_BRIDGE("AndroidBridge::GetStaticStringField %s", fieldName);
+
+ if (!jEnv) {
+ if (!jni::IsAvailable()) {
+ return false;
+ }
+ jEnv = jni::GetGeckoThreadEnv();
+ }
+
+ AutoLocalJNIFrame jniFrame(jEnv, 1);
+ AutoJNIClass cls(jEnv, className);
+ jfieldID field = cls.getStaticField(fieldName, "Ljava/lang/String;");
+
+ if (!field) {
+ return false;
+ }
+
+ jstring jstr = (jstring)jEnv->GetStaticObjectField(cls.getRawRef(), field);
+ if (!jstr) return false;
+
+ result.Assign(jni::String::Ref::From(jstr)->ToString());
+ return true;
+}
+
+namespace mozilla {
+class TracerRunnable : public Runnable {
+ public:
+ TracerRunnable() : Runnable("TracerRunnable") {
+ mTracerLock = new Mutex("TracerRunnable");
+ mTracerCondVar = new CondVar(*mTracerLock, "TracerRunnable");
+ mMainThread = do_GetMainThread();
+ }
+ ~TracerRunnable() {
+ delete mTracerCondVar;
+ delete mTracerLock;
+ mTracerLock = nullptr;
+ mTracerCondVar = nullptr;
+ }
+
+ virtual nsresult Run() {
+ MutexAutoLock lock(*mTracerLock);
+ if (!AndroidBridge::Bridge()) return NS_OK;
+
+ mHasRun = true;
+ mTracerCondVar->Notify();
+ return NS_OK;
+ }
+
+ bool Fire() {
+ if (!mTracerLock || !mTracerCondVar) return false;
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = false;
+ mMainThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ while (!mHasRun) mTracerCondVar->Wait();
+ return true;
+ }
+
+ void Signal() {
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = true;
+ mTracerCondVar->Notify();
+ }
+
+ private:
+ Mutex* mTracerLock;
+ CondVar* mTracerCondVar;
+ bool mHasRun;
+ nsCOMPtr<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() {}
+
+NS_IMETHODIMP nsAndroidBridge::ContentDocumentChanged(
+ mozIDOMWindowProxy* aWindow) {
+ AndroidBridge::Bridge()->ContentDocumentChanged(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAndroidBridge::IsContentDocumentDisplayed(
+ mozIDOMWindowProxy* aWindow, bool* aRet) {
+ *aRet = AndroidBridge::Bridge()->IsContentDocumentDisplayed(aWindow);
+ return NS_OK;
+}
+
+uint32_t AndroidBridge::GetScreenOrientation() {
+ ALOG_BRIDGE("AndroidBridge::GetScreenOrientation");
+
+ int16_t orientation = java::GeckoAppShell::GetScreenOrientation();
+
+ return static_cast<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);
+}
+
+NS_IMETHODIMP nsAndroidBridge::GetIsFennec(bool* aIsFennec) {
+ *aIsFennec = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAndroidBridge::GetBrowserApp(
+ nsIAndroidBrowserApp** aBrowserApp) {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (appShell) NS_IF_ADDREF(*aBrowserApp = appShell->GetBrowserApp());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAndroidBridge::SetBrowserApp(
+ nsIAndroidBrowserApp* aBrowserApp) {
+ nsAppShell* const appShell = nsAppShell::Get();
+ if (appShell) appShell->SetBrowserApp(aBrowserApp);
+ return NS_OK;
+}
+
+extern "C" __attribute__((visibility("default"))) jobject JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv* env, jclass,
+ jlong size);
+
+void AndroidBridge::ContentDocumentChanged(mozIDOMWindowProxy* aWindow) {
+ if (RefPtr<nsWindow> widget =
+ nsWindow::From(nsPIDOMWindowOuter::From(aWindow))) {
+ widget->SetContentDocumentDisplayed(false);
+ }
+}
+
+bool AndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow) {
+ if (RefPtr<nsWindow> widget =
+ nsWindow::From(nsPIDOMWindowOuter::From(aWindow))) {
+ return widget->IsContentDocumentDisplayed();
+ }
+ return false;
+}
+
+jni::Object::LocalRef AndroidBridge::ChannelCreate(jni::Object::Param stream) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ auto rv = jni::Object::LocalRef::Adopt(
+ env, env->CallStaticObjectMethod(sBridge->jChannels,
+ sBridge->jChannelCreate, stream.Get()));
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return rv;
+}
+
+void AndroidBridge::InputStreamClose(jni::Object::Param obj) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ env->CallVoidMethod(obj.Get(), sBridge->jClose);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+}
+
+uint32_t AndroidBridge::InputStreamAvailable(jni::Object::Param obj) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ auto rv = env->CallIntMethod(obj.Get(), sBridge->jAvailable);
+ MOZ_CATCH_JNI_EXCEPTION(env);
+ return rv;
+}
+
+nsresult AndroidBridge::InputStreamRead(jni::Object::Param obj, char* aBuf,
+ uint32_t aCount, uint32_t* aRead) {
+ JNIEnv* const env = jni::GetEnvForThread();
+ auto arr = jni::ByteBuffer::New(aBuf, aCount);
+ jint read =
+ env->CallIntMethod(obj.Get(), sBridge->jByteBufferRead, arr.Get());
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (read <= 0) {
+ *aRead = 0;
+ return NS_OK;
+ }
+ *aRead = read;
+ return NS_OK;
+}