Bug 715444 - Handle races where the event being listened for comes before the listener is registered. r=jmaher
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 05 Jan 2012 21:36:17 -0500
changeset 86355 aae720a3208821564bd7ceed7a049f6c79d67c6f
parent 86354 91a8e4c55adbc9d8024cfe416d143446c291420b
child 86356 f5afa1e3faa9a5b96f645acb34c963087188c0ff
push idunknown
push userunknown
push dateunknown
reviewersjmaher
bugs715444
milestone12.0a1
Bug 715444 - Handle races where the event being listened for comes before the listener is registered. r=jmaher
build/mobile/robocop/Actions.java.in
build/mobile/robocop/FennecNativeActions.java.in
mobile/android/base/tests/BaseTest.java.in
mobile/android/base/tests/testBookmark.java.in
mobile/android/base/tests/testNewTab.java.in
--- a/build/mobile/robocop/Actions.java.in
+++ b/build/mobile/robocop/Actions.java.in
@@ -39,22 +39,31 @@
 
 package @ANDROID_PACKAGE_NAME@;
 import java.util.List;
 
 public interface Actions {
   public enum SpecialKey {
     DOWN, UP, LEFT, RIGHT, ENTER
   }
+
+  public interface EventExpecter {
+    /** Blocks until the event has been received. Subsequent calls will return immediately. */
+    public void blockForEvent();
+    /** Polls to see if the event has been received. Once this returns true, subsequent calls will also return true. */
+    public boolean eventReceived();
+  }
+
   /**
-   * Waits for a gecko event to be sent from the Gecko instance.
+   * Listens for a gecko event to be sent from the Gecko instance.
+   * 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
    */
-
-  void waitForGeckoEvent(String geckoEvent);
+  EventExpecter expectGeckoEvent(String geckoEvent);
   // Send the string kewsToSend to the application 
   void sendKeys(String keysToSend);
   //Send any of the above keys to the element
   void sendSpecialKey(SpecialKey button);
 
   void drag(int startingX, int endingX, int startingY, int endingY);
 }
--- a/build/mobile/robocop/FennecNativeActions.java.in
+++ b/build/mobile/robocop/FennecNativeActions.java.in
@@ -79,19 +79,16 @@ public class FennecNativeActions impleme
   private Class gel;
   private Class ge;
   private Class gas;
   private Method registerGEL;
   private Method unregisterGEL;
   private Method sendGE;
 
 
-  // If waiting for an event.
-  private SynchronousQueue waitqueue = new SynchronousQueue<Boolean>();
-
   public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation){
     this.solo = robocop;
     this.instr = instrumentation;
     // Set up reflexive access of java classes and methods.
     try {
       classLoader = activity.getClassLoader();
       gel = classLoader.loadClass("org.mozilla.gecko.GeckoEventListener");
       ge = classLoader.loadClass("org.mozilla.gecko.GeckoEvent");
@@ -111,61 +108,106 @@ public class FennecNativeActions impleme
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      } catch (IllegalArgumentException e) {
        e.printStackTrace();
      }
   }
 
   class wakeInvocationHandler implements InvocationHandler {
-    public wakeInvocationHandler(){};
+    private final GeckoEventExpecter mEventExpecter;
+
+    public wakeInvocationHandler(GeckoEventExpecter expecter) {
+      mEventExpecter = expecter;
+    }
+
     public Object invoke(Object proxy, Method method, Object[] args) {
       String methodName = method.getName();
       //Depending on the method, return a completely different type.
       if(methodName.equals("toString")) {
         return "wakeInvocationHandler";
       }
       if(methodName.equals("equals")) {
         return this == args[0];
       }
       if(methodName.equals("clone")) {
         return this;
       }
       if(methodName.equals("hashCode")) {
         return 314;
       }
       Log.i("Robocop", "Waking up on "+methodName);
-      waitqueue.offer(new Boolean(true));
+      mEventExpecter.notifyOfEvent();
       return null;
     }
   }
+
+  class GeckoEventExpecter implements EventExpecter {
+    private final String mGeckoEvent;
+    private final Object[] mRegistrationParams;
+    private boolean mEventReceived;
+
+    GeckoEventExpecter(String geckoEvent, Object[] registrationParams) {
+      mGeckoEvent = geckoEvent;
+      mRegistrationParams = registrationParams;
+    }
+
+    public synchronized void blockForEvent() {
+      while (! mEventReceived) {
+        try {
+          this.wait();
+        } catch (InterruptedException ie) {
+          ie.printStackTrace();
+          break;
+        }
+      }
+      Log.i("Robocop", "unblocked on expecter for " + mGeckoEvent);
+    }
+
+    public synchronized boolean eventReceived() {
+      return mEventReceived;
+    }
+
+    void notifyOfEvent() {
+      try {
+        unregisterGEL.invoke(null, mRegistrationParams);
+      } catch (IllegalAccessException e) {
+        e.printStackTrace();
+      } catch (InvocationTargetException e) {
+        e.printStackTrace();
+      }
+      Log.i("Robocop", "received event " + mGeckoEvent);
+      synchronized (this) {
+        mEventReceived = true;
+        this.notifyAll();
+      }
+    }
+  }
   
-  public void waitForGeckoEvent(String geckoEvent) {
+  public EventExpecter expectGeckoEvent(String geckoEvent) {
     Log.i("Robocop", "waiting for "+geckoEvent);
     try {
       Class [] interfaces = new Class[1];
       interfaces[0] = gel;
       Object[] finalParams = new Object[2];
       finalParams[0] = geckoEvent;
      
-      wakeInvocationHandler wIH = new wakeInvocationHandler();
+      GeckoEventExpecter expecter = new GeckoEventExpecter(geckoEvent, finalParams);
+      wakeInvocationHandler wIH = new wakeInvocationHandler(expecter);
       Object proxy = Proxy.newProxyInstance(classLoader, interfaces, wIH);
       finalParams[1] = proxy;
       registerGEL.invoke(null, finalParams);
       
-      waitqueue.take();
-      unregisterGEL.invoke(null, finalParams);
+      return expecter;
     } catch (IllegalAccessException e) {
       e.printStackTrace();
     } catch (InvocationTargetException e) {
       e.printStackTrace();
-    } catch (InterruptedException e) {
-      e.printStackTrace();
     }
-    Log.i("Robocop", "wait ends for: "+geckoEvent);
+    return null;
   }
 
   public void sendSpecialKey(SpecialKey button) {
     switch( button) {
       case DOWN:
         instr.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
         break;
       case UP:
--- a/mobile/android/base/tests/BaseTest.java.in
+++ b/mobile/android/base/tests/BaseTest.java.in
@@ -63,32 +63,36 @@ abstract class BaseTest extends Activity
         } catch (Throwable e) {
             e.printStackTrace();
         }
         getActivity().finish();
         super.tearDown();
     }
 
     protected final void enterUrl(String url) {
-        mActions.waitForGeckoEvent("Gecko:Ready");
+        mActions.expectGeckoEvent("Gecko:Ready").blockForEvent();
         Element awesomebar = mDriver.findElement("awesome_bar");
         awesomebar.click();
         getInstrumentation().waitForIdleSync();
 
         Element urlbar = mDriver.findElement("awesomebar_text");
         mActions.sendKeys(url);
         mAsserter.is(urlbar.getText(), url, "Awesomebar URL typed properly");
     }
 
+    protected final void hitEnterAndWait() {
+        Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
+        mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
+        // wait for screen to load
+        contentEventExpecter.blockForEvent();
+    }
+
     protected final void loadUrl(String url) {
         enterUrl(url);
-
-        mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
-        // wait for screen to load
-        mActions.waitForGeckoEvent("DOMContentLoaded");
+        hitEnterAndWait();
     }
 
     protected final void verifyUrl(String url) {
         Element awesomebar = mDriver.findElement("awesome_bar");
         Element urlbar = mDriver.findElement("awesomebar_text");
         awesomebar.click();
         getInstrumentation().waitForIdleSync();
         mAsserter.is(urlbar.getText(), url, "Awesomebar URL stayed the same");
--- a/mobile/android/base/tests/testBookmark.java.in
+++ b/mobile/android/base/tests/testBookmark.java.in
@@ -6,29 +6,28 @@ import @ANDROID_PACKAGE_NAME@.*;
 public class testBookmark extends BaseTest {
     private static final String URL = "http://mochi.test:8888/tests/robocop/robocop_blank_02.html";
 
     public void testBookmark() {
         enterUrl(URL);
 
         //Click the top item in the list.
         mActions.sendSpecialKey(Actions.SpecialKey.DOWN);
-        mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
-        mActions.waitForGeckoEvent("DOMContentLoaded");
+        hitEnterAndWait();
 
         verifyUrl(URL);
 
         //Click the Top item in the history list.
         getInstrumentation().waitForIdleSync();
         mActions.sendSpecialKey(Actions.SpecialKey.RIGHT);
         mActions.sendSpecialKey(Actions.SpecialKey.RIGHT);
         getInstrumentation().waitForIdleSync();
         mActions.sendSpecialKey(Actions.SpecialKey.DOWN);
         mActions.sendSpecialKey(Actions.SpecialKey.DOWN);
-        mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
-        mActions.waitForGeckoEvent("DOMContentLoaded");
+
+        hitEnterAndWait();
 
         getInstrumentation().waitForIdleSync();
         //Unfortunately, the item isn't constant so can't be tested.
         //awesomebar.click();
         //asserter.is(url, urlbar.getText(),"Shouldn't this be the last url in the history?");
     }
 }
--- a/mobile/android/base/tests/testNewTab.java.in
+++ b/mobile/android/base/tests/testNewTab.java.in
@@ -3,46 +3,39 @@ package @ANDROID_PACKAGE_NAME@.tests;
 
 import @ANDROID_PACKAGE_NAME@.*;
 
 public class testNewTab extends BaseTest {
     public void testNewTab() {
         // TODO: find a better way to not hardcode this url
         String url = "http://mochi.test:8888/tests/robocop/robocop_blank_01.html";
         String url2 = "http://mochi.test:8888/tests/robocop/robocop_blank_02.html";
-        mActions.waitForGeckoEvent("Gecko:Ready");
+        mActions.expectGeckoEvent("Gecko:Ready").blockForEvent();
         Element tabs = mDriver.findElement("tabs");
         // Add one tab
         tabs.click();
 
         Element urlbar = mDriver.findElement("awesomebar_text");
         getInstrumentation().waitForIdleSync();
         mActions.sendKeys(url);
         mAsserter.is(urlbar.getText(), url, "Awesomebar url is fine");
-        mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
-        mActions.waitForGeckoEvent("DOMContentLoaded");
-
-        try {
-            Thread.sleep(5000);
-        } catch (Throwable e) {
-        }
+        hitEnterAndWait();
 
         // See tab count
         Element tabCount = mDriver.findElement("tabs_count");
         mAsserter.is(tabCount.getText(), "2", "Number of tabs has increased");
 
         // Click tab list
         tabs.click();
         Element addTab = mDriver.findElement("add_tab");
 
         //Add another tab
         addTab.click();
         getInstrumentation().waitForIdleSync();
         mActions.sendKeys(url2);
         getInstrumentation().waitForIdleSync();
         mAsserter.is(urlbar.getText(), url2, "URL is still fine");
 
-        mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
-        mActions.waitForGeckoEvent("DOMContentLoaded");
+        hitEnterAndWait();
         //Check tab count another time.
         mAsserter.is(tabCount.getText(), "3", "Number of tabs has increased");
     }
 }