Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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;
import androidx.annotation.NonNull;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.annotation.WrapForJNI;
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 implements IXPCOMEventTarget {
@Override
public void execute(final Runnable runnable) {
dispatchNative(new JNIRunnable(runnable));
}
public static synchronized IXPCOMEventTarget mainThread() {
if (mMainThread == null) {
mMainThread = new AsyncProxy("main");
}
return mMainThread;
}
private static IXPCOMEventTarget mMainThread = null;
public static synchronized IXPCOMEventTarget launcherThread() {
if (mLauncherThread == null) {
mLauncherThread = new AsyncProxy("launcher");
}
return mLauncherThread;
}
private static IXPCOMEventTarget mLauncherThread = null;
/**
* Runs the provided runnable on the launcher thread. If this method is called from the launcher
* thread itself, the runnable will be executed immediately and synchronously.
*/
public static void runOnLauncherThread(@NonNull final Runnable runnable) {
final IXPCOMEventTarget launcherThread = launcherThread();
if (launcherThread.isOnCurrentThread()) {
// We're already on the launcher thread, just execute the runnable
runnable.run();
return;
}
launcherThread.execute(runnable);
}
public static void assertOnLauncherThread() {
if (BuildConfig.DEBUG_BUILD && !launcherThread().isOnCurrentThread()) {
throw new AssertionError("Expected to be running on XPCOM launcher thread");
}
}
public static void assertNotOnLauncherThread() {
if (BuildConfig.DEBUG_BUILD && launcherThread().isOnCurrentThread()) {
throw new AssertionError("Expected to not 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);
}
// Ensure that we see the right name in the Java debugger. We don't do this for mMainThread
// because its name was already set (in this context, "main" is the GeckoThread).
if (mMainThread != target) {
target.execute(
() -> {
Thread.currentThread().setName(name);
});
}
}
@Override
public native boolean isOnCurrentThread();
private native void dispatchNative(final JNIRunnable runnable);
@WrapForJNI
private static synchronized void resolveAndDispatch(final String name, final Runnable runnable) {
getTarget(name).execute(runnable);
}
private static native void resolveAndDispatchNative(final String name, final Runnable runnable);
@Override
protected native void disposeNative();
@WrapForJNI
private static final class JNIRunnable {
JNIRunnable(final Runnable inner) {
mInner = inner;
}
@WrapForJNI
void run() {
mInner.run();
}
private Runnable mInner;
}
private static final class AsyncProxy implements IXPCOMEventTarget {
private String mTargetName;
public AsyncProxy(final String targetName) {
mTargetName = targetName;
}
@Override
public void execute(final Runnable runnable) {
final IXPCOMEventTarget target = XPCOMEventTarget.getTarget(mTargetName);
if (target instanceof XPCOMEventTarget) {
target.execute(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 instanceof XPCOMEventTarget)) {
return false;
}
// Otherwise we have a real XPCOMEventTarget, so we can delegate
// this call to it.
return target.isOnCurrentThread();
}
}
}