summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /mobile/android/geckoview/src/main/java/org/mozilla/gecko/NativeQueue.java
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
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.java225
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;
+ }
+}