Cherry picking r9652 into releases/2.2


git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.2@9654 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/dom/client/AudioElement.java b/user/src/com/google/gwt/dom/client/AudioElement.java
new file mode 100644
index 0000000..2cf92e1
--- /dev/null
+++ b/user/src/com/google/gwt/dom/client/AudioElement.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 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.dom.client;
+
+/**
+ * Audio element.
+ * 
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change.
+ * </span>
+ * </p>
+ * 
+ * @see <a href="http://www.w3.org/TR/html5/video.html#audio">W3C HTML 5 Specification</a>
+ */
+@TagName(AudioElement.TAG)
+public class AudioElement extends MediaElement {
+
+  /**
+   * The tag for this element.
+   */
+  public static final String TAG = "audio";
+
+  protected AudioElement() {
+  }
+}
diff --git a/user/src/com/google/gwt/dom/client/DOMImplMozilla.java b/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
index 900fcf9..d892460 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplMozilla.java
@@ -19,25 +19,50 @@
  * Mozilla implementation of StandardBrowser.
  */
 class DOMImplMozilla extends DOMImplStandard {
-
-  /**
-   * Return true if using Gecko 1.9.0 (Firefox 3) or earlier.
-   * 
-   * @return true if using Gecko 1.9.0 (Firefox 3) or earlier.
-   */
-  @SuppressWarnings("unused")
-  private static native boolean isGecko190OrBefore() /*-{
+  
+  private static native int getGeckoVersion() /*-{
     var result = /rv:([0-9]+)\.([0-9]+)\.([0-9]+)?/.exec(navigator.userAgent.toLowerCase());
     if (result && result.length >= 3) {
       var version = (parseInt(result[1]) * 1000000) + (parseInt(result[2]) * 1000) + 
         parseInt(result.length == 4 ? result[3] : 0);
-      if (version <= 1009000) {
-        return true;
-      }
+      return version;
     }
-    return false;
+    return -1; // not gecko
   }-*/;
 
+  /**
+   * Return true if using Gecko 1.9.0 (Firefox 3) or earlier.
+   * 
+   * @return true if using Gecko 1.9.0 (Firefox 3) or earlier
+   */
+  @SuppressWarnings("unused")
+  private static boolean isGecko190OrBefore() {
+    int geckoVersion = getGeckoVersion();
+    return (geckoVersion != -1) && (geckoVersion <= 1009000);
+  }
+  
+  /**
+   * Return true if using Gecko 1.9.1 (Firefox 3.5) or earlier.
+   * 
+   * @return true if using Gecko 1.9.1 (Firefox 3.5) or earlier
+   */
+  @SuppressWarnings("unused")
+  private static boolean isGecko191OrBefore() {
+    int geckoVersion = getGeckoVersion();
+    return (geckoVersion != -1) && (geckoVersion <= 1009001);
+  }
+
+  /**
+   * Return true if using Gecko 2.0.0 (Firefox 4.0) or earlier.
+   * 
+   * @return true if using Gecko 2.0.0 (Firefox 4.0) or earlier
+   */
+  @SuppressWarnings("unused")
+  private static boolean isGecko2OrBefore() {
+    int geckoVersion = getGeckoVersion();
+    return (geckoVersion != -1) && (geckoVersion < 2000000);
+  }
+
   @Override
   public native void buttonClick(ButtonElement button) /*-{
     var doc = button.ownerDocument;
diff --git a/user/src/com/google/gwt/dom/client/Document.java b/user/src/com/google/gwt/dom/client/Document.java
index 256f88a..8981878 100644
--- a/user/src/com/google/gwt/dom/client/Document.java
+++ b/user/src/com/google/gwt/dom/client/Document.java
@@ -54,6 +54,15 @@
   }
 
   /**
+   * Creates an &lt;audio&gt; element.
+   * 
+   * @return the newly created element
+   */
+  public final AudioElement createAudioElement() {
+    return (AudioElement) DOMImpl.impl.createElement(this, AudioElement.TAG);
+  }
+
+  /**
    * Creates a &lt;base&gt; element.
    * 
    * @return the newly created element
@@ -1190,6 +1199,15 @@
   }-*/;
 
   /**
+   * Creates a &lt;video&gt; element.
+   * 
+   * @return the newly created element
+   */
+  public final VideoElement createVideoElement() {
+    return (VideoElement) DOMImpl.impl.createElement(this, VideoElement.TAG);
+  }
+
+  /**
    * Enables or disables scrolling of the document.
    * 
    * @param enable whether scrolling should be enabled or disabled
diff --git a/user/src/com/google/gwt/dom/client/MediaElement.java b/user/src/com/google/gwt/dom/client/MediaElement.java
new file mode 100644
index 0000000..16e24c3
--- /dev/null
+++ b/user/src/com/google/gwt/dom/client/MediaElement.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright 2011 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.dom.client;
+
+import com.google.gwt.media.dom.client.MediaError;
+import com.google.gwt.media.dom.client.TimeRanges;
+
+/**
+ * Common superclass for Audio and Video elements.
+ *
+ * See {@link <a href="http://www.w3.org/TR/html5/video.html">W3C HTML5 Video and Audio</a>}
+ */
+public class MediaElement extends Element {
+
+  /**
+   * Constant returned from {@link #canPlayType(String)}.
+   */
+  public static final String CAN_PLAY_PROBABLY = "probably";
+
+  /**
+   * Constant returned from {@link #canPlayType(String)}.
+   */
+  public static final String CAN_PLAY_MAYBE = "maybe";
+
+  /**
+   * Constant returned from {@link #canPlayType(String)}.
+   */
+  public static final String CANNOT_PLAY = "";
+
+  /**
+   * Constant returned from {@link #getReadyState()}.
+   */
+  public static final int HAVE_NOTHING = 0;
+
+  /**
+   * Constant returned from {@link #getReadyState()}.
+   */
+  public static final int HAVE_METADATA = 1;
+
+  /**
+   * Constant returned from {@link #getReadyState()}.
+   */
+  public static final int HAVE_CURRENT_DATA = 2;
+
+  /**
+   * Constant returned from {@link #getReadyState()}.
+   */
+  public static final int HAVE_FUTURE_DATA = 3;
+
+  /**
+   * Constant returned from {@link #getReadyState()}.
+   */
+  public static final int HAVE_ENOUGH_DATA = 4;
+
+  /**
+   * Constant returned from {@link #getNetworkState}.
+   */
+  public static final int NETWORK_EMPTY = 0;
+
+  /**
+   * Constant returned from {@link #getNetworkState}.
+   */
+  public static final int NETWORK_IDLE = 1;
+
+  /**
+   * Constant returned from {@link #getNetworkState}.
+   */
+  public static final int NETWORK_LOADING = 2;
+
+  /**
+   * Constant returned from {@link #getNetworkState}.
+   */
+  public static final int NETWORK_NO_SOURCE = 3;
+
+  /**
+   * Constant used by {@link #getPreload()} and {@link #setPreload(String)}.
+   */
+  public static final String PRELOAD_AUTO = "auto";
+
+  /**
+   * Constant used by {@link #getPreload()} and {@link #setPreload(String)}.
+   */
+  public static final String PRELOAD_METADATA = "metadata";
+
+  /**
+   * Constant used by {@link #getPreload()} and {@link #setPreload(String)}.
+   */
+  public static final String PRELOAD_NONE = "none";
+
+  protected MediaElement() {
+  }
+
+  /**
+   * Returns {@code true} if the native player is capable of playing content of
+   * the given MIME type.
+   *
+   * @param type a String representing a MIME type
+   * @return one of {@link #CAN_PLAY_PROBABLY}, {@link #CAN_PLAY_MAYBE}, or
+   *         {@link #CANNOT_PLAY}
+   */
+  public final native String canPlayType(String type) /*-{
+    return this.canPlayType(type);
+  }-*/;
+
+  /**
+   * Returns a {@link TimeRanges} object indicating which portions of the
+   * source have been buffered locally.
+   *
+   * @return a {@link TimeRanges} instance, or {@code null}.
+   */
+  public final native TimeRanges getBuffered() /*-{
+    return this.buffered;
+  }-*/;
+
+  /**
+   * Returns the URL of the current media source, or the empty String
+   * if no source is set.
+   *
+   * @return a String URL
+   */
+  public final native String getCurrentSrc() /*-{
+    return this.currentSrc;
+  }-*/;
+
+  /**
+   * Returns the current time within the source media stream.
+   *
+   * @return the time, in seconds, as a double
+   *
+   * @see #setCurrentTime(double)
+   */
+  public final native double getCurrentTime() /*-{
+    return this.currentTime;
+  }-*/;
+
+  /**
+   * Returns the default playback rate, where 1.0 corresponds to normal
+   * playback. If no rate has been set, 1.0 is returned.
+   *
+   * @return the current default playback rate, or 1.0 if it has not been set
+   *
+   * @see #setDefaultPlaybackRate(double)
+   */
+  public final double getDefaultPlaybackRate() {
+    return getDoubleAttr("defaultPlaybackRate", 1.0);
+  }
+
+  /**
+   * Returns the duration of the source media stream, in seconds.  If the
+   * duration is unknown, {@link Double#NaN} is returned.  For unbounded media
+   * streams, {@link Double#POSITIVE_INFINITY} is returned.
+   *
+   * @return a positive duration in seconds, NaN, or Infinity
+   */
+  public final native double getDuration() /*-{
+    return this.duration;
+  }-*/;
+
+  /**
+   * Returns the type of error that has occurred while attempting to load
+   * and play the media.  If no error has occurred, {@code null} is returned.
+   *
+   * @return a {@link MediaError} instance, or {@code null}
+   */
+  public final native MediaError getError() /*-{
+    return this.error || null;
+  }-*/;
+
+  /**
+   * Returns the time to which the media stream was seeked at the time it was
+   * loaded, in seconds, or 0.0 if the position is unknown.
+   *
+   * @return the initial time, or 0.0 if unknown
+   */
+  public final double getInitialTime() {
+    return getDoubleAttr("initialTime", 0.0);
+  }
+
+  /**
+   * Returns the network state, one of {@link #NETWORK_EMPTY},
+   * {@link #NETWORK_IDLE}, {@link #NETWORK_LOADING}, or
+   * {@link #NETWORK_NO_SOURCE}.
+   *
+   * @return an integer constant indicating the network state
+   *
+   * @see #NETWORK_EMPTY
+   * @see #NETWORK_IDLE
+   * @see #NETWORK_LOADING
+   * @see #NETWORK_NO_SOURCE
+   */
+  public final native int getNetworkState() /*-{
+    return this.networkState;
+  }-*/;
+
+  /**
+   * Returns the playback rate, where 1.0 corresponds to normal
+   * playback.  If the rate has not been set, 1.0 is returned.
+   *
+   * @return the playback rate, if known, otherwise 1.0
+   *
+   * @see #setPlaybackRate(double)
+   */
+  public final native double getPlaybackRate() /*-{
+    var rate = this.playbackRate;
+    if (rate != null && typeof(rate) == 'number') {
+      return rate;
+    }
+    return 1.0;
+  }-*/;
+
+  /**
+   * Returns a {@link TimeRanges} object indicating which portions of the
+   * source have been played.
+   *
+   * @return a {@link TimeRanges} instance, or {@code null}.
+   */
+  public final native TimeRanges getPlayed() /*-{
+    return this.played;
+  }-*/;
+
+  /**
+   * Returns the preload setting, one of {@link #PRELOAD_AUTO},
+   * {@link #PRELOAD_METADATA}, or {@link #PRELOAD_NONE}.
+   *
+   * @return the preload setting
+   *
+   * @see #setPreload(String)
+   * @see #PRELOAD_AUTO
+   * @see #PRELOAD_METADATA
+   * @see #PRELOAD_NONE
+   */
+  public final native String getPreload() /*-{
+    return this.preload;
+  }-*/;
+
+  /**
+   * Returns the current state of the media with respect to rendering the
+   * current playback position, as one of the constants
+   * {@link #HAVE_CURRENT_DATA}, {@link #HAVE_ENOUGH_DATA},
+   * {@link #HAVE_FUTURE_DATA}, {@link #HAVE_METADATA}, or {@link #HAVE_NOTHING}
+   * .
+   *
+   * @return an integer constant indicating the ready state
+   *
+   * @see #HAVE_CURRENT_DATA
+   * @see #HAVE_ENOUGH_DATA
+   * @see #HAVE_FUTURE_DATA
+   * @see #HAVE_METADATA
+   * @see #HAVE_NOTHING
+   */
+  public final native int getReadyState() /*-{
+    return this.readyState;
+  }-*/;
+
+  /**
+   * Returns a {@link TimeRanges} object indicating which portions of the
+   * source are seekable.
+   *
+   * @return a {@link TimeRanges} instance, or {@code null}.
+   */
+  public final native TimeRanges getSeekable() /*-{
+    return this.seekable;
+  }-*/;
+
+  /**
+   * Returns the source URL for the media, or {@code null} if none is set.
+   *
+   * @return a String URL or {@code null}
+   *
+   * @see #setSrc(String)
+   */
+  public final native String getSrc() /*-{
+    return this.getAttribute('src');
+  }-*/;
+
+  /**
+   * Returns the time corresponding to the zero time in the media timeline,
+   * measured in seconds since midnight, January 1 1970 UTC, or
+   * {@link Double#NaN} if none is specified.
+   *
+   * @return the start time
+   */
+  public final double getStartOffsetTime() {
+    return getDoubleAttr("startOffsetTime", Double.NaN);
+  }
+
+  /**
+   * Returns the current audio volume setting for the media, as a number
+   * between 0.0 and 1.0.
+   *
+   * @return a number between 0.0 (silent) and 1.0 (loudest)
+   *
+   * @see #setVolume(double)
+   */
+  public final native double getVolume() /*-{
+    return this.volume;
+  }-*/;
+
+  /**
+   * Returns {@code true} if the media player should display interactive
+   * controls (for example, to control play/pause, seek position, and volume),
+   * {@code false} otherwise.
+   *
+   * @return whether controls should be displayed
+   *
+   * @see #setControls(boolean)
+   */
+  public final native boolean hasControls() /*-{
+    return this.hasAttribute('controls');
+  }-*/;
+
+  /**
+   * Returns {@code true} if playback has reached the end of the media, {@code
+   * false} otherwise.
+   *
+   * @return whether playback has ended
+   */
+  public final native boolean hasEnded() /*-{
+    return this.ended;
+  }-*/;
+
+  /**
+   * Returns {@code true} if autoplay is enabled, {@code false} otherwise. When
+   * autoplay is enabled, the user agent will begin playback automatically as
+   * soon as it can do so without stopping.
+   *
+   * @return the autoplay setting
+   *
+   * @see #setAutoplay(boolean)
+   */
+  public final native boolean isAutoplay() /*-{
+    return this.hasAttribute('autoplay');
+  }-*/;
+
+  /**
+   * Returns {@code true} if the user agent is to seek back to the start of the
+   * media once playing has ended, {@code false} otherwise.
+   *
+   * @return the loop setting
+   *
+   * @see #setLoop(boolean)
+   */
+  public final native boolean isLoop() /*-{
+    return this.hasAttribute('loop');
+  }-*/;
+
+  /**
+   * Returns {@code true} if the volume is to be muted (overriding the normal
+   * volume setting), {@code false} otherwise.
+   *
+   * @return the muting setting
+   *
+   * @see #setMuted(boolean)
+   * @see #getVolume()
+   * @see #setVolume(double)
+   */
+  public final native boolean isMuted() /*-{
+    return !!this.muted;
+  }-*/;
+
+  /**
+   * Returns {@code true} if playback is paused, {@code false} otherwise.
+   *
+   * @return the paused setting
+   *
+   * @see #pause()
+   * @see #play()
+   */
+  public final native boolean isPaused() /*-{
+    return !!this.paused;
+  }-*/;
+
+  /**
+   * Returns {@code true} if the playback position is in the process of changing
+   * discontinuously, e.g., by use of the interactive controls, {@code false}
+   * otherwise.
+   *
+   * @return the seeking status
+   *
+   * @see #setControls(boolean)
+   * @see #hasControls()
+   */
+  public final native boolean isSeeking() /*-{
+    return !!this.seeking;
+  }-*/;
+
+  /**
+   * Causes the resource to be loaded.
+   */
+  public final native void load() /*-{
+    this.load();
+  }-*/;
+
+  /**
+   * Causes playback of the resource to be paused.
+   */
+  public final native void pause() /*-{
+    this.pause();
+  }-*/;
+
+  /**
+   * Causes playback of the resource to be started or resumed.
+   */
+  public final native void play() /*-{
+    this.play();
+  }-*/;
+
+  /**
+   * Enables or disables autoplay of the resource.
+   *
+   * @param autoplay if {@code true}, enable autoplay
+   *
+   * @see #isAutoplay()
+   */
+  public final void setAutoplay(boolean autoplay) {
+    setBooleanAttr("autoplay", autoplay);
+  }
+
+  /**
+   * Enables or disables interactive controls.
+   *
+   * @param controls if {@code true}, enable controls
+   *
+   * @see #hasControls()
+   */
+  public final void setControls(boolean controls) {
+    setBooleanAttr("controls", controls);
+  }
+
+  /**
+   * Sets the current playback time within the media stream, in seconds.
+   *
+   * @param time a number within the ranges given by {@link #getSeekable()}
+   *
+   * @see #getCurrentTime()
+   */
+  public final native void setCurrentTime(double time) /*-{
+    this.currentTime = time;
+  }-*/;
+
+  /**
+   * Sets the default playback rate.
+   *
+   * @param rate a double value
+   *
+   * @see #getDefaultPlaybackRate()
+   */
+  public final native void setDefaultPlaybackRate(double rate) /*-{
+    this.defaultPlaybackRate = rate;
+  }-*/;
+
+  /**
+   * Enables or disables looping.
+   *
+   * @param loop if {@code true}, enable looping
+   *
+   * @see #isLoop()
+   */
+  public final void setLoop(boolean loop) {
+    setBooleanAttr("loop", loop);
+  }
+
+  /**
+   * Enables or disables muting.
+   *
+   * @param muted if {@code true}, enable muting
+   *
+   * @see #isMuted()
+   */
+  public final native void setMuted(boolean muted) /*-{
+    this.muted = muted;
+  }-*/;
+
+  /**
+   * Sets the playback rate.
+   *
+   * @param rate a double value
+   *
+   * @see #getPlaybackRate()
+   */
+  public final native void setPlaybackRate(double rate) /*-{
+    this.playbackRate = rate;
+  }-*/;
+
+  /**
+   * Changes the preload setting to one of {@link #PRELOAD_AUTO},
+   * {@link #PRELOAD_METADATA}, or {@link #PRELOAD_NONE}.
+   *
+   * @param preload a String constants
+   *
+   * @see #getPreload()
+   * @see #setPreload(String)
+   * @see #PRELOAD_AUTO
+   * @see #PRELOAD_METADATA
+   * @see #PRELOAD_NONE
+   */
+  public final native void setPreload(String preload) /*-{
+    this.preload = preload;
+  }-*/;
+
+  /**
+   * Sets the source URL for the media.
+   *
+   * @param url a String URL
+   *
+   * @see #getSrc()
+   */
+  public final native void setSrc(String url) /*-{
+    this.src = url;
+  }-*/;
+
+  /**
+   * Sets the playback volume.
+   *
+   * @param volume a value between 0.0 (silent) and 1.0 (loudest)
+   *
+   * @see #getVolume()
+   */
+  public final native void setVolume(double volume) /*-{
+    this.volume = volume
+  }-*/;
+
+  private native double getDoubleAttr(String name, double def) /*-{
+    var value = this.getAttribute(name);
+    if (value == null || typeof(value) == 'undefined') {
+      return def;
+    }
+    return value;
+  }-*/;
+
+  private void setBooleanAttr(String name, boolean value) {
+    if (value) {
+      setAttribute(name, "");
+    } else {
+      removeAttribute(name);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/dom/client/VideoElement.java b/user/src/com/google/gwt/dom/client/VideoElement.java
new file mode 100644
index 0000000..3fecf60
--- /dev/null
+++ b/user/src/com/google/gwt/dom/client/VideoElement.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2011 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.dom.client;
+
+/**
+ * Video element.
+ * 
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change.
+ * </span>
+ * </p>
+ * 
+ * @see <a href="http://www.w3.org/TR/html5/video.html#video">W3C HTML 5 Specification</a>
+ */
+@TagName(VideoElement.TAG)
+public class VideoElement extends MediaElement {
+
+  /**
+   * The tag for this element.
+   */
+  public static final String TAG = "video";
+
+  protected VideoElement() {
+  }
+
+  /**
+   * Gets the height of the element.
+   * 
+   * @return the height, in pixels
+   * @see #setHeight(int)
+   */
+  public final native int getHeight() /*-{
+    return this.height;
+  }-*/;
+
+  /**
+   * Returns a poster URL.
+   * 
+   * @return a URL containing a poster image
+   *
+   * @see #setPoster(String)
+   */
+  public final native String getPoster() /*-{
+    return this.poster;
+  }-*/;
+
+  /**
+   * Gets the intrinsic height of video within the element.
+   * 
+   * @return the height, in pixels
+   * @see #setHeight(int)
+   */
+  public final native int getVideoHeight() /*-{
+    return this.videoHeight;
+  }-*/;
+
+  /**
+   * Gets the instrinsic width of the video within the element.
+   * 
+   * @return the width, in pixels
+   * @see #setWidth(int)
+   */
+  public final native int getVideoWidth() /*-{
+    return this.videoWidth;
+  }-*/;
+  
+  /**
+   * Gets the width of the element.
+   * 
+   * @return the width, in pixels
+   * @see #setWidth(int)
+   */
+  public final native int getWidth() /*-{
+    return this.width;
+  }-*/;
+
+  /**
+   * Sets the height of the element.
+   * 
+   * @param height the height, in pixels
+   * @see #getHeight()
+   */
+  public final native void setHeight(int height) /*-{
+    this.height = height;
+  }-*/;
+
+  /**
+   * Sets the poster URL.
+   * 
+   * @param url the poster image URL
+   * @see #getPoster
+   */
+  public final native void setPoster(String url) /*-{
+    this.poster = url;
+  }-*/;
+
+  /**
+   * Sets the width of the element.
+   * 
+   * @param width the width, in pixels
+   * @see #getWidth()
+   */
+  public final native void setWidth(int width) /*-{
+    this.width = width;
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/media/Media.gwt.xml b/user/src/com/google/gwt/media/Media.gwt.xml
new file mode 100644
index 0000000..4588b70
--- /dev/null
+++ b/user/src/com/google/gwt/media/Media.gwt.xml
@@ -0,0 +1,20 @@
+<!--
+  Copyright 2011 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.
+-->
+<module>
+  <inherits name="com.google.gwt.user.User"/>
+  <inherits name="com.google.gwt.media.dom.DOM"/>
+  <source path="client"/>
+</module>
diff --git a/user/src/com/google/gwt/media/client/Audio.java b/user/src/com/google/gwt/media/client/Audio.java
new file mode 100644
index 0000000..210c90a
--- /dev/null
+++ b/user/src/com/google/gwt/media/client/Audio.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2011 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.media.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.AudioElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.PartialSupport;
+import com.google.gwt.user.client.ui.FocusWidget;
+
+/**
+ * <p>
+ * A widget representing an &lt;audio&gt; element.
+ * 
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change.
+ * </span>
+ * </p>
+ * 
+ * This widget may not be supported on all browsers.
+ */
+@PartialSupport
+public class Audio extends FocusWidget {
+  private static AudioElementSupportDetector detector;
+
+  /**
+   * Return a new {@link Audio} if supported,  and null otherwise.
+   * 
+   * @return a new {@link Audio} if supported, and null otherwise
+   */
+  public static Audio createIfSupported() {
+    if (detector == null) {
+      detector = GWT.create(AudioElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return null;
+    }
+    AudioElement element = Document.get().createAudioElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return null;
+    }
+    return new Audio(element);
+  }
+
+  /**
+   * Runtime check for whether the audio element is supported in this browser.
+   * 
+   * @return whether the audio element is supported
+   */
+  public static boolean isSupported() {
+    if (detector == null) {
+      detector = GWT.create(AudioElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return false;
+    }
+    AudioElement element = Document.get().createAudioElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Protected constructor. Use {@link #createIfSupported()} to create an Audio.
+   */
+  private Audio(AudioElement element) {
+    setElement(element);
+  }
+
+  /**
+   * Creates an Audio widget with a given source URL.
+   * 
+   * @param src a String URL
+   */
+  public Audio(String src) {
+    setElement(Document.get().createAudioElement());
+    getAudioElement().setSrc(src);
+  }
+
+  /**
+   * Returns the attached AudioElement.
+   * 
+   * @return the AudioElement
+   */
+  public AudioElement getAudioElement() {
+    return this.getElement().cast();
+  }
+
+  /**
+   * Detector for browser support of {@link AudioElement}.
+   */
+  private static class AudioElementSupportDetector {
+    /**
+     * Using a run-time check, return true if the {@link AudioElement} is 
+     * supported.
+     * 
+     * @return true if supported, false otherwise.
+     */
+    static native boolean isSupportedRunTime(AudioElement element) /*-{
+      return !!element.getContext;
+    }-*/;
+
+    /**
+     * Using a compile-time check, return true if {@link AudioElement} might 
+     * be supported.
+     * 
+     * @return true if might be supported, false otherwise.
+     */
+    boolean isSupportedCompileTime() {
+      // will be true in AudioElementSupportDetectedMaybe
+      // will be false in AudioElementSupportDetectedNo
+      return false;
+    }
+  }
+
+  /**
+   * Detector for permutations that might support {@link AudioElement}.
+   */
+  @SuppressWarnings("unused")
+  private static class AudioElementSupportDetectedMaybe
+      extends AudioElementSupportDetector {
+    /**
+     * Using a compile-time check, return true if {@link AudioElement} might be
+     * supported.
+     *
+     * @return true if might be supported, false otherwise.
+     */
+    @Override
+    boolean isSupportedCompileTime() {
+      return true;
+    }
+  }
+
+  /**
+   * Detector for permutations that do not support {@link AudioElement}.
+   */
+  @SuppressWarnings("unused")
+  private static class AudioElementSupportDetectedNo
+      extends AudioElementSupportDetector {
+    /**
+     * Using a compile-time check, return true if {@link AudioElement} might be
+     * supported.
+     *
+     * @return true if might be supported, false otherwise.
+     */
+    @Override
+    boolean isSupportedCompileTime() {
+      return false;
+    } 
+  }
+}
diff --git a/user/src/com/google/gwt/media/client/Video.java b/user/src/com/google/gwt/media/client/Video.java
new file mode 100644
index 0000000..bd1eac7
--- /dev/null
+++ b/user/src/com/google/gwt/media/client/Video.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2011 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.media.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.VideoElement;
+import com.google.gwt.user.client.ui.FocusWidget;
+
+/**
+ * <p>
+ * A widget representing a &lt;video&gt; element.
+ * 
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change.
+ * </span>
+ * </p>
+ * 
+ * This widget may not be supported on all browsers.
+ */
+public class Video extends FocusWidget {
+  private static VideoElementSupportDetector detector;
+
+  /**
+   * Return a new {@link Video} if supported,  and null otherwise.
+   * 
+   * @return a new {@link Video} if supported, and null otherwise
+   */
+  public static Video createIfSupported() {
+    if (detector == null) {
+      detector = GWT.create(VideoElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return null;
+    }
+    VideoElement element = Document.get().createVideoElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return null;
+    }
+    return new Video(element);
+  }
+
+  /**
+   * Runtime check for whether the video element is supported in this browser.
+   * 
+   * @return whether the video element is supported
+   */
+  public static boolean isSupported() {
+    if (detector == null) {
+      detector = GWT.create(VideoElementSupportDetector.class);
+    }
+    if (!detector.isSupportedCompileTime()) {
+      return false;
+    }
+    VideoElement element = Document.get().createVideoElement();
+    if (!detector.isSupportedRunTime(element)) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Protected constructor. Use {@link #createIfSupported()} to create a Video.
+   */
+  private Video(VideoElement element) {
+    setElement(element);
+  }
+
+  /**
+   * Creates a Video widget.
+   */
+  public Video() {
+    setElement(Document.get().createVideoElement());
+  }
+  
+  /**
+   * Creates a Video widget with a given source URL.
+   * 
+   * @param src a String URL
+   */
+  public Video(String src) {
+    setElement(Document.get().createVideoElement());
+    getVideoElement().setSrc(src);
+  }
+
+  /**
+   * Returns the attached VideoElement.
+   * 
+   * @return the VideoElement
+   */
+  public VideoElement getVideoElement() {
+    return this.getElement().cast();
+  }
+
+  /**
+   * Detector for browser support of {@link VideoElement}.
+   */
+  private static class VideoElementSupportDetector {
+    /**
+     * Using a run-time check, return true if the {@link VideoElement} is 
+     * supported.
+     * 
+     * @return true if supported, false otherwise.
+     */
+    static native boolean isSupportedRunTime(VideoElement element) /*-{
+      return !!element.getContext;
+    }-*/;
+
+    /**
+     * Using a compile-time check, return true if {@link VideoElement} might 
+     * be supported.
+     * 
+     * @return true if might be supported, false otherwise.
+     */
+    boolean isSupportedCompileTime() {
+      // will be true in VideoElementSupportDetectedMaybe
+      // will be false in VideoElementSupportDetectedNo
+      return false;
+    }
+  }
+
+  /**
+   * Detector for permutations that might support {@link VideoElement}.
+   */
+  @SuppressWarnings("unused")
+  private static class VideoElementSupportDetectedMaybe
+      extends VideoElementSupportDetector {
+    /**
+     * Using a compile-time check, return true if {@link VideoElement} might be
+     * supported.
+     *
+     * @return true if might be supported, false otherwise.
+     */
+    @Override
+    boolean isSupportedCompileTime() {
+      return true;
+    }
+  }
+
+  /**
+   * Detector for permutations that do not support {@link VideoElement}.
+   */
+  @SuppressWarnings("unused")
+  private static class VideoElementSupportDetectedNo
+      extends VideoElementSupportDetector {
+    /**
+     * Using a compile-time check, return true if {@link VideoElement} might be
+     * supported.
+     *
+     * @return true if might be supported, false otherwise.
+     */
+    @Override
+    boolean isSupportedCompileTime() {
+      return false;
+    } 
+  }
+}
diff --git a/user/src/com/google/gwt/media/client/package-info.java b/user/src/com/google/gwt/media/client/package-info.java
new file mode 100644
index 0000000..ae09cd7
--- /dev/null
+++ b/user/src/com/google/gwt/media/client/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * <p>
+ * Widgets for HTML Audio and Video support.
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.media.client;
diff --git a/user/src/com/google/gwt/media/dom/DOM.gwt.xml b/user/src/com/google/gwt/media/dom/DOM.gwt.xml
new file mode 100644
index 0000000..961e68d
--- /dev/null
+++ b/user/src/com/google/gwt/media/dom/DOM.gwt.xml
@@ -0,0 +1,18 @@
+<!--
+  Copyright 2011 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.
+-->
+<module>
+  <source path="client"/>
+</module>
diff --git a/user/src/com/google/gwt/media/dom/client/MediaError.java b/user/src/com/google/gwt/media/dom/client/MediaError.java
new file mode 100644
index 0000000..1d0aea6
--- /dev/null
+++ b/user/src/com/google/gwt/media/dom/client/MediaError.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 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.media.dom.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * <p>
+ * A {@link JavaScriptObject} indicating the type of error encountered by a
+ * {@link com.google.gwt.dom.client.MediaElement MediaElement}.
+ * 
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change.
+ * </span>
+ * </p>
+ * 
+ * @see com.google.gwt.dom.client.MediaElement#getError()
+ */
+public final class MediaError extends JavaScriptObject {
+
+  /**
+   * A constant returned by {@link #getCode} indicating that playback
+   * was aborted at the user's request. 
+   */
+  public static final int MEDIA_ERR_ABORTED = 1;
+
+  /**
+   * A constant returned by {@link #getCode} indicating that playback
+   * was aborted due to a network error. 
+   */
+  public static final int MEDIA_ERR_NETWORK = 2;
+
+  /**
+   * A constant returned by {@link #getCode} indicating that playback
+   * was aborted due to an error in decoding. 
+   */
+  public static final int MEDIA_ERR_DECODE = 3;
+
+  /**
+   * A constant returned by {@link #getCode} indicating that the format
+   * of the source stream was unsuitable for playback. 
+   */
+  public static final int MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
+
+  protected MediaError() {
+  }
+
+  /**
+   * Returns an error code indicating the reason for the error. 
+   *
+   * @return one of {@link MediaError#MEDIA_ERR_ABORTED},
+   * {@link MediaError#MEDIA_ERR_NETWORK}, {@link MediaError#MEDIA_ERR_DECODE},
+   * or {@link MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED}
+   */
+  public native int getCode() /*-{
+    return this.code;
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/media/dom/client/TimeRanges.java b/user/src/com/google/gwt/media/dom/client/TimeRanges.java
new file mode 100644
index 0000000..a11bf89
--- /dev/null
+++ b/user/src/com/google/gwt/media/dom/client/TimeRanges.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2011 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.media.dom.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * <p>
+ * A {@link JavaScriptObject} representing a time range returned from a
+ * {@link com.google.gwt.dom.client.MediaElement MediaElement}.
+ * 
+ * <p>
+ * <span style="color:red">Experimental API: This API is still under development
+ * and is subject to change.
+ * </span>
+ * </p>
+ * 
+ * @see com.google.gwt.dom.client.MediaElement#getBuffered()
+ * @see com.google.gwt.dom.client.MediaElement#getPlayed()
+ * @see com.google.gwt.dom.client.MediaElement#getSeekable()
+ */
+public final class TimeRanges extends JavaScriptObject {
+
+  protected TimeRanges() {
+  }
+
+  /**
+   * Returns the end time of the range indexed by {@code index}.
+   * 
+   * @param index the range index, between 0 (inclusive) and {@link #length()}
+   *          (exclusive)
+   * @return a double indicating the end time in seconds
+   * 
+   * @see #start(int)
+   */
+  public native double end(int index) /*-{
+    return this.end(index);
+  }-*/;
+
+  /**
+   * Returns the number of distinct ranges contained in this object.
+   *
+   * @return an integer number of ranges
+   */
+  public native int length() /*-{
+    return this.length;
+  }-*/;
+  
+  /**
+   * Returns the start time of the range indexed by {@code index}.
+   * 
+   * @param index the range index, between 0 (inclusive) and {@link #length()}
+   *          (exclusive)
+   * @return a double indicating the start time in seconds
+   * 
+   * @see #end(int)
+   */
+  public native double start(int index) /*-{
+    return this.start(index);
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/media/dom/client/package-info.java b/user/src/com/google/gwt/media/dom/client/package-info.java
new file mode 100644
index 0000000..88e740d
--- /dev/null
+++ b/user/src/com/google/gwt/media/dom/client/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * <p>
+ * DOM classes for HTML Audio and Video support.
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.gwt.media.dom.client;
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 905c37e..f6277dc 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -26,6 +26,7 @@
    <inherits name="com.google.gwt.editor.Editor" />
    <inherits name="com.google.gwt.resources.Resources"/>
    <inherits name="com.google.gwt.layout.Layout"/>
+   <inherits name="com.google.gwt.media.Media"/>
    <inherits name="com.google.gwt.uibinder.UiBinder"/>
    <inherits name="com.google.gwt.user.AsyncProxy"/>
    <inherits name="com.google.gwt.user.RemoteService"/>
diff --git a/user/test/com/google/gwt/media/MediaSuite.java b/user/test/com/google/gwt/media/MediaSuite.java
new file mode 100644
index 0000000..3174327
--- /dev/null
+++ b/user/test/com/google/gwt/media/MediaSuite.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.media;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests of Media.
+ */
+public class MediaSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("Test suite for Media GWTTestCases");
+    
+    /*
+     * Tests disabled temporarily
+     *  suite.addTestSuite(AudioTest.class);
+     *  suite.addTestSuite(VideoTest.class);
+     */
+    
+    return suite;
+  }
+
+  private MediaSuite() {
+  }
+}
diff --git a/user/test/com/google/gwt/media/MediaTest.gwt.xml b/user/test/com/google/gwt/media/MediaTest.gwt.xml
new file mode 100644
index 0000000..11f6d65
--- /dev/null
+++ b/user/test/com/google/gwt/media/MediaTest.gwt.xml
@@ -0,0 +1,26 @@
+<!--                                                                        -->
+<!-- Copyright 2011 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module>
+  <!-- Inherit the JUnit support -->
+  <inherits name='com.google.gwt.junit.JUnit'/>
+
+  <!-- Make media files available on the server by abusing the 'script' tag -->
+  <source path="client"/>
+  <script src="jabberwocky.ogg"></script>
+  <script src="jabberwocky.mp3"></script>
+  <script src="testogg.ogv"></script>
+  <script src="testh264.mp4"></script>
+  <script src="poster.jpg"></script>
+</module>
diff --git a/user/test/com/google/gwt/media/client/AudioTest.java b/user/test/com/google/gwt/media/client/AudioTest.java
new file mode 100644
index 0000000..e491cd4
--- /dev/null
+++ b/user/test/com/google/gwt/media/client/AudioTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 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.media.client;
+
+import com.google.gwt.dom.client.AudioElement;
+import com.google.gwt.dom.client.MediaElement;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.user.client.ui.RootPanel;
+
+/**
+ * Tests {@link AudioElement}.
+ *
+ * Because HtmlUnit does not support HTML5, you will need to run these tests
+ * manually in order to have them run. To do that, go to "run configurations" or
+ * "debug configurations", select the test you would like to run, and put this
+ * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
+ */
+@DoNotRunWith(Platform.HtmlUnitUnknown)
+public class AudioTest extends MediaTest {
+  Audio audio;
+
+  final static String audioUrlMp3 = "jabberwocky.mp3";
+  final static String audioFormatMp3 = "audio/mpeg";
+  final static String audioUrlOgg = "jabberwocky.ogg";
+  final static String audioFormatOgg = "audio/ogg";
+
+  @Override
+  public MediaElement getElement() {
+    if (audio == null) {
+      return null;
+    }
+    return audio.getAudioElement();
+  }
+
+  @Override
+  public String getElementState() {
+    StringBuilder sb = new StringBuilder();
+    AudioElement e = audio.getAudioElement();
+    sb.append("AudioElement[");
+    sb.append("currentSrc=");
+    sb.append(e.getCurrentSrc());
+    sb.append(",currentTime=");
+    sb.append(e.getCurrentTime());
+    sb.append(",defaultPlaybackRate=");
+    sb.append(e.getDefaultPlaybackRate());
+    sb.append(",duration=");
+    sb.append(e.getDuration());
+    sb.append(",initialTime=");
+    sb.append(e.getInitialTime());
+    sb.append(",networkState=");
+    sb.append(e.getNetworkState());
+    sb.append(",playbackRate=");
+    sb.append(e.getPlaybackRate());
+    sb.append(",preload=");
+    sb.append(e.getPreload());
+    sb.append(",readyState=");
+    sb.append(e.getReadyState());
+    sb.append(",src=");
+    sb.append(e.getSrc());
+    sb.append(",startOffsetTime=");
+    sb.append(e.getStartOffsetTime());
+    sb.append(",seekable=");
+    sb.append(e.getSeekable());
+    sb.append(",volume=");
+    sb.append(e.getVolume());
+    sb.append("]");
+    return sb.toString();
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.media.MediaTest";
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    audio = Audio.createIfSupported();
+    
+    if (audio == null) {
+      return; // don't continue if not supported
+    }
+
+    AudioElement element = audio.getAudioElement();
+    String canPlayMp3 = element.canPlayType(audioFormatMp3);
+    String canPlayOgg = element.canPlayType(audioFormatOgg);
+    if (!canPlayMp3.equalsIgnoreCase(MediaElement.CANNOT_PLAY)) {
+      element.setSrc(audioUrlMp3);
+    } else if (!canPlayOgg.equalsIgnoreCase(MediaElement.CANNOT_PLAY)) {
+      element.setSrc(audioUrlOgg);
+    } else {
+      throw new Exception("Could not find suitable audio format");
+    }
+    RootPanel.get().add(audio);
+  }
+
+  @Override
+  protected void gwtTearDown() throws Exception {
+    if (audio == null) {
+      return; // don't continue if not supported
+    }
+    
+    audio.getAudioElement().pause();
+    RootPanel.get().remove(audio);
+  }
+}
diff --git a/user/test/com/google/gwt/media/client/MediaTest.java b/user/test/com/google/gwt/media/client/MediaTest.java
new file mode 100644
index 0000000..9eda220
--- /dev/null
+++ b/user/test/com/google/gwt/media/client/MediaTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2011 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.media.client;
+
+import com.google.gwt.dom.client.MediaElement;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.Timer;
+
+import junit.framework.Assert;
+
+/**
+ * Base test for {@link MediaElement}.
+ * 
+ * Do not call this class directly. To use, extend this class and override the
+ * getElement and isSupported methods.
+ * 
+ * Because HtmlUnit does not support HTML5, you will need to run these tests
+ * manually in order to have them run. To do that, go to "run configurations" or
+ * "debug configurations", select the test you would like to run, and put this
+ * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
+ */
+@DoNotRunWith(Platform.HtmlUnitUnknown)
+public abstract class MediaTest extends GWTTestCase {
+
+  native boolean isOldFirefox() /*-{
+    return @com.google.gwt.dom.client.DOMImplMozilla::isGecko191OrBefore()();
+  }-*/;
+
+  native boolean isFirefox40OrEarlier() /*-{
+    return @com.google.gwt.dom.client.DOMImplMozilla::isGecko2OrBefore()();
+  }-*/;
+
+  public void disabled_testCurrentTime() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    Assert.assertTrue("currentTime must be positive.",
+        element.getCurrentTime() >= 0.0);
+
+    double seekTime = 2.0; // seconds
+    element.setCurrentTime(seekTime);
+    Assert.assertEquals("currentTime must be able to be set.", seekTime,
+        element.getCurrentTime());
+  }
+
+  /**
+   * Return the MediaElement associated with the test.
+   * 
+   * @return the MediaElement associated with the test.
+   */
+  public MediaElement getElement() {
+    return null;
+  }
+
+  public abstract String getElementState();
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.media.MediaTest";
+  }
+
+  public void testAutoPlay() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    element.setAutoplay(false);
+    assertFalse("Autoplay should be off.", element.isAutoplay());
+    element.setAutoplay(true);
+    assertTrue("Autoplay should be on.", element.isAutoplay());
+  }
+
+  public void testControls() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    element.setControls(false);
+    assertFalse("Controls should be off.", element.hasControls());
+    element.setControls(true);
+    assertTrue("Controls should be on.", element.hasControls());
+  }
+
+  public void testCurrentSrc() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    element.load();
+    Assert.assertNotNull("currentSrc should be set in these tests.",
+        element.getCurrentSrc());
+  }
+
+  public void testLoop() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    element.setLoop(false);
+    assertFalse("Loop should be off.", element.isLoop());
+    element.setLoop(true);
+    assertTrue("Loop should be on.", element.isLoop());
+  }
+
+  public void testMuted() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    element.setMuted(true);
+    assertTrue("Muted should be true.", element.isMuted());
+    element.setMuted(false);
+    assertFalse("Muted should be false.", element.isMuted());
+  }
+
+  public void testNetworkState() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+    int state = element.getNetworkState();
+    assertTrue("Illegal network state", state == MediaElement.NETWORK_EMPTY
+        || state == MediaElement.NETWORK_IDLE
+        || state == MediaElement.NETWORK_LOADING
+        || state == MediaElement.NETWORK_NO_SOURCE);
+  }
+
+  public void testPlay() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    int waitMillis = 10000;
+    delayTestFinish(3 * waitMillis);
+
+    element.setPlaybackRate(1.0);
+    element.play();
+
+    // wait a little, then make sure it played
+    new Timer() {
+      @Override
+      public void run() {
+        finishTest();
+      }
+    }.schedule(waitMillis);
+  }
+
+  public void testPlaybackRate() {
+    final MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    int waitMillis = 5000;
+    delayTestFinish(3 * waitMillis);
+
+    assertEquals("Default playback rate should be 1.0", 1.0,
+        element.getDefaultPlaybackRate());
+
+    element.play();
+
+    // wait a little, then make sure it played
+    new Timer() {
+      @Override
+      public void run() {
+        // set rate to 2.0
+        double rate = 2.0;
+        element.setPlaybackRate(rate);
+        assertEquals("Should be able to change playback rate", rate,
+            element.getPlaybackRate());
+
+        // return to 1.0
+        rate = 1.0;
+        element.setPlaybackRate(rate);
+        assertEquals("Should be able to change playback rate", rate,
+            element.getPlaybackRate());
+
+        finishTest();
+      }
+    }.schedule(waitMillis);
+  }
+
+  public void disabled_testPreload() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+    if (isFirefox40OrEarlier()) {
+      return; // don't continue on older versions of Firefox.
+    }
+    
+    String state = element.getPreload();
+    assertNotNull(state);
+    assertTrue("Illegal preload state", state.equals(MediaElement.PRELOAD_AUTO)
+        || state.equals(MediaElement.PRELOAD_METADATA)
+        || state.equals(MediaElement.PRELOAD_NONE));
+
+    element.setPreload(MediaElement.PRELOAD_METADATA);
+    assertEquals("Preload should be able to be set.",
+        MediaElement.PRELOAD_METADATA, element.getPreload());
+  }
+
+  public void testReadyState() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    int state = element.getReadyState();
+    assertTrue("Illegal ready state", state == MediaElement.HAVE_CURRENT_DATA
+        || state == MediaElement.HAVE_ENOUGH_DATA
+        || state == MediaElement.HAVE_FUTURE_DATA
+        || state == MediaElement.HAVE_METADATA
+        || state == MediaElement.HAVE_NOTHING);
+  }
+
+  public void testVolume() {
+    MediaElement element = getElement();
+    if (element == null) {
+      return; // don't continue if not supported
+    }
+
+    element.setVolume(0.5);
+    assertEquals("Volume should be at one-half loudness.", 0.5,
+        element.getVolume());
+    element.setVolume(0.75);
+    assertEquals("Volume should be at three-quarters loudness.", 0.75,
+        element.getVolume());
+  }
+}
diff --git a/user/test/com/google/gwt/media/client/VideoTest.java b/user/test/com/google/gwt/media/client/VideoTest.java
new file mode 100644
index 0000000..069ef0f
--- /dev/null
+++ b/user/test/com/google/gwt/media/client/VideoTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2011 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.media.client;
+
+import com.google.gwt.dom.client.MediaElement;
+import com.google.gwt.dom.client.VideoElement;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.RootPanel;
+
+/**
+ * Tests {@link VideoElement}.
+ * 
+ * Because HtmlUnit does not support HTML5, you will need to run these tests
+ * manually in order to have them run. To do that, go to "run configurations" or
+ * "debug configurations", select the test you would like to run, and put this
+ * line in the VM args under the arguments tab: -Dgwt.args="-runStyle Manual:1"
+ */
+@DoNotRunWith(Platform.HtmlUnitUnknown)
+public class VideoTest extends MediaTest {
+  Video video;
+
+  final static String posterUrl = "poster.jpg";
+  final static String videoUrlH264 = "testh264.mp4";
+  final static String videoFormatH264 = "video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"";
+  final static String videoUrlOgg = "testogg.ogv";
+  final static String videoFormatOgg = "video/ogg; codecs=\"theora, vorbis\"";
+
+  final static int videoWidth = 480;
+  final static int videoHeight = 270;
+
+  @Override
+  public MediaElement getElement() {
+    if (video == null) {
+      return null;
+    }
+    return video.getVideoElement();
+  }
+
+  @Override
+  public String getElementState() {
+    StringBuilder sb = new StringBuilder();
+    VideoElement e = video.getVideoElement();
+
+    sb.append("AudioElement[");
+    sb.append("currentSrc=");
+    sb.append(e.getCurrentSrc());
+    sb.append(",currentTime=");
+    sb.append(e.getCurrentTime());
+    sb.append(",defaultPlaybackRate=");
+    sb.append(e.getDefaultPlaybackRate());
+    sb.append(",duration=");
+    sb.append(e.getDuration());
+    sb.append(",height=");
+    sb.append(e.getHeight());
+    sb.append(",initialTime=");
+    sb.append(e.getInitialTime());
+    sb.append(",networkState=");
+    sb.append(e.getNetworkState());
+    sb.append(",playbackRate=");
+    sb.append(e.getPlaybackRate());
+    sb.append(",poster=");
+    sb.append(e.getPoster());
+    sb.append(",preload=");
+    sb.append(e.getPreload());
+    sb.append(",readyState=");
+    sb.append(e.getReadyState());
+    sb.append(",src=");
+    sb.append(e.getSrc());
+    sb.append(",startOffsetTime=");
+    sb.append(e.getStartOffsetTime());
+    sb.append(",seekable=");
+    sb.append(e.getSeekable());
+    sb.append(",videoHeight=");
+    sb.append(e.getVideoHeight());
+    sb.append(",videoWidth=");
+    sb.append(e.getVideoWidth());
+    sb.append(",volume=");
+    sb.append(e.getVolume());
+    sb.append(",width=");
+    sb.append(e.getWidth());
+    sb.append("]");
+    return sb.toString();
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.media.MediaTest";
+  }
+
+  public void testPoster() {
+    if (video == null) {
+      return; // don't continue if not supported
+    }
+
+    VideoElement element = video.getVideoElement();
+    element.setPoster(posterUrl);
+    String poster = element.getPoster();
+    assertEquals(posterUrl, poster.substring(poster.lastIndexOf('/') + 1));
+  }
+
+  public void testSize() {
+    if (video == null) {
+      return; // don't continue if not supported
+    }
+
+    int width = 100;
+    int height = 200;
+    VideoElement element = video.getVideoElement();
+    element.setWidth(width);
+    element.setHeight(height);
+    assertEquals(width, element.getWidth());
+    assertEquals(height, element.getHeight());
+  }
+
+  public void testVideoSize() {
+    if (video == null) {
+      return; // don't continue if not supported
+    }
+
+    int waitMillis = 5000;
+    delayTestFinish(3 * waitMillis);
+
+    final VideoElement element = video.getVideoElement();
+    element.play();
+
+    // wait a little, then make sure it played
+    new Timer() {
+      @Override
+      public void run() {
+        assertEquals("Element = " + getElementState() + ", expected width "
+            + videoWidth, videoWidth, element.getVideoWidth());
+        assertEquals("Element = " + getElementState() + ", expected height "
+            + videoHeight, videoHeight, element.getVideoHeight());
+        finishTest();
+      }
+    }.schedule(waitMillis);
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    video = Video.createIfSupported();
+    
+    if (video == null) {
+      return; // don't continue if not supported
+    }
+
+    VideoElement element = video.getVideoElement();
+    if (!element.canPlayType(videoFormatH264).equalsIgnoreCase(
+        MediaElement.CANNOT_PLAY)) {
+      element.setSrc(videoUrlH264);
+    } else if (!element.canPlayType(videoFormatOgg).equalsIgnoreCase(
+        MediaElement.CANNOT_PLAY)) {
+      element.setSrc(videoUrlOgg);
+    } else {
+      throw new Exception("Could not find suitable video format");
+    }
+    RootPanel.get().add(video);
+  }
+
+  @Override
+  protected void gwtTearDown() throws Exception {
+    if (video == null) {
+      return; // don't continue if not supported
+    }
+    
+    video.getVideoElement().pause();
+    RootPanel.get().remove(video);
+  }
+}
diff --git a/user/test/com/google/gwt/media/public/jabberwocky.mp3 b/user/test/com/google/gwt/media/public/jabberwocky.mp3
new file mode 100644
index 0000000..ead8687
--- /dev/null
+++ b/user/test/com/google/gwt/media/public/jabberwocky.mp3
Binary files differ
diff --git a/user/test/com/google/gwt/media/public/jabberwocky.ogg b/user/test/com/google/gwt/media/public/jabberwocky.ogg
new file mode 100644
index 0000000..d9a6b0f
--- /dev/null
+++ b/user/test/com/google/gwt/media/public/jabberwocky.ogg
Binary files differ
diff --git a/user/test/com/google/gwt/media/public/poster.jpg b/user/test/com/google/gwt/media/public/poster.jpg
new file mode 100644
index 0000000..980ed41
--- /dev/null
+++ b/user/test/com/google/gwt/media/public/poster.jpg
Binary files differ
diff --git a/user/test/com/google/gwt/media/public/testh264.mp4 b/user/test/com/google/gwt/media/public/testh264.mp4
new file mode 100644
index 0000000..df28ab1
--- /dev/null
+++ b/user/test/com/google/gwt/media/public/testh264.mp4
Binary files differ
diff --git a/user/test/com/google/gwt/media/public/testogg.ogv b/user/test/com/google/gwt/media/public/testogg.ogv
new file mode 100644
index 0000000..f428074
--- /dev/null
+++ b/user/test/com/google/gwt/media/public/testogg.ogv
Binary files differ