diff options
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java')
-rw-r--r-- | mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java new file mode 100644 index 0000000000..7932e6c839 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java @@ -0,0 +1,225 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * vim: ts=4 sw=4 expandtab: + * 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 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); + } + + private volatile State mState; + private final State mReadyState; + + public NativeQueue(final State initial, final State ready) { + mState = initial; + mReadyState = ready; + } + + public boolean isReady() { + return getState().isAtLeast(mReadyState); + } + + public State getState() { + return mState; + } + + public boolean setState(final State newState) { + return checkAndSetState(null, newState); + } + + public synchronized boolean checkAndSetState(final State expectedState, final State newState) { + if (expectedState != null && !mState.is(expectedState)) { + return false; + } + 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 */ final ArrayList<QueuedCall> mQueue = 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 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 (getState().isAtLeast(state)) { + invokeMethod(method, obj, argValues.toArray()); + return; + } + + mQueue.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 + * isReady condition. + * + * @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 synchronized void queueUntilReady( + final Object obj, final String methodName, final Object... args) { + queueNativeCallLocked(obj.getClass(), methodName, obj, args, mReadyState); + } + + /** + * Queue a call to the given static method if the given current state does not satisfy the isReady + * condition. + * + * @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 synchronized void queueUntilReady( + final Class<?> cls, final String methodName, final Object... args) { + queueNativeCallLocked(cls, methodName, null, args, mReadyState); + } + + /** + * Queue a call to the given instance method if the given current state does not satisfy the given + * 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 synchronized void queueUntil( + final State state, final Object obj, final String methodName, final Object... args) { + queueNativeCallLocked(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 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 synchronized void queueUntil( + final State state, final Class<?> cls, final String methodName, final Object... args) { + queueNativeCallLocked(cls, methodName, null, args, state); + } + + // Run all queued methods + private void flushQueuedLocked(final State state) { + int lastSkipped = -1; + for (int i = 0; i < mQueue.size(); i++) { + final QueuedCall call = mQueue.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. + mQueue.set(i, null); + + invokeMethod(call.method, call.target, call.args); + } + if (lastSkipped < 0) { + // We're done here; release the memory + mQueue.clear(); + } else if (lastSkipped < mQueue.size() - 1) { + // We skipped some; free up null entries at the end, + // but keep all the previous entries for later. + mQueue.subList(lastSkipped + 1, mQueue.size()).clear(); + } + } + + public synchronized void reset(final State initial) { + mQueue.clear(); + mState = initial; + } +} |