Fixes Safari history issues stemming from setting location.hash to the empty
string.

Patch by: jgw
Review by: jat
Issues: 2905, 2909



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3681 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
index a37a6c2..2f72256 100644
--- a/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
+++ b/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
@@ -59,9 +59,9 @@
   @Override
   protected void nativeUpdate(String historyToken) {
     if (isOldSafari) {
-      nativeUpdateImpl(historyToken);
+      oldNativeUpdate(historyToken);
     } else {
-      super.nativeUpdate(historyToken);
+      newNativeUpdate(historyToken);
     }
   }
 
@@ -79,7 +79,16 @@
     @com.google.gwt.user.client.impl.HistoryImpl::fireHistoryChangedImpl(Ljava/lang/String;)($wnd.__gwt_historyToken);
   }-*/;
 
-  private native void nativeUpdateImpl(String historyToken) /*-{
+  private native void newNativeUpdate(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);
+  }-*/;
+
+  private native void oldNativeUpdate(String historyToken) /*-{
     // Use a bizarre meta refresh trick to update the url's hash, without
     // creating a history entry.
     var meta = $doc.createElement('meta');
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 aff7103..cdafbe8 100644
--- a/user/test/com/google/gwt/user/client/ui/HistoryTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
@@ -15,13 +15,15 @@
  */
 package com.google.gwt.user.client.ui;
 
+import java.util.ArrayList;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
 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 java.util.ArrayList;
-
 /**
  * Tests for the history system.
  * 
@@ -277,6 +279,33 @@
     History.newItem(token);
   }
 
+  /**
+   * Test that using an empty history token works properly. There have been
+   * problems (see issue 2905) with this in the past on Safari.
+   */
+  public void testEmptyHistoryToken() {
+    final ArrayList<Object> counter = new ArrayList<Object>();
+
+    addHistoryListenerImpl(new HistoryListener() {
+      public void onHistoryChanged(String historyToken) {
+        counter.add(new Object());
+        assertFalse("Browser is borked by empty history token", isBorked());
+      }
+    });
+
+    History.newItem("x");
+    History.newItem("");
+
+    assertEquals("Expected two history events", 2, counter.size());
+  }
+
+  // Used by testEmptyHistoryToken() to catch a bizarre failure mode on Safari.
+  private static boolean isBorked() {
+    Element e = Document.get().createDivElement();
+    e.setInnerHTML("string");
+    return e.getInnerHTML().length() == 0;
+  }
+
   @Override
   protected void gwtTearDown() throws Exception {
     if (historyListener != null) {