Bug 1619778 - Add `GeckoThread#waitForState()`. r=geckoview-reviewers,aklotz a=jcristau
authorJames Willcox <snorp@snorp.net>
Fri, 13 Mar 2020 19:49:48 +0000
changeset 580708 b8069adfb9ac2270a4765f1b4428df374e4c5e48
parent 580707 bb8f832e48dc540373b5c0bf1c32ec010abd931a
child 580709 45be288db1b73a73779b952fe197dedb7c9c492d
push id12927
push userapavel@mozilla.com
push dateThu, 26 Mar 2020 17:37:55 +0000
treeherdermozilla-beta@45be288db1b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, aklotz, jcristau
bugs1619778
milestone75.0
Bug 1619778 - Add `GeckoThread#waitForState()`. r=geckoview-reviewers,aklotz a=jcristau This allows us to asynchronously wait for a given `GeckoThread` state to be reached. Differential Revision: https://phabricator.services.mozilla.com/D66585
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoThread.java
@@ -8,38 +8,41 @@ 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.process.GeckoProcessManager;
 import org.mozilla.gecko.process.GeckoProcessType;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.geckoview.BuildConfig;
+import org.mozilla.geckoview.GeckoResult;
 
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
 import android.os.Process;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.StringTokenizer;
 
 public class GeckoThread extends Thread {
     private static final String LOGTAG = "GeckoThread";
 
@@ -125,16 +128,17 @@ public class GeckoThread extends Thread 
     @WrapForJNI
     private static final ClassLoader clsLoader = GeckoThread.class.getClassLoader();
     @WrapForJNI
     private static MessageQueue msgQueue;
     @WrapForJNI
     private static int uiThreadId;
 
     private static TelemetryUtils.Timer sInitTimer;
+    private static LinkedList<StateGeckoResult> sStateListeners = new LinkedList<>();
 
     // Main process parameters
     public static final int FLAG_DEBUGGING = 1 << 0; // Debugging mode.
     public static final int FLAG_PRELOAD_CHILD = 1 << 1; // Preload child during main thread start.
     public static final int FLAG_ENABLE_NATIVE_CRASHREPORTER = 1 << 2; // Enable native crash reporting.
 
     public static final long DEFAULT_TIMEOUT = 5000;
 
@@ -157,16 +161,23 @@ public class GeckoThread extends Thread 
 
         public int prefsFd;
         public int prefMapFd;
         public int ipcFd;
         public int crashFd;
         public int crashAnnotationFd;
     }
 
+    private static class StateGeckoResult extends GeckoResult<Void> {
+        final State state;
+        public StateGeckoResult(final State state) {
+            this.state = state;
+        }
+    }
+
     GeckoThread() {
         // Request more (virtual) stack space to avoid overflows in the CSS frame
         // constructor. 8 MB matches desktop.
         super(null, null, "Gecko", 8 * 1024 * 1024);
     }
 
     @WrapForJNI
     private static boolean isChildProcess() {
@@ -622,32 +633,64 @@ public class GeckoThread extends Thread 
         final boolean result = sNativeQueue.checkAndSetState(expectedState, newState);
         if (result) {
             Log.d(LOGTAG, "State changed to " + newState);
 
             if (sInitTimer != null && isRunning()) {
                 sInitTimer.stop();
                 sInitTimer = null;
             }
+
+            notifyStateListeners();
         }
         return result;
     }
 
     @WrapForJNI(stubName = "SpeculativeConnect")
     private static native void speculativeConnectNative(String uri);
 
     public static void speculativeConnect(final String uri) {
         // This is almost always called before Gecko loads, so we don't
         // bother checking here if Gecko is actually loaded or not.
         // Speculative connection depends on proxy settings,
         // so the earliest it can happen is after profile is ready.
         queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class,
                              "speculativeConnectNative", uri);
     }
 
+    @UiThread
+    public static GeckoResult<Void> waitForState(final State state) {
+        final StateGeckoResult result = new StateGeckoResult(state);
+        if (isStateAtLeast(state)) {
+            result.complete(null);
+            return result;
+        }
+
+        synchronized (sStateListeners) {
+            sStateListeners.add(result);
+        }
+        return result;
+    }
+
+    private static void notifyStateListeners() {
+        synchronized (sStateListeners) {
+            final LinkedList<StateGeckoResult> newListeners = new LinkedList<>();
+            for (final StateGeckoResult result : sStateListeners) {
+                if (!isStateAtLeast(result.state)) {
+                    newListeners.add(result);
+                    continue;
+                }
+
+                result.complete(null);
+            }
+
+            sStateListeners = newListeners;
+        }
+    }
+
     @WrapForJNI(stubName = "OnPause", dispatchTo = "gecko")
     private static native void nativeOnPause();
 
     public static void onPause() {
         if (isStateAtLeast(State.PROFILE_READY)) {
             nativeOnPause();
         } else {
             queueNativeCallUntil(State.PROFILE_READY, GeckoThread.class,