Making media events bitless, freeing up a few event bits, since modern,
implementing browsers don't leak the way old ones did.

The following constants, marked as 'experimental', have been removed:
- com.google.gwt.user.client.Event.MEDIAEVENTS
- com.google.gwt.user.client.Event.ONCANPLAYTHROUGH
- com.google.gwt.user.client.Event.ONENDED
- com.google.gwt.user.client.Event.ONPROGRESS

Review at http://gwt-code-reviews.appspot.com/1447816

Review by: jlabanca@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10274 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt23_24userApi.conf b/tools/api-checker/config/gwt23_24userApi.conf
index bceabcc..4f061ad 100644
--- a/tools/api-checker/config/gwt23_24userApi.conf
+++ b/tools/api-checker/config/gwt23_24userApi.conf
@@ -142,3 +142,8 @@
 # Overloading AbstractHasData constructor to allow a widget or element as the root.
 com.google.gwt.user.cellview.client.AbstractHasData::AbstractHasData(Lcom/google/gwt/dom/client/Element;ILcom/google/gwt/view/client/ProvidesKey;) OVERLOADED_METHOD_CALL
 
+# Bitless media events no longer these experimental API constants
+com.google.gwt.user.client.Event::MEDIAEVENTS MISSING
+com.google.gwt.user.client.Event::ONCANPLAYTHROUGH MISSING
+com.google.gwt.user.client.Event::ONENDED MISSING
+com.google.gwt.user.client.Event::ONPROGRESS MISSING
diff --git a/user/src/com/google/gwt/media/client/MediaBase.java b/user/src/com/google/gwt/media/client/MediaBase.java
index 3bca63a..0de2450 100644
--- a/user/src/com/google/gwt/media/client/MediaBase.java
+++ b/user/src/com/google/gwt/media/client/MediaBase.java
@@ -49,17 +49,20 @@
     setElement(element);
   }
 
+  @Override
   public HandlerRegistration addCanPlayThroughHandler(
       CanPlayThroughHandler handler) {
-    return addDomHandler(handler, CanPlayThroughEvent.getType());
+    return addBitlessDomHandler(handler, CanPlayThroughEvent.getType());
   }
 
+  @Override
   public HandlerRegistration addEndedHandler(EndedHandler handler) {
-    return addDomHandler(handler, EndedEvent.getType());
+    return addBitlessDomHandler(handler, EndedEvent.getType());
   }
 
+  @Override
   public HandlerRegistration addProgressHandler(ProgressHandler handler) {
-    return addDomHandler(handler, ProgressEvent.getType());
+    return addBitlessDomHandler(handler, ProgressEvent.getType());
   }
 
   /**
diff --git a/user/src/com/google/gwt/user/client/Event.java b/user/src/com/google/gwt/user/client/Event.java
index 317ff7e..f1e646e 100644
--- a/user/src/com/google/gwt/user/client/Event.java
+++ b/user/src/com/google/gwt/user/client/Event.java
@@ -399,50 +399,6 @@
   public static final int GESTUREEVENTS = ONGESTURESTART | ONGESTURECHANGE | ONGESTUREEND;
 
   /**
-   * Fired when media is fully buffered and can be played through to the end.
-   * 
-   * <p>
-   * <span style="color:red">Experimental API: This API is still under development
-   * and is subject to change.
-   * </span>
-   * </p>
-   */
-  public static final int ONCANPLAYTHROUGH = 0x20000000;
-
-  /**
-   * Fired when media stops playing.
-   * 
-   * <p>
-   * <span style="color:red">Experimental API: This API is still under development
-   * and is subject to change.
-   * </span>
-   * </p>
-   */
-  public static final int ONENDED = 0x8000000;
-
-  /**
-   * Fired when progress is made downloading media.
-   * 
-   * <p>
-   * <span style="color:red">Experimental API: This API is still under development
-   * and is subject to change.
-   * </span>
-   * </p>
-   */
-  public static final int ONPROGRESS = 0x10000000;
-
-  /**
-   * A bit-mask covering all media events.
-   * 
-   * <p>
-   * <span style="color:red">Experimental API: This API is still under development
-   * and is subject to change.
-   * </span>
-   * </p>
-   */
-  public static final int MEDIAEVENTS = ONCANPLAYTHROUGH | ONENDED | ONPROGRESS;
-
-  /**
    * Value returned by accessors when the actual integer value is undefined. In
    * Development Mode, most accessors assert that the requested attribute is
    * reliable across all supported browsers.
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 9493730..c0bdbf9 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImpl.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImpl.java
@@ -92,9 +92,6 @@
     case "gesturestart": return 0x1000000;
     case "gesturechange": return 0x2000000;
     case "gestureend": return 0x4000000;
-    case "ended": return 0x8000000;
-    case "progress": return 0x10000000;
-    case "canplaythrough": return 0x20000000;
     default: return -1;
     }
   }-*/; 
diff --git a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
index d573da6..8c6f7fc 100644
--- a/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
+++ b/user/src/com/google/gwt/user/client/impl/DOMImplStandard.java
@@ -220,20 +220,39 @@
   }-*/;
 
   protected native void sinkBitlessEventImpl(Element elem, String eventTypeName) /*-{
-    if (eventTypeName == "drag")
-      elem.ondrag      = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
-    else if (eventTypeName == "dragend")
-      elem.ondragend   = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
-    else if (eventTypeName == "dragenter")
-      elem.ondragenter = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchDragEvent;
-    else if (eventTypeName == "dragleave")
-      elem.ondragleave = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
-    else if (eventTypeName == "dragover")
-      elem.ondragover  = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchDragEvent;
-    else if (eventTypeName == "dragstart")
-      elem.ondragstart = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
-    else if (eventTypeName == "drop")
-      elem.ondrop      = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
+    switch(eventTypeName) {
+      case "drag":
+        elem.ondrag           = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
+        break;
+      case "dragend":
+        elem.ondragend        = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
+        break;
+      case "dragenter":
+        elem.ondragenter      = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchDragEvent;
+        break;
+      case "dragleave":
+        elem.ondragleave      = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
+        break;
+      case "dragover":
+        elem.ondragover       = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchDragEvent;
+        break;
+      case "dragstart":
+        elem.ondragstart      = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
+        break;
+      case "drop":
+        elem.ondrop           = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent;
+        break;
+      case "canplaythrough":
+      case "ended":
+      case "progress":
+        // First call removeEventListener, so as not to add the same event listener more than once
+        elem.removeEventListener(eventTypeName, @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false); 
+        elem.addEventListener(eventTypeName, @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false); 
+        break;
+      default:
+        // catch missing cases
+        throw "Trying to sink unknown event type " + eventTypeName;
+    }
   }-*/;
 
   protected native void sinkEventsImpl(Element elem, int bits) /*-{
@@ -295,17 +314,6 @@
         @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
     if (chMask & 0x4000000) elem.ongestureend    = (bits & 0x4000000) ? 
         @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent : null;
-
-    if (bits & 0x8000000) {
-      elem.addEventListener('ended', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
-    } else {
-      elem.removeEventListener('ended', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
-    }
-    if (bits & 0x10000000) {
-      elem.addEventListener('progress', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
-    } else {
-      elem.removeEventListener('progress', @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent, false);
-    }
   }-*/;
 
   private native void releaseCaptureImpl(Element elem) /*-{
diff --git a/user/test/com/google/gwt/user/client/MediaEventsSinkTest.java b/user/test/com/google/gwt/user/client/MediaEventsSinkTest.java
index b457d91..44c7927 100644
--- a/user/test/com/google/gwt/user/client/MediaEventsSinkTest.java
+++ b/user/test/com/google/gwt/user/client/MediaEventsSinkTest.java
@@ -34,33 +34,92 @@
  */
 public class MediaEventsSinkTest extends GWTTestCase {
 
+  private static class CanPlayThroughHandlerImpl extends HandlerImpl implements
+      CanPlayThroughHandler {
+    @Override
+    public void onCanPlayThrough(CanPlayThroughEvent event) {
+      eventFired();
+    }
+  }
+
+  private static class EndedHandlerImpl extends HandlerImpl implements EndedHandler {
+    @Override
+    public void onEnded(EndedEvent event) {
+      eventFired();
+    }
+  }
+
+  private static class HandlerImpl {
+    private boolean fired = false;
+
+    public void eventFired() {
+      fired = true;
+    }
+
+    boolean hasEventFired() {
+      return fired;
+    }
+  }
+
+  private static class ProgressHandlerImpl extends HandlerImpl implements ProgressHandler {
+    @Override
+    public void onProgress(ProgressEvent event) {
+      eventFired();
+    }
+  }
+
+  /**
+   * Interface to create a widget.
+   * 
+   * @param <W> the widget type
+   */
+  private interface WidgetCreator<W extends Widget & HasAllMediaHandlers> {
+    /**
+     * Create a widget to test.
+     * 
+     * @return the new widget
+     */
+    W createWidget();
+  }
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.user.User";
   }
 
-  public void testAudioMediaEventsSinkByAddingHandler() {
+  public void testAudioEventSink() {
+    // skip tests on browsers that do not support audio
     if (!Audio.isSupported()) {
       return;
     }
-    verifyProgressEventSinkOnAddHandler(Audio.createIfSupported());
-    verifyEndedEventSinkOnAddHandler(Audio.createIfSupported());
-    verifyCanPlayThroughEventSinkOnAddHandler(Audio.createIfSupported());
+
+    verifyMediaEventSink(new WidgetCreator<Audio>() {
+      @Override
+      public Audio createWidget() {
+        return Audio.createIfSupported();
+      }
+    });
   }
 
-  public void testVideoMediaEventsSinkByAddingHandler() {
-    if (!Audio.isSupported()) {
+  public void testEventBitsUnmapped() throws Exception {
+    ProgressEvent.getType();
+    assertEquals(-1, Event.getTypeInt("progress"));
+    assertEquals(-1, Event.getTypeInt("ended"));
+    assertEquals(-1, Event.getTypeInt("canplaythrough"));
+  }
+
+  public void testVideoEventSink() {
+    // skip tests on browsers that do not support video
+    if (!Video.isSupported()) {
       return;
     }
-    verifyProgressEventSinkOnAddHandler(Video.createIfSupported());
-    verifyEndedEventSinkOnAddHandler(Video.createIfSupported());
-    verifyCanPlayThroughEventSinkOnAddHandler(Video.createIfSupported());
-  }
 
-  public void testMediaEventBitFieldsNotTriviallyZero() {
-    assertNotSame(0, Event.ONCANPLAYTHROUGH);
-    assertNotSame(0, Event.ONPROGRESS);
-    assertNotSame(0, Event.ONENDED);
+    verifyMediaEventSink(new WidgetCreator<Video>() {
+      @Override
+      public Video createWidget() {
+        return Video.createIfSupported();
+      }
+    });
   }
 
   @Override
@@ -70,91 +129,41 @@
     super.gwtTearDown();
   }
 
-  private <W extends Widget & HasAllMediaHandlers> void assertNotSunkAfterAttach(
-      W w, String eventName, boolean isSunk) {
-    assertFalse("Event should not be sunk on " + w.getClass().getName()
-        + " until a " + eventName + " handler has been added", isSunk);
-  }
+  private <W extends Widget & HasAllMediaHandlers> void verifyCanPlayThroughEventSink(W w) {
+    CanPlayThroughHandlerImpl handler = new CanPlayThroughHandlerImpl();
+    w.addCanPlayThroughHandler(handler);
 
-  private <W extends Widget & HasAllMediaHandlers> void assertSunkAfterAddHandler(
-      W w, String eventName, boolean isSunk) {
-    assertTrue("Event should have been sunk on " + w.getClass().getName()
-        + " once the widget has been attached and a " + eventName
-        + " handler has been added", isSunk);
-  }
-
-  private boolean isCanPlayThroughEventSunk(Element e) {
-    return (DOM.getEventsSunk(e) & Event.ONCANPLAYTHROUGH) != 0;
-  }
-
-  private boolean isEndedEventSunk(Element e) {
-    return (DOM.getEventsSunk(e) & Event.ONENDED) != 0;
-  }
-
-  private boolean isProgressEventSunk(Element e) {
-    return (DOM.getEventsSunk(e) & Event.ONPROGRESS) != 0;
-  }
-
-  private <W extends Widget & HasAllMediaHandlers> void verifyCanPlayThroughEventSinkOnAddHandler(
-      W w) {
-    verifyCanPlayThroughEventSinkOnAddHandler(w, w.getElement());
-  }
-
-  private <W extends Widget & HasAllMediaHandlers> void verifyCanPlayThroughEventSinkOnAddHandler(
-      W w, Element e) {
-    RootPanel.get().add(w);
-
-    assertNotSunkAfterAttach(w, CanPlayThroughEvent.getType().getName(),
-        isCanPlayThroughEventSunk(e));
-
-    w.addCanPlayThroughHandler(new CanPlayThroughHandler() {
-      public void onCanPlayThrough(CanPlayThroughEvent event) {
-      }
+    assertFalse(handler.hasEventFired());
+    w.fireEvent(new CanPlayThroughEvent() {
     });
-
-    assertSunkAfterAddHandler(w, CanPlayThroughEvent.getType().getName(),
-        isCanPlayThroughEventSunk(e));
+    assertTrue(handler.hasEventFired());
   }
 
-  private <W extends Widget & HasAllMediaHandlers> void verifyEndedEventSinkOnAddHandler(
-      W w) {
-    verifyEndedEventSinkOnAddHandler(w, w.getElement());
-  }
+  private <W extends Widget & HasAllMediaHandlers> void verifyEndedEventSink(W w) {
+    EndedHandlerImpl handler = new EndedHandlerImpl();
+    w.addEndedHandler(handler);
 
-  private <W extends Widget & HasAllMediaHandlers> void verifyEndedEventSinkOnAddHandler(
-      W w, Element e) {
-    RootPanel.get().add(w);
-
-    assertNotSunkAfterAttach(w, EndedEvent.getType().getName(),
-        isEndedEventSunk(e));
-
-    w.addEndedHandler(new EndedHandler() {
-      public void onEnded(EndedEvent event) {
-      }
+    assertFalse(handler.hasEventFired());
+    w.fireEvent(new EndedEvent() {
     });
-
-    assertSunkAfterAddHandler(w, EndedEvent.getType().getName(),
-        isEndedEventSunk(e));
+    assertTrue(handler.hasEventFired());
   }
 
-  private <W extends Widget & HasAllMediaHandlers> void verifyProgressEventSinkOnAddHandler(
-      W w) {
-    verifyProgressEventSinkOnAddHandler(w, w.getElement());
+  private void verifyMediaEventSink(WidgetCreator<?>... creators) {
+    for (WidgetCreator<?> creator : creators) {
+      verifyProgressEventSink(creator.createWidget());
+      verifyCanPlayThroughEventSink(creator.createWidget());
+      verifyEndedEventSink(creator.createWidget());
+    }
   }
 
-  private <W extends Widget & HasAllMediaHandlers> void verifyProgressEventSinkOnAddHandler(
-      W w, Element e) {
-    RootPanel.get().add(w);
+  private <W extends Widget & HasAllMediaHandlers> void verifyProgressEventSink(W w) {
+    ProgressHandlerImpl handler = new ProgressHandlerImpl();
+    w.addProgressHandler(handler);
 
-    assertNotSunkAfterAttach(w, ProgressEvent.getType().getName(),
-        isProgressEventSunk(e));
-
-    w.addProgressHandler(new ProgressHandler() {
-      public void onProgress(ProgressEvent event) {
-      }
+    assertFalse(handler.hasEventFired());
+    w.fireEvent(new ProgressEvent() {
     });
-
-    assertSunkAfterAddHandler(w, ProgressEvent.getType().getName(),
-        isProgressEventSunk(e));
+    assertTrue(handler.hasEventFired());
   }
 }
\ No newline at end of file