Bug 1319558 - Add new EventDispatcher support to Robocop interface; r=gbrown
authorJim Chen <nchen@mozilla.com>
Fri, 09 Dec 2016 12:32:45 -0500
changeset 325597 6ed38cf05aaf57389f9a8b6680a454d247916c50
parent 325596 6e7b8826047c4f8b8b167f49a498047b5c9d3ebe
child 325598 1cce3b69bfc76377f5a0c015372d790b81f7b2bd
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersgbrown
bugs1319558
milestone53.0a1
Bug 1319558 - Add new EventDispatcher support to Robocop interface; r=gbrown Add expectGlobalEvent, expectWindowEvent, sendGlobalEvent, and sendWindowEvent to the Robocop Actions interface, along with changes to EventExpecter, to support GeckoBundle events on Gecko, UI, and background threads.
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/Actions.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeActions.java
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/Actions.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/Actions.java
@@ -1,13 +1,16 @@
 /* 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 org.mozilla.gecko.util.GeckoBundle;
+
 import android.database.Cursor;
 
 public interface Actions {
 
     /** Special keys supported by sendSpecialKey() */
     public enum SpecialKey {
         DOWN,
         UP,
@@ -21,41 +24,73 @@ public interface Actions {
     public interface EventExpecter {
         /** Blocks until the event has been received. Subsequent calls will return immediately. */
         public void blockForEvent();
         public void blockForEvent(long millis, boolean failOnTimeout);
 
         /** Blocks until the event has been received and returns data associated with the event. */
         public String blockForEventData();
 
+        /** Blocks until the event has been received and returns data associated with the event. */
+        public GeckoBundle blockForBundle();
+
         /**
          * Blocks until the event has been received, or until the timeout has been exceeded.
          * Returns the data associated with the event, if applicable.
          */
         public String blockForEventDataWithTimeout(long millis);
 
+        /**
+         * Blocks until the event has been received, or until the timeout has been exceeded.
+         * Returns the data associated with the event, if applicable.
+         */
+        public GeckoBundle blockForBundleWithTimeout(long millis);
+
         /** Polls to see if the event has been received. Once this returns true, subsequent calls will also return true. */
         public boolean eventReceived();
 
         /** Stop listening for events. */
         public void unregisterListener();
     }
 
     public interface RepeatedEventExpecter extends EventExpecter {
         /** Blocks until at least one event has been received, and no events have been received in the last <code>millis</code> milliseconds. */
         public void blockUntilClear(long millis);
     }
 
+    public enum EventType {
+        JSON,
+        GECKO,
+        UI,
+        BACKGROUND
+    }
+
     /**
      * Sends an event to Gecko.
      *
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     void sendGeckoEvent(String geckoEvent, String data);
 
+    /**
+     * Sends an event to the global EventDispatcher instance.
+     *
+     * @param event The event type
+     * @param data Data associated with the event
+     */
+    void sendGlobalEvent(String event, GeckoBundle data);
+
+    /**
+     * Sends an event to the GeckoApp-specific EventDispatcher instance.
+     *
+     * @param event The event type
+     * @param data Data associated with the event
+     */
+    void sendWindowEvent(String event, GeckoBundle data);
+
     public interface PrefWaiter {
         boolean isFinished();
         void waitForFinish();
         void waitForFinish(long timeoutMillis, boolean failOnTimeout);
     }
 
     public abstract static class PrefHandlerBase implements PrefsHelper.PrefHandler {
         /* package */ Assert asserter;
@@ -90,16 +125,36 @@ public interface Actions {
      * The returned object can be used to test if the event has been
      * received. Note that only one event is listened for.
      *
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     RepeatedEventExpecter expectGeckoEvent(String geckoEvent);
 
     /**
+     * Listens for an event on the global EventDispatcher instance.
+     * The returned object can be used to test if the event has been
+     * received. Note that only one event is listened for.
+     *
+     * @param type The thread type for the event
+     * @param event The name for the event
+     */
+    RepeatedEventExpecter expectGlobalEvent(EventType type, String event);
+
+    /**
+     * Listens for an event on the global EventDispatcher instance.
+     * The returned object can be used to test if the event has been
+     * received. Note that only one event is listened for.
+     *
+     * @param type The thread type for the event
+     * @param event The name for the event
+     */
+    RepeatedEventExpecter expectWindowEvent(EventType type, String event);
+
+    /**
      * Listens for a paint event. Note that calling expectPaint() will
      * invalidate the event expecters returned from any previous calls
      * to expectPaint(); calling any methods on those invalidated objects
      * will result in undefined behaviour.
      */
     RepeatedEventExpecter expectPaint();
 
     /**
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeActions.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/FennecNativeActions.java
@@ -10,16 +10,19 @@ import java.util.concurrent.LinkedBlocki
 import java.util.concurrent.TimeUnit;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.FennecNativeDriver.LogLevel;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.LayerView.DrawListener;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.sqlite.SQLiteBridge;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.database.Cursor;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.view.KeyEvent;
@@ -41,45 +44,80 @@ public class FennecNativeActions impleme
         GeckoLoader.loadSQLiteLibs(activity, activity.getApplication().getPackageResourcePath());
     }
 
     class GeckoEventExpecter implements RepeatedEventExpecter {
         private static final int MAX_WAIT_MS = 180000;
 
         private volatile boolean mIsRegistered;
 
+        private final EventDispatcher mDispatcher;
+        private final EventType mType;
         private final String mGeckoEvent;
+        private final BlockingQueue<Object> mEventDataQueue;
         private final GeckoEventListener mListener;
+        private final BundleEventListener mBundleListener;
 
         private volatile boolean mEventEverReceived;
-        private String mEventData;
-        private BlockingQueue<String> mEventDataQueue;
+        private Object mEventData;
 
-        GeckoEventExpecter(final String geckoEvent) {
+        GeckoEventExpecter(final EventDispatcher dispatcher, final EventType type,
+                           final String geckoEvent) {
             if (TextUtils.isEmpty(geckoEvent)) {
                 throw new IllegalArgumentException("geckoEvent must not be empty");
             }
 
+            mDispatcher = dispatcher;
+            mType = type;
             mGeckoEvent = geckoEvent;
-            mEventDataQueue = new LinkedBlockingQueue<String>();
+            mEventDataQueue = new LinkedBlockingQueue<>();
+            mIsRegistered = true;
 
-            final GeckoEventExpecter expecter = this;
-            mListener = new GeckoEventListener() {
+            if (type == EventType.JSON) {
+                mListener = new GeckoEventListener() {
+                    @Override
+                    public void handleMessage(final String event, final JSONObject message) {
+                        FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
+                                "handleMessage called for: " + event + "; expecting: " + mGeckoEvent);
+                        mAsserter.is(event, mGeckoEvent,
+                                "Given message occurred for registered event: " + message);
+
+                        notifyOfEvent(message.toString());
+                    }
+                };
+                mBundleListener = null;
+                dispatcher.registerGeckoThreadListener(mListener, geckoEvent);
+                return;
+            }
+
+            mListener = null;
+            mBundleListener = new BundleEventListener() {
                 @Override
-                public void handleMessage(final String event, final JSONObject message) {
+                public void handleMessage(final String event, final GeckoBundle message,
+                                          final EventCallback callback) {
                     FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
                             "handleMessage called for: " + event + "; expecting: " + mGeckoEvent);
-                    mAsserter.is(event, mGeckoEvent, "Given message occurred for registered event: " + message);
+                    mAsserter.is(event, mGeckoEvent,
+                            "Given message occurred for registered event: " + message);
 
-                    expecter.notifyOfEvent(message);
+                    // Because we cannot put null into the queue, we have to use an empty
+                    // bundle if we don't have a message.
+                    notifyOfEvent(message != null ? message : new GeckoBundle(0));
                 }
             };
 
-            EventDispatcher.getInstance().registerGeckoThreadListener(mListener, mGeckoEvent);
-            mIsRegistered = true;
+            if (type == EventType.GECKO) {
+                dispatcher.registerGeckoThreadListener(mBundleListener, geckoEvent);
+            } else if (type == EventType.UI) {
+                dispatcher.registerUiThreadListener(mBundleListener, geckoEvent);
+            } else if (type == EventType.BACKGROUND) {
+                dispatcher.registerBackgroundThreadListener(mBundleListener, geckoEvent);
+            } else {
+                throw new IllegalArgumentException("Unsupported thread type");
+            }
         }
 
         public void blockForEvent() {
             blockForEvent(MAX_WAIT_MS, true);
         }
 
         public void blockForEvent(long millis, boolean failOnTimeout) {
             if (!mIsRegistered) {
@@ -138,64 +176,102 @@ public class FennecNativeActions impleme
                 }
             }
             FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
                 "unblocked on expecter for " + mGeckoEvent);
         }
 
         public String blockForEventData() {
             blockForEvent();
-            return mEventData;
+            return (String) mEventData;
         }
 
         public String blockForEventDataWithTimeout(long millis) {
             blockForEvent(millis, false);
-            return mEventData;
+            return (String) mEventData;
+        }
+
+        public GeckoBundle blockForBundle() {
+            blockForEvent();
+            return (GeckoBundle) mEventData;
+        }
+
+        public GeckoBundle blockForBundleWithTimeout(long millis) {
+            blockForEvent(millis, false);
+            return (GeckoBundle) mEventData;
         }
 
         public void unregisterListener() {
             if (!mIsRegistered) {
                 throw new IllegalStateException("listener not registered");
             }
 
             FennecNativeDriver.log(LogLevel.INFO,
                     "EventExpecter: no longer listening for " + mGeckoEvent);
 
-            EventDispatcher.getInstance().unregisterGeckoThreadListener(mListener, mGeckoEvent);
+            if (mType == EventType.JSON) {
+                mDispatcher.unregisterGeckoThreadListener(mListener, mGeckoEvent);
+            } else if (mType == EventType.GECKO) {
+                mDispatcher.unregisterGeckoThreadListener(mBundleListener, mGeckoEvent);
+            } else if (mType == EventType.UI) {
+                mDispatcher.unregisterUiThreadListener(mBundleListener, mGeckoEvent);
+            } else if (mType == EventType.BACKGROUND) {
+                mDispatcher.unregisterBackgroundThreadListener(mBundleListener, mGeckoEvent);
+            } else {
+                throw new IllegalArgumentException("Unsupported thread type");
+            }
             mIsRegistered = false;
         }
 
         public boolean eventReceived() {
             return mEventEverReceived;
         }
 
-        void notifyOfEvent(final JSONObject message) {
+        /* package */ void notifyOfEvent(final Object data) {
             FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG,
                     "received event " + mGeckoEvent);
 
             mEventEverReceived = true;
 
             try {
-                mEventDataQueue.put(message.toString());
+                mEventDataQueue.put(data);
             } catch (InterruptedException e) {
                 FennecNativeDriver.log(LogLevel.ERROR,
-                    "EventExpecter dropped event: " + message.toString(), e);
+                    "EventExpecter dropped event: " + data, e);
             }
         }
     }
 
     public RepeatedEventExpecter expectGeckoEvent(final String geckoEvent) {
         FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent);
-        return new GeckoEventExpecter(geckoEvent);
+        return new GeckoEventExpecter(EventDispatcher.getInstance(), EventType.JSON, geckoEvent);
+    }
+
+    public RepeatedEventExpecter expectGlobalEvent(final EventType type, final String geckoEvent) {
+        FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent);
+        return new GeckoEventExpecter(EventDispatcher.getInstance(), type, geckoEvent);
+    }
+
+    public RepeatedEventExpecter expectWindowEvent(final EventType type, final String geckoEvent) {
+        FennecNativeDriver.log(FennecNativeDriver.LogLevel.DEBUG, "waiting for " + geckoEvent);
+        return new GeckoEventExpecter(GeckoApp.getEventDispatcher(), type, geckoEvent);
     }
 
     public void sendGeckoEvent(final String geckoEvent, final String data) {
         GeckoAppShell.notifyObservers(geckoEvent, data);
     }
 
+    public void sendGlobalEvent(final String event, final GeckoBundle data) {
+        EventDispatcher.getInstance().dispatch(event, data);
+    }
+
+    public void sendWindowEvent(final String event, final GeckoBundle data) {
+        GeckoApp.getEventDispatcher().dispatch(event, data);
+    }
+
     public static final class PrefProxy implements PrefsHelper.PrefHandler, PrefWaiter {
         public static final int MAX_WAIT_MS = 180000;
 
         /* package */ final PrefHandlerBase target;
         private final String[] expectedPrefs;
         private final ArrayList<String> seenPrefs = new ArrayList<>();
         private boolean finished = false;
 
@@ -361,16 +437,24 @@ public class FennecNativeActions impleme
             return null;
         }
 
         public synchronized String blockForEventDataWithTimeout(long millis) {
             blockForEvent(millis, false);
             return null;
         }
 
+        public GeckoBundle blockForBundle() {
+            throw new UnsupportedOperationException();
+        }
+
+        public GeckoBundle blockForBundleWithTimeout(long millis) {
+            throw new UnsupportedOperationException();
+        }
+
         public synchronized boolean eventReceived() {
             return mPaintDone;
         }
 
         public synchronized void blockUntilClear(long millis) {
             if (!mListening) {
                 throw new IllegalStateException("draw listener not registered");
             }