Update HistoryImpl to use modern browser standards

Removes all old history implementations. Especially the timer
implementations caused many headaches on mobile.
Instead of setting window.onhashchange directly this uses
window.addListener instead, unfortunately this does not work with
IE8 (window.attachEvent does not call our listener on programatic hash
updates),so we end up rebinding anyway. This implementation should be
very easy to update once we drop IE8 support.

Change-Id: Idd4b25d11f36402d6fa4737d6d5ec0ed98b15fca
diff --git a/eclipse/reference/code-museum/war/SingleIssue.html b/eclipse/reference/code-museum/war/SingleIssue.html
index 54683b7..4568019 100644
--- a/eclipse/reference/code-museum/war/SingleIssue.html
+++ b/eclipse/reference/code-museum/war/SingleIssue.html
@@ -6,7 +6,6 @@
 	</head>
 
 	<body>
-		<iframe src="javascript:''" id="__gwt_historyFrame" style="position:absolute;width:0;height:0;border:0"></iframe>
         <noscript>
           <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color:white; border: 1px solid red; padding: 4px; font-family: sans-serif">
             Your web browser must have JavaScript enabled
diff --git a/reference/Microbenchmarks/war/Microbenchmarks.html b/reference/Microbenchmarks/war/Microbenchmarks.html
index dddd1e2..d6dfde5 100644
--- a/reference/Microbenchmarks/war/Microbenchmarks.html
+++ b/reference/Microbenchmarks/war/Microbenchmarks.html
@@ -24,9 +24,6 @@
   <!--                                           -->
   <body>
 
-    <!-- OPTIONAL: include this if you want history support -->
-    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
-    
     <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
diff --git a/samples/dynatable/war/DynaTable.html b/samples/dynatable/war/DynaTable.html
index 68b801d..438e5a0 100644
--- a/samples/dynatable/war/DynaTable.html
+++ b/samples/dynatable/war/DynaTable.html
@@ -21,7 +21,6 @@
     <script type="text/javascript" language='javascript' src='dynatable/dynatable.nocache.js'></script>
   </head>
   <body>
-    <iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; border: 1px solid red; padding: 4px; font-family: sans-serif">
         Your web browser must have JavaScript enabled
diff --git a/samples/dynatablerf/src/main/webapp/DynaTableRf.html b/samples/dynatablerf/src/main/webapp/DynaTableRf.html
index f2db764..9e228a8 100644
--- a/samples/dynatablerf/src/main/webapp/DynaTableRf.html
+++ b/samples/dynatablerf/src/main/webapp/DynaTableRf.html
@@ -20,7 +20,6 @@
     <script type="text/javascript" language='javascript' src='dynatablerf/dynatablerf.nocache.js'></script>
   </head>
   <body>
-    <iframe src="javascript:''" id='__gwt_historyFrame' tabIndex='-1' style='width:0;height:0;border:0'></iframe>
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; border: 1px solid red; padding: 4px; font-family: sans-serif">
         Your web browser must have JavaScript enabled
diff --git a/samples/mobilewebapp/src/main/webapp/MobileWebApp.html b/samples/mobilewebapp/src/main/webapp/MobileWebApp.html
index 1d33a65..262898e 100644
--- a/samples/mobilewebapp/src/main/webapp/MobileWebApp.html
+++ b/samples/mobilewebapp/src/main/webapp/MobileWebApp.html
@@ -47,9 +47,6 @@
   <!--                                           -->
   <body>
 
-    <!-- OPTIONAL: include this if you want history support -->
-    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
-    
     <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
diff --git a/samples/showcase/war/Showcase.html b/samples/showcase/war/Showcase.html
index 8d47503..c7180a1 100644
--- a/samples/showcase/war/Showcase.html
+++ b/samples/showcase/war/Showcase.html
@@ -15,7 +15,6 @@
     <script language='javascript' src='showcase/showcase.nocache.js'></script>
   </head>
   <body>
-    <iframe src="javascript:''" id="__gwt_historyFrame" style="position:absolute;width:0;height:0;border:0"></iframe>
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color:white; border: 1px solid red; padding: 4px; font-family: sans-serif">
         Your web browser must have JavaScript enabled
diff --git a/samples/validation/src/main/webapp/Validation.html b/samples/validation/src/main/webapp/Validation.html
index 5dd8e63..0047cc6 100644
--- a/samples/validation/src/main/webapp/Validation.html
+++ b/samples/validation/src/main/webapp/Validation.html
@@ -41,9 +41,6 @@
   <!--                                           -->
   <body>
 
-    <!-- OPTIONAL: include this if you want history support -->
-    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
-
     <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
diff --git a/user/src/com/google/gwt/junit/public/junit-standards.html b/user/src/com/google/gwt/junit/public/junit-standards.html
index 4bb5cba..5e924db 100644
--- a/user/src/com/google/gwt/junit/public/junit-standards.html
+++ b/user/src/com/google/gwt/junit/public/junit-standards.html
@@ -64,7 +64,6 @@
 loadSelectionScript();
 -->
 </script>
-<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
 <noscript>
   <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
     Your web browser must have JavaScript enabled
diff --git a/user/src/com/google/gwt/junit/public/junit.html b/user/src/com/google/gwt/junit/public/junit.html
index 155ce90..d418f5c 100644
--- a/user/src/com/google/gwt/junit/public/junit.html
+++ b/user/src/com/google/gwt/junit/public/junit.html
@@ -63,7 +63,6 @@
 loadSelectionScript();
 -->
 </script>
-<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
 <noscript>
   <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
     Your web browser must have JavaScript enabled
diff --git a/user/src/com/google/gwt/user/History.gwt.xml b/user/src/com/google/gwt/user/History.gwt.xml
index 6dddccb..7a003a5 100644
--- a/user/src/com/google/gwt/user/History.gwt.xml
+++ b/user/src/com/google/gwt/user/History.gwt.xml
@@ -17,32 +17,18 @@
 <!-- This module is typically inherited via com.google.gwt.user.User        -->
 <!--                                                                        -->
 <module type="fileset">
-	<inherits name="com.google.gwt.core.Core"/>
-	<inherits name="com.google.gwt.user.UserAgent"/>
+  <inherits name="com.google.gwt.core.Core"/>
+  <inherits name="com.google.gwt.user.UserAgent"/>
 
-  <!-- Mozilla has a slightly different history implementation than the     -->
-  <!-- standard case.                                                       -->
-  <replace-with class="com.google.gwt.user.client.impl.HistoryImplMozilla">
-    <when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
-    <when-property-is name="user.agent" value="gecko1_8"/>
+  <!-- IE8 does not work with the standard implementation -->
+  <replace-with class="com.google.gwt.user.client.History.HistoryImplIE8">
+    <when-type-is class="com.google.gwt.user.client.History.HistoryImpl" />
+    <all>
+      <!-- prevent fallback for IE9 -->
+      <none>
+        <when-property-is name="user.agent" value="ie9" />
+      </none>
+      <when-property-is name="user.agent" value="ie8" />
+    </all>
   </replace-with>
-
-	<!-- Opera has yet another slightly different implementation. -->
-	<replace-with class="com.google.gwt.user.client.impl.HistoryImplTimer">
-		<when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
-		<when-property-is name="user.agent" value="opera"/>
-	</replace-with>
-
-	<!-- Safari is almost like Opera, but again slightly different. -->
-	<replace-with class="com.google.gwt.user.client.impl.HistoryImplSafari">
-		<when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
-		<when-property-is name="user.agent" value="safari"/>
-	</replace-with>
-
-  <!-- IE6 has a completely different history implementation. IE8 used the -->
-  <!-- standard implementation. -->
-	<replace-with class="com.google.gwt.user.client.impl.HistoryImplIE6">
-		<when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
-		<when-property-is name="user.agent" value="ie6"/>
-	</replace-with>
 </module>
diff --git a/user/src/com/google/gwt/user/client/History.java b/user/src/com/google/gwt/user/client/History.java
index 285ffee..eafdc76 100644
--- a/user/src/com/google/gwt/user/client/History.java
+++ b/user/src/com/google/gwt/user/client/History.java
@@ -16,13 +16,15 @@
 package com.google.gwt.user.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.impl.Disposable;
 import com.google.gwt.core.client.impl.Impl;
+import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.GwtEvent;
 import com.google.gwt.event.shared.HandlerManager;
 import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.impl.HistoryImpl;
 
 /**
  * This class allows you to interact with the browser's history stack. Each
@@ -60,6 +62,97 @@
  */
 public class History {
 
+  private static class HistoryEventSource implements HasValueChangeHandlers<String> {
+
+    private HandlerManager handlers = new HandlerManager(null);
+
+    @Override
+    public void fireEvent(GwtEvent<?> event) {
+      handlers.fireEvent(event);
+    }
+
+    @Override
+    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
+      return handlers.addHandler(ValueChangeEvent.getType(), handler);
+    }
+
+    public void fireValueChangedEvent(String newToken) {
+      ValueChangeEvent.fire(this, newToken);
+    }
+
+    public HandlerManager getHandlers() {
+      return handlers;
+    }
+  }
+
+  private static class HistoryImpl {
+
+    private JavaScriptObject handler;
+
+    public void attachListener(JavaScriptObject handler) {
+      this.handler = handler;
+      addDomListener(handler);
+    }
+
+    public void dispose() {
+      removeDomListener(handler);
+    }
+
+    public void maybeRunOtherListeners() {
+    }
+
+    private native void addDomListener(JavaScriptObject handler) /*-{
+      $wnd.addEventListener('hashchange', handler);
+    }-*/;
+
+    private native void removeDomListener(JavaScriptObject handler) /*-{
+      $wnd.removeEventListener('hashchange', handler);
+    }-*/;
+  }
+
+  // used for rebinding
+  @SuppressWarnings("unused")
+  private static class HistoryImplIE8 extends HistoryImpl {
+
+    private JavaScriptObject oldHandler;
+
+    @Override
+    public void attachListener(JavaScriptObject handler) {
+      oldHandler = getHashChangeHandler();
+      setListener(handler);
+    }
+
+    @Override
+    public void dispose() {
+      setListener(oldHandler);
+    }
+
+    @Override
+    public void maybeRunOtherListeners() {
+      // run the listener we removed if any
+      try {
+        if (oldHandler != null) {
+          executeJavaScriptFunction(oldHandler);
+        }
+      } catch (Exception e) {
+        // we do not care about errors from another module or JS
+      }
+    }
+
+    private native void setListener(JavaScriptObject handler) /*-{
+      $wnd.onhashchange = handler;
+    }-*/;
+
+    private native JavaScriptObject getHashChangeHandler() /*-{
+      return $wnd.onhashchange;
+    }-*/;
+
+    private native void executeJavaScriptFunction(JavaScriptObject f) /*-{
+      f();
+    }-*/;
+  }
+
+  @SuppressWarnings("deprecation")
   private static class WrapHistory extends BaseListenerWrapper<HistoryListener>
       implements ValueChangeHandler<String> {
     @Deprecated
@@ -75,33 +168,27 @@
       super(listener);
     }
 
+    @Override
     public void onValueChange(ValueChangeEvent<String> event) {
       listener.onHistoryChanged(event.getValue());
     }
   }
 
+  private static HistoryEventSource historyEventSource;
+  private static String token;
   private static HistoryImpl impl;
 
   static {
     impl = GWT.create(HistoryImpl.class);
-    if (!impl.init()) {
-      // Set impl to null as a flag to no-op future calls.
-      impl = null;
-
-      // Tell the user.
-      GWT.log("Unable to initialize the history subsystem; did you "
-          + "include the history frame in your host page? Try "
-          + "<iframe src=\"javascript:''\" id='__gwt_historyFrame' "
-          + "style='position:absolute;width:0;height:0;border:0'>"
-          + "</iframe>");
-    } else {
-      Impl.scheduleDispose(new Disposable() {
-        @Override
-        public void dispose() {
-          impl.dispose();
-        }
-      });
-    }
+    historyEventSource = new HistoryEventSource();
+    token = getDecodedHash();
+    impl.attachListener(getHistoryChangeHandler());
+    Impl.scheduleDispose(new Disposable() {
+      @Override
+      public void dispose() {
+        impl.dispose();
+      }
+    });
   }
 
   /**
@@ -112,9 +199,7 @@
    */
   @Deprecated
   public static void addHistoryListener(HistoryListener listener) {
-    if (impl != null) {
-      WrapHistory.add(listener);
-    }
+    WrapHistory.add(listener);
   }
 
   /**
@@ -126,13 +211,11 @@
    */
   public static HandlerRegistration addValueChangeHandler(
       ValueChangeHandler<String> handler) {
-    return impl != null ? impl.addValueChangeHandler(handler) : null;
+    return historyEventSource.addValueChangeHandler(handler);
   }
 
   /**
    * Programmatic equivalent to the user pressing the browser's 'back' button.
-   *
-   * Note that this does not work correctly on Safari 2.
    */
   public static native void back() /*-{
     $wnd.history.back();
@@ -144,9 +227,10 @@
    * @param historyToken the token to encode
    * @return the encoded token, suitable for use as part of a URI
    */
-  public static String encodeHistoryToken(String historyToken) {
-    return impl != null ? impl.encodeFragment(historyToken) : historyToken;
-  }
+  public static native String encodeHistoryToken(String historyToken) /*-{
+    // encodeURI() does *not* encode the '#' character.
+    return $wnd.encodeURI(historyToken).replace("#", "%23");
+  }-*/;
 
   /**
    * Fire
@@ -157,10 +241,8 @@
    * history handlers of the initial application state.
    */
   public static void fireCurrentHistoryState() {
-    if (impl != null) {
-      String token = getToken();
-      impl.fireHistoryChangedImpl(token);
-    }
+    String currentToken = getToken();
+    historyEventSource.fireValueChangedEvent(currentToken);
   }
 
   /**
@@ -181,7 +263,7 @@
    * @return the initial token, or the empty string if none is present.
    */
   public static String getToken() {
-    return impl != null ? HistoryImpl.getToken() : "";
+    return token;
   }
 
   /**
@@ -206,8 +288,14 @@
    *          event should be issued
    */
   public static void newItem(String historyToken, boolean issueEvent) {
-    if (impl != null) {
-      impl.newItem(historyToken, issueEvent);
+    historyToken = (historyToken == null) ? "" : historyToken;
+    if (!historyToken.equals(getToken())) {
+      token = historyToken;
+      String updateToken = encodeHistoryToken(historyToken);
+      nativeUpdate(updateToken);
+      if (issueEvent) {
+        historyEventSource.fireValueChangedEvent(historyToken);
+      }
     }
   }
 
@@ -224,9 +312,7 @@
    */
   @Deprecated
   public static void onHistoryChanged(String historyToken) {
-    if (impl != null) {
-      impl.fireHistoryChangedImpl(historyToken);
-    }
+    historyEventSource.fireValueChangedEvent(historyToken);
   }
 
   /**
@@ -236,8 +322,37 @@
    */
   @Deprecated
   public static void removeHistoryListener(HistoryListener listener) {
-    if (impl != null) {
-      WrapHistory.remove(impl.getHandlers(), listener);
+    WrapHistory.remove(historyEventSource.getHandlers(), listener);
+  }
+
+  private static native String decodeURI(String s) /*-{
+    return $wnd.decodeURI(s.replace("%23", "#"));
+  }-*/;
+
+  private static String getDecodedHash() {
+    String hashToken = Window.Location.getHash();
+    return hashToken.isEmpty() ? "" : decodeURI(hashToken.substring(1));
+  }
+
+  private static native JavaScriptObject getHistoryChangeHandler() /*-{
+    return $entry(@com.google.gwt.user.client.History::onHashChanged());
+  }-*/;
+
+  /**
+   * The standard updateHash implementation assigns to location.hash() with an
+   * encoded history token.
+   */
+  private static native void nativeUpdate(String historyToken) /*-{
+    $wnd.location.hash = historyToken;
+  }-*/;
+
+  // this is called from JS when the native onhashchange occurs
+  private static void onHashChanged() {
+    String hashToken = getDecodedHash();
+    if (!hashToken.equals(getToken())) {
+      token = hashToken;
+      historyEventSource.fireValueChangedEvent(hashToken);
     }
+    impl.maybeRunOtherListeners();
   }
 }
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImpl.java b/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
deleted file mode 100644
index af675cd..0000000
--- a/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2008 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.impl;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.event.shared.HandlerRegistration;
-
-/**
- * Native implementation associated with
- * {@link com.google.gwt.user.client.History}. User classes should not use this
- * class directly.
- * 
- * <p>
- * This base version uses the HTML5 standard window.onhashchange event to
- * determine when the URL hash identifier changes.
- * </p>
- */
-public class HistoryImpl implements HasValueChangeHandlers<String> {
-
-  private static String token = "";
-
-  public static String getToken() {
-    return (token == null) ? "" : token;
-  }
-
-  /**
-   * Sets whether the IE6 history implementation will update the URL hash when
-   * creating a new item. This should be used only for applications with large
-   * DOM structures that are suffering from performance problems when creating a
-   * new history item on IE6 and 7.
-   * 
-   * @deprecated This is no longer necessary, as the underlying performance
-   *             problem has been solved. It is now a no-op.
-   */
-  @Deprecated
-  @SuppressWarnings("unused")
-  public static void setUpdateHashOnIE6(boolean updateHash) {
-  }
-
-  protected static void setToken(String token) {
-    HistoryImpl.token = token;
-  }
-
-  private JavaScriptObject oldHandler;
-
-  private HandlerManager handlers = new HandlerManager(null);
-
-  /**
-   * Adds a {@link ValueChangeEvent} handler to be informed of changes to the
-   * browser's history stack.
-   * 
-   * @param handler the handler
-   */
-  public HandlerRegistration addValueChangeHandler(
-      ValueChangeHandler<String> handler) {
-    return handlers.addHandler(ValueChangeEvent.getType(), handler);
-  }
-
-  public native void dispose() /*-{
-    $wnd.onhashchange = this.@com.google.gwt.user.client.impl.HistoryImpl::oldHandler;
-  }-*/;
-
-  public native String encodeFragment(String fragment) /*-{
-    // encodeURI() does *not* encode the '#' character.
-    return encodeURI(fragment).replace("#", "%23");
-  }-*/;
-
-  public void fireEvent(GwtEvent<?> event) {
-    handlers.fireEvent(event);
-  }
-
-  /**
-   * Fires the {@link ValueChangeEvent} to all handlers with the given tokens.
-   */
-  public void fireHistoryChangedImpl(String newToken) {
-    ValueChangeEvent.fire(this, newToken);
-  }
-
-  public HandlerManager getHandlers() {
-    return handlers;
-  }
-
-  public native boolean init() /*-{
-    var token = '';
-
-    // Get the initial token from the url's hash component.
-    var hash = $wnd.location.hash;
-    if (hash.length > 0) {
-      token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
-    }
-
-    @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token);
-
-    var historyImpl = this;
-
-    historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::oldHandler = $wnd.onhashchange;
-
-    $wnd.onhashchange = $entry(function() {
-      var token = '', hash = $wnd.location.hash;
-      if (hash.length > 0) {
-        token = historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
-      }
-
-      historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
-      var oldHandler = historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::oldHandler;
-      if (oldHandler) {
-        oldHandler();
-      }
-    });
-
-    return true;
-  }-*/;
-
-  public final void newItem(String historyToken, boolean issueEvent) {
-    historyToken = (historyToken == null) ? "" : historyToken;
-    if (!historyToken.equals(getToken())) {
-      setToken(historyToken);
-      nativeUpdate(historyToken);
-      if (issueEvent) {
-        fireHistoryChangedImpl(historyToken);
-      }
-    }
-  }
-
-  public final void newItemOnEvent(String historyToken) {
-    historyToken = (historyToken == null) ? "" : historyToken;
-    if (!historyToken.equals(getToken())) {
-      setToken(historyToken);
-      nativeUpdateOnEvent(historyToken);
-      fireHistoryChangedImpl(historyToken);
-    }
-  }
-
-  protected native String decodeFragment(String encodedFragment) /*-{
-    // decodeURI() does *not* decode the '#' character.
-    return decodeURI(encodedFragment.replace("%23", "#"));
-  }-*/;
-
-  /**
-   * The standard updateHash implementation assigns to location.hash() with an
-   * encoded history token.
-   */
-  protected native void nativeUpdate(String historyToken) /*-{
-    $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
-  }-*/;
-
-  @SuppressWarnings("unused")
-  protected void nativeUpdateOnEvent(String historyToken) {
-    // Do nothing, the hash is already updated.
-  }
-}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java b/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
deleted file mode 100644
index 197b7e6..0000000
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright 2008 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.impl;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.user.client.Window.Location;
-
-/**
- * History implementation for IE6 and IE7, which do not support the onhashchange
- * event, and for which {@link HistoryImplTimer} will not work either.
- */
-class HistoryImplIE6 extends HistoryImpl {
-
-  /**
-   * Sanitizes an untrusted string to be used in an HTML context. NOTE: This
-   * method of escaping strings should only be used on Internet Explorer.
-   * 
-   * @param maybeHtml untrusted string that may contain html
-   * @return sanitized string
-   */
-  private static String escapeHtml(String maybeHtml) {
-    final Element div = Document.get().createDivElement();
-    div.setInnerText(maybeHtml);
-    return div.getInnerHTML();
-  }
-
-  private static native Element findHistoryFrame() /*-{
-    return $doc.getElementById('__gwt_historyFrame');
-  }-*/;
-
-  /**
-   * For IE6, reading from $wnd.location.hash drops part of the fragment if the
-   * fragment contains a '?'. To avoid this bug, we use location.href instead.
-   */
-  private static native String getLocationHash() /*-{
-    var href = $wnd.location.href;
-    var hashLoc = href.lastIndexOf("#");
-    return (hashLoc > 0) ? href.substring(hashLoc) : "";
-  }-*/;
-
-  private static native Element getTokenElement(Element historyFrame) /*-{
-    // Initialize the history iframe.  If '__gwt_historyToken' already exists, then
-    // we're probably backing into the app, so _don't_ set the iframe's location.
-    if (historyFrame.contentWindow) {
-      var doc = historyFrame.contentWindow.document;
-      return doc.getElementById('__gwt_historyToken');
-    }
-  }-*/;
-
-  protected Element historyFrame;
-  private boolean reloadedWindow;
-
-  @Override
-  public boolean init() {
-    historyFrame = findHistoryFrame();
-    if (historyFrame == null) {
-      return false;
-    }
-
-    initHistoryToken();
-
-    // Initialize the history iframe. If a token element already exists, then
-    // we're probably backing into the app, so _don't_ create a new item.
-    Element tokenElement = getTokenElement(historyFrame);
-    if (tokenElement != null) {
-      setToken(getTokenElementContent(tokenElement));
-    } else {
-      navigateFrame(getToken());
-    }
-
-    injectGlobalHandler();
-    initUrlCheckTimer();
-    return true;
-  }
-
-  @Override
-  protected final void nativeUpdate(String historyToken) {
-    /*
-     * Must update the location hash since it isn't already correct.
-     */
-    updateHash(historyToken);
-    navigateFrame(historyToken);
-  }
-
-  @Override
-  protected final void nativeUpdateOnEvent(String historyToken) {
-    updateHash(historyToken);
-  }
-
-  private native String getTokenElementContent(Element tokenElement) /*-{
-    return tokenElement.innerText;
-  }-*/;
-
-  /**
-   * The URL check timer sometimes reloads the window to work around an IE bug.
-   * However, the user might cancel the page navigation, resulting in a mismatch
-   * between the current history token and the URL hash value.
-   * 
-   * @return true if a canceled window reload was handled
-   */
-  private boolean handleWindowReloadCanceled() {
-    if (reloadedWindow) {
-      reloadedWindow = false;
-      updateHash(getToken());
-      return true;
-    }
-    return false;
-  }
-
-  private native void initHistoryToken() /*-{
-    // Assume an empty token.
-    var token = '';
-    // Get the initial token from the url's hash component.
-    var hash = @com.google.gwt.user.client.impl.HistoryImplIE6::getLocationHash()();
-    if (hash.length > 0) {
-      try {
-        token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
-      } catch (e) {
-        // Clear the bad hash (this can't have been a valid token).
-        $wnd.location.hash = '';
-      }
-    }
-    @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token);
-  }-*/;
-
-  private native void initUrlCheckTimer() /*-{
-    // This is the URL check timer.  It detects when an unexpected change
-    // occurs in the document's URL (e.g. when the user enters one manually
-    // or selects a 'favorite', but only the #hash part changes).  When this
-    // occurs, we _must_ reload the page.  This is because IE has a really
-    // nasty bug that totally mangles its history stack and causes the location
-    // bar in the UI to stop working under these circumstances.
-    var historyImplRef = this;
-    var urlChecker = $entry(function() {
-      $wnd.setTimeout(urlChecker, 250);
-
-      // Reset the hash if the user cancels a window reload triggered by the 
-      // urlChecker.
-      if (historyImplRef.@com.google.gwt.user.client.impl.HistoryImplIE6::handleWindowReloadCanceled()()) {
-        return;
-      }
-
-      var hash = @com.google.gwt.user.client.impl.HistoryImplIE6::getLocationHash()();
-      if (hash.length > 0) {
-        var token = '';
-        try {
-          token = historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
-        } catch (e) {
-          // If there's a bad hash, always reload. This could only happen if
-          // if someone entered or linked to a bad url.
-          historyImplRef.@com.google.gwt.user.client.impl.HistoryImplIE6::reloadWindow()();
-        }
-
-        var historyToken = @com.google.gwt.user.client.impl.HistoryImpl::getToken()();
-        if (historyToken && (token != historyToken)) {
-          historyImplRef.@com.google.gwt.user.client.impl.HistoryImplIE6::reloadWindow()();
-        }
-      }
-    });
-    urlChecker();
-  }-*/;
-
-  private native void injectGlobalHandler() /*-{
-    var historyImplRef = this;
-    var oldOnLoad = $wnd.__gwt_onHistoryLoad;
-
-    $wnd.__gwt_onHistoryLoad = $entry(function(token) {
-      historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
-
-      if (oldOnLoad) {
-        oldOnLoad(token);
-      }
-    });
-  }-*/;
-
-  private native void navigateFrame(String token) /*-{
-    var escaped = @com.google.gwt.user.client.impl.HistoryImplIE6::escapeHtml(Ljava/lang/String;)(token);
-    var doc = this.@com.google.gwt.user.client.impl.HistoryImplIE6::historyFrame.contentWindow.document;
-    doc.open();
-    doc.write('<html><body onload="if(parent.__gwt_onHistoryLoad)parent.__gwt_onHistoryLoad(__gwt_historyToken.innerText)"><div id="__gwt_historyToken">' + escaped + '</div></body></html>');
-    doc.close();
-  }-*/;
-
-  private void reloadWindow() {
-    reloadedWindow = true;
-    Location.reload();
-  }
-
-  private native void updateHash(String token) /*-{
-    $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(token);
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java b/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
deleted file mode 100644
index 720356f..0000000
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2008 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.impl;
-
-/**
- * History implementation for Mozilla-based browsers.
- */
-class HistoryImplMozilla extends HistoryImplTimer {
-
-  @Override
-  protected String decodeFragment(String encodedFragment) {
-    // IE11 chooses the firefox permutation
-    // make sure we use the code from HistoryImpl instead of relying on FF behaviour
-    if (isIE11()) {
-      return super.decodeFragment(encodedFragment);
-    }
-    // Mozilla browsers pre-decode the result of location.hash, so there's no
-    // need to decode it again (which would in fact be incorrect).
-    return encodedFragment;
-  }
-
-  /**
-   * When the historyToken is blank or null, we are not able to set
-   * $wnd.location.hash to the empty string, due to a bug in Mozilla. Every time
-   * $wnd.location.hash is set to the empty string, one of the characters at the
-   * end of the URL stored in $wnd.location is 'eaten'. To get around this bug,
-   * we generate the module's URL, and we append a '#' character onto the end.
-   * Without the '#' character at the end of the URL, Mozilla would reload the
-   * page from the server.
-   */
-  @Override
-  protected native void nativeUpdate(String historyToken) /*-{
-    if (historyToken.length == 0) {
-      var s = $wnd.location.href;
-      // Pull off any hash.
-      var i = s.indexOf('#');
-      if (i != -1)
-        s = s.substring(0, i);
-
-      $wnd.location = s + '#';
-    } else {
-      $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
-    }
-  }-*/;
-
-  private native boolean isIE11() /*-{
-    return $wnd.navigator.userAgent.indexOf('Trident') != -1
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
deleted file mode 100644
index d3ffaa0..0000000
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2008 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.impl;
-
-/**
- * Safari implementation of
- * {@link com.google.gwt.user.client.impl.HistoryImplTimer}.
- * 
- * This implementation works on both Safari 2 and 3, by detecting the version
- * and reverting to a stub implementation for Safari 2.
- */
-class HistoryImplSafari extends HistoryImplTimer {
-
-  @Override
-  protected native void nativeUpdate(String historyToken) /*-{
-    // Safari gets into a weird state (issue 2905) when setting the hash
-    // component of the url to an empty string, but works fine as long as you
-    // at least add a '#' to the end of the url. So we get around this by
-    // recreating the url, rather than just setting location.hash.
-    $wnd.location = $wnd.location.href.split('#')[0] + '#' +
-      this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java b/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java
deleted file mode 100644
index 2015f7d..0000000
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplTimer.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2008 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.impl;
-
-/**
- * Base class for history implementations that use a timer rather than the
- * onhashchange event.
- */
-class HistoryImplTimer extends HistoryImpl {
-
-  @Override
-  public native boolean init() /*-{
-    var token = '';
-
-    // Get the initial token from the url's hash component.
-    var hash = $wnd.location.hash;
-    if (hash.length > 0) {
-      token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
-    }
-
-    @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token);
-
-    // Create the timer that checks the browser's url hash every 1/4 s.
-    var historyImpl = this;
-
-    var checkHistory = $entry(function() {
-      var token = '', hash = $wnd.location.hash;
-      if (hash.length > 0) {
-        token = historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
-      }
-
-      historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
-    });
-
-    var checkHistoryCycle = function() {
-      @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(checkHistoryCycle, 250);
-      checkHistory();
-    }
-
-    // Kick off the timer.
-    checkHistoryCycle();
-    return true;
-  }-*/;
-}
diff --git a/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/_moduleShortName_.htmlsrc b/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/_moduleShortName_.htmlsrc
index 5b730e4..7c0c58b 100644
--- a/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/_moduleShortName_.htmlsrc
+++ b/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/_moduleShortName_.htmlsrc
@@ -33,9 +33,6 @@
   <!--                                           -->
   <body>
 
-    <!-- OPTIONAL: include this if you want history support -->
-    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
-    
     <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
     <noscript>
       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
diff --git a/user/test/com/google/gwt/i18n/public_es_AR/junit-standards.html b/user/test/com/google/gwt/i18n/public_es_AR/junit-standards.html
index a998bc9..6952c9b 100644
--- a/user/test/com/google/gwt/i18n/public_es_AR/junit-standards.html
+++ b/user/test/com/google/gwt/i18n/public_es_AR/junit-standards.html
@@ -63,7 +63,6 @@
 loadSelectionScript();
 -->
 </script>
-<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
 <noscript>
   <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
     Your web browser must have JavaScript enabled
diff --git a/user/test/com/google/gwt/i18n/public_es_AR/junit.html b/user/test/com/google/gwt/i18n/public_es_AR/junit.html
index 31c0114..f0782cc 100644
--- a/user/test/com/google/gwt/i18n/public_es_AR/junit.html
+++ b/user/test/com/google/gwt/i18n/public_es_AR/junit.html
@@ -62,7 +62,6 @@
 loadSelectionScript();
 -->
 </script>
-<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
 <noscript>
   <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
     Your web browser must have JavaScript enabled
diff --git a/user/test/com/google/gwt/i18n/public_es_MX/junit-standards.html b/user/test/com/google/gwt/i18n/public_es_MX/junit-standards.html
index db0b393..05bcd9d 100644
--- a/user/test/com/google/gwt/i18n/public_es_MX/junit-standards.html
+++ b/user/test/com/google/gwt/i18n/public_es_MX/junit-standards.html
@@ -63,7 +63,6 @@
 loadSelectionScript();
 -->
 </script>
-<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
 <noscript>
   <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
     Your web browser must have JavaScript enabled
diff --git a/user/test/com/google/gwt/i18n/public_es_MX/junit.html b/user/test/com/google/gwt/i18n/public_es_MX/junit.html
index 924012c..adc2908 100644
--- a/user/test/com/google/gwt/i18n/public_es_MX/junit.html
+++ b/user/test/com/google/gwt/i18n/public_es_MX/junit.html
@@ -62,7 +62,6 @@
 loadSelectionScript();
 -->
 </script>
-<iframe src="javascript:''" id='__gwt_historyFrame' style='position:absolute;width:0;height:0;border:0'></iframe>
 <noscript>
   <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
     Your web browser must have JavaScript enabled
diff --git a/user/test/com/google/gwt/user/HistoryDisabledTest.gwt.xml b/user/test/com/google/gwt/user/HistoryDisabledTest.gwt.xml
deleted file mode 100644
index 8e9cd65..0000000
--- a/user/test/com/google/gwt/user/HistoryDisabledTest.gwt.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--                                                                        -->
-<!-- Copyright 2010 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 type="fileset">
-  <inherits name="com.google.gwt.user.User"/>
-
-  <!-- Replaces HistoryImpl with one that is disabled and does not -->
-  <!-- initialize.                                                 -->
-  <replace-with class="com.google.gwt.user.client.impl.HistoryImplDisabled">
-    <when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
-  </replace-with>
-</module>
diff --git a/user/test/com/google/gwt/user/MiscSuite.java b/user/test/com/google/gwt/user/MiscSuite.java
index 1ad8318..018aaa9 100644
--- a/user/test/com/google/gwt/user/MiscSuite.java
+++ b/user/test/com/google/gwt/user/MiscSuite.java
@@ -24,7 +24,6 @@
 import com.google.gwt.user.client.DragAndDropEventsSinkTest;
 import com.google.gwt.user.client.EventTest;
 import com.google.gwt.user.client.GestureEventSinkTest;
-import com.google.gwt.user.client.HistoryDisabledTest;
 import com.google.gwt.user.client.TimerTest;
 import com.google.gwt.user.client.TouchEventSinkTest;
 import com.google.gwt.user.client.WindowTest;
@@ -51,7 +50,6 @@
     suite.addTestSuite(DragAndDropEventsSinkTest.class);
     suite.addTestSuite(EventTest.class);
     suite.addTestSuite(GestureEventSinkTest.class);
-    suite.addTestSuite(HistoryDisabledTest.class);
     suite.addTestSuite(ImageBundleGeneratorTest.class);
     suite.addTestSuite(LayoutTest.class);
     suite.addTestSuite(TimerTest.class);
diff --git a/user/test/com/google/gwt/user/client/HistoryDisabledTest.java b/user/test/com/google/gwt/user/client/HistoryDisabledTest.java
deleted file mode 100644
index 4b4b138..0000000
--- a/user/test/com/google/gwt/user/client/HistoryDisabledTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2010 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.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.junit.client.GWTTestCase;
-
-/**
- * Tests for {@link History} when History is disabled. Most of these tests are
- * just assuring that we don't hit an NPE or JS error.
- */
-public class HistoryDisabledTest extends GWTTestCase {
-
-  @Override
-  public String getModuleName() {
-    return "com.google.gwt.user.HistoryDisabledTest";
-  }
-
-  @SuppressWarnings("deprecation")
-  public void testAddHistoryListener() {
-    HistoryListener listener = new HistoryListener() {
-      public void onHistoryChanged(String historyToken) {
-      }
-    };
-    History.addHistoryListener(listener);
-    History.removeHistoryListener(listener);
-  }
-
-  public void testAddValueChangeHandler() {
-    HandlerRegistration reg = History.addValueChangeHandler(new ValueChangeHandler<String>() {
-      public void onValueChange(ValueChangeEvent<String> event) {
-      }
-    });
-    assertNull(reg);
-  }
-
-  public void testFireCurrentHistoryState() {
-    HandlerRegistration reg = History.addValueChangeHandler(new ValueChangeHandler<String>() {
-      public void onValueChange(ValueChangeEvent<String> event) {
-        fail("Handler should not have been added.");
-      }
-    });
-    assertNull(reg);
-    History.fireCurrentHistoryState();
-  }
-
-  @SuppressWarnings("deprecation")
-  public void testOnHistoryChanged() {
-    HandlerRegistration reg = History.addValueChangeHandler(new ValueChangeHandler<String>() {
-      public void onValueChange(ValueChangeEvent<String> event) {
-        fail("Handler should not have been added.");
-      }
-    });
-    assertNull(reg);
-    History.onHistoryChanged("test");
-  }
-
-  public void testGetToken() {
-    assertEquals("", History.getToken());
-  }
-
-  public void testNewItem() {
-    HandlerRegistration reg = History.addValueChangeHandler(new ValueChangeHandler<String>() {
-      public void onValueChange(ValueChangeEvent<String> event) {
-        fail("Handler should not have been added.");
-      }
-    });
-    assertNull(reg);
-
-    History.newItem("test");
-    assertEquals("", History.getToken());
-    History.newItem("test", true);
-    assertEquals("", History.getToken());
-  }
-}
diff --git a/user/test/com/google/gwt/user/client/impl/HistoryImplDisabled.java b/user/test/com/google/gwt/user/client/impl/HistoryImplDisabled.java
deleted file mode 100644
index d51a535..0000000
--- a/user/test/com/google/gwt/user/client/impl/HistoryImplDisabled.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2010 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.impl;
-
-/**
- * An implementation of history that is disabled.
- */
-public class HistoryImplDisabled extends HistoryImpl {
-  @Override
-  public boolean init() {
-    return false;
-  }
-}
diff --git a/user/test/com/google/gwt/user/client/ui/HistoryTest.java b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
index 9706aa5..0bd90d0 100644
--- a/user/test/com/google/gwt/user/client/ui/HistoryTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
@@ -17,12 +17,15 @@
 
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
 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.History;
-import com.google.gwt.user.client.HistoryListener;
 import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
 
 import java.util.ArrayList;
 
@@ -33,51 +36,17 @@
  */
 public class HistoryTest extends GWTTestCase {
 
-  private static native String getCurrentLocationHash() /*-{
-    var href = $wnd.location.href;
-
-    splitted = href.split("#");
-    if (splitted.length != 2) {
-      return null;
+  private static String getCurrentLocationHash() {
+    // Firefox automatically decodes location.hash so parse it from Window.Location.getHref
+    String href = Window.Location.getHref();
+    String[] split = href.split("#");
+    if (split.length != 2) {
+      fail("can not read history token");
     }
+    return split[1];
+  }
 
-    hashPortion = splitted[1];
-
-    return hashPortion;
-  }-*/;
-
-  /*
-   * Copied from UserAgentPropertyGenerator and HistoryImplSafari.
-   */
-  private static native boolean isSafari2() /*-{
-    var ua = navigator.userAgent;
-    
-    // copied from UserAgentPropertyGenerator
-    if (ua.indexOf("webkit") == -1) {
-      return false;
-    }
-    
-    // copied from HistoryImplSafari
-    var exp = / AppleWebKit\/([\d]+)/;
-    var result = exp.exec(ua);
-    if (result) {
-      // The standard history implementation works fine on WebKit >= 522
-      // (Safari 3 beta).
-      if (parseInt(result[1]) >= 522) {
-        return false;
-      }
-    }
-  
-    // The standard history implementation works just fine on the iPhone, which
-    // unfortunately reports itself as WebKit/420+.
-    if (ua.indexOf('iPhone') != -1) {
-      return false;
-    }
-  
-    return true;
-  }-*/;
-
-  private HistoryListener historyListener;
+  private HandlerRegistration handlerRegistration;
   private Timer timer;
 
   @Override
@@ -90,9 +59,11 @@
     final String escToken = "%24%24%24";
 
     delayTestFinish(5000);
-    addHistoryListenerImpl(new HistoryListener() {
-      public void onHistoryChanged(String token) {
-        assertEquals(escToken, token);
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        assertEquals(escToken, event.getValue());
         finishTest();
       }
     });
@@ -106,9 +77,11 @@
   public void testEmptyHistoryTokens() {
     delayTestFinish(5000);
 
-    addHistoryListenerImpl(new HistoryListener() {
-      public void onHistoryChanged(String historyToken) {
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
 
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        String historyToken = event.getValue();
         if (historyToken == null) {
           fail("historyToken should not be null");
         }
@@ -133,21 +106,24 @@
    */
   public void testNoEvents() {
     delayTestFinish(5000);
-    addHistoryListenerImpl(new HistoryListener() {
-      {
-        timer = new Timer() {
-          public void run() {
-            finishTest();
-          }
-        };
-        timer.schedule(500);
-      }
 
-      public void onHistoryChanged(String historyToken) {
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
         fail("onHistoryChanged should not have been called");
       }
     });
+
     History.newItem("testNoEvents", false);
+
+    timer = new Timer() {
+      @Override
+      public void run() {
+        finishTest();
+      }
+    };
+    timer.schedule(500);
   }
 
   /*
@@ -156,11 +132,6 @@
    */
   @DoNotRunWith(Platform.HtmlUnitUnknown)
   public void testHistory() {
-    if (isSafari2()) {
-      // History.back() is broken on Safari2, so we skip this test.
-      return;
-    }
-    
     /*
      * Sentinel token which should only be seen if tokens are lost during the
      * rest of the test. Without this, History.back() might send the browser too
@@ -168,25 +139,31 @@
      */
     History.newItem("if-you-see-this-then-history-went-back-too-far");
 
+    final String historyToken1 = "token 1";
+    final String historyToken2 = "token 2";
     delayTestFinish(10000);
-    addHistoryListenerImpl(new HistoryListener() {
+
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
       private int state = 0;
 
-      public void onHistoryChanged(String historyToken) {
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        String historyToken = event.getValue();
         switch (state) {
           case 0: {
-            if (!historyToken.equals("foo bar")) {
-              fail("Expecting token 'foo bar', but got: " + historyToken);
+            if (!historyToken.equals(historyToken1)) {
+              fail("Expecting token '" + historyToken1 + "', but got: " + historyToken);
             }
 
             state = 1;
-            History.newItem("baz");
+            History.newItem(historyToken2);
             break;
           }
 
           case 1: {
-            if (!historyToken.equals("baz")) {
-              fail("Expecting token 'baz', but got: " + historyToken);
+            if (!historyToken.equals(historyToken2)) {
+              fail("Expecting token '" + historyToken2 + "', but got: " + historyToken);
             }
 
             state = 2;
@@ -195,8 +172,8 @@
           }
 
           case 2: {
-            if (!historyToken.equals("foo bar")) {
-              fail("Expecting token 'foo bar' after History.back(), but got: " + historyToken);
+            if (!historyToken.equals(historyToken1)) {
+              fail("Expecting token '" + historyToken1 + "', but got: " + historyToken);
             }
             finishTest();
             break;
@@ -204,28 +181,20 @@
         }
       }
     });
-    
-    /*
-     * Delay kicking off the history transitions, so the browser has time to process
-     * the initial sentinel token
-     */
-    new Timer() {
-      @Override
-      public void run() {
-        History.newItem("foo bar");
-      }
-    }.schedule(5000);
+
+    History.newItem(historyToken1);
   }
 
   /**
-   * Verify that {@link HistoryListener#onHistoryChanged(String)} is only
-   * called once per {@link History#newItem(String)}. 
+   * Verify that {@link ValueChangeHandler#onValueChange(ValueChangeEvent)}
+   * is only called once per {@link History#newItem(String)}.
    */
   public void testHistoryChangedCount() {
     delayTestFinish(5000);
     timer = new Timer() {
       private int count = 0;
       
+      @Override
       public void run() {
         if (count++ == 0) {
           // verify that duplicates don't issue another event
@@ -236,10 +205,11 @@
         }
       }
     };
-    addHistoryListenerImpl(new HistoryListener() {
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
       final ArrayList<Object> counter = new ArrayList<Object>();
 
-      public void onHistoryChanged(String historyToken) {
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
         counter.add(null);
         if (counter.size() != 1) {
           fail("onHistoryChanged called multiple times");
@@ -248,6 +218,7 @@
         timer.schedule(500);
       }
     });
+
     History.newItem("testHistoryChangedCount");
   }
 
@@ -256,19 +227,43 @@
     final String shouldBeEncodedAs = "%25%20%5E%5B%5D%7C%22%3C%3E%7B%7D%5C";
 
     delayTestFinish(5000);
-    addHistoryListenerImpl(new HistoryListener() {
-      public void onHistoryChanged(String token) {
-        if (!isSafari2()) {
-          // Safari2 does not update the URL, so we don't verify it
-          assertEquals(shouldBeEncodedAs, getCurrentLocationHash());
-        }
-        assertEquals(shouldBeEncoded, token);
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        assertEquals(shouldBeEncodedAs, getCurrentLocationHash());
+        assertEquals(shouldBeEncoded, event.getValue());
         finishTest();
       }
     });
     History.newItem(shouldBeEncoded);
   }
 
+  /**
+   * Test to make sure that there is no double unescaping of hash values.
+   * See https://bugzilla.mozilla.org/show_bug.cgi?id=483304
+   */
+  @DoNotRunWith(Platform.HtmlUnitUnknown)
+  public void testNoDoubleTokenUnEscaping() {
+    final String shouldBeEncoded = "abc%20abc";
+
+    delayTestFinish(5000);
+
+    History.newItem(shouldBeEncoded);
+    History.newItem("someOtherToken");
+    History.back();
+    // allow browser to update the url
+    timer = new Timer() {
+      @Override
+      public void run() {
+        // make sure that value in url actually matches the original token
+        assertEquals(shouldBeEncoded, History.getToken());
+        finishTest();
+      }
+    };
+    timer.schedule(200);
+  }
+
   /*
    * HtmlUnit reports:
    *   expected=abc;,/?:@&=+$-_.!~*()ABC123foo
@@ -279,16 +274,15 @@
     final String shouldNotChange = "abc;,/?:@&=+$-_.!~*()ABC123foo";
 
     delayTestFinish(5000);
-    addHistoryListenerImpl(new HistoryListener() {
-      public void onHistoryChanged(String token) {
-        if (!isSafari2()) {
-          // Safari2 does not update the URL, so we don't verify it
-          assertEquals(shouldNotChange, getCurrentLocationHash());
-        }
-        assertEquals(shouldNotChange, token);
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        assertEquals(shouldNotChange, event.getValue());
         finishTest();
       }
     });
+
     History.newItem(shouldNotChange);
   }
 
@@ -301,8 +295,11 @@
     delayTestFinish(5000);
     final String token = "foo?bar";
 
-    addHistoryListenerImpl(new HistoryListener() {
-      public void onHistoryChanged(String historyToken) {
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
+        String historyToken = event.getValue();
         if (historyToken == null) {
           fail("historyToken should not be null");
         }
@@ -310,6 +307,7 @@
         finishTest();
       }
     });
+
     History.newItem(token);
   }
 
@@ -323,8 +321,10 @@
   public void testEmptyHistoryToken() {
     final ArrayList<Object> counter = new ArrayList<Object>();
 
-    addHistoryListenerImpl(new HistoryListener() {
-      public void onHistoryChanged(String historyToken) {
+    addHistoryListenerImpl(new ValueChangeHandler<String>() {
+
+      @Override
+      public void onValueChange(ValueChangeEvent<String> event) {
         counter.add(new Object());
         assertFalse("Browser is borked by empty history token", isBorked());
       }
@@ -345,9 +345,9 @@
 
   @Override
   protected void gwtTearDown() throws Exception {
-    if (historyListener != null) {
-      History.removeHistoryListener(historyListener);
-      historyListener = null;
+    if (handlerRegistration != null) {
+      handlerRegistration.removeHandler();
+      handlerRegistration = null;
     }
     if (timer != null) {
       timer.cancel();
@@ -355,8 +355,7 @@
     }
   }
 
-  private void addHistoryListenerImpl(HistoryListener historyListener) {
-    this.historyListener = historyListener;
-    History.addHistoryListener(historyListener);
-  }  
+  private void addHistoryListenerImpl(ValueChangeHandler<String> handler) {
+    this.handlerRegistration = History.addValueChangeHandler(handler);
+  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/PopupHistoryDisabledTest.java b/user/test/com/google/gwt/user/client/ui/PopupHistoryDisabledTest.java
deleted file mode 100644
index 9e42b22..0000000
--- a/user/test/com/google/gwt/user/client/ui/PopupHistoryDisabledTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2010 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.ui;
-
-import com.google.gwt.junit.DoNotRunWith;
-import com.google.gwt.junit.Platform;
-import com.google.gwt.junit.client.GWTTestCase;
-
-/**
- * Tests for {@link PopupPanel} when history is disabled.
- */
-@DoNotRunWith(Platform.HtmlUnitBug)
-public class PopupHistoryDisabledTest extends GWTTestCase {
-
-  @Override
-  public String getModuleName() {
-    return "com.google.gwt.user.HistoryDisabledTest";
-  }
-
-  /**
-   * Issue 4584: Make sure that we do not throw an NPE when history is disabled.
-   */
-  public void testShowHide() {
-    PopupPanel p = new PopupPanel(true, false);
-    p.show();
-    p.hide();
-  }
-}