aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatti Tahvonen <matti@vaadin.com>2017-01-13 17:07:36 +0200
committerPekka Hyvönen <pekka@vaadin.com>2017-01-13 17:07:36 +0200
commit62c44dd77e47c908361a87332182f2e2465972c0 (patch)
tree2485171f3a1326d0f7bf0a067569e535dd18d626
parentfe536b3efc4ed3d6637fead851c01375af91f762 (diff)
downloadvaadin-framework-62c44dd77e47c908361a87332182f2e2465972c0.tar.gz
vaadin-framework-62c44dd77e47c908361a87332182f2e2465972c0.zip
Support for HTML5 push/replaceState for proper deep linking features (#8116)
* Added support for HTML5 push/replaceState for proper deep linkin features * Automated test script now works at least on chrome * Uses html5 push/popstate to implement uri fragment feature * fire legacy fragment change events also via popstate events rpc calls * send new fragments via pushstate mechanism * formatting * Formatting and adding test and workaround for IE bug * Formatting and depracated UriFragmentListener * Aligned naming in the new API * Ignored IE due to web driver bug Tested a workaround with javascript based window.location.href fetch, but that don’t seem to work stable enough.
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VUI.java63
-rw-r--r--client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java68
-rwxr-xr-xclient/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml3
-rw-r--r--server/src/main/java/com/vaadin/server/Page.java203
-rw-r--r--server/src/main/java/com/vaadin/ui/UI.java15
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/ui/UIConstants.java5
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/ui/UIServerRpc.java2
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/ui/PushStateAndReplaceState.java80
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/ui/UriFragment.java8
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/ui/PushStateAndReplaceStateTest.java69
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/ui/UriFragmentTest.java7
-rw-r--r--uitest/src/test/java/com/vaadin/tests/navigator/NavigatorViewBlocksBackButtonActionTest.java11
-rw-r--r--uitest/src/test/java/com/vaadin/tests/urifragments/FragmentHandlingAndAsynchUIUpdateTest.java11
-rw-r--r--uitest/src/test/java/com/vaadin/tests/urifragments/SettingNullFragmentTest.java11
14 files changed, 447 insertions, 109 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VUI.java b/client/src/main/java/com/vaadin/client/ui/VUI.java
index ad4077d396..79c918f711 100644
--- a/client/src/main/java/com/vaadin/client/ui/VUI.java
+++ b/client/src/main/java/com/vaadin/client/ui/VUI.java
@@ -26,11 +26,7 @@ import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.HasResizeHandlers;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
-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.http.client.URL;
-import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.SimplePanel;
@@ -46,7 +42,6 @@ import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.shared.ui.ui.UIConstants;
/**
*
@@ -99,51 +94,8 @@ public class VUI extends SimplePanel implements ResizeHandler,
/** For internal use only. May be removed or replaced in the future. */
public boolean resizeLazy = false;
- private HandlerRegistration historyHandlerRegistration;
-
private TouchScrollHandler touchScrollHandler;
- /**
- * The current URI fragment, used to avoid sending updates if nothing has
- * changed.
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public String currentFragment;
-
- /**
- * Listener for URI fragment changes. Notifies the server of the new value
- * whenever the value changes.
- */
- private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
-
- @Override
- public void onValueChange(ValueChangeEvent<String> event) {
- String newFragment = event.getValue();
-
- // Send the location to the server if the fragment has changed
- // and flush active connectors in UI.
- if (!newFragment.equals(currentFragment) && connection != null) {
- /*
- * Ensure the fragment is properly encoded in all browsers
- * (#10769)
- *
- * createUrlBuilder does not properly pass an empty fragment to
- * UrlBuilder on Webkit browsers so do it manually (#11686)
- */
- String location = Window.Location.createUrlBuilder()
- .setHash(URL
- .decodeQueryString(Window.Location.getHash()))
- .buildString();
-
- currentFragment = newFragment;
- connection.flushActiveConnector();
- connection.updateVariable(id, UIConstants.LOCATION_VARIABLE,
- location, true);
- }
- }
- };
-
private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
new ScheduledCommand() {
@@ -186,21 +138,6 @@ public class VUI extends SimplePanel implements ResizeHandler,
}
}
- @Override
- protected void onAttach() {
- super.onAttach();
- historyHandlerRegistration = History
- .addValueChangeHandler(historyChangeHandler);
- currentFragment = History.getToken();
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- historyHandlerRegistration.removeHandler();
- historyHandlerRegistration = null;
- }
-
/**
* Stop monitoring for parent element resizes.
*/
diff --git a/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java b/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java
index 180898453b..49475901c9 100644
--- a/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/main/java/com/vaadin/client/ui/ui/UIConnector.java
@@ -43,18 +43,17 @@ import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
+import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.Focusable;
@@ -105,6 +104,8 @@ import com.vaadin.shared.ui.ui.UIState;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.UI;
+import elemental.client.Browser;
+
@Connect(value = UI.class, loadStyle = LoadStyle.EAGER)
public class UIConnector extends AbstractSingleComponentContainerConnector
implements Paintable, MayScrollChildren {
@@ -115,6 +116,12 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
private HandlerRegistration windowOrderRegistration;
+ /*
+ * Used to workaround IE bug related to popstate events and certain fragment
+ * only changes
+ */
+ private String currentLocation;
+
private final StateChangeHandler childStateChangeHandler = new StateChangeHandler() {
@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
@@ -232,6 +239,28 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}
}
});
+
+ Browser.getWindow().setOnpopstate(evt -> {
+ final String newLocation = Browser.getWindow().getLocation()
+ .toString();
+ getRpcProxy(UIServerRpc.class).popstate(newLocation);
+ currentLocation = newLocation;
+ });
+ // IE doesn't fire popstate correctly with certain hash changes.
+ // Simulate the missing event with History handler.
+ if (BrowserInfo.get().isIE()) {
+ History.addValueChangeHandler(evt -> {
+ final String newLocation = Browser.getWindow().getLocation()
+ .toString();
+ if (!newLocation.equals(currentLocation)) {
+ currentLocation = newLocation;
+ getRpcProxy(UIServerRpc.class).popstate(
+ Browser.getWindow().getLocation().toString());
+ }
+ });
+ currentLocation = Browser.getWindow().getLocation().toString();
+ }
+
}
private native void open(String url, String name)
@@ -402,34 +431,13 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
scrollIntoView(connector);
}
- if (uidl.hasAttribute(UIConstants.LOCATION_VARIABLE)) {
- String location = uidl
- .getStringAttribute(UIConstants.LOCATION_VARIABLE);
- String newFragment;
-
- int fragmentIndex = location.indexOf('#');
- if (fragmentIndex >= 0) {
- // Decode fragment to avoid double encoding (#10769)
- newFragment = URL.decodePathSegment(
- location.substring(fragmentIndex + 1));
-
- if (newFragment.isEmpty()
- && Location.getHref().indexOf('#') == -1) {
- // Ensure there is a trailing # even though History and
- // Location.getHash() treat null and "" the same way.
- Location.assign(Location.getHref() + "#");
- }
- } else {
- // No fragment in server-side location, but can't completely
- // remove the browser fragment since that would reload the page
- newFragment = "";
- }
-
- getWidget().currentFragment = newFragment;
-
- if (!newFragment.equals(History.getToken())) {
- History.newItem(newFragment, true);
- }
+ if (uidl.hasAttribute(UIConstants.ATTRIBUTE_PUSH_STATE)) {
+ Browser.getWindow().getHistory().pushState(null, "",
+ uidl.getStringAttribute(UIConstants.ATTRIBUTE_PUSH_STATE));
+ }
+ if (uidl.hasAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE)) {
+ Browser.getWindow().getHistory().replaceState(null, "", uidl
+ .getStringAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE));
}
if (firstPaint) {
diff --git a/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml b/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml
index ad345e44e2..477ec43288 100755
--- a/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml
+++ b/client/src/main/resources/com/vaadin/DefaultWidgetSet.gwt.xml
@@ -8,8 +8,7 @@
<inherits name="com.vaadin.Vaadin" />
- <!-- Elemental is used for handling Json only -->
- <inherits name="elemental.Json" />
+ <inherits name="elemental.Elemental" />
<inherits name="com.google.gwt.precompress.Precompress" />
diff --git a/server/src/main/java/com/vaadin/server/Page.java b/server/src/main/java/com/vaadin/server/Page.java
index c96114b3f7..bf55e7ce9f 100644
--- a/server/src/main/java/com/vaadin/server/Page.java
+++ b/server/src/main/java/com/vaadin/server/Page.java
@@ -251,7 +251,9 @@ public class Page implements Serializable {
* changes.
*
* @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
+ * @deprecated Use {@link PopStateListener} instead
*/
+ @Deprecated
@FunctionalInterface
public interface UriFragmentChangedListener extends Serializable {
/**
@@ -273,6 +275,32 @@ public class Page implements Serializable {
"uriFragmentChanged", UriFragmentChangedEvent.class);
/**
+ * Listener that that gets notified when the URI of the page changes due to
+ * back/forward functionality of the browser.
+ *
+ * @see Page#addPopStateListener(PopStateListener)
+ * @since 8.0
+ */
+ @FunctionalInterface
+ public interface PopStateListener extends Serializable {
+ /**
+ * Event handler method invoked when the URI fragment of the page
+ * changes. Please note that the initial URI fragment has already been
+ * set when a new UI is initialized, so there will not be any initial
+ * event for listeners added during {@link UI#init(VaadinRequest)}.
+ *
+ * @see Page#addUriFragmentChangedListener(UriFragmentChangedListener)
+ *
+ * @param event
+ * the URI fragment changed event
+ */
+ public void uriChanged(PopStateEvent event);
+ }
+
+ private static final Method URI_CHANGED_METHOD = ReflectTools.findMethod(
+ Page.PopStateListener.class, "uriChanged", PopStateEvent.class);
+
+ /**
* Resources to be opened automatically on next repaint. The list is
* automatically cleared when it has been sent to the client.
*/
@@ -328,6 +356,53 @@ public class Page implements Serializable {
}
}
+ /**
+ * Event fired when the URI of a <code>Page</code> changes (aka HTML 5
+ * popstate event) on the client side due to browsers back/forward
+ * functionality.
+ *
+ * @see Page#addPopStateListener(PopStateListener)
+ * @since 8.0
+ */
+ public static class PopStateEvent extends EventObject {
+
+ /**
+ * The new URI as String
+ */
+ private final String uri;
+
+ /**
+ * Creates a new instance of PopstateEvent.
+ *
+ * @param source
+ * the Source of the event.
+ * @param uri
+ * the new uri
+ */
+ public PopStateEvent(Page source, String uri) {
+ super(source);
+ this.uri = uri;
+ }
+
+ /**
+ * Gets the page in which the uri has changed.
+ *
+ * @return the page in which the uri has changed
+ */
+ public Page getPage() {
+ return (Page) getSource();
+ }
+
+ /**
+ * Get the new URI
+ *
+ * @return the new uri
+ */
+ public String getUri() {
+ return uri;
+ }
+ }
+
@FunctionalInterface
private static interface InjectedStyle extends Serializable {
public void paint(int id, PaintTarget target) throws PaintException;
@@ -483,6 +558,9 @@ public class Page implements Serializable {
private String windowName;
+ private String newPushState;
+ private String newReplaceState;
+
public Page(UI uI, PageState state) {
this.uI = uI;
this.state = state;
@@ -516,7 +594,10 @@ public class Page implements Serializable {
* @param listener
* the URI fragment listener to add
* @return a registration object for removing the listener
+ * @deprecated Use {@link Page#addPopStateListener(PopStateListener)}
+ * instead
*/
+ @Deprecated
public Registration addUriFragmentChangedListener(
Page.UriFragmentChangedListener listener) {
return addListener(UriFragmentChangedEvent.class, listener,
@@ -524,6 +605,27 @@ public class Page implements Serializable {
}
/**
+ * Adds a listener that gets notified every time the URI of this page is
+ * changed due to back/forward functionality of the browser.
+ * <p>
+ * Note that one only gets notified when the back/forward button affects
+ * history changes with-in same UI, created by
+ * {@link Page#pushState(String)} or {@link Page#replaceState(String)}
+ * functions.
+ *
+ * @see #getLocation()
+ * @see Registration
+ *
+ * @param listener
+ * the Popstate listener to add
+ * @return a registration object for removing the listener
+ * @since 8.0
+ */
+ public Registration addPopStateListener(Page.PopStateListener listener) {
+ return addListener(PopStateEvent.class, listener, URI_CHANGED_METHOD);
+ }
+
+ /**
* Removes a URI fragment listener that was previously added to this page.
*
* @param listener
@@ -580,6 +682,7 @@ public class Page implements Serializable {
try {
location = new URI(location.getScheme(),
location.getSchemeSpecificPart(), newUriFragment);
+ pushState(location);
} catch (URISyntaxException e) {
// This should not actually happen as the fragment syntax is not
// constrained
@@ -588,7 +691,6 @@ public class Page implements Serializable {
if (fireEvents) {
fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
}
- uI.markAsDirty();
}
private void fireEvent(EventObject event) {
@@ -866,9 +968,14 @@ public class Page implements Serializable {
notifications = null;
}
- if (location != null) {
- target.addAttribute(UIConstants.LOCATION_VARIABLE,
- location.toString());
+ if (newPushState != null) {
+ target.addAttribute(UIConstants.ATTRIBUTE_PUSH_STATE, newPushState);
+ newPushState = null;
+ }
+ if (newReplaceState != null) {
+ target.addAttribute(UIConstants.ATTRIBUTE_REPLACE_STATE,
+ newReplaceState);
+ newReplaceState = null;
}
if (styles != null) {
@@ -932,6 +1039,84 @@ public class Page implements Serializable {
}
/**
+ * Updates the browsers URI without causing actual page change. This method
+ * is useful if you wish implement "deep linking" to your application.
+ * Calling the method also adds a new entry to clients browser history and
+ * you can further use {@link PopStateListener} to track the usage of
+ * back/forward feature in browser.
+ * <p>
+ * Note, the current implementation supports setting only one new uri in one
+ * user interaction.
+ *
+ * @param uri
+ * to be used for pushState operation. The URI is resolved over
+ * the current location. If the given URI is absolute, it must be
+ * of same origin as the current URI or the browser will not
+ * accept the new value.
+ * @since 8.0
+ */
+ public void pushState(String uri) {
+ newPushState = uri;
+ uI.markAsDirty();
+ location = location.resolve(uri);
+ }
+
+ /**
+ * Updates the browsers URI without causing actual page change. This method
+ * is useful if you wish implement "deep linking" to your application.
+ * Calling the method also adds a new entry to clients browser history and
+ * you can further use {@link PopStateListener} to track the usage of
+ * back/forward feature in browser.
+ * <p>
+ * Note, the current implementation supports setting only one new uri in one
+ * user interaction.
+ *
+ * @param uri
+ * the URI to be used for pushState operation. The URI is
+ * resolved over the current location. If the given URI is
+ * absolute, it must be of same origin as the current URI or the
+ * browser will not accept the new value.
+ * @since 8.0
+ */
+ public void pushState(URI uri) {
+ pushState(uri.toString());
+ }
+
+ /**
+ * Updates the browsers URI without causing actual page change in the same
+ * way as {@link #pushState(String)}, but does not add new entry to browsers
+ * history.
+ *
+ * @param uri
+ * the URI to be used for replaceState operation. The URI is
+ * resolved over the current location. If the given URI is
+ * absolute, it must be of same origin as the current URI or the
+ * browser will not accept the new value.
+ * @since 8.0
+ */
+ public void replaceState(String uri) {
+ newReplaceState = uri;
+ uI.markAsDirty();
+ location = location.resolve(uri);
+ }
+
+ /**
+ * Updates the browsers URI without causing actual page change in the same
+ * way as {@link #pushState(URI)}, but does not add new entry to browsers
+ * history.
+ *
+ * @param uri
+ * the URI to be used for replaceState operation. The URI is
+ * resolved over the current location. If the given URI is
+ * absolute, it must be of same origin as the current URI or the
+ * browser will not accept the new value.
+ * @since 8.0
+ */
+ public void replaceState(URI uri) {
+ replaceState(uri.toString());
+ }
+
+ /**
* For internal use only. Used to update the server-side location when the
* client-side location changes.
*
@@ -943,7 +1128,7 @@ public class Page implements Serializable {
*/
@Deprecated
public void updateLocation(String location) {
- updateLocation(location, true);
+ updateLocation(location, true, false);
}
/**
@@ -957,8 +1142,11 @@ public class Page implements Serializable {
* @param fireEvents
* whether to fire {@link UriFragmentChangedEvent} if the URI
* fragment changes
+ * @param firePopstate
+ * whether to fire {@link PopStateEvent}
*/
- public void updateLocation(String location, boolean fireEvents) {
+ public void updateLocation(String location, boolean fireEvents,
+ boolean firePopstate) {
try {
String oldUriFragment = this.location.getFragment();
this.location = new URI(location);
@@ -967,6 +1155,9 @@ public class Page implements Serializable {
&& !SharedUtil.equals(oldUriFragment, newUriFragment)) {
fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
}
+ if (firePopstate) {
+ fireEvent(new PopStateEvent(this, location));
+ }
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
diff --git a/server/src/main/java/com/vaadin/ui/UI.java b/server/src/main/java/com/vaadin/ui/UI.java
index 437a188361..c0dabb37c0 100644
--- a/server/src/main/java/com/vaadin/ui/UI.java
+++ b/server/src/main/java/com/vaadin/ui/UI.java
@@ -193,6 +193,12 @@ public abstract class UI extends AbstractSingleComponentContainer
public void acknowledge() {
// Nothing to do, just need the message to be sent and processed
}
+
+ @Override
+ public void popstate(String uri) {
+ getPage().updateLocation(uri, true, true);
+
+ }
};
private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() {
@Override
@@ -437,11 +443,6 @@ public abstract class UI extends AbstractSingleComponentContainer
actionManager.handleActions(variables, this);
}
- if (variables.containsKey(UIConstants.LOCATION_VARIABLE)) {
- String location = (String) variables
- .get(UIConstants.LOCATION_VARIABLE);
- getPage().updateLocation(location, true);
- }
}
/*
@@ -789,10 +790,10 @@ public abstract class UI extends AbstractSingleComponentContainer
int newWidth = page.getBrowserWindowWidth();
int newHeight = page.getBrowserWindowHeight();
- page.updateLocation(oldLocation.toString(), false);
+ page.updateLocation(oldLocation.toString(), false, false);
page.updateBrowserWindowSize(oldWidth, oldHeight, false);
- page.updateLocation(newLocation.toString(), true);
+ page.updateLocation(newLocation.toString(), true, false);
page.updateBrowserWindowSize(newWidth, newHeight, true);
}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/ui/UIConstants.java b/shared/src/main/java/com/vaadin/shared/ui/ui/UIConstants.java
index 17d28cb5eb..086164e750 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/ui/UIConstants.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/ui/UIConstants.java
@@ -28,7 +28,10 @@ public class UIConstants implements Serializable {
public static final String NOTIFICATION_HTML_CONTENT_NOT_ALLOWED = "useplain";
@Deprecated
- public static final String LOCATION_VARIABLE = "location";
+ public static final String ATTRIBUTE_PUSH_STATE = "ps";
+
+ @Deprecated
+ public static final String ATTRIBUTE_REPLACE_STATE = "rs";
@Deprecated
public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style";
diff --git a/shared/src/main/java/com/vaadin/shared/ui/ui/UIServerRpc.java b/shared/src/main/java/com/vaadin/shared/ui/ui/UIServerRpc.java
index e6c7beb3e7..15391dc023 100644
--- a/shared/src/main/java/com/vaadin/shared/ui/ui/UIServerRpc.java
+++ b/shared/src/main/java/com/vaadin/shared/ui/ui/UIServerRpc.java
@@ -35,6 +35,8 @@ public interface UIServerRpc extends ClickRpc, ServerRpc {
* should always be called to ensure the message is flushed right away.
*/
public void poll();
+
+ public void popstate(String uri);
@NoLoadingIndicator
public void acknowledge();
diff --git a/uitest/src/main/java/com/vaadin/tests/components/ui/PushStateAndReplaceState.java b/uitest/src/main/java/com/vaadin/tests/components/ui/PushStateAndReplaceState.java
new file mode 100644
index 0000000000..22788f326c
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/ui/PushStateAndReplaceState.java
@@ -0,0 +1,80 @@
+package com.vaadin.tests.components.ui;
+
+import java.net.URI;
+
+import com.vaadin.server.Page;
+import com.vaadin.server.Page.PopStateEvent;
+import com.vaadin.server.Page.PopStateListener;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractReindeerTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Notification;
+
+public class PushStateAndReplaceState extends AbstractReindeerTestUI {
+
+ private final Label locationLabel = new Label();
+ private CheckBox replace;
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ locationLabel.setId("locationLabel");
+ addComponent(locationLabel);
+ updateLabel();
+
+ getPage().addPopStateListener(new PopStateListener() {
+
+ @Override
+ public void uriChanged(PopStateEvent event) {
+ Notification.show("Popstate event");
+ updateLabel();
+ }
+ });
+
+ replace = new CheckBox("replace");
+ replace.setId("replace");
+ addComponent(replace);
+
+ addComponent(createButton("test", "Move to ./test",
+ Page.getCurrent().getLocation().toString() + "/test"));
+ addComponent(createButton("X", "Move to X", "X"));
+ addComponent(createButton("root_X", "Move to /X", "/X"));
+ }
+
+ private Button createButton(String id, String caption,
+ final String newUri) {
+ Button button = new Button(caption, new Button.ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ if (replace.getValue()) {
+ getPage().replaceState(newUri);
+ } else {
+ getPage().pushState(newUri);
+ }
+ updateLabel();
+ }
+ });
+
+ button.setId(id);
+
+ return button;
+ }
+
+ private void updateLabel() {
+ URI location = getPage().getLocation();
+ locationLabel.setValue("Current Location: " + location.toString());
+ }
+
+ @Override
+ public String getTestDescription() {
+ return "Modern web framework shouldn't force you to use hashbang style urls for deep linking";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return null;
+ }
+
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/ui/UriFragment.java b/uitest/src/main/java/com/vaadin/tests/components/ui/UriFragment.java
index 0d0fdc4b63..61d7776439 100644
--- a/uitest/src/main/java/com/vaadin/tests/components/ui/UriFragment.java
+++ b/uitest/src/main/java/com/vaadin/tests/components/ui/UriFragment.java
@@ -1,5 +1,6 @@
package com.vaadin.tests.components.ui;
+import com.vaadin.server.ExternalResource;
import com.vaadin.server.Page;
import com.vaadin.server.Page.UriFragmentChangedEvent;
import com.vaadin.server.VaadinRequest;
@@ -7,6 +8,7 @@ import com.vaadin.tests.components.AbstractReindeerTestUI;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Label;
+import com.vaadin.ui.Link;
public class UriFragment extends AbstractReindeerTestUI {
@@ -29,6 +31,12 @@ public class UriFragment extends AbstractReindeerTestUI {
addComponent(createButton("test", "Navigate to #test", "test"));
addComponent(createButton("empty", "Navigate to #", ""));
addComponent(createButton("null", "setUriFragment(null)", null));
+
+ Link link = new Link("Navigate to #linktest",
+ new ExternalResource("#linktest"));
+ link.setId("link");
+ addComponent(link);
+
}
private Button createButton(String id, String caption,
diff --git a/uitest/src/test/java/com/vaadin/tests/components/ui/PushStateAndReplaceStateTest.java b/uitest/src/test/java/com/vaadin/tests/components/ui/PushStateAndReplaceStateTest.java
new file mode 100644
index 0000000000..a57cbf6ed5
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/ui/PushStateAndReplaceStateTest.java
@@ -0,0 +1,69 @@
+package com.vaadin.tests.components.ui;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.junit.Test;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+
+import com.vaadin.testbench.By;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class PushStateAndReplaceStateTest extends MultiBrowserTest {
+
+ @Test
+ public void testUriFragment() throws Exception {
+ driver.get(getTestUrl());
+ assertUri(getTestUrl());
+
+ hitButton("test");
+
+ assertUri(getTestUrl() + "/test");
+
+ driver.navigate().back();
+
+ driver.findElement(By.className("v-Notification")).getText()
+ .contains("Popstate event");
+
+ assertUri(getTestUrl());
+
+ hitButton("test");
+ URI base = new URI(getTestUrl() + "/test");
+ hitButton("X");
+ URI current = base.resolve("X");
+ driver.findElement(By.xpath("//*[@id = 'replace']/input")).click();
+ hitButton("root_X");
+ current = current.resolve("/X");
+
+ assertUri(current.toString());
+
+ // Now that last change was with replace state, two back calls should go
+ // to initial
+ driver.navigate().back();
+ driver.navigate().back();
+
+ assertUri(getTestUrl());
+
+ }
+
+ private void assertUri(String uri) {
+ final String expectedText = "Current Location: " + uri;
+ waitUntil(new ExpectedCondition<Boolean>() {
+
+ @Override
+ public Boolean apply(WebDriver input) {
+ return expectedText.equals(getLocationLabelValue());
+ }
+ });
+
+ assertEquals(uri, driver.getCurrentUrl());
+ }
+
+ private String getLocationLabelValue() {
+ String text = vaadinElementById("locationLabel").getText();
+ return text;
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/ui/UriFragmentTest.java b/uitest/src/test/java/com/vaadin/tests/components/ui/UriFragmentTest.java
index e7fbf4f7ec..731573b770 100644
--- a/uitest/src/test/java/com/vaadin/tests/components/ui/UriFragmentTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/components/ui/UriFragmentTest.java
@@ -7,6 +7,7 @@ import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
+import com.vaadin.testbench.By;
import com.vaadin.tests.tb3.MultiBrowserTest;
public class UriFragmentTest extends MultiBrowserTest {
@@ -42,6 +43,12 @@ public class UriFragmentTest extends MultiBrowserTest {
navigateToNull(); // Setting to null when there is a fragment actually
// sets it to #
assertEquals("Current URI fragment:", getFragmentLabelValue());
+
+ // ensure IE works with new popstate based implementation, see
+ // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/3740423/
+ driver.findElement(By.xpath("//*[@id = 'link']/a")).click();
+ assertFragment("linktest");
+
}
private void assertFragment(String fragment) {
diff --git a/uitest/src/test/java/com/vaadin/tests/navigator/NavigatorViewBlocksBackButtonActionTest.java b/uitest/src/test/java/com/vaadin/tests/navigator/NavigatorViewBlocksBackButtonActionTest.java
index 11848344ba..4507c12cfe 100644
--- a/uitest/src/test/java/com/vaadin/tests/navigator/NavigatorViewBlocksBackButtonActionTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/navigator/NavigatorViewBlocksBackButtonActionTest.java
@@ -17,15 +17,26 @@ package com.vaadin.tests.navigator;
import static org.junit.Assert.assertEquals;
+import java.util.List;
+
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.DesiredCapabilities;
import com.vaadin.testbench.elements.ButtonElement;
import com.vaadin.tests.tb3.MultiBrowserTest;
public class NavigatorViewBlocksBackButtonActionTest extends MultiBrowserTest {
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ // IE web driver fails to read fragment properly, these must be tested
+ // manually. See
+ // https://github.com/SeleniumHQ/selenium-google-code-issue-archive/issues/7966
+ return getBrowsersExcludingIE();
+ }
+
@Test
public void testIfConfirmBack() {
openTestURL();
diff --git a/uitest/src/test/java/com/vaadin/tests/urifragments/FragmentHandlingAndAsynchUIUpdateTest.java b/uitest/src/test/java/com/vaadin/tests/urifragments/FragmentHandlingAndAsynchUIUpdateTest.java
index 4da3ae1541..13b18de516 100644
--- a/uitest/src/test/java/com/vaadin/tests/urifragments/FragmentHandlingAndAsynchUIUpdateTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/urifragments/FragmentHandlingAndAsynchUIUpdateTest.java
@@ -18,9 +18,12 @@ package com.vaadin.tests.urifragments;
import static com.vaadin.tests.urifragments.FragmentHandlingAndAsynchUIUpdate.FRAG_NAME_TPL;
import static com.vaadin.tests.urifragments.FragmentHandlingAndAsynchUIUpdate.START_FRAG_ID;
+import java.util.List;
+
import org.junit.Test;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition;
import com.vaadin.testbench.By;
@@ -33,6 +36,14 @@ import com.vaadin.tests.tb3.MultiBrowserTest;
*/
public class FragmentHandlingAndAsynchUIUpdateTest extends MultiBrowserTest {
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ // IE web driver fails to read fragment properly, these must be tested
+ // manually. See
+ // https://github.com/SeleniumHQ/selenium-google-code-issue-archive/issues/7966
+ return getBrowsersExcludingIE();
+ }
+
/**
* The case when we successively set 10 fragments, go back 9 times and then
* go forward 9 times
diff --git a/uitest/src/test/java/com/vaadin/tests/urifragments/SettingNullFragmentTest.java b/uitest/src/test/java/com/vaadin/tests/urifragments/SettingNullFragmentTest.java
index db700b4393..f728be937a 100644
--- a/uitest/src/test/java/com/vaadin/tests/urifragments/SettingNullFragmentTest.java
+++ b/uitest/src/test/java/com/vaadin/tests/urifragments/SettingNullFragmentTest.java
@@ -15,8 +15,11 @@
*/
package com.vaadin.tests.urifragments;
+import java.util.List;
+
import org.junit.Test;
import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition;
import com.vaadin.tests.tb3.MultiBrowserTest;
@@ -29,6 +32,14 @@ import com.vaadin.tests.tb3.MultiBrowserTest;
*/
public class SettingNullFragmentTest extends MultiBrowserTest {
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ // IE web driver fails to read fragment properly, these must be tested
+ // manually. See
+ // https://github.com/SeleniumHQ/selenium-google-code-issue-archive/issues/7966
+ return getBrowsersExcludingIE();
+ }
+
@Test
public void testSettingNullURIFragment() throws Exception {
openTestURL();