Make history token encoding configurable.
Change-Id: Iead6f6196dfe992c3182a3882afb2f08cee78552
diff --git a/user/src/com/google/gwt/user/History.gwt.xml b/user/src/com/google/gwt/user/History.gwt.xml
index 7a003a5..ea649c9 100644
--- a/user/src/com/google/gwt/user/History.gwt.xml
+++ b/user/src/com/google/gwt/user/History.gwt.xml
@@ -20,6 +20,19 @@
<inherits name="com.google.gwt.core.Core"/>
<inherits name="com.google.gwt.user.UserAgent"/>
+ <!-- Option to disable GWT double encoding of history tokens. -->
+ <!-- This double encoding is necessary to make History work across -->
+ <!-- browsers (especially Firefox). -->
+ <!-- If applications choose to disable double encoding they are responsible -->
+ <!-- for ensuring that only tokens are used that work across browsers. -->
+ <define-property name="history.noDoubleEncoding" values="true,false" />
+ <set-property name="history.noDoubleEncoding" value="false" />
+
+ <replace-with class="com.google.gwt.user.client.History.NoopHistoryTokenEncoder">
+ <when-type-is class="com.google.gwt.user.client.History.HistoryTokenEncoder" />
+ <when-property-is name="history.noDoubleEncoding" value="true" />
+ </replace-with>
+
<!-- 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" />
diff --git a/user/src/com/google/gwt/user/client/History.java b/user/src/com/google/gwt/user/client/History.java
index b9d675f..5f3a167 100644
--- a/user/src/com/google/gwt/user/client/History.java
+++ b/user/src/com/google/gwt/user/client/History.java
@@ -83,6 +83,38 @@
}
/**
+ * HistoryTokenEncoder is responsible for encoding and decoding history token,
+ * thus ensuring that tokens are safe to use in the browsers URL.
+ */
+ private static class HistoryTokenEncoder {
+ public native String encode(String toEncode) /*-{
+ // encodeURI() does *not* encode the '#' character.
+ return $wnd.encodeURI(toEncode).replace("#", "%23");
+ }-*/;
+
+ public native String decode(String toDecode) /*-{
+ return $wnd.decodeURI(toDecode.replace("%23", "#"));
+ }-*/;
+ }
+
+ /**
+ * NoopHistoryTokenEncoder does not perform any encoding.
+ */
+ // Used from rebinding
+ @SuppressWarnings("unused")
+ private static class NoopHistoryTokenEncoder extends HistoryTokenEncoder {
+ @Override
+ public String encode(String toEncode) {
+ return toEncode;
+ }
+
+ @Override
+ public String decode(String toDecode) {
+ return toDecode;
+ }
+ }
+
+ /**
* History implementation using hash tokens.
* <p>This is the default implementation for all browsers except IE8.
*/
@@ -107,17 +139,6 @@
public void replaceToken(String historyToken) {
Window.Location.replace("#" + historyToken);
}
-
- // Only kept in deferred binding to allow mocking frameworks to intercept calls
- public native String decodeHistoryToken(String historyToken) /*-{
- return $wnd.decodeURI(historyToken.replace("%23", "#"));
- }-*/;
-
- // Only kept in deferred binding to allow mocking frameworks to intercept calls
- public native String encodeHistoryToken(String historyToken) /*-{
- // encodeURI() does *not* encode the '#' character.
- return $wnd.encodeURI(historyToken).replace("#", "%23");
- }-*/;
}
/**
@@ -177,6 +198,7 @@
private static HistoryImpl impl = GWT.create(HistoryImpl.class);
private static HistoryEventSource historyEventSource = new HistoryEventSource();
+ private static HistoryTokenEncoder tokenEncoder = GWT.create(HistoryTokenEncoder.class);
private static String token = getDecodedHash();
/**
@@ -216,7 +238,7 @@
* @return the encoded token, suitable for use as part of a URI
*/
public static String encodeHistoryToken(String historyToken) {
- return impl.encodeHistoryToken(historyToken);
+ return tokenEncoder.encode(historyToken);
}
/**
@@ -360,7 +382,7 @@
if (hashToken == null || hashToken.isEmpty()) {
return "";
}
- return impl.decodeHistoryToken(hashToken.substring(1));
+ return tokenEncoder.decode(hashToken.substring(1));
}
// this is called from JS when the native onhashchange occurs
diff --git a/user/test/com/google/gwt/user/HistoryTestNoopTokenEncoder.gwt.xml b/user/test/com/google/gwt/user/HistoryTestNoopTokenEncoder.gwt.xml
new file mode 100644
index 0000000..edc6b29
--- /dev/null
+++ b/user/test/com/google/gwt/user/HistoryTestNoopTokenEncoder.gwt.xml
@@ -0,0 +1,18 @@
+<!-- -->
+<!-- Copyright 2014 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"/>
+ <set-property name="history.noDoubleEncoding" value="true" />
+</module>
diff --git a/user/test/com/google/gwt/user/UiPart2Suite.java b/user/test/com/google/gwt/user/UiPart2Suite.java
index 8ba4117..05ff391 100644
--- a/user/test/com/google/gwt/user/UiPart2Suite.java
+++ b/user/test/com/google/gwt/user/UiPart2Suite.java
@@ -21,6 +21,7 @@
import com.google.gwt.user.client.ui.HeaderPanelTest;
import com.google.gwt.user.client.ui.HiddenTest;
import com.google.gwt.user.client.ui.HistoryTest;
+import com.google.gwt.user.client.ui.HistoryTestNoopTokenEncoder;
import com.google.gwt.user.client.ui.HorizontalPanelTest;
import com.google.gwt.user.client.ui.HorizontalSplitPanelTest;
import com.google.gwt.user.client.ui.HyperlinkTest;
@@ -85,6 +86,7 @@
suite.addTestSuite(HeaderPanelTest.class);
suite.addTestSuite(HiddenTest.class);
suite.addTestSuite(HistoryTest.class);
+ suite.addTestSuite(HistoryTestNoopTokenEncoder.class);
suite.addTestSuite(HorizontalPanelTest.class);
suite.addTestSuite(HorizontalSplitPanelTest.class);
suite.addTestSuite(HTMLPanelTest.class);
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 9093f4a..c3985e9 100644
--- a/user/test/com/google/gwt/user/client/ui/HistoryTest.java
+++ b/user/test/com/google/gwt/user/client/ui/HistoryTest.java
@@ -56,6 +56,14 @@
return "com.google.gwt.user.User";
}
+ protected String getHistoryToken2() {
+ return "token 2";
+ }
+
+ protected String getHistoryToken2_encoded() {
+ return "token%202";
+ }
+
// TODO(dankurka): Fix up HTML unit hash change handling
@DoNotRunWith(Platform.HtmlUnitUnknown)
public void testClickLink() {
@@ -176,8 +184,8 @@
*/
History.newItem("if-you-see-this-then-history-went-back-too-far");
- final String historyToken1 = "token 1";
- final String historyToken2 = "token 2";
+ final String historyToken1 = "token1";
+ final String historyToken2 = getHistoryToken2();
delayTestFinish(10000);
addHistoryListenerImpl(new ValueChangeHandler<String>() {
@@ -243,12 +251,11 @@
}
};
addHistoryListenerImpl(new ValueChangeHandler<String>() {
- final ArrayList<Object> counter = new ArrayList<Object>();
+ private int count = 0;
@Override
public void onValueChange(ValueChangeEvent<String> event) {
- counter.add(null);
- if (counter.size() != 1) {
+ if (count++ != 0) {
fail("onHistoryChanged called multiple times");
}
// wait 500ms to see if we get called multiple times
@@ -268,9 +275,9 @@
*/
History.newItem("if-you-see-this-then-history-went-back-too-far");
- final String historyToken1 = "token 1";
- final String historyToken2 = "token 2";
- final String historyToken3 = "token 3";
+ final String historyToken1 = "token1";
+ final String historyToken2 = getHistoryToken2();
+ final String historyToken3 = "token3";
delayTestFinish(10000);
@@ -337,9 +344,10 @@
* app containing our test module.
*/
History.newItem("if-you-see-this-then-history-went-back-too-far");
- final String historyToken1 = "token 1";
- final String historyToken2 = "token 2";
- final String historyToken2_encoded = "token%202";
+
+ final String historyToken1 = "token1";
+ final String historyToken2 = getHistoryToken2();
+ final String historyToken2_encoded = getHistoryToken2_encoded();
History.newItem(historyToken1);
diff --git a/user/test/com/google/gwt/user/client/ui/HistoryTestNoopTokenEncoder.java b/user/test/com/google/gwt/user/client/ui/HistoryTestNoopTokenEncoder.java
new file mode 100644
index 0000000..0e54ae5
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/HistoryTestNoopTokenEncoder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014 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;
+
+/**
+ * Tests for the history system without encoding of history tokens.
+ */
+public class HistoryTestNoopTokenEncoder extends HistoryTest {
+
+ private static native boolean isFirefox() /*-{
+ var ua = navigator.userAgent.toLowerCase();
+ var docMode = $doc.documentMode;
+ return (ua.indexOf('gecko') != -1 && typeof(docMode) == 'undefined');
+ }-*/;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.HistoryTestNoopTokenEncoder";
+ }
+
+ protected String getHistoryToken2() {
+ return isFirefox() ? "token2" : "token 2";
+ }
+
+ protected String getHistoryToken2_encoded() {
+ return isFirefox() ? "token2" : "token%202";
+ }
+
+ public void testTokenEscaping() {
+ if (isFirefox()) {
+ return; // encoding is broken for Firefox.
+ }
+ super.testTokenEscaping();
+ }
+}