Added a NativePreviewEvent to replce the current EventPreview system.  Now, multiple widgets can preview the same event, instead of just the one at the top of the stack.  This fixes a couple of subtle bugs and adds some use cases, such as listening for user timeout.

Patch by: jlabanca
Review by: ecc
Issue: 1275



git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4385 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java
index 67b5d33..e51c222 100644
--- a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue1932.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -21,8 +21,9 @@
 import com.google.gwt.museum.client.common.AbstractIssue;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.EventPreview;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.Grid;
@@ -116,29 +117,29 @@
     sandbox.getElement().getStyle().setProperty("cursor", "crosshair");
 
     // Keep the crosshair under the cursor
-    DOM.addEventPreview(new EventPreview() {
-      public boolean onEventPreview(Event event) {
+    Event.addNativePreviewHandler(new NativePreviewHandler() {
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
         // Ignore events outside of the sandbox
-        Element target = DOM.eventGetTarget(event);
+        Event nativeEvent = event.getNativeEvent();
+        Element target = nativeEvent.getTarget();
         if (!sandbox.getElement().isOrHasChild(target)
             && !positioner.getElement().isOrHasChild(target)) {
           positioner.removeFromParent();
-          return true;
+          return;
         }
 
-        switch (DOM.eventGetType(event)) {
+        switch (nativeEvent.getTypeInt()) {
           case Event.ONMOUSEMOVE:
-            int absX = event.getClientX() + Window.getScrollLeft();
-            int absY = event.getClientY() + Window.getScrollTop();
+            int absX = nativeEvent.getClientX() + Window.getScrollLeft();
+            int absY = nativeEvent.getClientY() + Window.getScrollTop();
             RootPanel.get().add(positioner, absX, absY);
 
-            echo.setHTML("event.clientX: " + event.getClientX() + "<br>"
-                + "event.clientY: " + event.getClientY() + "<br>"
+            echo.setHTML("event.clientX: " + nativeEvent.getClientX() + "<br>"
+                + "event.clientY: " + nativeEvent.getClientY() + "<br>"
                 + "absolute left: " + positioner.getAbsoluteLeft() + "<br>"
                 + "absolute top: " + positioner.getAbsoluteTop());
             break;
         }
-        return true;
       }
     });
 
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForDialogBox.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForDialogBox.java
index 403d7be..40df95a 100644
--- a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForDialogBox.java
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/VisualsForDialogBox.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,13 +17,18 @@
 
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.MouseDownEvent;
 import com.google.gwt.event.dom.client.MouseDownHandler;
 import com.google.gwt.museum.client.common.AbstractIssue;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.DialogBox;
 import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwt.user.client.ui.Widget;
 
 import java.util.HashMap;
@@ -80,12 +85,14 @@
             maybeClose = true;
             return;
           }
+          break;
         case Event.ONMOUSEUP:
           if (maybeClose && isCloseBoxEvent(event)) {
             maybeClose = false;
             hide();
             return;
           }
+          break;
       }
       maybeClose = false;
       super.onBrowserEvent(event);
@@ -149,22 +156,46 @@
 
   @Override
   public Widget createIssue() {
+    // Create a few extra dialog boxes
+    final DialogBox dialog0 = showCustomDialog(true, true, false, "Dialog 0",
+        "I cannot be dragged or closed until Dialog 2 is closed", 0, 100);
+    final DialogBox dialog1 = showCustomDialog(true, false, false, "Dialog 1",
+        "I cannot be dragged or closed until Dialog 2 is closed", 0, 200);
+    final DialogBox dialog2 = showCustomDialog(false, false, true, "Dialog 2",
+        "I can be dragged", 0, 300);
+    final DialogBox dialog3 = showCustomDialog(
+        true,
+        false,
+        false,
+        "Dialog 3",
+        "I should auto close as soon as you click outside of me and Dialog 4 or greater",
+        0, 400);
+    final DialogBox dialog4 = showCustomDialog(false, false, false, "Dialog 4",
+        "I can be dragged", 0, 500);
+
     final VisibleDialogBox dialog = showVisibleDialog();
 
     SimplePanel panel = new SimplePanel() {
       @Override
       protected void onUnload() {
-        dialog.hide();
+        if (dialog.isAttached()) {
+          dialog.hide();
+          dialog0.hide();
+          dialog1.hide();
+          dialog2.hide();
+          dialog3.hide();
+          dialog4.hide();
+        }
       }
     };
-
     return panel;
   }
 
   @Override
   public String getInstructions() {
     return "Confirm color change on mouse over caption, that the "
-        + "custom close box works, and that each mouse event fires.";
+        + "custom close box works, and that each mouse event fires.  "
+        + "Verify that the text in each DialogBox is true.";
   }
 
   @Override
@@ -177,6 +208,37 @@
     return false;
   }
 
+  private DialogBox showCustomDialog(boolean autoHide,
+      boolean previewAllEvents, boolean modal, String caption, String message,
+      int left, int top) {
+    final DialogBox dialog = new DialogBox(autoHide, modal);
+    dialog.setPreviewingAllNativeEvents(previewAllEvents);
+
+    // Set the caption
+    caption += " (autoHide=" + dialog.isAutoHideEnabled();
+    caption += ", previewAllEvents=" + dialog.isPreviewingAllNativeEvents();
+    caption += ", modal=" + dialog.isModal() + ")";
+    dialog.setText(caption);
+
+    // Set the content
+    Label content = new Label(message);
+    if (autoHide || previewAllEvents) {
+      dialog.setWidget(content);
+    } else {
+      VerticalPanel vPanel = new VerticalPanel();
+      vPanel.add(content);
+      vPanel.add(new Button("Close", new ClickHandler() {
+        public void onClick(ClickEvent event) {
+          dialog.hide();
+        }
+      }));
+      dialog.setWidget(vPanel);
+    }
+    dialog.setPopupPosition(left, top);
+    dialog.show();
+    return dialog;
+  }
+
   private VisibleDialogBox showVisibleDialog() {
     final VisibleDialogBox dialog = new VisibleDialogBox();
     dialog.setModal(false);
diff --git a/user/src/com/google/gwt/event/dom/client/DomEvent.java b/user/src/com/google/gwt/event/dom/client/DomEvent.java
index d751bdf..7e2df65 100644
--- a/user/src/com/google/gwt/event/dom/client/DomEvent.java
+++ b/user/src/com/google/gwt/event/dom/client/DomEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -29,7 +29,8 @@
  * @param <H> handler type
  * 
  */
-public abstract class DomEvent<H extends EventHandler> extends GwtEvent<H> {
+public abstract class DomEvent<H extends EventHandler> extends GwtEvent<H>
+    implements HasNativeEvent {
   /**
    * Type class used by dom event subclasses. Type is specialized for dom in
    * order to carry information about the native event.
@@ -61,10 +62,10 @@
      * 
      * 
      * @param eventToSink the integer value used by sink events to set up event
-     * handling for this dom type
+     *          handling for this dom type
      * @param eventName the raw native event name
      * @param flyweight the instance that will be used as a flyweight to wrap a
-     * native event
+     *          native event
      */
     protected Type(int eventToSink, String eventName, DomEvent<H> flyweight) {
       this.flyweight = flyweight;
@@ -100,7 +101,7 @@
   }
 
   private static PrivateMap<Type<?>> registered;
- 
+
   /**
    * Fires the given native event on the specified handlers.
    * 
@@ -122,7 +123,7 @@
       }
     }
   }
- 
+
   // This method can go away once we have eager clinits.
   static void init() {
     registered = new PrivateMap<Type<?>>();
@@ -130,11 +131,6 @@
 
   private Event nativeEvent;
 
-  /**
-   * Gets the underlying native event for this {@link DomEvent}.
-   * 
-   * @return gets the native event
-   */
   public final Event getNativeEvent() {
     assertLive();
     return nativeEvent;
diff --git a/user/src/com/google/gwt/event/dom/client/HasNativeEvent.java b/user/src/com/google/gwt/event/dom/client/HasNativeEvent.java
new file mode 100644
index 0000000..510c273
--- /dev/null
+++ b/user/src/com/google/gwt/event/dom/client/HasNativeEvent.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.event.dom.client;
+
+import com.google.gwt.user.client.Event;
+
+/**
+ * An object that implements this interface has a native event associated with
+ * it.
+ */
+public interface HasNativeEvent {
+  /**
+   * Gets the underlying native event.
+   * 
+   * @return the native event
+   */
+  Event getNativeEvent();
+}
diff --git a/user/src/com/google/gwt/event/shared/GwtEvent.java b/user/src/com/google/gwt/event/shared/GwtEvent.java
index 8eaba34..33787e2 100644
--- a/user/src/com/google/gwt/event/shared/GwtEvent.java
+++ b/user/src/com/google/gwt/event/shared/GwtEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -137,18 +137,19 @@
   }
 
   /**
-   * Revives the event. Used when recycling event instances.
+   * Kill the event.  After the event has been killed, users cannot really on
+   * its values or functions being available.
    */
-  protected void revive() {
-    dead = false;
+  protected void kill() {
+    dead = true;
     source = null;
   }
 
   /**
-   * Called after the event manager has finished processing the event.
+   * Revives the event. Used when recycling event instances.
    */
-  void onRelease() {
-    dead = true;
+  protected void revive() {
+    dead = false;
     source = null;
   }
 
diff --git a/user/src/com/google/gwt/event/shared/HandlerManager.java b/user/src/com/google/gwt/event/shared/HandlerManager.java
index 898ad91..ca4138e 100644
--- a/user/src/com/google/gwt/event/shared/HandlerManager.java
+++ b/user/src/com/google/gwt/event/shared/HandlerManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -378,7 +378,7 @@
     }
     if (oldSource == null) {
       // This was my event, so I should kill it now that I'm done.
-      event.onRelease();
+      event.kill();
     } else {
       // Restoring the source for the next handler to use.
       event.setSource(oldSource);
diff --git a/user/src/com/google/gwt/user/client/DOM.java b/user/src/com/google/gwt/user/client/DOM.java
index b7865d5..8ddd25b 100644
--- a/user/src/com/google/gwt/user/client/DOM.java
+++ b/user/src/com/google/gwt/user/client/DOM.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -39,6 +39,7 @@
   private static Element sCaptureElem;
 
   // <BrowserEventPreview>
+  @SuppressWarnings("deprecation")
   private static ArrayList<EventPreview> sEventPreviewStack;
 
   /**
@@ -49,7 +50,10 @@
    * handlers only receive explicitly sunk events.
    * 
    * @param preview the event preview to be added to the stack.
+   * @deprecated replaced by
+   *             {@link Event#addNativePreviewHandler(Event.NativePreviewHandler)}
    */
+  @Deprecated
   public static void addEventPreview(EventPreview preview) {
     impl.maybeInitializeEventSystem();
 
@@ -90,6 +94,7 @@
    * @return <code>true</code> if they are in fact the same element
    * @deprecated Use identity comparison.
    */
+  @Deprecated
   public static boolean compare(Element elem1, Element elem2) {
     return elem1 == elem2;
   }
@@ -376,8 +381,7 @@
   }
 
   /**
-   * Generates a unique DOM id. The id is of the form "gwt-id-<unique
-   * integer>".
+   * Generates a unique DOM id. The id is of the form "gwt-id-<unique integer>".
    * 
    * @return a unique DOM id
    */
@@ -934,8 +938,8 @@
    *          <code>&lt;option&gt;</code>; cannot be <code>null</code>
    * @param index the index at which to insert the child
    */
-  public static void insertListItem(Element selectElem, String item, String value,
-      int index) {
+  public static void insertListItem(Element selectElem, String item,
+      String value, int index) {
     SelectElement select = selectElem.<SelectElement> cast();
     OptionElement option = Document.get().createOptionElement();
     option.setText(item);
@@ -999,7 +1003,11 @@
    * capture events, though any preview underneath it will begin to do so.
    * 
    * @param preview the event preview to be removed from the stack
+   * @deprecated use {@link com.google.gwt.event.shared.HandlerRegistration}
+   *             returned from
+   *             {@link Event#addNativePreviewHandler(Event.NativePreviewHandler)}
    */
+  @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
@@ -1265,17 +1273,22 @@
    * @param evt a handle to the event being previewed
    * @return <code>false</code> to cancel the event
    */
+  @SuppressWarnings("deprecation")
   static boolean previewEvent(Event evt) {
+    // Fire a NativePreviewEvent to NativePreviewHandlers
+    boolean ret = Event.fireNativePreviewEvent(evt);
+
     // If event previews are present, redirect events to the topmost of them.
-    boolean ret = true;
     if (sEventPreviewStack != null && sEventPreviewStack.size() > 0) {
       EventPreview preview = sEventPreviewStack.get(sEventPreviewStack.size() - 1);
-      if (!(ret = preview.onEventPreview(evt))) {
-        // If the preview cancels the event, stop it from bubbling and
-        // performing its default action.
-        eventCancelBubble(evt, true);
-        eventPreventDefault(evt);
-      }
+      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) {
+      eventCancelBubble(evt, true);
+      eventPreventDefault(evt);
     }
 
     return ret;
diff --git a/user/src/com/google/gwt/user/client/Event.java b/user/src/com/google/gwt/user/client/Event.java
index f4dccd6..4ec22fe 100644
--- a/user/src/com/google/gwt/user/client/Event.java
+++ b/user/src/com/google/gwt/user/client/Event.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -17,6 +17,13 @@
 
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.HasNativeEvent;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * <p>
@@ -42,6 +49,166 @@
  * </p>
  */
 public class Event extends JavaScriptObject {
+  /**
+   * Represents a preview of a native {@link Event}.
+   */
+  public static class NativePreviewEvent extends GwtEvent<NativePreviewHandler>
+      implements HasNativeEvent {
+
+    /**
+     * Handler type.
+     */
+    private static Type<NativePreviewHandler> TYPE;
+
+    /**
+     * The singleton instance of {@link NativePreviewEvent}.
+     */
+    private static NativePreviewEvent singleton;
+
+    /**
+     * Gets the type associated with this event.
+     * 
+     * @return returns the handler type
+     */
+    public static Type<NativePreviewHandler> getType() {
+      if (TYPE == null) {
+        TYPE = new Type<NativePreviewHandler>();
+      }
+      return TYPE;
+    }
+
+    /**
+     * Fire a {@link NativePreviewEvent} for the native event.
+     * 
+     * @param handlers the list of {@link NativePreviewHandler}
+     * @param nativeEvent the native event
+     * @return true to fire the event normally, false to cancel the event
+     */
+    static boolean fire(List<NativePreviewHandler> handlers, Event nativeEvent) {
+      if (TYPE != null && handlers != null) {
+        // Revive the event
+        if (singleton == null) {
+          singleton = new NativePreviewEvent();
+        } else {
+          singleton.revive();
+        }
+        singleton.setNativeEvent(nativeEvent);
+
+        // Fire the event to all handlers
+        int numHandlers = handlers.size();
+        for (int i = numHandlers - 1; i >= 0; i--) {
+          handlers.get(i).onPreviewNativeEvent(singleton);
+        }
+
+        // Kill the event and return
+        singleton.kill();
+        return !(singleton.isCanceled() && !singleton.isConsumed());
+      }
+      return true;
+    }
+
+    /**
+     * A boolean indicating that the native event should be canceled.
+     */
+    private boolean isCanceled = false;
+
+    /**
+     * A boolean indicating whether or not canceling the native event should be
+     * prevented. This supercedes {@link #isCanceled}.
+     */
+    private boolean isConsumed = false;
+
+    /**
+     * The event being previewed.
+     */
+    private Event nativeEvent;
+
+    /**
+     * Cancel the native event and prevent it from firing. Note that the event
+     * can still fire if another handler calls {@link #consume()}.
+     * 
+     * Classes overriding this method should still call super.cancel().
+     */
+    public void cancel() {
+      isCanceled = true;
+    }
+
+    /**
+     * Consume the native event and prevent it from being canceled, even if it
+     * has already been canceled by another handler.
+     * {@link NativePreviewHandler} that fire first have priority over later
+     * handlers, so all handlers should check if the event has already been
+     * canceled before calling this method.
+     */
+    public void consume() {
+      isConsumed = true;
+    }
+
+    public Event getNativeEvent() {
+      return nativeEvent;
+    }
+
+    /**
+     * Has the event already been canceled? Note that {@link #isConsumed()} will
+     * still return true if the native event has also been consumed.
+     * 
+     * @return true if the event has been canceled
+     * @see #cancel()
+     */
+    public boolean isCanceled() {
+      return isCanceled;
+    }
+
+    /**
+     * Has the native event been consumed? Note that {@link #isCanceled()} will
+     * still return true if the native event has also been canceled.
+     * 
+     * @return true if the event has been consumed
+     * @see #consume()
+     */
+    public boolean isConsumed() {
+      return isConsumed;
+    }
+
+    @Override
+    protected void dispatch(NativePreviewHandler handler) {
+      handler.onPreviewNativeEvent(this);
+    }
+
+    @Override
+    protected Type<NativePreviewHandler> getAssociatedType() {
+      return TYPE;
+    }
+
+    @Override
+    protected void revive() {
+      super.revive();
+      isCanceled = false;
+      isConsumed = false;
+      nativeEvent = null;
+    }
+
+    /**
+     * Set the native event.
+     * 
+     * @param nativeEvent the native {@link Event} being previewed.
+     */
+    private void setNativeEvent(Event nativeEvent) {
+      this.nativeEvent = nativeEvent;
+    }
+  }
+
+  /**
+   * Handler interface for {@link NativePreviewEvent} events.
+   */
+  public static interface NativePreviewHandler extends EventHandler {
+    /**
+     * Called when {@link NativePreviewEvent} is fired.
+     * 
+     * @param event the {@link NativePreviewEvent} that was fired
+     */
+    void onPreviewNativeEvent(NativePreviewEvent event);
+  }
 
   /**
    * The left mouse button (used in {@link DOM#eventGetButton(Event)}).
@@ -184,6 +351,14 @@
   public static final int UNDEFINED = 0;
 
   /**
+   * 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.
+   */
+  private static ArrayList<NativePreviewHandler> handlers;
+
+  /**
    * 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
    * to their listeners. Note that the event preview will receive <u>all </u>
@@ -191,12 +366,54 @@
    * handlers only receive explicitly sunk events.
    * 
    * @param preview the event preview to be added to the stack.
+   * @deprecated replaced by
+   *             {@link #addNativePreviewHandler(NativePreviewHandler)}
    */
+  @Deprecated
   public static void addEventPreview(EventPreview preview) {
     DOM.addEventPreview(preview);
   }
 
   /**
+   * <p>
+   * Adds a {@link NativePreviewHandler} that will receive all events before
+   * they are fired to their handlers. Note that the handler will receive
+   * <u>all</u> native events, including those received due to bubbling, whereas
+   * normal event handlers only receive explicitly sunk events.
+   * </p>
+   * 
+   * <p>
+   * Unlike other event handlers, {@link NativePreviewHandler} are fired in the
+   * reverse order that they are added, such that the last
+   * {@link NativePreviewEvent} that was added is the first to be fired.
+   * </p>
+   * 
+   * @param handler the {@link NativePreviewHandler}
+   * @return {@link HandlerRegistration} used to remove this handler
+   */
+  public static HandlerRegistration addNativePreviewHandler(
+      final NativePreviewHandler handler) {
+    assert handler != null : "Cannot add a null handler";
+    // Initialize the type
+    NativePreviewEvent.getType();
+    if (handlers == null) {
+      handlers = new ArrayList<NativePreviewHandler>();
+    }
+    handlers.add(handler);    
+    return new HandlerRegistration() {
+      public void removeHandler() {
+        if (handlers != null) {
+          handlers.remove(handler);
+          if (handlers.size() == 0) {
+            // Set handlers to null so we can optimize fireNativePreviewEvent
+            handlers = null;
+          }
+        }
+      }
+    };
+  }
+
+  /**
    * Gets the current event that is being fired. The current event is only
    * available within the lifetime of the onBrowserEvent function. Once the
    * onBrowserEvent method returns, the current event is reset to null.
@@ -234,7 +451,10 @@
    * capture events, though any preview underneath it will begin to do so.
    * 
    * @param preview the event preview to be removed from the stack
+   * @deprecated use {@link HandlerRegistration} returned from
+   *             {@link Event#addNativePreviewHandler(NativePreviewHandler)}
    */
+  @Deprecated
   public static void removeEventPreview(EventPreview preview) {
     DOM.removeEventPreview(preview);
   }
@@ -263,6 +483,16 @@
   }
 
   /**
+   * Fire a {@link NativePreviewEvent} for the native event.
+   * 
+   * @param nativeEvent the native event
+   * @return true to fire the event normally, false to cancel the event
+   */
+  static boolean fireNativePreviewEvent(Event nativeEvent) {
+    return NativePreviewEvent.fire(handlers, nativeEvent);
+  }
+
+  /**
    * Not directly instantiable. Subclasses should also define a protected no-arg
    * constructor to prevent client code from directly instantiating the class.
    */
@@ -355,7 +585,7 @@
    * </p>
    * 
    * @return the Unicode character or key code.
-   * @see com.google.gwt.user.client.ui.KeyboardListener
+   * @see com.google.gwt.event.dom.client.KeyCodes
    */
   public final int getKeyCode() {
     return DOM.eventGetKeyCode(this);
diff --git a/user/src/com/google/gwt/user/client/EventPreview.java b/user/src/com/google/gwt/user/client/EventPreview.java
index 3b2503d..a419256 100644
--- a/user/src/com/google/gwt/user/client/EventPreview.java
+++ b/user/src/com/google/gwt/user/client/EventPreview.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -19,7 +19,10 @@
  * A listener interface for previewing browser events.
  * 
  * @see com.google.gwt.user.client.DOM#addEventPreview(EventPreview)
+ * @deprecated replaced by
+ *             {@link com.google.gwt.user.client.Event.NativePreviewHandler}
  */
+@Deprecated
 public interface EventPreview {
 
   /**
@@ -29,6 +32,9 @@
    * @param event the browser event
    * @return <code>false</code> to cancel the event
    * @see DOM#addEventPreview(EventPreview)
+   * @deprecated replaced by
+   *             {@link com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)}
    */
+  @Deprecated
   boolean onEventPreview(Event event);
 }
diff --git a/user/src/com/google/gwt/user/client/ui/DialogBox.java b/user/src/com/google/gwt/user/client/ui/DialogBox.java
index f4b328e..e1d3e34 100644
--- a/user/src/com/google/gwt/user/client/ui/DialogBox.java
+++ b/user/src/com/google/gwt/user/client/ui/DialogBox.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -34,6 +34,7 @@
 import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
 
 /**
  * A form of popup that has a caption area at the top and can be dragged by the
@@ -229,18 +230,6 @@
     super.onBrowserEvent(event);
   }
 
-  @Override
-  public boolean onEventPreview(Event event) {
-    // We need to preventDefault() on mouseDown events (outside of the
-    // DialogBox content) to keep text from being selected when it
-    // is dragged.
-    if (DOM.eventGetType(event) == Event.ONMOUSEDOWN && isCaptionEvent(event)) {
-      DOM.eventPreventDefault(event);
-    }
-
-    return super.onEventPreview(event);
-  }
-
   /**
    * @deprecated Use {@link #beginDragging} instead and {@link #getCaption}
    * instead
@@ -401,6 +390,20 @@
     ensureDebugId(getCellElement(1, 1), baseID, "content");
   }
 
+  @Override
+  protected void onPreviewNativeEvent(NativePreviewEvent event) {
+    // We need to preventDefault() on mouseDown events (outside of the
+    // DialogBox content) to keep text from being selected when it
+    // is dragged.
+    Event nativeEvent = event.getNativeEvent();
+    if (!event.isCanceled() && nativeEvent.getTypeInt() == Event.ONMOUSEDOWN
+        && isCaptionEvent(nativeEvent)) {
+      nativeEvent.preventDefault();
+    }
+
+    super.onPreviewNativeEvent(event);
+  }
+
   private boolean isCaptionEvent(Event event) {
     return getCellElement(0, 1).getParentElement().isOrHasChild(
         event.getTarget());
diff --git a/user/src/com/google/gwt/user/client/ui/MenuBar.java b/user/src/com/google/gwt/user/client/ui/MenuBar.java
index 2cc23cf..a8b00b6 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuBar.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -27,6 +27,7 @@
 import com.google.gwt.user.client.DeferredCommand;
 import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
 import com.google.gwt.user.client.ui.PopupPanel.AnimationType;
 
 import java.util.ArrayList;
@@ -958,29 +959,34 @@
     popup = new DecoratedPopupPanel(true, false, "menuPopup") {
       {
         setWidget(item.getSubMenu());
+        setPreviewingAllNativeEvents(true);
         item.getSubMenu().onShow();
       }
 
       @Override
-      public boolean onEventPreview(Event event) {
+      protected void onPreviewNativeEvent(NativePreviewEvent event) {
         // Hook the popup panel's event preview. We use this to keep it from
         // auto-hiding when the parent menu is clicked.
-        switch (DOM.eventGetType(event)) {
-          case Event.ONMOUSEDOWN:
-            // If the event target is part of the parent menu, suppress the
-            // event altogether.
-            Element target = DOM.eventGetTarget(event);
-            Element parentMenuElement = item.getParentMenu().getElement();
-            if (DOM.isOrHasChild(parentMenuElement, target)) {
-              return false;
-            }
-            boolean cancel = super.onEventPreview(event);
-            if (cancel) {
-              selectItem(null);
-            }
-            return cancel;
+        if (!event.isCanceled()) {
+          Event nativeEvent = event.getNativeEvent();
+          switch (nativeEvent.getTypeInt()) {
+            case Event.ONMOUSEDOWN:
+              // If the event target is part of the parent menu, suppress the
+              // event altogether.
+              com.google.gwt.dom.client.Element target = nativeEvent.getTarget();
+              Element parentMenuElement = item.getParentMenu().getElement();
+              if (parentMenuElement.isOrHasChild(target)) {
+                event.cancel();
+                return;
+              }
+              super.onPreviewNativeEvent(event);
+              if (event.isCanceled()) {
+                selectItem(null);
+              }
+              return;
+          }
         }
-        return super.onEventPreview(event);
+        super.onPreviewNativeEvent(event);
       }
     };
     popup.setAnimationType(AnimationType.ONE_WAY_CORNER);
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 6064c12..105b3be 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -18,6 +18,7 @@
 import com.google.gwt.animation.client.Animation;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.event.logical.shared.HasCloseHandlers;
@@ -26,10 +27,11 @@
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.DeferredCommand;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.EventPreview;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
 import com.google.gwt.user.client.ui.impl.PopupImpl;
 
 /**
@@ -55,10 +57,12 @@
  * {@example com.google.gwt.examples.PopupPanelExample}
  * </p>
  * <h3>CSS Style Rules</h3>
- * <ul class='css'>
- * <li>.gwt-PopupPanel { the outside of the popup }</li>
- * <li>.gwt-PopupPanel .popupContent { the wrapper around the content }</li>
- * </ul>
+ * <dl>
+ * <dt>.gwt-PopupPanel</dt>
+ * <dd>the outside of the popup</dd>
+ * <dt>.gwt-PopupPanel .popupContent</dt>
+ * <dd>the wrapper around the content</dd>
+ * </dl>
  */
 @SuppressWarnings("deprecation")
 public class PopupPanel extends SimplePanel implements SourcesPopupEvents,
@@ -265,7 +269,9 @@
    */
   private AnimationType animType = AnimationType.CENTER;
 
-  private boolean autoHide, modal, showing;
+  private HandlerRegistration nativePreviewHandlerRegistration;
+
+  private boolean autoHide, previewAllNativeEvents, modal, showing;
 
   // Used to track requested size across changing child widgets
   private String desiredHeight;
@@ -273,7 +279,7 @@
   private String desiredWidth;
 
   private boolean isAnimationEnabled = false;
-  
+
   private Element autoHidePartner;
 
   /**
@@ -357,7 +363,7 @@
 
     if (!initiallyShowing) {
       setAnimationEnabled(initiallyAnimated);
-      // Run the animation.  The popup is already visible, so we can skip the
+      // Run the animation. The popup is already visible, so we can skip the
       // call to setState.
       if (initiallyAnimated) {
         impl.setClip(getElement(), "rect(0px, 0px, 0px, 0px)");
@@ -433,6 +439,10 @@
       return;
     }
     showing = false;
+    if (nativePreviewHandlerRegistration != null) {
+      nativePreviewHandlerRegistration.removeHandler();
+      nativePreviewHandlerRegistration = null;
+    }
 
     // Hide the popup
     resizeAnimation.setState(false);
@@ -463,74 +473,22 @@
     return modal;
   }
 
-  @SuppressWarnings("deprecation")
+  /**
+   * Returns <code>true</code> if the popup should preview all native events,
+   * even if the event has already been consumed by another popup.
+   * 
+   * @return true if previewAllNativeEvents is enabled, false if disabled
+   */
+  public boolean isPreviewingAllNativeEvents() {
+    return previewAllNativeEvents;
+  }
+
+  /**
+   * @deprecated use {@link #onPreviewNativeEvent(NativePreviewEvent)} instead
+   */
+  @Deprecated
   public boolean onEventPreview(Event event) {
-    Element target = DOM.eventGetTarget(event);
-
-    boolean eventTargetsPopup = (target != null)
-        && DOM.isOrHasChild(getElement(), target);
-
-    int type = DOM.eventGetType(event);
-    switch (type) {
-      case Event.ONKEYDOWN: {
-        boolean allow = onKeyDownPreview((char) DOM.eventGetKeyCode(event),
-            KeyboardListenerCollection.getKeyboardModifiers(event));
-        return allow && (eventTargetsPopup || !modal);
-      }
-      case Event.ONKEYUP: {
-        boolean allow = onKeyUpPreview((char) DOM.eventGetKeyCode(event),
-            KeyboardListenerCollection.getKeyboardModifiers(event));
-        return allow && (eventTargetsPopup || !modal);
-      }
-      case Event.ONKEYPRESS: {
-        boolean allow = onKeyPressPreview((char) DOM.eventGetKeyCode(event),
-            KeyboardListenerCollection.getKeyboardModifiers(event));
-        return allow && (eventTargetsPopup || !modal);
-      }
-
-      case Event.ONMOUSEDOWN:
-        // Don't eat events if event capture is enabled, as this can interfere
-        // with dialog dragging, for example.
-        if (DOM.getCaptureElement() != null) {
-          return true;
-        }
-
-        if (!eventTargetsPopup && shouldAutoHide(event)) {
-          hide(true);
-          return true;
-        }
-        break;
-      case Event.ONMOUSEUP:
-      case Event.ONMOUSEMOVE:
-      case Event.ONCLICK:
-      case Event.ONDBLCLICK: {
-        // Don't eat events if event capture is enabled, as this can interfere
-        // with dialog dragging, for example.
-        if (DOM.getCaptureElement() != null) {
-          return true;
-        }
-
-        // If it's an outside click and auto-hide is enabled:
-        // hide the popup and _don't_ eat the event. ONMOUSEDOWN is used to
-        // prevent problems with showing a popup in response to a mousedown.
-        if (!eventTargetsPopup && shouldAutoHide(event)
-            && (type == Event.ONMOUSEDOWN)) {
-          hide(true);
-          return true;
-        }
-
-        break;
-      }
-
-      case Event.ONFOCUS: {
-        if (modal && !eventTargetsPopup && (target != null)) {
-          blur(target);
-          return false;
-        }
-      }
-    }
-
-    return !modal || eventTargetsPopup;
+    return true;
   }
 
   /**
@@ -541,7 +499,9 @@
    * @param modifiers keyboard modifiers, as specified in
    *          {@link com.google.gwt.event.dom.client.KeyCodes}.
    * @return <code>false</code> to suppress the event
+   * @deprecated use {@link #onPreviewNativeEvent(NativePreviewEvent)} instead
    */
+  @Deprecated
   public boolean onKeyDownPreview(char key, int modifiers) {
     return true;
   }
@@ -554,7 +514,9 @@
    * @param modifiers keyboard modifiers, as specified in
    *          {@link com.google.gwt.event.dom.client.KeyCodes}.
    * @return <code>false</code> to suppress the event
+   * @deprecated use {@link #onPreviewNativeEvent(NativePreviewEvent)} instead
    */
+  @Deprecated
   public boolean onKeyPressPreview(char key, int modifiers) {
     return true;
   }
@@ -567,7 +529,9 @@
    * @param modifiers keyboard modifiers, as specified in
    *          {@link com.google.gwt.event.dom.client.KeyCodes}.
    * @return <code>false</code> to suppress the event
+   * @deprecated use {@link #onPreviewNativeEvent(NativePreviewEvent)} instead
    */
+  @Deprecated
   public boolean onKeyUpPreview(char key, int modifiers) {
     return true;
   }
@@ -592,8 +556,9 @@
   }
 
   /**
-   * If the auto hide partner is non null, its mouse events will 
-   * not hide a panel set to autohide.
+   * If the auto hide partner is non null, its mouse events will not hide a
+   * panel set to autohide.
+   * 
    * @param element new auto hide partner
    */
   public void setAutoHidePartner(Element element) {
@@ -633,7 +598,7 @@
   public void setModal(boolean modal) {
     this.modal = modal;
   }
-  
+
   /**
    * Sets the popup's position relative to the browser's client area. The
    * popup's position may be set before calling {@link #show()}.
@@ -655,8 +620,8 @@
     // called before show() is called (so a popup can be positioned without it
     // 'jumping' on the screen).
     Element elem = getElement();
-    DOM.setStyleAttribute(elem, "left", left + "px");
-    DOM.setStyleAttribute(elem, "top", top + "px");
+    elem.getStyle().setPropertyPx("left", left);
+    elem.getStyle().setPropertyPx("top", top);
   }
 
   /**
@@ -676,13 +641,31 @@
     setVisible(true);
   }
 
+  /**
+   * <p>
+   * When enabled, the popup will preview all native events, even if another
+   * popup was opened after this one.
+   * </p>
+   * <p>
+   * If autoHide is enabled, enabling this feature will cause the popup to
+   * autoHide even if another non-modal popup was shown after it. If this
+   * feature is disabled, the popup will only autoHide if it was the last popup
+   * opened.
+   * </p>
+   * 
+   * @param previewAllNativeEvents true to enable, false to disable
+   */
+  public void setPreviewingAllNativeEvents(boolean previewAllNativeEvents) {
+    this.previewAllNativeEvents = previewAllNativeEvents;
+  }
+
   @Override
   public void setTitle(String title) {
     Element containerElement = getContainerElement();
     if (title == null || title.length() == 0) {
-      DOM.removeElementAttribute(containerElement, "title");
+      containerElement.removeAttribute("title");
     } else {
-      DOM.setElementAttribute(containerElement, "title", title);
+      containerElement.setAttribute("title", title);
     }
   }
 
@@ -744,7 +727,11 @@
       return;
     }
     showing = true;
-    DOM.addEventPreview(this);
+    nativePreviewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        previewNativeEvent(event);
+      }
+    });
     resizeAnimation.setState(true);
   }
 
@@ -768,24 +755,24 @@
   }
 
   @Override
-  protected Element getContainerElement() {
+  protected com.google.gwt.user.client.Element getContainerElement() {
     return impl.getContainerElement(getPopupImplElement());
   }
 
   @Override
-  protected Element getStyleElement() {
+  protected com.google.gwt.user.client.Element getStyleElement() {
     return impl.getStyleElement(getPopupImplElement());
   }
 
   /**
-   * This method is called when a widget is detached from the browser's
-   * document. To receive notification before the PopupPanel is removed from the
-   * document, override the {@link Widget#onUnload()} method instead.
+   * @see NativePreviewHandler#onPreviewNativeEvent(NativePreviewEvent)
    */
-  @Override
-  protected void onDetach() {
-    DOM.removeEventPreview(this);
-    super.onDetach();
+  @SuppressWarnings("deprecation")
+  protected void onPreviewNativeEvent(NativePreviewEvent event) {
+    // Cancel the event based on the deprecated onEventPreview() method
+    if (!event.isCanceled() && !onEventPreview(event.getNativeEvent())) {
+      event.cancel();
+    }
   }
 
   /**
@@ -846,9 +833,25 @@
     }
   }-*/;
 
-  private boolean eventInPartner(Event event) {
+  /**
+   * Does the event target the partner element?
+   * 
+   * @param event the native event
+   * @return true if the event targets the partner
+   */
+  private boolean eventTargetsPartner(Event event) {
     return autoHidePartner != null
-      && autoHidePartner.isOrHasChild(event.getTarget());
+        && autoHidePartner.isOrHasChild(event.getTarget());
+  }
+
+  /**
+   * Does the event target this popup?
+   * 
+   * @param event the native event
+   * @return true if the event targets the popup
+   */
+  private boolean eventTargetsPopup(Event event) {
+    return getElement().isOrHasChild(event.getTarget());
   }
 
   /**
@@ -859,7 +862,7 @@
    * 
    * @return the Element that {@link PopupImpl} creates and expects
    */
-  private Element getPopupImplElement() {
+  private com.google.gwt.user.client.Element getPopupImplElement() {
     return DOM.getFirstChild(super.getContainerElement());
   }
 
@@ -991,10 +994,103 @@
     }
     setPopupPosition(left, top);
   }
-  
-  private boolean shouldAutoHide(Event event) {
-    boolean shouldAutoHide = autoHide && !eventInPartner(event);
-    return shouldAutoHide;
-  }
 
+  /**
+   * Preview the {@link NativePreviewEvent}.
+   * 
+   * @param event the {@link NativePreviewEvent}
+   */
+  private void previewNativeEvent(NativePreviewEvent event) {
+    // If the event has been canceled or consumed, ignore it
+    if (event.isCanceled() || (!previewAllNativeEvents && event.isConsumed())) {
+      // We need to ensure that we cancel the event even if its been consumed so
+      // that popups lower on the stack do not auto hide
+      if (modal) {
+        event.cancel();
+      }
+      return;
+    }
+
+    // Fire the event hook and return if the event is canceled
+    onPreviewNativeEvent(event);
+    if (event.isCanceled()) {
+      return;
+    }
+
+    // If the event targets the popup or the partner, consume it
+    Event nativeEvent = event.getNativeEvent();
+    boolean eventTargetsPopupOrPartner = eventTargetsPopup(nativeEvent)
+        || eventTargetsPartner(nativeEvent);
+    if (eventTargetsPopupOrPartner) {
+      event.consume();
+    }
+
+    // Cancel the event if it doesn't target the modal popup. Note that the
+    // event can be both canceled and consumed.
+    if (modal) {
+      event.cancel();
+    }
+
+    // Switch on the event type
+    int type = nativeEvent.getTypeInt();
+    switch (type) {
+      case Event.ONKEYDOWN: {
+        if (!onKeyDownPreview((char) nativeEvent.getKeyCode(),
+            KeyboardListenerCollection.getKeyboardModifiers(nativeEvent))) {
+          event.cancel();
+        }
+        return;
+      }
+      case Event.ONKEYUP: {
+        if (!onKeyUpPreview((char) nativeEvent.getKeyCode(),
+            KeyboardListenerCollection.getKeyboardModifiers(nativeEvent))) {
+          event.cancel();
+        }
+        return;
+      }
+      case Event.ONKEYPRESS: {
+        if (!onKeyPressPreview((char) nativeEvent.getKeyCode(),
+            KeyboardListenerCollection.getKeyboardModifiers(nativeEvent))) {
+          event.cancel();
+        }
+        return;
+      }
+
+      case Event.ONMOUSEDOWN:
+        // Don't eat events if event capture is enabled, as this can
+        // interfere with dialog dragging, for example.
+        if (DOM.getCaptureElement() != null) {
+          event.consume();
+          return;
+        }
+
+        if (!eventTargetsPopupOrPartner && autoHide) {
+          hide(true);
+          return;
+        }
+        break;
+      case Event.ONMOUSEUP:
+      case Event.ONMOUSEMOVE:
+      case Event.ONCLICK:
+      case Event.ONDBLCLICK: {
+        // Don't eat events if event capture is enabled, as this can
+        // interfere with dialog dragging, for example.
+        if (DOM.getCaptureElement() != null) {
+          event.consume();
+          return;
+        }
+        break;
+      }
+
+      case Event.ONFOCUS: {
+        Element target = nativeEvent.getTarget();
+        if (modal && !eventTargetsPopupOrPartner && (target != null)) {
+          blur(target);
+          event.cancel();
+          return;
+        }
+        break;
+      }
+    }
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/SuggestBox.java b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
index 8fd59b2..eacf785 100644
--- a/user/src/com/google/gwt/user/client/ui/SuggestBox.java
+++ b/user/src/com/google/gwt/user/client/ui/SuggestBox.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -240,6 +240,7 @@
       super(true, false, "suggestPopup");
       setWidget(suggestionMenu);
       setStyleName(STYLENAME_DEFAULT);
+      setPreviewingAllNativeEvents(true);
     }
 
     /**
@@ -475,6 +476,7 @@
    * 
    * @deprecated use addSelectionHandler instead.
    */
+  @Deprecated
   public void addEventHandler(final SuggestionHandler handler) {
     ListenerWrapper.Suggestion.add(this, handler);
   }
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 56d1d5b..b9c3d79 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -18,6 +18,7 @@
 import com.google.gwt.junit.tools.GWTTestSuite;
 import com.google.gwt.user.client.CommandExecutorTest;
 import com.google.gwt.user.client.CookieTest;
+import com.google.gwt.user.client.EventTest;
 import com.google.gwt.user.client.WindowTest;
 import com.google.gwt.user.client.ui.AbsolutePanelTest;
 import com.google.gwt.user.client.ui.AnchorTest;
@@ -112,6 +113,7 @@
     suite.addTestSuite(DockPanelTest.class);
     suite.addTestSuite(DOMTest.class);
     suite.addTestSuite(ElementWrappingTest.class);
+    suite.addTestSuite(EventTest.class);
     suite.addTestSuite(FastStringMapTest.class);
     suite.addTestSuite(FlexTableTest.class);
     suite.addTestSuite(FlowPanelTest.class);
diff --git a/user/test/com/google/gwt/user/client/EventTest.java b/user/test/com/google/gwt/user/client/EventTest.java
new file mode 100644
index 0000000..a894e85
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/EventTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+
+/**
+ * Test Case for {@link Event}.
+ */
+public class EventTest extends GWTTestCase {
+  /**
+   * An EventPreview used for testing.
+   */
+  @SuppressWarnings("deprecation")
+  private static class TestEventPreview implements EventPreview {
+    private boolean doCancel;
+    private boolean isFired = false;
+
+    /**
+     * Construct a new {@link TestEventPreview}.
+     * 
+     * @param doCancel if true, cancel the event
+     */
+    public TestEventPreview(boolean doCancel) {
+      this.doCancel = doCancel;
+    }
+
+    @Deprecated
+    public boolean onEventPreview(Event event) {
+      assertFalse(isFired);
+      isFired = true;
+      return !doCancel;
+    }
+
+    public void assertIsFired(boolean expected) {
+      assertEquals(expected, isFired);
+    }
+  }
+
+  /**
+   * A NativePreviewHandler used for testing.
+   */
+  private static class TestNativePreviewHandler implements NativePreviewHandler {
+    private boolean doCancel;
+    private boolean doPreventCancel;
+    private boolean isFired = false;
+
+    /**
+     * Construct a new {@link TestNativePreviewHandler}.
+     * 
+     * @param doCancel if true, cancel the event
+     * @param doPreventCancel if true, prevent the event from being canceled
+     */
+    public TestNativePreviewHandler(boolean doCancel, boolean doPreventCancel) {
+      this.doCancel = doCancel;
+      this.doPreventCancel = doPreventCancel;
+    }
+
+    public void onPreviewNativeEvent(NativePreviewEvent event) {
+      assertFalse(isFired);
+      isFired = true;
+      if (doCancel) {
+        event.cancel();
+      }
+      if (doPreventCancel) {
+        event.consume();
+      }
+    }
+
+    public void assertIsFired(boolean expected) {
+      assertEquals(expected, isFired);
+    }
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+
+  /**
+   * Test that {@link Event#fireNativePreviewEvent(Event)} returns the correct
+   * value if the native event is canceled.
+   */
+  public void testFireNativePreviewEventCancel() {
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(true,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(true,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+    assertFalse(Event.fireNativePreviewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+  }
+
+  /**
+   * Test that {@link Event#fireNativePreviewEvent(Event)} returns the correct
+   * value if the native event is prevented from being canceled, even if another
+   * handler cancels the event.
+   */
+  public void testFireNativePreviewEventPreventCancel() {
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        true);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(true,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+    assertTrue(Event.fireNativePreviewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+  }
+
+  /**
+   * Test that {@link Event#fireNativePreviewEvent(Event)} fires handlers in
+   * reverse order. Also verify that the legacy EventPreview fires last.
+   */
+  @SuppressWarnings("deprecation")
+  public void testFireNativePreviewEventReverseOrder() {
+    final TestEventPreview preview = new TestEventPreview(false);
+    final TestNativePreviewHandler handler0 = new TestNativePreviewHandler(
+        false, false) {
+      @Override
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        super.onPreviewNativeEvent(event);
+        preview.assertIsFired(false);
+      }
+    };
+    final TestNativePreviewHandler handler1 = new TestNativePreviewHandler(
+        false, false) {
+      @Override
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        super.onPreviewNativeEvent(event);
+        handler0.assertIsFired(false);
+        preview.assertIsFired(false);
+      }
+    };
+    final TestNativePreviewHandler handler2 = new TestNativePreviewHandler(
+        false, false) {
+      @Override
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        super.onPreviewNativeEvent(event);
+        handler0.assertIsFired(false);
+        handler1.assertIsFired(false);
+        preview.assertIsFired(false);
+      }
+    };
+    final TestNativePreviewHandler handler3 = new TestNativePreviewHandler(
+        false, false) {
+      @Override
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        super.onPreviewNativeEvent(event);
+        handler0.assertIsFired(false);
+        handler1.assertIsFired(false);
+        handler2.assertIsFired(false);
+        preview.assertIsFired(false);
+      }
+    };
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+    HandlerRegistration reg2 = Event.addNativePreviewHandler(handler2);
+    HandlerRegistration reg3 = Event.addNativePreviewHandler(handler3);
+    DOM.addEventPreview(preview);
+    assertTrue(DOM.previewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    handler2.assertIsFired(true);
+    handler3.assertIsFired(true);
+    preview.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+    reg2.removeHandler();
+    reg3.removeHandler();
+    DOM.removeEventPreview(preview);
+  }
+
+  /**
+   * Test removal of a {@link NativePreviewHandler} works.
+   */
+  public void testFireNativePreviewEventRemoval() {
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(false,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+    reg1.removeHandler();
+    assertTrue(Event.fireNativePreviewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(false);
+    reg0.removeHandler();
+  }
+
+  /**
+   * Test that {@link Event#fireNativePreviewEvent(Event)} returns the correct
+   * value if the native event is not canceled.
+   */
+  public void testFireNativePreviewEventWithoutCancel() {
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(false,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+    assertTrue(Event.fireNativePreviewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+  }
+
+  /**
+   * Test that {@link Event#fireNativePreviewEvent(Event)} returns the correct
+   * value if no handlers are present.
+   */
+  public void testFireNativePreviewEventWithoutHandlers() {
+    assertTrue(Event.fireNativePreviewEvent(null));
+  }
+
+  /**
+   * Test that legacy EventPreview and NativePreviewHandlers can both cancel the
+   * event.
+   */
+  @Deprecated
+  public void testLegacyEventPreviewCancelByBoth() {
+    // Add handlers
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(true,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+
+    // Add legacy EventPreview
+    TestEventPreview preview = new TestEventPreview(true);
+    DOM.addEventPreview(preview);
+
+    // Fire the event
+    assertFalse(DOM.previewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    preview.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+    DOM.removeEventPreview(preview);
+  }
+
+  /**
+   * Test that legacy EventPreview can cancel the event.
+   */
+  @Deprecated
+  public void testLegacyEventPreviewCancelByEventPreview() {
+    // Add handlers
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(false,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+
+    // Add legacy EventPreview
+    TestEventPreview preview = new TestEventPreview(true);
+    DOM.addEventPreview(preview);
+
+    // Fire the event
+    assertFalse(DOM.previewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    preview.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+    DOM.removeEventPreview(preview);
+  }
+
+  /**
+   * Test that legacy EventPreview still fires after the NativeHandler cancels
+   * the event.
+   */
+  @Deprecated
+  public void testLegacyEventPreviewCancelByHandler() {
+    // Add handlers
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(true,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+
+    // Add legacy EventPreview
+    TestEventPreview preview = new TestEventPreview(false);
+    DOM.addEventPreview(preview);
+
+    // Fire the event
+    assertFalse(DOM.previewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    preview.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+    DOM.removeEventPreview(preview);
+  }
+
+  /**
+   * Test that legacy EventPreview still fires after the NativeHandlers without
+   * canceling the event.
+   */
+  @Deprecated
+  public void testLegacyEventPreviewWithoutCancel() {
+    // Add handlers
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(false,
+        false);
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(false,
+        false);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+
+    // Add legacy EventPreview
+    TestEventPreview preview = new TestEventPreview(false);
+    DOM.addEventPreview(preview);
+
+    // Fire the event
+    assertTrue(DOM.previewEvent(null));
+    handler0.assertIsFired(true);
+    handler1.assertIsFired(true);
+    preview.assertIsFired(true);
+    reg0.removeHandler();
+    reg1.removeHandler();
+    DOM.removeEventPreview(preview);
+  }
+
+  /**
+   * Test the accessors in {@link NativePreviewEvent}.
+   */
+  public void testNativePreviewEventAccessors() {
+    // cancelNativeEvent
+    {
+      NativePreviewEvent event = new NativePreviewEvent();
+      assertFalse(event.isCanceled());
+      event.cancel();
+      assertTrue(event.isCanceled());
+    }
+
+    // preventCancelNativeEvent
+    {
+      NativePreviewEvent event = new NativePreviewEvent();
+      assertFalse(event.isConsumed());
+      event.consume();
+      assertTrue(event.isConsumed());
+    }
+
+    // revive
+    {
+      NativePreviewEvent event = new NativePreviewEvent();
+      event.cancel();
+      event.consume();
+      assertTrue(event.isCanceled());
+      assertTrue(event.isConsumed());
+      event.revive();
+      assertFalse(event.isCanceled());
+      assertFalse(event.isConsumed());
+    }
+  }
+
+  /**
+   * Test that the singleton instance of {@link NativePreviewEvent} is revived
+   * correctly.
+   */
+  public void testReviveNativePreviewEvent() {
+    // Fire the event and cancel it
+    TestNativePreviewHandler handler0 = new TestNativePreviewHandler(true, true);
+    HandlerRegistration reg0 = Event.addNativePreviewHandler(handler0);
+    Event.fireNativePreviewEvent(null);
+    handler0.assertIsFired(true);
+    reg0.removeHandler();
+
+    // Fire the event again, but don't cancel it
+    TestNativePreviewHandler handler1 = new TestNativePreviewHandler(false,
+        false) {
+      @Override
+      public void onPreviewNativeEvent(NativePreviewEvent event) {
+        assertFalse(event.isCanceled());
+        assertFalse(event.isConsumed());
+        super.onPreviewNativeEvent(event);
+      }
+    };
+    HandlerRegistration reg1 = Event.addNativePreviewHandler(handler1);
+    assertTrue(Event.fireNativePreviewEvent(null));
+    handler1.assertIsFired(true);
+    reg1.removeHandler();
+  }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/PopupTest.java b/user/test/com/google/gwt/user/client/ui/PopupTest.java
index f42b84c..8aa91e3 100644
--- a/user/test/com/google/gwt/user/client/ui/PopupTest.java
+++ b/user/test/com/google/gwt/user/client/ui/PopupTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2009 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -74,6 +74,12 @@
     assertTrue(popup.isAutoHideEnabled());
     popup.setAutoHideEnabled(false);
     assertFalse(popup.isAutoHideEnabled());
+
+    // PreviewAllNativeEvents enabled
+    popup.setPreviewingAllNativeEvents(true);
+    assertTrue(popup.isPreviewingAllNativeEvents());
+    popup.setPreviewingAllNativeEvents(false);
+    assertFalse(popup.isPreviewingAllNativeEvents());
   }
 
   /**