diff options
author | Matti Tahvonen <matti@vaadin.com> | 2017-01-13 17:07:36 +0200 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2017-01-13 17:07:36 +0200 |
commit | 62c44dd77e47c908361a87332182f2e2465972c0 (patch) | |
tree | 2485171f3a1326d0f7bf0a067569e535dd18d626 | |
parent | fe536b3efc4ed3d6637fead851c01375af91f762 (diff) | |
download | vaadin-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.
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(); |