Allow multiple DomEvent types per DOM event

It’s possible that different libraries provide DomEvent types for the
same DOM event (mainly HTML 5 events, e.g. transitionend). If that
happens only one library will work, as GWT only knows the last
registered DomEvent type. To fix this a one-to-many mapping between
DOM event name and DomEvent types has been introduced.

This patch also contributes to issue 8379 which aims to make the
event system more extendable.

Bug: issue 7698
Change-Id: Iedbac84881484849548e1829d61eb13a6c7d00e3
(cherry picked from commit a5327b8db8379737cf2775b784e8e7241a63cc59)
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 1500589..dbebdf3 100644
--- a/user/src/com/google/gwt/event/dom/client/DomEvent.java
+++ b/user/src/com/google/gwt/event/dom/client/DomEvent.java
@@ -21,6 +21,9 @@
 import com.google.gwt.event.shared.GwtEvent;
 import com.google.gwt.event.shared.HasHandlers;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * {@link DomEvent} is a subclass of {@link GwtEvent} that provides events that
  * underlying native browser event object as well as a subclass of {@link Type}
@@ -64,7 +67,12 @@
       if (registered == null) {
         init();
       }
-      registered.unsafePut(eventName, this);
+      List<Type<?>> types = registered.unsafeGet(eventName);
+      if (types == null) {
+        types = new ArrayList<Type<?>>();
+        registered.unsafePut(eventName, types);
+      }
+      types.add(this);
       name = eventName;
     }
 
@@ -78,7 +86,7 @@
     }
   }
 
-  private static PrivateMap<Type<?>> registered;
+  private static PrivateMap<List<Type<?>>> registered;
 
   /**
    * Fires the given native event on the specified handlers.
@@ -104,26 +112,28 @@
     assert nativeEvent != null : "nativeEvent must not be null";
 
     if (registered != null) {
-      final DomEvent.Type<?> typeKey = registered.unsafeGet(nativeEvent.getType());
-      if (typeKey != null) {
-        // Store and restore native event just in case we are in recursive
-        // loop.
-        NativeEvent currentNative = typeKey.flyweight.nativeEvent;
-        Element currentRelativeElem = typeKey.flyweight.relativeElem;
-        typeKey.flyweight.setNativeEvent(nativeEvent);
-        typeKey.flyweight.setRelativeElement(relativeElem);
+      List<Type<?>> types = registered.unsafeGet(nativeEvent.getType());
+      if (types != null) {
+        for (DomEvent.Type<?> type : types) {
+          // Store and restore native event just in case we are in recursive
+          // loop.
+          NativeEvent currentNative = type.flyweight.nativeEvent;
+          Element currentRelativeElem = type.flyweight.relativeElem;
+          type.flyweight.setNativeEvent(nativeEvent);
+          type.flyweight.setRelativeElement(relativeElem);
 
-        handlerSource.fireEvent(typeKey.flyweight);
+          handlerSource.fireEvent(type.flyweight);
 
-        typeKey.flyweight.setNativeEvent(currentNative);
-        typeKey.flyweight.setRelativeElement(currentRelativeElem);
+          type.flyweight.setNativeEvent(currentNative);
+          type.flyweight.setRelativeElement(currentRelativeElem);
+        }
       }
     }
   }
 
   // This method can go away once we have eager clinits.
   static void init() {
-    registered = new PrivateMap<Type<?>>();
+    registered = new PrivateMap<List<Type<?>>>();
   }
 
   private NativeEvent nativeEvent;
diff --git a/user/test/com/google/gwt/event/dom/client/DomEventTest.java b/user/test/com/google/gwt/event/dom/client/DomEventTest.java
index 65f35e4..154e434 100644
--- a/user/test/com/google/gwt/event/dom/client/DomEventTest.java
+++ b/user/test/com/google/gwt/event/dom/client/DomEventTest.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.event.shared.HandlerTestBase;
@@ -32,6 +33,30 @@
     public boolean flag = false;
   }
 
+  interface CustomClickHandler extends EventHandler {
+    void onClick(CustomClickEvent evt);
+  }
+
+  static class CustomClickEvent extends MouseEvent<CustomClickHandler> {
+
+    public static final Type<CustomClickHandler> TYPE =
+        new Type<CustomClickHandler>("click", new CustomClickEvent());
+
+    public static Type<CustomClickHandler> getType() {
+      return TYPE;
+    }
+
+    @Override
+    public Type<CustomClickHandler> getAssociatedType() {
+      return TYPE;
+    }
+
+    @Override
+    protected void dispatch(CustomClickHandler handler) {
+      handler.onClick(this);
+    }
+  }
+
   public void testKeyEvents() {
 
     final Flag flag = new Flag();
@@ -152,6 +177,33 @@
     assertTrue("Never received expected mouse-down event", flag.flag);
   }
 
+  public void testMultipleDomEventTypesPerEventName() {
+    Button b = new Button();
+    RootPanel.get().add(b);
+
+    final Flag first = new Flag();
+    b.addClickHandler(new ClickHandler() {
+      @Override
+      public void onClick(ClickEvent event) {
+        first.flag = true;
+      }
+    });
+
+    final Flag second = new Flag();
+    b.addDomHandler(new CustomClickHandler() {
+      @Override
+      public void onClick(CustomClickEvent event) {
+        second.flag = true;
+      }
+    }, CustomClickEvent.getType());
+
+    NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, false, false, false, false);
+    b.getElement().dispatchEvent(event);
+
+    assertTrue("Never received expected click event", first.flag);
+    assertTrue("Never received expected click event", second.flag);
+  }
+
   private void checkFire(DomEvent<?> event, HandlerRegistration registration,
       Flag flag, String eventName) {