Bug 1459301 - 2. Allow nested loopUntilIdle calls; r?snorp draft
authorJim Chen <nchen@mozilla.com>
Fri, 11 May 2018 14:59:49 -0400
changeset 794346 1147bfdba62ecd991c99c07e5ba78edd13a0c9a3
parent 794345 628e26129539ce07e55a437dbb41736ef3763d05
child 794347 6367e92b8faa56e5018a0a47729c42ed6c811dbc
push id109648
push userbmo:nchen@mozilla.com
push dateFri, 11 May 2018 19:00:34 +0000
reviewerssnorp
bugs1459301
milestone62.0a1
Bug 1459301 - 2. Allow nested loopUntilIdle calls; r?snorp There may be cases where loopUntilIdle calls are nested. Allow these calls by canceling the previous timeout when we set a new timeout. This patch also adds some general optimizations to loopUntilIdle, so we don't create a bunch of objects with every call. MozReview-Commit-ID: 9glZu8ZTVZ5
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -87,21 +87,24 @@ public class GeckoSessionTestRule extend
     private static final long DEFAULT_ARM_DEVICE_TIMEOUT_MILLIS = 30000;
     private static final long DEFAULT_ARM_EMULATOR_TIMEOUT_MILLIS = 120000;
     private static final long DEFAULT_X86_DEVICE_TIMEOUT_MILLIS = 30000;
     private static final long DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS = 5000;
     private static final long DEFAULT_IDE_DEBUG_TIMEOUT_MILLIS = 86400000;
 
     public static final String APK_URI_PREFIX = "resource://android/";
 
+    private static final Method sGetNextMessage;
     private static final Method sOnPageStop;
     private static final Method sOnNewSession;
 
     static {
         try {
+            sGetNextMessage = MessageQueue.class.getDeclaredMethod("next");
+            sGetNextMessage.setAccessible(true);
             sOnPageStop = GeckoSession.ProgressDelegate.class.getMethod(
                     "onPageStop", GeckoSession.class, boolean.class);
             sOnNewSession = GeckoSession.NavigationDelegate.class.getMethod(
                     "onNewSession", GeckoSession.class, String.class, GeckoResponse.class);
         } catch (final NoSuchMethodException e) {
             throw new RuntimeException(e);
         }
     }
@@ -651,16 +654,47 @@ public class GeckoSessionTestRule extend
         }
 
         final HashSet<Class<?>> set = new HashSet<>(list);
         return set.toArray(new Class<?>[set.size()]);
     }
 
     private static final List<Class<?>> CALLBACK_CLASSES = Arrays.asList(getCallbackClasses());
 
+    private static final class TimeoutRunnable implements Runnable {
+        private long timeout;
+
+        public void set(final long timeout) {
+            this.timeout = timeout;
+            cancel();
+            HANDLER.postDelayed(this, timeout);
+        }
+
+        public void cancel() {
+            HANDLER.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            throw new TimeoutException("Timed out after " + timeout + "ms");
+        }
+    }
+
+    /* package */ static final Handler HANDLER = new Handler(Looper.getMainLooper());
+    private static final TimeoutRunnable TIMEOUT_RUNNABLE = new TimeoutRunnable();
+    private static final MessageQueue.IdleHandler IDLE_HANDLER = new MessageQueue.IdleHandler() {
+        @Override
+        public boolean queueIdle() {
+            final Message msg = Message.obtain(HANDLER);
+            msg.obj = HANDLER;
+            HANDLER.sendMessageAtFrontOfQueue(msg);
+            return false; // Remove this idle handler.
+        }
+    };
+
     private static GeckoRuntime sRuntime;
     private static RDPConnection sRDPConnection;
     private static long sLongestWait;
 
     public final Environment env = new Environment();
 
     protected final Instrumentation mInstrumentation =
             InstrumentationRegistry.getInstrumentation();
@@ -1114,81 +1148,55 @@ public class GeckoSessionTestRule extend
      * timeout is not specified, return immediately. If loop is already idle and timeout is
      * specified, wait for a message to arrive first; an exception is thrown if timeout
      * expires during the wait.
      *
      * @param timeout Wait timeout in milliseconds or 0 to not wait.
      */
     protected static void loopUntilIdle(final long timeout) {
         // Adapted from GeckoThread.pumpMessageLoop.
-        final Looper looper = Looper.myLooper();
-        final MessageQueue queue = looper.getQueue();
-        final Handler handler = new Handler(looper);
-        final MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
-            @Override
-            public boolean queueIdle() {
-                final Message msg = Message.obtain(handler);
-                msg.obj = handler;
-                handler.sendMessageAtFrontOfQueue(msg);
-                return false; // Remove this idle handler.
-            }
-        };
-
-        final Method getNextMessage;
-        try {
-            getNextMessage = queue.getClass().getDeclaredMethod("next");
-        } catch (final NoSuchMethodException e) {
-            throw new RuntimeException(e);
-        }
-        getNextMessage.setAccessible(true);
-
-        final Runnable timeoutRunnable = new Runnable() {
-            @Override
-            public void run() {
-                throw new TimeoutException("Timed out after " + timeout + "ms");
-            }
-        };
+        final MessageQueue queue = HANDLER.getLooper().getQueue();
         if (timeout > 0) {
-            handler.postDelayed(timeoutRunnable, timeout);
+            TIMEOUT_RUNNABLE.set(timeout);
         } else {
-            queue.addIdleHandler(idleHandler);
+            queue.addIdleHandler(IDLE_HANDLER);
         }
 
         final long startTime = SystemClock.uptimeMillis();
         try {
             while (true) {
                 final Message msg;
                 try {
-                    msg = (Message) getNextMessage.invoke(queue);
+                    msg = (Message) sGetNextMessage.invoke(queue);
                 } catch (final IllegalAccessException | InvocationTargetException e) {
                     throw unwrapRuntimeException(e);
                 }
-                if (msg.getTarget() == handler && msg.obj == handler) {
+                if (msg.getTarget() == HANDLER && msg.obj == HANDLER) {
                     // Our idle signal.
                     break;
                 } else if (msg.getTarget() == null) {
-                    looper.quit();
+                    HANDLER.getLooper().quit();
                     return;
                 }
                 msg.getTarget().dispatchMessage(msg);
 
                 if (timeout > 0) {
-                    handler.removeCallbacks(timeoutRunnable);
-                    queue.addIdleHandler(idleHandler);
+                    TIMEOUT_RUNNABLE.cancel();
+                    queue.addIdleHandler(IDLE_HANDLER);
                 }
             }
 
             final long waitDuration = SystemClock.uptimeMillis() - startTime;
             if (waitDuration > sLongestWait) {
                 sLongestWait = waitDuration;
                 Log.i(LOGTAG, "New longest wait: " + waitDuration + "ms");
             }
         } finally {
             if (timeout > 0) {
-                handler.removeCallbacks(timeoutRunnable);
+                TIMEOUT_RUNNABLE.cancel();
             }
         }
     }
 
     /**
      * Wait until a page load has finished on any session. A session must have started a
      * page load since the last wait, or this method will wait indefinitely.
      */