Skip to content

Commit

Permalink
fix: defer location and query tracking (#14742) (#14791)
Browse files Browse the repository at this point in the history
Track of location and query string after response from server
happens too early when handling server side navigation,
because history.pushState is executed within a setTimeout.
This change defers the tracking to the next event loop cycle
in order to get the correct values.

Fixes #14323

Co-authored-by: Marco Collovati <marco@vaadin.com>
  • Loading branch information
vaadin-bot and mcollovati committed Oct 11, 2022
1 parent ad197a1 commit 97b7e71
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 2 deletions.
11 changes: 9 additions & 2 deletions flow-client/src/main/java/com/vaadin/client/PopStateHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Objects;

import com.google.gwt.core.client.Scheduler;
import com.vaadin.client.flow.RouterLinkHandler;

import elemental.client.Browser;
Expand Down Expand Up @@ -63,12 +64,18 @@ public void bind() {
// track the location and query string (#6107) after the latest response
// from server
registry.getRequestResponseTracker()
.addResponseHandlingEndedHandler(event -> {
.addResponseHandlingEndedHandler(event ->
// history.pushState(...) instruction from server side
// is invoked within a setTimeout(), so we need to defer
// the retrieval of window.location properties on next
// event loop cycle, otherwise we get values before
// they change (#14323)
Scheduler.get().scheduleDeferred(() -> {
pathAfterPreviousResponse = Browser.getWindow()
.getLocation().getPathname();
queryAfterPreviousResponse = Browser.getWindow()
.getLocation().getSearch();
});
}));
Browser.getWindow().addEventListener("popstate", this::onPopStateEvent);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2000-2022 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.navigation.ui;

import java.util.Collections;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.OptionalParameter;
import com.vaadin.flow.router.QueryParameters;
import com.vaadin.flow.router.Route;

@Route(value = "first")
public class FirstView extends Div implements HasUrlParameter<String> {

static final String PARAM_NAV_BUTTON_ID = "firstViewPramNavButton";
static final String NAV_BUTTON_ID = "firstViewNavButton";
static final String QUERY_LABEL_ID = "query";

private final Label queryLabel;

public FirstView() {
queryLabel = new Label();
queryLabel.setId(QUERY_LABEL_ID);
add(queryLabel);

NativeButton button = new NativeButton("Change query parameter",
e -> UI.getCurrent().navigate("first/1", QueryParameters
.simple(Collections.singletonMap("query", "bar"))));
button.setId(PARAM_NAV_BUTTON_ID);
add(button);

button = new NativeButton("Change view",
e -> UI.getCurrent().navigate("second"));
button.setId(NAV_BUTTON_ID);
add(button);

}

@Override
public void setParameter(BeforeEvent beforeEvent,
@OptionalParameter String param) {
queryLabel.setText(beforeEvent.getLocation().getQueryParameters()
.getQueryString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2022 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.navigation.ui;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;

@Route(value = "second")
public class SecondView extends Div {

static final String BUTTON_ID = "secondViewButton";

public SecondView() {
NativeButton button = new NativeButton("Change query parameter",
e -> UI.getCurrent().navigate("first"));
button.setId(BUTTON_ID);
add(button);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2000-2022 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.navigation.ui;

import org.junit.Assert;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import com.vaadin.flow.testutil.ChromeBrowserTest;

public class BrowserNavigationServerRoundTripIT extends ChromeBrowserTest {

@Override
protected String getTestPath() {
return "/first";
}

@Test
public void testForwardingToViewInSetParameter() {
final String baseLoc = "/first";
getDriver().get(getRootURL() + baseLoc + "/1?query=foo");
waitForDevServer();

WebElement button = findElement(By.id(FirstView.PARAM_NAV_BUTTON_ID));
button.click();

final String queryValue0 = findElement(By.id(FirstView.QUERY_LABEL_ID))
.getText();
Assert.assertEquals("should have received query parameter value 'bar'",
"query=bar", queryValue0);

getDriver().navigate().back();

final String queryValue1 = findElement(By.id(FirstView.QUERY_LABEL_ID))
.getText();
Assert.assertEquals("should have received query parameter value 'foo'",
"query=foo", queryValue1);
}

@Test
public void backAndForwardBrowserButton_triggerServerSideRoundTrip() {
open();
waitForDevServer();

WebElement button = findElement(By.id(FirstView.NAV_BUTTON_ID));
button.click();

waitForElementPresent(By.id(SecondView.BUTTON_ID));

getDriver().navigate().back();

waitForElementPresent(By.id(FirstView.NAV_BUTTON_ID));

getDriver().navigate().forward();

waitForElementPresent(By.id(SecondView.BUTTON_ID));
}
}

0 comments on commit 97b7e71

Please sign in to comment.