From c78829b68276b09b22fb2fae056f26b5a29c181d Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Mon, 20 Jan 2020 07:44:07 +0000 Subject: [PATCH] Bug 1594820: Part 1 - Modify XPCOMEventTarget to accept method calls before JNI is ready; r=snorp Since `XPCOMEventTarget` uses JNI, this patch makes it possible for consumers to retrieve and invoke methods on one without needing to worry about whether JNI is actually up yet. To achieve this, we create the `IXPCOMEventTarget` interface, and observe that both of its methods can be handled by a proxy if JNI is not ready: * Calls to `dispatch` may be enqueued until JNI is up; * Observe that, when JNI is not up yet, the result of `isOnCurrentThread` can never be `true`. Once JNI is up and the event targets have been resolved, the proxies are replaced with the real, concrete `XPCOMEventTarget`s and are no longer used for the remainder of the Gecko instance's lifetime. Differential Revision: https://phabricator.services.mozilla.com/D57837 --HG-- extra : moz-landing-system : lando --- .../mozilla/gecko/util/IXPCOMEventTarget.java | 11 +++ .../mozilla/gecko/util/XPCOMEventTarget.java | 96 +++++++++++++++++-- widget/android/nsAppShell.cpp | 30 +++--- 3 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IXPCOMEventTarget.java diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IXPCOMEventTarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IXPCOMEventTarget.java new file mode 100644 index 000000000000..6f94f518b08e --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IXPCOMEventTarget.java @@ -0,0 +1,11 @@ +/* -*- Mode: Java; c-basic-offset: 4; 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/. */ + +package org.mozilla.gecko.util; + +public interface IXPCOMEventTarget { + public void dispatch(final Runnable runnable); + public boolean isOnCurrentThread(); +} diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XPCOMEventTarget.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XPCOMEventTarget.java index 3e36bdfc2aef..6c06aa5067ab 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XPCOMEventTarget.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/XPCOMEventTarget.java @@ -6,43 +6,81 @@ package org.mozilla.gecko.util; import org.mozilla.gecko.annotation.WrapForJNI; +import org.mozilla.gecko.GeckoThread; import org.mozilla.gecko.mozglue.JNIObject; +import org.mozilla.geckoview.BuildConfig; /** * Wrapper for nsIEventTarget, enabling seamless dispatch of java runnables to * Gecko event queues. */ @WrapForJNI -public final class XPCOMEventTarget extends JNIObject { +public final class XPCOMEventTarget extends JNIObject implements IXPCOMEventTarget { + @Override public void dispatch(final Runnable runnable) { dispatchNative(new JNIRunnable(runnable)); } - public static synchronized XPCOMEventTarget mainThread() { + public static synchronized IXPCOMEventTarget mainThread() { if (mMainThread == null) { - mMainThread = createWrapper("main"); + mMainThread = new AsyncProxy("main"); } return mMainThread; } - private static XPCOMEventTarget mMainThread = null; + private static IXPCOMEventTarget mMainThread = null; - public static synchronized XPCOMEventTarget launcherThread() { + public static synchronized IXPCOMEventTarget launcherThread() { if (mLauncherThread == null) { - mLauncherThread = createWrapper("launcher"); + mLauncherThread = new AsyncProxy("launcher"); } return mLauncherThread; } - private static XPCOMEventTarget mLauncherThread = null; + private static IXPCOMEventTarget mLauncherThread = null; + public static void assertOnLauncherThread() { + if (BuildConfig.DEBUG && !launcherThread().isOnCurrentThread()) { + throw new IllegalThreadStateException("Expected to be running on XPCOM launcher thread"); + } + } + + private static synchronized IXPCOMEventTarget getTarget(final String name) { + if (name.equals("launcher")) { + return mLauncherThread; + } else if (name.equals("main")) { + return mMainThread; + } else { + throw new RuntimeException("Attempt to assign to unknown thread named " + name); + } + } + + @WrapForJNI + private static synchronized void setTarget(final String name, final XPCOMEventTarget target) { + if (name.equals("main")) { + mMainThread = target; + } else if (name.equals("launcher")) { + mLauncherThread = target; + } else { + throw new RuntimeException("Attempt to assign to unknown thread named " + name); + } + } + + @Override public native boolean isOnCurrentThread(); - private native void dispatchNative(JNIRunnable runnable); - private static native XPCOMEventTarget createWrapper(String name); + + private native void dispatchNative(final JNIRunnable runnable); + + @WrapForJNI + private static synchronized void resolveAndDispatch(final String name, final Runnable runnable) { + getTarget(name).dispatch(runnable); + } + + private static native void resolveAndDispatchNative(final String name, final Runnable runnable); @Override protected native void disposeNative(); @WrapForJNI - final class JNIRunnable { + private static final class JNIRunnable { JNIRunnable(final Runnable inner) { mInner = inner; } @@ -54,5 +92,43 @@ public final class XPCOMEventTarget extends JNIObject { private Runnable mInner; } + + private static final class AsyncProxy implements IXPCOMEventTarget { + private String mTargetName; + + public AsyncProxy(final String targetName) { + mTargetName = targetName; + } + + @Override + public void dispatch(final Runnable runnable) { + final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName); + + if (target != null && target instanceof XPCOMEventTarget) { + target.dispatch(runnable); + return; + } + + GeckoThread.queueNativeCallUntil(GeckoThread.State.JNI_READY, + XPCOMEventTarget.class, "resolveAndDispatchNative", + String.class, mTargetName, Runnable.class, runnable); + } + + @Override + public boolean isOnCurrentThread() { + final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName); + + // If target is not yet a XPCOMEventTarget then JNI is not + // initialized yet. If JNI is not initialized yet, then we cannot + // possibly be running on a target with an XPCOMEventTarget. + if (target == null || !(target instanceof XPCOMEventTarget)) { + return false; + } + + // Otherwise we have a real XPCOMEventTarget, so we can delegate + // this call to it. + return target.isOnCurrentThread(); + } + } } diff --git a/widget/android/nsAppShell.cpp b/widget/android/nsAppShell.cpp index 07aaaf030c07..56db39bd8afd 100644 --- a/widget/android/nsAppShell.cpp +++ b/widget/android/nsAppShell.cpp @@ -363,24 +363,26 @@ class XPCOMEventTargetWrapper final bool IsOnCurrentThread() { return mTarget->IsOnCurrentThread(); } - // Instantiates a wrapper. The Java code calls this only once per wrapped - // thread, and caches the result. - static java::XPCOMEventTarget::LocalRef CreateWrapper( - mozilla::jni::String::Param aName) { - nsString name(aName->ToString()); - nsCOMPtr target; - if (name.EqualsLiteral("main")) { - target = do_GetMainThread(); - } else if (name.EqualsLiteral("launcher")) { - target = ipc::GetIPCLauncher(); - } else { - MOZ_CRASH("Trying to create JNI wrapper for unknown XPCOM thread"); + static void Init() { + java::XPCOMEventTarget::Natives::Init(); + CreateWrapper(NS_LITERAL_STRING("main"), do_GetMainThread()); + if (XRE_IsParentProcess()) { + CreateWrapper(NS_LITERAL_STRING("launcher"), ipc::GetIPCLauncher()); } + } + static void CreateWrapper(mozilla::jni::String::Param aName, + nsCOMPtr aTarget) { auto java = java::XPCOMEventTarget::New(); - auto native = MakeUnique(target.forget()); + auto native = MakeUnique(aTarget.forget()); AttachNative(java, std::move(native)); - return java; + + java::XPCOMEventTarget::SetTarget(aName, java); + } + + static void ResolveAndDispatchNative(mozilla::jni::String::Param aName, + mozilla::jni::Object::Param aRunnable) { + java::XPCOMEventTarget::ResolveAndDispatch(aName, aRunnable); } explicit XPCOMEventTargetWrapper(already_AddRefed aTarget)