[google-web-toolkit commit] r3234 - in releases/1.5: samples/showcase/src/com/google/gwt/sample/showcase/client user/src/com/...

4 views
Skip to first unread message

codesite...@google.com

unread,
Jul 16, 2008, 7:09:47 PM7/16/08
to gwt...@gmail.com
Author: j...@google.com
Date: Wed Jul 16 16:09:09 2008
New Revision: 3234

Removed:
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplOpera.java
Modified:
releases/1.5/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java
releases/1.5/user/src/com/google/gwt/user/History.gwt.xml
releases/1.5/user/src/com/google/gwt/user/client/History.java
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplFrame.java
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
releases/1.5/user/test/com/google/gwt/user/UISuite.java
releases/1.5/user/test/com/google/gwt/user/client/ui/HistoryTest.java

Log:
Refactor history support to fix a problem with how onHistoryChanged was being
called, which meant that exceptions in the listener could be silently dropped.
As part of the refactor, we added the ability to create a history item that
will not result in a call to onHistoryChanged (issue 99), and made programmatic
calls to newItem result in synchronous calls to onHistoryChanged
(rather than
having to wait for a timer to notice the change).

Issue: 99
Patch by: jat, scottb
Review by: jgw

Modified: releases/1.5/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java
==============================================================================
---
releases/1.5/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java (original)
+++
releases/1.5/samples/showcase/src/com/google/gwt/sample/showcase/client/Showcase.java
Wed Jul 16 16:09:09 2008
@@ -175,16 +175,6 @@
private Application app;

/**
- * When the user selects a {@link TreeItem} from the main menu, we
- * synchronously change the current example and add a history token for
- * history support. However, adding the history token causes the history
- * changed event to fire, which normally selects the associated menu
item and
- * displays the example. We don't want to repeat this twice, so we
use a
- * boolean to indicate that we want to ignore the history changed event.
- */
- private boolean ignoreNextHistoryEvent = false;
-
- /**
* A mapping of history tokens to their associated menu items.
*/
private Map<String, TreeItem> itemTokens = new HashMap<String, TreeItem>();
@@ -224,12 +214,6 @@
// Setup a history listener to reselect the associate menu item
final HistoryListener historyListener = new HistoryListener() {
public void onHistoryChanged(String historyToken) {
- // Ignore the event if the user selected the content from the
main menu
- if (ignoreNextHistoryEvent) {
- ignoreNextHistoryEvent = false;
- return;
- }
-
TreeItem item = itemTokens.get(historyToken);
if (item != null) {
// Select the associated TreeItem
@@ -248,11 +232,6 @@
public void onMenuItemSelected(TreeItem item) {
ContentWidget content = itemWidgets.get(item);
if (content != null && !content.equals(app.getContent())) {
- // Show the new example
- displayContentWidget(content);
-
- // Update the history token, but ignore the next history event
- ignoreNextHistoryEvent = true;
History.newItem(getContentWidgetToken(content));
}
}

Modified: releases/1.5/user/src/com/google/gwt/user/History.gwt.xml
==============================================================================
--- releases/1.5/user/src/com/google/gwt/user/History.gwt.xml (original)
+++ releases/1.5/user/src/com/google/gwt/user/History.gwt.xml Wed Jul
16 16:09:09 2008
@@ -1,5 +1,5 @@
<!--
-->
-<!-- Copyright 2007 Google Inc.
-->
+<!-- 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 -->
<!-- may obtain a copy of the License at
-->
@@ -46,10 +46,4 @@
<when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
<when-property-is name="user.agent" value="safari"/>
</replace-with>
-
- <!-- Opera has yet another history implementation. -->
- <replace-with class="com.google.gwt.user.client.impl.HistoryImplOpera">
- <when-type-is class="com.google.gwt.user.client.impl.HistoryImpl"/>
- <when-property-is name="user.agent" value="opera"/>
- </replace-with>
</module>

Modified: releases/1.5/user/src/com/google/gwt/user/client/History.java
==============================================================================
--- releases/1.5/user/src/com/google/gwt/user/client/History.java (original)
+++ releases/1.5/user/src/com/google/gwt/user/client/History.java Wed
Jul 16 16:09:09 2008
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -16,11 +16,8 @@
package com.google.gwt.user.client;

import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.user.client.impl.HistoryImpl;

-import java.util.ArrayList;
-
/**
* This class allows you to interact with the browser's history stack. Each
* "item" on the stack is represented by a single string, referred to
as a
@@ -39,10 +36,24 @@
* <h3>Example</h3>
* {@example com.google.gwt.examples.HistoryExample}
* </p>
+ *
+ * <p>
+ * <h3>URL Encoding</h3>
+ * Any valid characters may be used in the history token and will survive
+ * round-trips through newItem to getToken()/onHistoryChanged(), but
most will
+ * be encoded in the user-visible URL. The following US-ASCII
characters are not
+ * encoded on any currently supported browser (but may be in the
future due to
+ * future browser changes):
+ * <ul>
+ * <li>a-z
+ * <li>A-Z
+ * <li>0-9
+ * <li>;,/?:@&=+$-_.!~*()
+ * </ul>
+ * </p>
*/
public class History {

- private static ArrayList<HistoryListener> historyListeners = new ArrayList<HistoryListener>();
private static HistoryImpl impl;

static {
@@ -55,7 +66,8 @@
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 src=\"javascript:''\" id='__gwt_historyFrame' "
+ + "style='position:absolute;width:0;height:0;border:0'>"
+ "</iframe>", null);
}
}
@@ -66,11 +78,13 @@
* @param listener the listener to be added
*/
public static void addHistoryListener(HistoryListener listener) {
- historyListeners.add(listener);
+ HistoryImpl.addHistoryListener(listener);
}

/**
* 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();
@@ -94,29 +108,33 @@
* @return the initial token, or the empty string if none is present.
*/
public static String getToken() {
- return impl != null ? impl.getToken() : "";
+ return impl != null ? HistoryImpl.getToken() : "";
}

/**
* Adds a new browser history entry. In hosted mode, the 'back' and 'forward'
* actions are accessible via the standard Alt-Left and Alt-Right keystrokes.
- * Calling this method will cause {@link #onHistoryChanged} to be
called as
- * well.
+ * Calling this method will cause {@link
HistoryListener#onHistoryChanged} to
+ * be called as well.
*
* @param historyToken the token to associate with the new history item
*/
public static void newItem(String historyToken) {
- if (impl != null) {
- impl.newItem(historyToken);
- }
+ newItem(historyToken, true);
}

- public static void onHistoryChanged(String historyToken) {
- UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
- if (handler != null) {
- fireHistoryChangedAndCatch(historyToken, handler);
- } else {
- fireHistoryChangedImpl(historyToken);
+ /**
+ * Adds a new browser history entry. In hosted mode, the 'back' and 'forward'
+ * actions are accessible via the standard Alt-Left and Alt-Right keystrokes.
+ * Calling this method will cause {@link
HistoryListener#onHistoryChanged} to
+ * be called as well if and only if issueEvent is true.
+ *
+ * @param historyToken the token to associate with the new history item
+ * @param issueEvent true if an onHistoryChanged event should be issued
+ */
+ public static void newItem(String historyToken, boolean issueEvent) {
+ if (impl != null) {
+ impl.newItem(historyToken, issueEvent);
}
}

@@ -126,21 +144,6 @@
* @param listener the listener to be removed
*/
public static void removeHistoryListener(HistoryListener listener) {
- historyListeners.remove(listener);
- }
-
- private static void fireHistoryChangedAndCatch(String historyToken,
- UncaughtExceptionHandler handler) {
- try {
- fireHistoryChangedImpl(historyToken);
- } catch (Throwable e) {
- handler.onUncaughtException(e);
- }
- }
-
- private static void fireHistoryChangedImpl(String historyToken) {
- for (HistoryListener listener : historyListeners) {
- listener.onHistoryChanged(historyToken);
- }
+ HistoryImpl.removeHistoryListener(listener);
}
}

Modified: releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
==============================================================================
---
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImpl.java (original)
+++
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImpl.java
Wed Jul 16 16:09:09 2008
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -15,27 +15,102 @@
*/
package com.google.gwt.user.client.impl;

-import com.google.gwt.user.client.History;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.user.client.HistoryListener;
+
+import java.util.ArrayList;

/**
* Native implementation associated with
* {@link com.google.gwt.user.client.History}.
+ *
+ * User classes should not use this class directly.
*/
public abstract class HistoryImpl {

- protected static void onHistoryChanged(String historyToken) {
- History.onHistoryChanged(historyToken);
+ private static ArrayList<HistoryListener> historyListeners = new ArrayList<HistoryListener>();
+
+ /**
+ * Adds a listener to be informed of changes to the browser's
history stack.
+ *
+ * @param listener the listener to be added
+ */
+ public static void addHistoryListener(HistoryListener listener) {
+ historyListeners.add(listener);
+ }
+
+ public static native String getToken() /*-{
+ return $wnd.__gwt_historyToken || "";
+ }-*/;
+
+ /**
+ * Removes a history listener.
+ *
+ * @param listener the listener to be removed
+ */
+ public static void removeHistoryListener(HistoryListener listener) {
+ historyListeners.remove(listener);
}

- public native String getToken() /*-{
- return $wnd.__gwt_historyToken;
+ protected static native void setToken(String token) /*-{
+ $wnd.__gwt_historyToken = token;
}-*/;

+ private static void fireHistoryChanged(String historyToken) {
+ UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
+ if (handler != null) {
+ fireHistoryChangedAndCatch(historyToken, handler);
+ } else {
+ fireHistoryChangedImpl(historyToken);
+ }
+ }
+
+ private static void fireHistoryChangedAndCatch(String historyToken,
+ UncaughtExceptionHandler handler) {
+ try {
+ fireHistoryChangedImpl(historyToken);
+ } catch (Throwable e) {
+ handler.onUncaughtException(e);
+ }
+ }
+
+ private static void fireHistoryChangedImpl(String historyToken) {
+ // TODO: replace this copy when a more general solution to event handlers
+ // wanting to remove themselves from the listener list is implemented.
+
+ // This is necessary to avoid a CurrentModificationException in hosted
+ // mode, as the listeners may try to remove themselves from the
list while
+ // it is being iterated, such as in HistoryTest.
+ HistoryListener[] listenersToInvoke = historyListeners.toArray(new HistoryListener[historyListeners.size()]);
+ for (HistoryListener listener : listenersToInvoke) {
+ listener.onHistoryChanged(historyToken);
+ }
+ }
+
public abstract boolean init();

- public abstract void newItem(String historyToken);
+ 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);
+ fireHistoryChanged(historyToken);
+ }
+ }

- protected native String decodeFragment(String encodedFragment) /*-{
+ protected native String decodeFragment(String encodedFragment) /*-{
// decodeURI() does *not* decode the '#' character.
return decodeURI(encodedFragment.replace("%23", "#"));
}-*/;
@@ -45,7 +120,7 @@
return encodeURI(fragment).replace("#", "%23");
}-*/;

- protected native void setToken(String token) /*-{
- $wnd.__gwt_historyToken = token;
- }-*/;
+ protected abstract void nativeUpdate(String historyToken);
+
+ protected abstract void nativeUpdateOnEvent(String historyToken);
}

Modified: releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplFrame.java
==============================================================================
---
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplFrame.java (original)
+++
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplFrame.java
Wed Jul 16 16:09:09 2008
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -36,7 +36,7 @@
}
}-*/;

- private Element historyFrame;
+ protected Element historyFrame;

@Override
public boolean init() {
@@ -53,24 +53,35 @@
if (tokenElement != null) {
setToken(getTokenElementContent(tokenElement));
} else {
- newItemImpl(historyFrame, getToken(), true);
+ navigateFrame(getToken());
}

injectGlobalHandler();
return true;
}

- @Override
- public void newItem(String historyToken) {
- newItemImpl(historyFrame, historyToken, false);
- }
-
protected abstract String getTokenElementContent(Element tokenElement);

protected abstract void initHistoryToken();

protected abstract void injectGlobalHandler();

- protected abstract void newItemImpl(Element historyFrame,
- String historyToken, boolean forceAdd);
+ @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);
+ }
+
+ protected abstract void navigateFrame(String historyToken);
+
+ protected abstract void updateHash(String historyToken);
+
}

Modified: releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
==============================================================================
---
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java (original)
+++
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplIE6.java
Wed Jul 16 16:09:09 2008
@@ -23,7 +23,7 @@
* {@link com.google.gwt.user.client.impl.HistoryImplFrame}.
*/
class HistoryImplIE6 extends HistoryImplFrame {
-
+
/**
* Sanitizes an untrusted string to be used in an HTML context.
NOTE: This
* method of escaping strings should only be used on Internet Explorer.
@@ -40,7 +40,7 @@

/**
* 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.
+ * fragment contains a '?'. To avoid this bug, we use location.href instead.
*/
@SuppressWarnings("unused")
private static native String getLocationHash() /*-{
@@ -65,54 +65,42 @@

@Override
protected 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 {
- $wnd.__gwt_historyToken = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
+ token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
} catch (e) {
- // Clear the bad hash and __gwt_historyToken
- // (this can't have been a valid token).
+ // Clear the bad hash (this can't have been a valid token).
$wnd.location.hash = '';
- $wnd.__gwt_historyToken = '';
}
- return;
}
-
- // There was no hash. Just start off with an empty token.
- $wnd.__gwt_historyToken = '';
+ @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token);
}-*/;

@Override
protected native void injectGlobalHandler() /*-{
var historyImplRef = this;
-
+
$wnd.__gwt_onHistoryLoad = function(token) {
- // Change the URL and notify the application that its history frame
- // is changing.
-
- if (token != $wnd.__gwt_historyToken) {
- $wnd.__gwt_historyToken = token;
- $wnd.location.hash = historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(token);
-
- @com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)(token);
- }
+ historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
};
}-*/;

- @Override
- protected native void newItemImpl(Element historyFrame, String
historyToken, boolean forceAdd) /*-{
- historyToken =
@com.google.gwt.user.client.impl.HistoryImplIE6::escapeHtml(Ljava/lang/String;)(historyToken
|| "");
- var encodedGwtHistoryToken =
@com.google.gwt.user.client.impl.HistoryImplIE6::escapeHtml(Ljava/lang/String;)($wnd.__gwt_historyToken
|| "");
-
- if (forceAdd || (encodedGwtHistoryToken != historyToken)) {
- var doc = historyFrame.contentWindow.document;
- doc.open();
- doc.write('<html><body
onload="if(parent.__gwt_onHistoryLoad)parent.__gwt_onHistoryLoad(__gwt_historyToken.innerText)"><div
id="__gwt_historyToken">' + historyToken + '</div></body></html>');
- doc.close();
- }
+ protected 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.HistoryImplFrame::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();
}-*/;
-
+
+ protected native void updateHash(String token) /*-{
+ $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(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
@@ -122,6 +110,7 @@
// bar in the UI to stop working under these circumstances.
var historyImplRef = this;
var urlChecker = function() {
+ $wnd.setTimeout(urlChecker, 250);
var hash = @com.google.gwt.user.client.impl.HistoryImplIE6::getLocationHash()();
if (hash.length > 0) {
var token = '';
@@ -132,12 +121,12 @@
// if someone entered or linked to a bad url.
$wnd.location.reload();
}
-
- if ($wnd.__gwt_historyToken && (token !=
$wnd.__gwt_historyToken)) {
+
+ var historyToken = @com.google.gwt.user.client.impl.HistoryImpl::getToken()();
+ if (historyToken && (token != historyToken)) {
$wnd.location.reload();
}
}
- $wnd.setTimeout(urlChecker, 250);
};
urlChecker();
}-*/;

Modified: releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
==============================================================================
---
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java (original)
+++
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplMozilla.java
Wed Jul 16 16:09:09 2008
@@ -1,5 +1,5 @@
/*
- * Copyright 2007 Google Inc.
+ * 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
@@ -19,17 +19,26 @@
* History implementation for Mozilla-based browsers.
*/
class HistoryImplMozilla extends HistoryImplStandard {
+
+ @Override
+ protected String decodeFragment(String 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
- public native void newItem(String historyToken) /*-{
- // 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.
- if (historyToken == null || historyToken.length == 0) {
+ protected native void nativeUpdate(String historyToken) /*-{
+ if (historyToken.length == 0) {
var s = $wnd.location.href;
// Pull off any hash.
var i = s.indexOf('#');
@@ -41,11 +50,4 @@
$wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(historyToken);
}
}-*/;
-
- @Override
- protected String decodeFragment(String 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;
- }
}

Modified: releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
==============================================================================
---
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java (original)
+++
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplSafari.java
Wed Jul 16 16:09:09 2008
@@ -51,35 +51,35 @@
if (isOldSafari) {
initImpl();
return true;
+ } else {
+ return super.init();
}
-
- return super.init();
}

@Override
- public void newItem(String historyToken) {
+ protected void nativeUpdate(String historyToken) {
if (isOldSafari) {
- newItemImpl(historyToken);
- return;
+ nativeUpdateImpl(historyToken);
+ } else {
+ super.nativeUpdate(historyToken);
}
-
- super.newItem(historyToken);
}

private native void initImpl() /*-{
- $wnd.__gwt_historyToken = '';
+ var token = '';

// Get the initial token from the url's hash component.
var hash = $wnd.location.hash;
if (hash.length > 0) {
- $wnd.__gwt_historyToken =
- this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
+ token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
}

- @com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)($wnd.__gwt_historyToken);
+ @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token);
+
+ @com.google.gwt.user.client.impl.HistoryImpl::fireHistoryChangedImpl(Ljava/lang/String;)($wnd.__gwt_historyToken);
}-*/;

- private native void newItemImpl(String historyToken) /*-{
+ private native void nativeUpdateImpl(String historyToken) /*-{
// Use a bizarre meta refresh trick to update the url's hash, without
// creating a history entry.
var meta = $doc.createElement('meta');
@@ -92,9 +92,5 @@
window.setTimeout(function() {
$doc.body.removeChild(meta);
}, 1);
-
- // Update the global history token and fire the history event.
- $wnd.__gwt_historyToken = historyToken;
- @com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)($wnd.__gwt_historyToken);
}-*/;
}

Modified: releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
==============================================================================
---
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java (original)
+++
releases/1.5/user/src/com/google/gwt/user/client/impl/HistoryImplStandard.java
Wed Jul 16 16:09:09 2008
@@ -22,27 +22,27 @@

@Override
public native boolean init() /*-{
- $wnd.__gwt_historyToken = '';
+ var token = '';

// Get the initial token from the url's hash component.
var hash = $wnd.location.hash;
- if (hash.length > 0)
- $wnd.__gwt_historyToken = hash.substring(1);
+ 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;
$wnd.__checkHistory = function() {
+ $wnd.setTimeout($wnd.__checkHistory, 250);
+
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));
}

- if (token != $wnd.__gwt_historyToken) {
- $wnd.__gwt_historyToken = token;
- @com.google.gwt.user.client.impl.HistoryImpl::onHistoryChanged(Ljava/lang/String;)(token);
- }
-
- $wnd.setTimeout($wnd.__checkHistory, 250);
+ historyImpl.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
};

// Kick off the timer.
@@ -50,11 +50,16 @@
return true;
}-*/;

- @Override
- public native void newItem(String historyToken) /*-{
- if (historyToken == null) {
- historyToken = "";
- }
+ /**
+ * 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);
}-*/;
+
+ @Override
+ protected void nativeUpdateOnEvent(String historyToken) {
+ // Do nothing, the hash is already updated.
+ }
}

Modified: releases/1.5/user/test/com/google/gwt/user/UISuite.java
==============================================================================
--- releases/1.5/user/test/com/google/gwt/user/UISuite.java (original)
+++ releases/1.5/user/test/com/google/gwt/user/UISuite.java Wed Jul 16
16:09:09 2008
@@ -44,6 +44,7 @@
import com.google.gwt.user.client.ui.GridTest;
import com.google.gwt.user.client.ui.HTMLPanelTest;
import com.google.gwt.user.client.ui.HiddenTest;
+import com.google.gwt.user.client.ui.HistoryTest;
import com.google.gwt.user.client.ui.HorizontalPanelTest;
import com.google.gwt.user.client.ui.HyperlinkTest;
import com.google.gwt.user.client.ui.ImageTest;
@@ -113,7 +114,7 @@
// suite.addTestSuite(FormPanelTest.class);
suite.addTestSuite(GridTest.class);
suite.addTestSuite(HiddenTest.class);
- // suite.addTestSuite(HistoryTest.class);
+ suite.addTestSuite(HistoryTest.class);
suite.addTestSuite(HorizontalPanelTest.class);
suite.addTestSuite(HTMLPanelTest.class);
suite.addTestSuite(HyperlinkTest.class);

Modified: releases/1.5/user/test/com/google/gwt/user/client/ui/HistoryTest.java
==============================================================================
---
releases/1.5/user/test/com/google/gwt/user/client/ui/HistoryTest.java (original)
+++
releases/1.5/user/test/com/google/gwt/user/client/ui/HistoryTest.java
Wed Jul 16 16:09:09 2008
@@ -18,6 +18,10 @@
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;

/**
* Tests for the history system.
@@ -26,53 +30,48 @@
*/
public class HistoryTest extends GWTTestCase {

- @Override
- public String getModuleName() {
- return "com.google.gwt.user.User";
- }
-
private static native String getCurrentLocationHash() /*-{
var href = $wnd.location.href;
-
+
splitted = href.split("#");
if (splitted.length != 2) {
return null;
}
-
+
hashPortion = splitted[1];
-
+
return hashPortion;
}-*/;
-
- public void testTokenEscaping() {
- final String shouldBeEncoded = "% ^[]|\"<>{}\\`";
- final String shouldBeEncodedAs = "%25%20%5E%5B%5D%7C%22%3C%3E%7B%7D%5C%60";
-
- delayTestFinish(5000);
- History.addHistoryListener(new HistoryListener() {
- public void onHistoryChanged(String token) {
- assertEquals(shouldBeEncodedAs, getCurrentLocationHash());
- assertEquals(shouldBeEncoded, token);
- finishTest();
- History.removeHistoryListener(this);
+
+ /*
+ * Copied from HistoryImplSafari.
+ */
+ private static native boolean isSafari2() /*-{
+ var exp = / AppleWebKit\/([\d]+)/;
+ var result = exp.exec(navigator.userAgent);
+ if (result) {
+ // The standard history implementation works fine on WebKit >= 522
+ // (Safari 3 beta).
+ if (parseInt(result[1]) >= 522) {
+ return false;
}
- });
- History.newItem(shouldBeEncoded);
- }
+ }

- public void testTokenNonescaping() {
- final String shouldNotChange = "abc;,/?:@&=+$-_.!~*'()ABC123foo";
-
- delayTestFinish(5000);
- History.addHistoryListener(new HistoryListener() {
- public void onHistoryChanged(String token) {
- assertEquals(shouldNotChange, getCurrentLocationHash());
- assertEquals(shouldNotChange, token);
- finishTest();
- History.removeHistoryListener(this);
- }
- });
- History.newItem(shouldNotChange);
+ // The standard history implementation works just fine on the
iPhone, which
+ // unfortunately reports itself as WebKit/420+.
+ if (navigator.userAgent.indexOf('iPhone') != -1) {
+ return false;
+ }
+
+ return true;
+ }-*/;
+
+ private HistoryListener historyListener;
+ private Timer timer;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
}

/* Tests against issue #572: Double unescaping of history tokens. */
@@ -80,23 +79,77 @@
final String escToken = "%24%24%24";

delayTestFinish(5000);
- History.addHistoryListener(new HistoryListener() {
+ addHistoryListenerImpl(new HistoryListener() {
public void onHistoryChanged(String token) {
assertEquals(escToken, token);
finishTest();
- History.removeHistoryListener(this);
}
});
History.newItem(escToken);
}

/*
+ * Tests against issue #879: Ensure that empty history tokens do not add
+ * additional characters after the '#' symbol in the URL.
+ */
+ public void testEmptyHistoryTokens() {
+ delayTestFinish(5000);
+
+ addHistoryListenerImpl(new HistoryListener() {
+ public void onHistoryChanged(String historyToken) {
+
+ if (historyToken == null) {
+ fail("historyToken should not be null");
+ }
+
+ if (historyToken.equals("foobar")) {
+ History.newItem("");
+ } else {
+ assertEquals(0, historyToken.length());
+ finishTest();
+ }
+ }
+ });
+
+ // We must first start out with a non-blank history token. Adding
a blank
+ // history token in the initial state will not cause an onHistoryChanged
+ // event to fire.
+ History.newItem("foobar");
+ }
+
+ /**
+ * Verify that no events are issued via newItem if there were not reqeuested.
+ */
+ public void testNoEvents() {
+ addHistoryListenerImpl(new HistoryListener() {
+ {
+ timer = new Timer() {
+ public void run() {
+ finishTest();
+ }
+ };
+ timer.schedule(500);
+ }
+
+ public void onHistoryChanged(String historyToken) {
+ fail("onHistoryChanged should not have been called");
+ }
+ });
+ delayTestFinish(5000);
+ History.newItem("testNoEvents", false);
+ }
+
+ /*
* Ensure that non-url-safe strings (such as those containing
spaces) are
* encoded/decoded correctly, and that programmatic 'back' works.
*/
public void testHistory() {
+ if (isSafari2()) {
+ // History.back() is broken on Safari2, so we skip this test.
+ return;
+ }
delayTestFinish(5000);
- History.addHistoryListener(new HistoryListener() {
+ addHistoryListenerImpl(new HistoryListener() {
private int state = 0;

public void onHistoryChanged(String historyToken) {
@@ -123,10 +176,9 @@

case 2: {
if (!historyToken.equals("foo bar")) {
- fail("Expecting token 'foo bar', but got: " + historyToken);
+ fail("Expecting token 'foo bar' after back, but got: " + historyToken);
}
finishTest();
- History.removeHistoryListener(this);
break;
}
}
@@ -136,58 +188,112 @@
History.newItem("foo bar");
}

- /*
- * Tests against issue #879: Ensure that empty history tokens do not add
- * additional characters after the '#' symbol in the URL.
+ /**
+ * Verify that {@link HistoryListener#onHistoryChanged(String)} is only
+ * called once per {@link History#newItem(String)}.
*/
- public void testEmptyHistoryTokens() {
- delayTestFinish(5000);
+ public void testHistoryChangedCount() {
+ timer = new Timer() {
+ private int count = 0;
+
+ public void run() {
+ if (count++ == 0) {
+ Window.alert("first timer");
+ // verify that duplicates don't issue another event
+ History.newItem("testHistoryChangedCount");
+ timer.schedule(500);
+ } else {
+ Window.alert("second timer");
+ finishTest();
+ }
+ }
+ };
+ addHistoryListenerImpl(new HistoryListener() {
+ final ArrayList<Object> counter = new ArrayList<Object>();

- History.addHistoryListener(new HistoryListener() {
public void onHistoryChanged(String historyToken) {
-
- if (historyToken == null) {
- fail("historyToken should not be null");
+ counter.add(null);
+ if (counter.size() != 1) {
+ fail("onHistoryChanged called multiple times");
}
+ // wait 500ms to see if we get called multiple times
+ timer.schedule(500);
+ }
+ });
+ delayTestFinish(5000);
+ History.newItem("testHistoryChangedCount");
+ }

- if (historyToken.equals("foobar")) {
- History.newItem("");
- } else {
- assertEquals(0, historyToken.length());
- finishTest();
- History.removeHistoryListener(this);
+ public void testTokenEscaping() {
+ final String shouldBeEncoded = "% ^[]|\"<>{}\\";
+ 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);
+ finishTest();
}
});
+ History.newItem(shouldBeEncoded);
+ }

- // We must first start out with a non-blank history token. Adding
a blank
- // history token in the initial state will not cause an onHistoryChanged
- // event to fire.
- History.newItem("foobar");
+ public void testTokenNonescaping() {
+ 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);
+ finishTest();
+ }
+ });
+ History.newItem(shouldNotChange);
}

/*
* Test against issue #2500. IE6 has a bug that causes it to not
report any
- * part of the current fragment after a '?' when read from location.hash;
- * make sure that on affected browsers, we're not relying on this.
+ * part of the current fragment after a '?' when read from
location.hash; make
+ * sure that on affected browsers, we're not relying on this.
*/
public void testTokenWithQuestionmark() {
delayTestFinish(5000);
final String token = "foo?bar";

- History.addHistoryListener(new HistoryListener() {
+ addHistoryListenerImpl(new HistoryListener() {
public void onHistoryChanged(String historyToken) {
-
if (historyToken == null) {
fail("historyToken should not be null");
}
-
assertEquals(token, historyToken);
- History.removeHistoryListener(this);
finishTest();
}
});
-
History.newItem(token);
}
+
+ @Override
+ protected void gwtTearDown() throws Exception {
+ if (historyListener != null) {
+ History.removeHistoryListener(historyListener);
+ historyListener = null;
+ }
+ if (timer != null) {
+ timer.cancel();
+ timer = null;
+ }
+ }
+
+ private void addHistoryListenerImpl(HistoryListener historyListener) {
+ this.historyListener = historyListener;
+ History.addHistoryListener(historyListener);
+ }
}

Reply all
Reply to author
Forward
0 new messages