Bug 1343613 - [1.7] Refactor native call queuing out of GeckoThread. r=jchen

This commit is contained in:
Eugen Sawin 2017-03-06 18:15:17 +01:00
Родитель 2cf62698de
Коммит 5db48b0409
6 изменённых файлов: 406 добавлений и 204 удалений

Просмотреть файл

@ -287,6 +287,7 @@ gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x
'gfx/VsyncSource.java',
'InputConnectionListener.java',
'InputMethods.java',
'NativeQueue.java',
'NotificationListener.java',
'NSSBridge.java',
'permissions/PermissionBlock.java',

Просмотреть файл

@ -8,12 +8,10 @@ package org.mozilla.gecko;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.NativeQueue.StateHolder;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@ -28,19 +26,14 @@ import android.util.Log;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.StringTokenizer;
public class GeckoThread extends Thread {
private static final String LOGTAG = "GeckoThread";
public enum State {
public enum State implements NativeQueue.State {
// After being loaded by class loader.
@WrapForJNI INITIAL(0),
// After launching Gecko thread
@ -73,47 +66,30 @@ public class GeckoThread extends Thread {
this.rank = rank;
}
public boolean is(final State other) {
@Override
public boolean is(final NativeQueue.State other) {
return this == other;
}
public boolean isAtLeast(final State other) {
return this.rank >= other.rank;
@Override
public boolean isAtLeast(final NativeQueue.State other) {
if (other instanceof State) {
return this.rank >= ((State) other).rank;
}
return false;
}
}
public boolean isAtMost(final State other) {
return this.rank <= other.rank;
}
private static final StateHolder sStateHolder =
new StateHolder(State.INITIAL, State.RUNNING);
// Inclusive
public boolean isBetween(final State min, final State max) {
return this.rank >= min.rank && this.rank <= max.rank;
}
/* package */ static StateHolder getStateHolder() {
return sStateHolder;
}
public static final State MIN_STATE = State.INITIAL;
public static final State MAX_STATE = State.EXITED;
private static volatile State sState = State.INITIAL;
private static class QueuedCall {
public Method method;
public Object target;
public Object[] args;
public State state;
public QueuedCall(final Method method, final Object target,
final Object[] args, final State state) {
this.method = method;
this.target = target;
this.args = args;
this.state = state;
}
}
private static final int QUEUED_CALLS_COUNT = 16;
private static final ArrayList<QueuedCall> QUEUED_CALLS = new ArrayList<>(QUEUED_CALLS_COUNT);
private static final Runnable UI_THREAD_CALLBACK = new Runnable() {
@Override
public void run() {
@ -258,153 +234,6 @@ public class GeckoThread extends Thread {
return isState(State.RUNNING);
}
// Invoke the given Method and handle checked Exceptions.
private static void invokeMethod(final Method method, final Object obj, final Object[] args) {
try {
method.setAccessible(true);
method.invoke(obj, args);
} catch (final IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
} catch (final InvocationTargetException e) {
throw new UnsupportedOperationException("Cannot make call", e.getCause());
}
}
// Queue a call to the given method.
private static void queueNativeCallLocked(final Class<?> cls, final String methodName,
final Object obj, final Object[] args,
final State state) {
final ArrayList<Class<?>> argTypes = new ArrayList<>(args.length);
final ArrayList<Object> argValues = new ArrayList<>(args.length);
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Class) {
argTypes.add((Class<?>) args[i]);
argValues.add(args[++i]);
continue;
}
Class<?> argType = args[i].getClass();
if (argType == Boolean.class) argType = Boolean.TYPE;
else if (argType == Byte.class) argType = Byte.TYPE;
else if (argType == Character.class) argType = Character.TYPE;
else if (argType == Double.class) argType = Double.TYPE;
else if (argType == Float.class) argType = Float.TYPE;
else if (argType == Integer.class) argType = Integer.TYPE;
else if (argType == Long.class) argType = Long.TYPE;
else if (argType == Short.class) argType = Short.TYPE;
argTypes.add(argType);
argValues.add(args[i]);
}
final Method method;
try {
method = cls.getDeclaredMethod(
methodName, argTypes.toArray(new Class<?>[argTypes.size()]));
} catch (final NoSuchMethodException e) {
throw new IllegalArgumentException("Cannot find method", e);
}
if (!Modifier.isNative(method.getModifiers())) {
// As a precaution, we disallow queuing non-native methods. Queuing non-native
// methods is dangerous because the method could end up being called on either
// the original thread or the Gecko thread depending on timing. Native methods
// usually handle this by posting an event to the Gecko thread automatically,
// but there is no automatic mechanism for non-native methods.
throw new UnsupportedOperationException("Not allowed to queue non-native methods");
}
if (isStateAtLeast(state)) {
invokeMethod(method, obj, argValues.toArray());
return;
}
QUEUED_CALLS.add(new QueuedCall(
method, obj, argValues.toArray(), state));
}
/**
* Queue a call to the given static method until Gecko is in the given state.
*
* @param state The Gecko state in which the native call could be executed.
* Default is State.RUNNING, which means this queued call will
* run when Gecko is at or after RUNNING state.
* @param cls Class that declares the static method.
* @param methodName Name of the static method.
* @param args Args to call the static method with; to specify a parameter type,
* pass in a Class instance first, followed by the value.
*/
public static void queueNativeCallUntil(final State state, final Class<?> cls,
final String methodName, final Object... args) {
synchronized (QUEUED_CALLS) {
queueNativeCallLocked(cls, methodName, null, args, state);
}
}
/**
* Queue a call to the given static method until Gecko is in the RUNNING state.
*/
public static void queueNativeCall(final Class<?> cls, final String methodName,
final Object... args) {
synchronized (QUEUED_CALLS) {
queueNativeCallLocked(cls, methodName, null, args, State.RUNNING);
}
}
/**
* Queue a call to the given instance method until Gecko is in the given state.
*
* @param state The Gecko state in which the native call could be executed.
* @param obj Object that declares the instance method.
* @param methodName Name of the instance method.
* @param args Args to call the instance method with; to specify a parameter type,
* pass in a Class instance first, followed by the value.
*/
public static void queueNativeCallUntil(final State state, final Object obj,
final String methodName, final Object... args) {
synchronized (QUEUED_CALLS) {
queueNativeCallLocked(obj.getClass(), methodName, obj, args, state);
}
}
/**
* Queue a call to the given instance method until Gecko is in the RUNNING state.
*/
public static void queueNativeCall(final Object obj, final String methodName,
final Object... args) {
synchronized (QUEUED_CALLS) {
queueNativeCallLocked(obj.getClass(), methodName, obj, args, State.RUNNING);
}
}
// Run all queued methods
private static void flushQueuedNativeCallsLocked(final State state) {
int lastSkipped = -1;
for (int i = 0; i < QUEUED_CALLS.size(); i++) {
final QueuedCall call = QUEUED_CALLS.get(i);
if (call == null) {
// We already handled the call.
continue;
}
if (!state.isAtLeast(call.state)) {
// The call is not ready yet; skip it.
lastSkipped = i;
continue;
}
// Mark as handled.
QUEUED_CALLS.set(i, null);
invokeMethod(call.method, call.target, call.args);
}
if (lastSkipped < 0) {
// We're done here; release the memory
QUEUED_CALLS.clear();
QUEUED_CALLS.trimToSize();
} else if (lastSkipped < QUEUED_CALLS.size() - 1) {
// We skipped some; free up null entries at the end,
// but keep all the previous entries for later.
QUEUED_CALLS.subList(lastSkipped + 1, QUEUED_CALLS.size()).clear();
}
}
private static void loadGeckoLibs(final Context context, final String resourcePath) {
GeckoLoader.loadSQLiteLibs(context, resourcePath);
GeckoLoader.loadNSSLibs(context, resourcePath);
@ -616,7 +445,7 @@ public class GeckoThread extends Thread {
* @return True if the current Gecko thread state matches
*/
public static boolean isState(final State state) {
return sState.is(state);
return sStateHolder.getState().is(state);
}
/**
@ -627,7 +456,7 @@ public class GeckoThread extends Thread {
* @return True if the current Gecko thread state matches
*/
public static boolean isStateAtLeast(final State state) {
return sState.isAtLeast(state);
return sStateHolder.getState().isAtLeast(state);
}
/**
@ -638,7 +467,7 @@ public class GeckoThread extends Thread {
* @return True if the current Gecko thread state matches
*/
public static boolean isStateAtMost(final State state) {
return sState.isAtMost(state);
return state.isAtLeast(sStateHolder.getState());
}
/**
@ -650,28 +479,18 @@ public class GeckoThread extends Thread {
* @return True if the current Gecko thread state matches
*/
public static boolean isStateBetween(final State minState, final State maxState) {
return sState.isBetween(minState, maxState);
return isStateAtLeast(minState) && isStateAtMost(maxState);
}
@WrapForJNI(calledFrom = "gecko")
private static void setState(final State newState) {
ThreadUtils.assertOnGeckoThread();
synchronized (QUEUED_CALLS) {
flushQueuedNativeCallsLocked(newState);
sState = newState;
}
sStateHolder.setState(newState);
}
@WrapForJNI(calledFrom = "gecko")
private static boolean checkAndSetState(final State currentState, final State newState) {
synchronized (QUEUED_CALLS) {
if (sState == currentState) {
flushQueuedNativeCallsLocked(newState);
sState = newState;
return true;
}
}
return false;
private static boolean checkAndSetState(final State expectedState,
final State newState) {
return sStateHolder.checkAndSetState(expectedState, newState);
}
@WrapForJNI(stubName = "SpeculativeConnect")
@ -735,4 +554,36 @@ public class GeckoThread extends Thread {
private static void requestUiThreadCallback(long delay) {
ThreadUtils.getUiHandler().postDelayed(UI_THREAD_CALLBACK, delay);
}
/**
* Queue a call to the given static method until Gecko is in the RUNNING state.
*/
public static void queueNativeCall(final Class<?> cls, final String methodName,
final Object... args) {
NativeQueue.queueUntil(getStateHolder(), State.RUNNING, cls, methodName, args);
}
/**
* Queue a call to the given instance method until Gecko is in the RUNNING state.
*/
public static void queueNativeCall(final Object obj, final String methodName,
final Object... args) {
NativeQueue.queueUntil(getStateHolder(), State.RUNNING, obj, methodName, args);
}
/**
* Queue a call to the given instance method until Gecko is in the RUNNING state.
*/
public static void queueNativeCallUntil(final State state, final Object obj, final String methodName,
final Object... args) {
NativeQueue.queueUntil(getStateHolder(), state, obj, methodName, args);
}
/**
* Queue a call to the given static method until Gecko is in the RUNNING state.
*/
public static void queueNativeCallUntil(final State state, final Class<?> cls, final String methodName,
final Object... args) {
NativeQueue.queueUntil(getStateHolder(), state, cls, methodName, args);
}
}

Просмотреть файл

@ -12,6 +12,7 @@ import org.mozilla.gecko.annotation.ReflectionTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.mozglue.JNIObject;
import org.mozilla.gecko.NativeQueue.StateHolder;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
@ -41,6 +42,38 @@ public class GeckoView extends LayerView
private static final boolean DEBUG = false;
/* package */ enum State implements NativeQueue.State {
@WrapForJNI INITIAL(0),
@WrapForJNI READY(1);
private int rank;
private State(int rank) {
this.rank = rank;
}
@Override
public boolean is(final NativeQueue.State other) {
return this == other;
}
@Override
public boolean isAtLeast(final NativeQueue.State other) {
if (other instanceof State) {
return this.rank >= ((State) other).rank;
}
return false;
}
}
private final StateHolder mStateHolder =
new StateHolder(State.INITIAL, State.READY);
@WrapForJNI(calledFrom = "gecko")
private void setState(final State newState) {
mStateHolder.setState(newState);
}
private final EventDispatcher mEventDispatcher = new EventDispatcher();
private ChromeDelegate mChromeDelegate;

Просмотреть файл

@ -0,0 +1,219 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
public class NativeQueue {
private static final String LOGTAG = "GeckoNativeQueue";
public interface State {
boolean is(final State other);
boolean isAtLeast(final State other);
}
public static class StateHolder {
private volatile State mState;
private final State mReadyState;
public StateHolder(final State initial, final State ready) {
this.mState = initial;
this.mReadyState = ready;
}
public boolean isReady() {
return getState().isAtLeast(mReadyState);
}
public State getReadyState() {
return mReadyState;
}
public State getState() {
return mState;
}
public boolean setState(final State newState) {
return checkAndSetState(null, newState);
}
public boolean checkAndSetState(final State expectedState,
final State newState) {
synchronized (NativeQueue.sQueue) {
if (expectedState != null && !mState.is(expectedState)) {
return false;
}
NativeQueue.flushQueuedLocked(newState);
mState = newState;
return true;
}
}
}
private static class QueuedCall {
public Method method;
public Object target;
public Object[] args;
public State state;
public QueuedCall(final Method method, final Object target,
final Object[] args, final State state) {
this.method = method;
this.target = target;
this.args = args;
this.state = state;
}
}
private static final int QUEUED_CALLS_COUNT = 16;
/* package */ static final ArrayList<QueuedCall> sQueue =
new ArrayList<>(QUEUED_CALLS_COUNT);
// Invoke the given Method and handle checked Exceptions.
private static void invokeMethod(final Method method, final Object obj,
final Object[] args) {
try {
method.setAccessible(true);
method.invoke(obj, args);
} catch (final IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
} catch (final InvocationTargetException e) {
throw new UnsupportedOperationException("Cannot make call", e.getCause());
}
}
// Queue a call to the given method.
private static void queueNativeCallLocked(final StateHolder stateHolder,
final Class<?> cls,
final String methodName,
final Object obj,
final Object[] args,
final State state) {
final ArrayList<Class<?>> argTypes = new ArrayList<>(args.length);
final ArrayList<Object> argValues = new ArrayList<>(args.length);
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Class) {
argTypes.add((Class<?>) args[i]);
argValues.add(args[++i]);
continue;
}
Class<?> argType = args[i].getClass();
if (argType == Boolean.class) argType = Boolean.TYPE;
else if (argType == Byte.class) argType = Byte.TYPE;
else if (argType == Character.class) argType = Character.TYPE;
else if (argType == Double.class) argType = Double.TYPE;
else if (argType == Float.class) argType = Float.TYPE;
else if (argType == Integer.class) argType = Integer.TYPE;
else if (argType == Long.class) argType = Long.TYPE;
else if (argType == Short.class) argType = Short.TYPE;
argTypes.add(argType);
argValues.add(args[i]);
}
final Method method;
try {
method = cls.getDeclaredMethod(
methodName, argTypes.toArray(new Class<?>[argTypes.size()]));
} catch (final NoSuchMethodException e) {
throw new IllegalArgumentException("Cannot find method", e);
}
if (!Modifier.isNative(method.getModifiers())) {
// As a precaution, we disallow queuing non-native methods. Queuing non-native
// methods is dangerous because the method could end up being called on either
// the original thread or the Gecko thread depending on timing. Native methods
// usually handle this by posting an event to the Gecko thread automatically,
// but there is no automatic mechanism for non-native methods.
throw new UnsupportedOperationException("Not allowed to queue non-native methods");
}
if (stateHolder.getState().isAtLeast(state)) {
invokeMethod(method, obj, argValues.toArray());
return;
}
sQueue.add(new QueuedCall(
method, obj, argValues.toArray(), state));
}
/**
* Queue a call to the given instance method if the given current state does
* not satisfy the given state.
*
* @param stateHolder The state holder used to query the current state.
* @param state The state in which the native call could be executed.
* @param obj Object that declares the instance method.
* @param methodName Name of the instance method.
* @param args Args to call the instance method with; to specify a parameter
* type, pass in a Class instance first, followed by the value.
*/
public static void queueUntil(final StateHolder stateHolder,
final State state,
final Object obj,
final String methodName,
final Object... args) {
synchronized (sQueue) {
queueNativeCallLocked(stateHolder, obj.getClass(), methodName, obj,
args, state);
}
}
/**
* Queue a call to the given static method if the given current state does
* not satisfy the given state.
*
* @param stateHolder The state holder used to query the current state.
* @param state The state in which the native call could be executed.
* @param cls Class that declares the static method.
* @param methodName Name of the instance method.
* @param args Args to call the instance method with; to specify a parameter
* type, pass in a Class instance first, followed by the value.
*/
public static void queueUntil(final StateHolder stateHolder,
final State state,
final Class<?> cls,
final String methodName,
final Object... args) {
synchronized (sQueue) {
queueNativeCallLocked(stateHolder, cls, methodName, null, args, state);
}
}
// Run all queued methods
private static void flushQueuedLocked(final State state) {
int lastSkipped = -1;
for (int i = 0; i < sQueue.size(); i++) {
final QueuedCall call = sQueue.get(i);
if (call == null) {
// We already handled the call.
continue;
}
if (!state.isAtLeast(call.state)) {
// The call is not ready yet; skip it.
lastSkipped = i;
continue;
}
// Mark as handled.
sQueue.set(i, null);
invokeMethod(call.method, call.target, call.args);
}
if (lastSkipped < 0) {
// We're done here; release the memory
sQueue.clear();
sQueue.trimToSize();
} else if (lastSkipped < sQueue.size() - 1) {
// We skipped some; free up null entries at the end,
// but keep all the previous entries for later.
sQueue.subList(lastSkipped + 1, sQueue.size()).clear();
}
}
}

Просмотреть файл

@ -1000,6 +1000,33 @@ auto GeckoThread::State::RUNNING() -> State::LocalRef
const char GeckoView::name[] =
"org/mozilla/gecko/GeckoView";
constexpr char GeckoView::SetState_t::name[];
constexpr char GeckoView::SetState_t::signature[];
auto GeckoView::SetState(mozilla::jni::Object::Param a0) const -> void
{
return mozilla::jni::Method<SetState_t>::Call(GeckoView::mCtx, nullptr, a0);
}
const char GeckoView::State::name[] =
"org/mozilla/gecko/GeckoView$State";
constexpr char GeckoView::State::INITIAL_t::name[];
constexpr char GeckoView::State::INITIAL_t::signature[];
auto GeckoView::State::INITIAL() -> State::LocalRef
{
return mozilla::jni::Field<INITIAL_t>::Get(State::Context(), nullptr);
}
constexpr char GeckoView::State::READY_t::name[];
constexpr char GeckoView::State::READY_t::signature[];
auto GeckoView::State::READY() -> State::LocalRef
{
return mozilla::jni::Field<READY_t>::Get(State::Context(), nullptr);
}
const char GeckoView::Window::name[] =
"org/mozilla/gecko/GeckoView$Window";

Просмотреть файл

@ -2988,14 +2988,85 @@ public:
explicit GeckoView(const Context& ctx) : ObjectBase<GeckoView>(ctx) {}
class State;
class Window;
struct SetState_t {
typedef GeckoView Owner;
typedef void ReturnType;
typedef void SetterType;
typedef mozilla::jni::Args<
mozilla::jni::Object::Param> Args;
static constexpr char name[] = "setState";
static constexpr char signature[] =
"(Lorg/mozilla/gecko/GeckoView$State;)V";
static const bool isStatic = false;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::GECKO;
static const mozilla::jni::DispatchTarget dispatchTarget =
mozilla::jni::DispatchTarget::CURRENT;
};
auto SetState(mozilla::jni::Object::Param) const -> void;
static const int32_t LOAD_DEFAULT = 0;
static const int32_t LOAD_NEW_TAB = 1;
static const int32_t LOAD_SWITCH_TAB = 2;
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::GECKO;
};
class GeckoView::State : public mozilla::jni::ObjectBase<State>
{
public:
static const char name[];
explicit State(const Context& ctx) : ObjectBase<State>(ctx) {}
struct INITIAL_t {
typedef State Owner;
typedef State::LocalRef ReturnType;
typedef State::Param SetterType;
typedef mozilla::jni::Args<> Args;
static constexpr char name[] = "INITIAL";
static constexpr char signature[] =
"Lorg/mozilla/gecko/GeckoView$State;";
static const bool isStatic = true;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::ANY;
static const mozilla::jni::DispatchTarget dispatchTarget =
mozilla::jni::DispatchTarget::CURRENT;
};
static auto INITIAL() -> State::LocalRef;
struct READY_t {
typedef State Owner;
typedef State::LocalRef ReturnType;
typedef State::Param SetterType;
typedef mozilla::jni::Args<> Args;
static constexpr char name[] = "READY";
static constexpr char signature[] =
"Lorg/mozilla/gecko/GeckoView$State;";
static const bool isStatic = true;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::ANY;
static const mozilla::jni::DispatchTarget dispatchTarget =
mozilla::jni::DispatchTarget::CURRENT;
};
static auto READY() -> State::LocalRef;
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::ANY;