Merged the legecy EventPreview stack into the new NativePreviewHandler stack so legacy EventPreviews will only fire if they are at the top of the combined stack.  A previous change took PopupPanels off the legacy stack, which promoted other EventPreview to the top and broke legacy code.

Patch by: jlabanca
Review by: ecc (desk)
Issue: 3285



git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4464 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/DOM.java b/user/src/com/google/gwt/user/client/DOM.java
index 082a87e..a51597c 100644
--- a/user/src/com/google/gwt/user/client/DOM.java
+++ b/user/src/com/google/gwt/user/client/DOM.java
@@ -23,8 +23,6 @@
 import com.google.gwt.dom.client.SelectElement;
 import com.google.gwt.user.client.impl.DOMImpl;
 
-import java.util.ArrayList;
-
 /**
  * This class provides a set of static methods that allow you to manipulate the
  * browser's Document Object Model (DOM). It contains methods for manipulating
@@ -32,16 +30,11 @@
  * {@link com.google.gwt.user.client.Event events}.
  */
 public class DOM {
-
   // The current event being fired
   private static Event currentEvent = null;
   private static final DOMImpl impl = GWT.create(DOMImpl.class);
   private static Element sCaptureElem;
 
-  // <BrowserEventPreview>
-  @SuppressWarnings("deprecation")
-  private static ArrayList<EventPreview> sEventPreviewStack;
-
   /**
    * Adds an event preview to the preview stack. As long as this preview remains
    * on the top of the stack, it will receive all events before they are fired
@@ -55,14 +48,7 @@
    */
   @Deprecated
   public static void addEventPreview(EventPreview preview) {
-    impl.maybeInitializeEventSystem();
-
-    // Add the event preview to the stack. It will automatically
-    // begin receiving events.
-    if (sEventPreviewStack == null) {
-      sEventPreviewStack = new ArrayList<EventPreview>();
-    }
-    sEventPreviewStack.add(preview);
+    ListenerWrapper.NativePreview.add(preview);
   }
 
   /**
@@ -532,7 +518,7 @@
   }
 
   /**
-   * Gets the key-repeat state of this event.  Only IE supports this attribute.
+   * Gets the key-repeat state of this event. Only IE supports this attribute.
    * 
    * @param evt the event to be tested
    * @return <code>true</code> if this key event was an auto-repeat
@@ -909,8 +895,8 @@
    * 
    * @param parent the parent element
    * @param child the child element to add to <code>parent</code>
-   * @param before an existing child element of <code>parent</code> before
-   *          which <code>child</code> will be inserted
+   * @param before an existing child element of <code>parent</code> before which
+   *          <code>child</code> will be inserted
    */
   public static void insertBefore(Element parent, Element child, Element before) {
     parent.insertBefore(child, before);
@@ -930,10 +916,10 @@
   }
 
   /**
-   * Creates an <code>&lt;option&gt;</code> element and inserts it as a child
-   * of the specified <code>&lt;select&gt;</code> element. If the index is
-   * less than zero, or greater than or equal to the length of the list, then
-   * the option element will be appended to the end of the list.
+   * Creates an <code>&lt;option&gt;</code> element and inserts it as a child of
+   * the specified <code>&lt;select&gt;</code> element. If the index is less
+   * than zero, or greater than or equal to the length of the list, then the
+   * option element will be appended to the end of the list.
    * 
    * @param selectElem the <code>&lt;select&gt;</code> element
    * @param item the text of the new item; cannot be <code>null</code>
@@ -1012,12 +998,7 @@
    */
   @Deprecated
   public static void removeEventPreview(EventPreview preview) {
-    // Remove the event preview from the stack. If it was on top,
-    // any preview underneath it will automatically begin to
-    // receive events.
-    if (sEventPreviewStack != null) {
-      sEventPreviewStack.remove(preview);
-    }
+    ListenerWrapper.NativePreview.remove(preview);
   }
 
   /**
@@ -1270,6 +1251,13 @@
   }
 
   /**
+   * Initialize the event system if it has not already been initialized.
+   */
+  static void maybeInitializeEventSystem() {
+    impl.maybeInitializeEventSystem();
+  }
+
+  /**
    * This method is called directly by native code when event preview is being
    * used.
    * 
@@ -1281,12 +1269,6 @@
     // Fire a NativePreviewEvent to NativePreviewHandlers
     boolean ret = Event.fireNativePreviewEvent(evt);
 
-    // If event previews are present, redirect events to the topmost of them.
-    if (sEventPreviewStack != null && sEventPreviewStack.size() > 0) {
-      EventPreview preview = sEventPreviewStack.get(sEventPreviewStack.size() - 1);
-      ret = preview.onEventPreview(evt) && ret;
-    }
-
     // If the preview cancels the event, stop it from bubbling and performing
     // its default action. Check for a null evt to allow unit tests to run.
     if (!ret && evt != null) {
diff --git a/user/src/com/google/gwt/user/client/Event.java b/user/src/com/google/gwt/user/client/Event.java
index ff156f1..4ef3269 100644
--- a/user/src/com/google/gwt/user/client/Event.java
+++ b/user/src/com/google/gwt/user/client/Event.java
@@ -30,9 +30,9 @@
  * An opaque handle to a native DOM Event. An <code>Event</code> cannot be
  * created directly. Instead, use the <code>Event</code> type when returning a
  * native DOM event from JSNI methods. An <code>Event</code> passed back into
- * JSNI becomes the original DOM event the <code>Event</code> was created
- * from, and can be accessed in JavaScript code as expected. This is typically
- * done by calling methods in the {@link com.google.gwt.user.client.DOM} class.
+ * JSNI becomes the original DOM event the <code>Event</code> was created from,
+ * and can be accessed in JavaScript code as expected. This is typically done by
+ * calling methods in the {@link com.google.gwt.user.client.DOM} class.
  * </p>
  */
 public class Event extends JavaScriptObject {
@@ -85,6 +85,7 @@
         int numHandlers = handlers.size();
         for (int i = numHandlers - 1; i >= 0; i--) {
           handlers.get(i).onPreviewNativeEvent(singleton);
+          singleton.isFirstHandler = false;
         }
 
         // Kill the event and return
@@ -106,6 +107,12 @@
     private boolean isConsumed = false;
 
     /**
+     * A boolean indicating that the current handler is at the top of the event
+     * preview stack.
+     */
+    private boolean isFirstHandler = false;
+
+    /**
      * The event being previewed.
      */
     private Event nativeEvent;
@@ -162,6 +169,15 @@
       return isConsumed;
     }
 
+    /**
+     * Is the current handler the first to preview this event?
+     * 
+     * @return true if the current handler is the first to preview the event
+     */
+    public boolean isFirstHandler() {
+      return isFirstHandler;
+    }
+
     @Override
     protected void dispatch(NativePreviewHandler handler) {
       handler.onPreviewNativeEvent(this);
@@ -172,6 +188,7 @@
       super.revive();
       isCanceled = false;
       isConsumed = false;
+      isFirstHandler = true;
       nativeEvent = null;
     }
 
@@ -304,7 +321,8 @@
   public static final int ONSCROLL = 0x04000;
 
   /**
-   * Fired when the user requests an element's context menu (usually by right-clicking).
+   * Fired when the user requests an element's context menu (usually by
+   * right-clicking).
    * 
    * Note that not all browsers will fire this event (notably Opera, as of 9.5).
    */
@@ -328,7 +346,7 @@
       | ONMOUSEOVER | ONMOUSEOUT;
 
   /**
-   * Value returned by accessors when the actual integer value is undefined.  In
+   * Value returned by accessors when the actual integer value is undefined. In
    * hosted mode, most accessors assert that the requested attribute is reliable
    * across all supported browsers.
    * 
@@ -338,12 +356,11 @@
   public static final int UNDEFINED = 0;
 
   /**
-   * The list of {@link NativePreviewHandler}.  We use a list instead of a
+   * The list of {@link NativePreviewHandler}. We use a list instead of a
    * handler manager for efficiency and because we want to fire the handlers in
-   * reverse order.  When the last handler is removed, handlers is reset to
-   * null.
+   * reverse order. When the last handler is removed, handlers is reset to null.
    */
-  private static ArrayList<NativePreviewHandler> handlers;
+  static ArrayList<NativePreviewHandler> handlers;
 
   /**
    * Adds an event preview to the preview stack. As long as this preview remains
@@ -381,12 +398,14 @@
   public static HandlerRegistration addNativePreviewHandler(
       final NativePreviewHandler handler) {
     assert handler != null : "Cannot add a null handler";
+    DOM.maybeInitializeEventSystem();
+
     // Initialize the type
     NativePreviewEvent.getType();
     if (handlers == null) {
       handlers = new ArrayList<NativePreviewHandler>();
     }
-    handlers.add(handler);    
+    handlers.add(handler);
     return new HandlerRegistration() {
       public void removeHandler() {
         if (handlers != null) {
@@ -430,7 +449,7 @@
    * @see #setCapture(Element)
    */
   public static void releaseCapture(Element elem) {
-    DOM.releaseCapture(elem.<com.google.gwt.user.client.Element>cast());
+    DOM.releaseCapture(elem.<com.google.gwt.user.client.Element> cast());
   }
 
   /**
@@ -453,7 +472,7 @@
    * @param elem the element on which to set mouse capture
    */
   public static void setCapture(Element elem) {
-    DOM.setCapture(elem.<com.google.gwt.user.client.Element>cast());
+    DOM.setCapture(elem.<com.google.gwt.user.client.Element> cast());
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/ListenerWrapper.java b/user/src/com/google/gwt/user/client/ListenerWrapper.java
index bcd0115..6a80c85 100644
--- a/user/src/com/google/gwt/user/client/ListenerWrapper.java
+++ b/user/src/com/google/gwt/user/client/ListenerWrapper.java
@@ -25,21 +25,21 @@
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
 
 import java.util.EventListener;
 
 /**
- * Legacy listener support hierarchy for <code>com.google.gwt.user.client</code>.
- * Gathers the bulk of the legacy glue code in one place, for easy deletion when
- * Listener methods are deleted. 
+ * Legacy listener support for <code>com.google.gwt.user.client</code>. Gathers
+ * the bulk of the legacy glue code in one place, for easy deletion when
+ * Listener methods are deleted.
  * 
- * @see com.google.gwt.user.ListenerWrapper
  * @param <T> listener type
  */
 @Deprecated
 abstract class ListenerWrapper<T> implements EventHandler {
-  public static class HistoryChange extends ListenerWrapper<HistoryListener> implements
-      ValueChangeHandler<String> {
+  public static class HistoryChange extends ListenerWrapper<HistoryListener>
+      implements ValueChangeHandler<String> {
     @Deprecated
     public static void add(HistoryListener listener) {
       History.addValueChangeHandler(new HistoryChange(listener));
@@ -58,8 +58,46 @@
     }
   }
 
-  public static class WindowClose extends ListenerWrapper<WindowCloseListener> implements
-      Window.ClosingHandler, CloseHandler<Window> {
+  public static class NativePreview extends ListenerWrapper<EventPreview>
+      implements Event.NativePreviewHandler {
+    @Deprecated
+    public static void add(EventPreview listener) {
+      Event.addNativePreviewHandler(new NativePreview(listener));
+    }
+
+    public static void remove(EventPreview listener) {
+      if (Event.handlers == null) {
+        return;
+      }
+      int handlerCount = Event.handlers.size();
+      // We only want to remove the first instance, as the legacy listener does
+      for (int i = 0; i < handlerCount; i++) {
+        Event.NativePreviewHandler handler = Event.handlers.get(i);
+        if (handler instanceof NativePreview
+            && ((NativePreview) handler).listener.equals(listener)) {
+          Event.handlers.remove(handler);
+          return;
+        }
+      }
+    }
+
+    private NativePreview(EventPreview listener) {
+      super(listener);
+    }
+
+    public void onPreviewNativeEvent(NativePreviewEvent event) {
+      // The legacy EventHandler should only fire if it is on the top of the
+      // stack (ie. the last one added).
+      if (event.isFirstHandler()) {
+        if (!listener.onEventPreview(event.getNativeEvent())) {
+          event.cancel();
+        }
+      }
+    }
+  }
+
+  public static class WindowClose extends ListenerWrapper<WindowCloseListener>
+      implements Window.ClosingHandler, CloseHandler<Window> {
     @Deprecated
     public static void add(WindowCloseListener listener) {
       WindowClose handler = new WindowClose(listener);
@@ -89,8 +127,8 @@
     }
   }
 
-  public static class WindowResize extends ListenerWrapper<WindowResizeListener> implements
-      ResizeHandler {
+  public static class WindowResize extends
+      ListenerWrapper<WindowResizeListener> implements ResizeHandler {
     @Deprecated
     public static void add(WindowResizeListener listener) {
       Window.addResizeHandler(new WindowResize(listener));
@@ -110,8 +148,8 @@
     }
   }
 
-  public static class WindowScroll extends ListenerWrapper<WindowScrollListener> implements
-      Window.ScrollHandler {
+  public static class WindowScroll extends
+      ListenerWrapper<WindowScrollListener> implements Window.ScrollHandler {
     @Deprecated
     public static void add(WindowScrollListener listener) {
       Window.addWindowScrollHandler(new WindowScroll(listener));
@@ -131,7 +169,6 @@
     }
   }
 
-
   // This is an internal helper method with the current formulation, we have
   // lost the info needed to make it safe by this point.
   @SuppressWarnings("unchecked")
@@ -153,7 +190,7 @@
       }
     }
   }
-  
+
   /**
    * Listener being wrapped.
    */
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImpl.java b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
index 30da21c..09ffb25 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
@@ -157,9 +157,6 @@
 
   public abstract void insertChild(Element parent, Element child, int index);
 
-  /**
-   * Initialize the event system if it has not already been initialized.
-   */
   public void maybeInitializeEventSystem() {
     if (!eventSystemIsInitialized) {
       initEventSystem();
diff --git a/user/src/com/google/gwt/user/client/ui/PopupPanel.java b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
index 707fc0b..981d9cb 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -829,7 +829,7 @@
   @SuppressWarnings("deprecation")
   protected void onPreviewNativeEvent(NativePreviewEvent event) {
     // Cancel the event based on the deprecated onEventPreview() method
-    if (!event.isCanceled() && !onEventPreview(event.getNativeEvent())) {
+    if (event.isFirstHandler() && !onEventPreview(event.getNativeEvent())) {
       event.cancel();
     }
   }
diff --git a/user/test/com/google/gwt/user/client/EventTest.java b/user/test/com/google/gwt/user/client/EventTest.java
index a894e85..42c7516 100644
--- a/user/test/com/google/gwt/user/client/EventTest.java
+++ b/user/test/com/google/gwt/user/client/EventTest.java
@@ -132,17 +132,20 @@
 
   /**
    * Test that {@link Event#fireNativePreviewEvent(Event)} fires handlers in
-   * reverse order. Also verify that the legacy EventPreview fires last.
+   * reverse order, and that the legacy EventPreview fires only if it is at the
+   * top of the stack.
    */
   @SuppressWarnings("deprecation")
   public void testFireNativePreviewEventReverseOrder() {
-    final TestEventPreview preview = new TestEventPreview(false);
+    final TestEventPreview preview0 = new TestEventPreview(false);
+    final TestEventPreview preview1 = new TestEventPreview(false);
     final TestNativePreviewHandler handler0 = new TestNativePreviewHandler(
         false, false) {
       @Override
       public void onPreviewNativeEvent(NativePreviewEvent event) {
         super.onPreviewNativeEvent(event);
-        preview.assertIsFired(false);
+        preview0.assertIsFired(false);
+        preview1.assertIsFired(true);
       }
     };
     final TestNativePreviewHandler handler1 = new TestNativePreviewHandler(
@@ -151,7 +154,8 @@
       public void onPreviewNativeEvent(NativePreviewEvent event) {
         super.onPreviewNativeEvent(event);
         handler0.assertIsFired(false);
-        preview.assertIsFired(false);
+        preview0.assertIsFired(false);
+        preview1.assertIsFired(true);
       }
     };
     final TestNativePreviewHandler handler2 = new TestNativePreviewHandler(
@@ -161,7 +165,8 @@
         super.onPreviewNativeEvent(event);
         handler0.assertIsFired(false);
         handler1.assertIsFired(false);
-        preview.assertIsFired(false);
+        preview0.assertIsFired(false);
+        preview1.assertIsFired(true);
       }
     };
     final TestNativePreviewHandler handler3 = new TestNativePreviewHandler(
@@ -172,25 +177,29 @@
         handler0.assertIsFired(false);
         handler1.assertIsFired(false);
         handler2.assertIsFired(false);
-        preview.assertIsFired(false);
+        preview0.assertIsFired(false);
+        preview1.assertIsFired(true);
       }
     };
+    DOM.addEventPreview(preview0);
     HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
     HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
     HandlerRegistration reg2 = Event.addNativePreviewHandler(handler2);
     HandlerRegistration reg3 = Event.addNativePreviewHandler(handler3);
-    DOM.addEventPreview(preview);
+    DOM.addEventPreview(preview1);
     assertTrue(DOM.previewEvent(null));
     handler0.assertIsFired(true);
     handler1.assertIsFired(true);
     handler2.assertIsFired(true);
     handler3.assertIsFired(true);
-    preview.assertIsFired(true);
+    preview0.assertIsFired(false);
+    preview1.assertIsFired(true);
     reg0.removeHandler();
     reg1.removeHandler();
     reg2.removeHandler();
     reg3.removeHandler();
-    DOM.removeEventPreview(preview);
+    DOM.removeEventPreview(preview0);
+    DOM.removeEventPreview(preview1);
   }
 
   /**