Skip to content

Commit 992063d

Browse files
committed
Make getExtendedClientDetails() always return non-null
Browser details are now sent eagerly with the init request, but getExtendedClientDetails() could still return null before the data arrives. This changes the API to always return a non-null instance, using a placeholder with default values (screenWidth = -1, windowName = null) until actual browser details are available. For PreserveOnRefresh functionality, the code now checks if windowName is null rather than checking if the entire ExtendedClientDetails object is null. This is necessary because: - Screen dimensions are always sent with the init request - But window.name may be empty/undefined in new browser windows - This causes windowName = null even when other details are present - Different windows with windowName = null would incorrectly share cached components Changes: - Made ExtendedClientDetails constructor public (marked as internal-only) - UIInternals.getExtendedClientDetails() creates placeholder if null - Page.getExtendedClientDetails() simplified to delegate to UIInternals - Page.retrieveExtendedClientDetails() checks screenWidth == -1 - AbstractNavigationStateRenderer: Changed 3 locations to check windowName == null instead of details == null - WebComponentUI: Changed to check windowName == null This fixes PreserveOnRefreshIT.refreshInDifferentWindow_componentIsRecreated test failure where components were incorrectly preserved across different browser windows due to cache key collisions.
1 parent e0a4fc7 commit 992063d

File tree

6 files changed

+45
-42
lines changed

6 files changed

+45
-42
lines changed

flow-server/src/main/java/com/vaadin/flow/component/internal/UIInternals.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,12 +1347,20 @@ public UI getUI() {
13471347
}
13481348

13491349
/**
1350-
* The extended client details, if obtained, are cached in this field.
1350+
* Returns the extended client details. If browser details have not been
1351+
* received yet, returns a placeholder instance with default values (all
1352+
* dimensions set to -1). The placeholder will be updated with actual values
1353+
* when the browser details are received.
13511354
*
1352-
* @return the extended client details, or {@literal null} if not yet
1353-
* received.
1355+
* @return the extended client details (never {@code null})
13541356
*/
13551357
public ExtendedClientDetails getExtendedClientDetails() {
1358+
if (extendedClientDetails == null) {
1359+
// Create placeholder with default values
1360+
extendedClientDetails = new ExtendedClientDetails(ui, null, null,
1361+
null, null, null, null, null, null, null, null, null, null,
1362+
null, null, null, null);
1363+
}
13561364
return extendedClientDetails;
13571365
}
13581366

flow-server/src/main/java/com/vaadin/flow/component/page/ExtendedClientDetails.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public class ExtendedClientDetails implements Serializable {
101101
* @param navigatorPlatform
102102
* navigation platform received from the browser
103103
*/
104-
ExtendedClientDetails(UI ui, String screenWidth, String screenHeight,
104+
public ExtendedClientDetails(UI ui, String screenWidth, String screenHeight,
105105
String windowInnerWidth, String windowInnerHeight,
106106
String bodyClientWidth, String bodyClientHeight, String tzOffset,
107107
String rawTzOffset, String dstShift, String dstInEffect,

flow-server/src/main/java/com/vaadin/flow/component/page/Page.java

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -496,18 +496,7 @@ public interface ExtendedClientDetailsReceiver extends Serializable {
496496
* @return the extended client details (never {@code null})
497497
*/
498498
public ExtendedClientDetails getExtendedClientDetails() {
499-
ExtendedClientDetails details = ui.getInternals()
500-
.getExtendedClientDetails();
501-
if (details == null) {
502-
// Create placeholder instance with default values
503-
ExtendedClientDetails placeholder = new ExtendedClientDetails(ui,
504-
null, null, null, null, null, null, null, null, null, null,
505-
null, null, null, null, null, null);
506-
// Store placeholder immediately so we don't return null
507-
ui.getInternals().setExtendedClientDetails(placeholder);
508-
return placeholder;
509-
}
510-
return details;
499+
return ui.getInternals().getExtendedClientDetails();
511500
}
512501

513502
/**
@@ -526,12 +515,12 @@ public void retrieveExtendedClientDetails(
526515
ExtendedClientDetailsReceiver receiver) {
527516
ExtendedClientDetails details = ui.getInternals()
528517
.getExtendedClientDetails();
529-
if (details != null && details.getScreenWidth() != -1) {
518+
if (details.getScreenWidth() != -1) {
530519
// Already fetched and complete, call receiver immediately
531520
receiver.receiveDetails(details);
532521
} else {
533-
// Not available or placeholder, trigger refresh
534-
getExtendedClientDetails().refresh(receiver::receiveDetails);
522+
// Placeholder with default values, trigger refresh
523+
details.refresh(receiver::receiveDetails);
535524
}
536525
}
537526

flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentUI.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,22 @@ private void connectWebComponent(WebComponentConnectEvent event) {
172172

173173
if (!shouldBePreserved) {
174174
attachCreatedWebComponent(webComponentConfiguration.get(), event);
175-
} else if (getInternals().getExtendedClientDetails() != null) {
176-
attachCachedOrCreatedWebComponent(webComponentConfiguration.get(),
177-
event, getComponentHash(event,
178-
getInternals().getExtendedClientDetails()));
179175
} else {
180-
getPage().retrieveExtendedClientDetails(extendedClientDetails -> {
176+
ExtendedClientDetails details = getInternals()
177+
.getExtendedClientDetails();
178+
if (details.getWindowName() == null) {
179+
getPage().retrieveExtendedClientDetails(
180+
extendedClientDetails -> {
181+
attachCachedOrCreatedWebComponent(
182+
webComponentConfiguration.get(), event,
183+
getComponentHash(event,
184+
extendedClientDetails));
185+
});
186+
} else {
181187
attachCachedOrCreatedWebComponent(
182188
webComponentConfiguration.get(), event,
183-
getComponentHash(event, extendedClientDetails));
184-
});
189+
getComponentHash(event, details));
190+
}
185191
}
186192
}
187193

flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -274,22 +274,22 @@ private boolean populateChain(ArrayList<HasElement> chain,
274274
if (chain.isEmpty() && isPreservePartialTarget(
275275
navigationState.getNavigationTarget(), routeLayoutTypes)) {
276276
UI ui = event.getUI();
277-
if (ui.getInternals().getExtendedClientDetails() == null) {
277+
ExtendedClientDetails details = ui.getInternals()
278+
.getExtendedClientDetails();
279+
if (details.getWindowName() == null) {
278280
PreservedComponentCache cache = ui.getSession()
279281
.getAttribute(PreservedComponentCache.class);
280282
if (cache != null && !cache.isEmpty()) {
281283
// As there is a cached chain we get the client details
282284
// to get the window name so we can determine if the
283285
// cache contains a chain for us to use.
284286
ui.getPage().retrieveExtendedClientDetails(
285-
details -> handle(event));
287+
d -> handle(event));
286288
return true;
287289
}
288290
} else {
289291
Optional<List<HasElement>> partialChain = getWindowPreservedChain(
290-
ui.getSession(),
291-
ui.getInternals().getExtendedClientDetails()
292-
.getWindowName());
292+
ui.getSession(), details.getWindowName());
293293
if (partialChain.isPresent()) {
294294
List<HasElement> oldChain = partialChain.get();
295295
disconnectElements(oldChain, ui);
@@ -992,18 +992,18 @@ private Optional<ArrayList<HasElement>> getPreservedChain(
992992
final UI ui = event.getUI();
993993
final VaadinSession session = ui.getSession();
994994

995-
if (ui.getInternals().getExtendedClientDetails() == null) {
995+
final ExtendedClientDetails details = ui.getInternals()
996+
.getExtendedClientDetails();
997+
if (details.getWindowName() == null) {
996998
if (hasPreservedChainOfLocation(session, location)) {
997999
// We may have a cached instance for this location, but we
9981000
// need to retrieve the window name before we can determine
9991001
// this, so execute a client-side request.
1000-
ui.getPage().retrieveExtendedClientDetails(
1001-
details -> handle(event));
1002+
ui.getPage().retrieveExtendedClientDetails(d -> handle(event));
10021003
return Optional.empty();
10031004
}
10041005
} else {
1005-
final String windowName = ui.getInternals()
1006-
.getExtendedClientDetails().getWindowName();
1006+
final String windowName = details.getWindowName();
10071007
final Optional<ArrayList<HasElement>> maybePreserved = getPreservedChain(
10081008
session, windowName, event.getLocation());
10091009
if (maybePreserved.isPresent()) {
@@ -1052,7 +1052,7 @@ private void setPreservedChain(ArrayList<HasElement> chain,
10521052
final ExtendedClientDetails extendedClientDetails = ui.getInternals()
10531053
.getExtendedClientDetails();
10541054

1055-
if (extendedClientDetails == null) {
1055+
if (extendedClientDetails.getWindowName() == null) {
10561056
// We need first to retrieve the window name in order to cache the
10571057
// component chain for later potential refreshes.
10581058
ui.getPage().retrieveExtendedClientDetails(

flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/ExtendedClientDetailsView.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ protected void onShow() {
3939
// Display initial values immediately
4040
ExtendedClientDetails details = UI.getCurrentOrThrow().getPage()
4141
.getExtendedClientDetails();
42-
displayDetails(details, screenWidth, screenHeight,
43-
windowInnerWidth, windowInnerHeight, bodyElementWidth,
44-
bodyElementHeight, devicePixelRatio, touchDevice);
42+
displayDetails(details, screenWidth, screenHeight, windowInnerWidth,
43+
windowInnerHeight, bodyElementWidth, bodyElementHeight,
44+
devicePixelRatio, touchDevice);
4545

4646
// the sizing values cannot be set with JS but pixel ratio and touch
4747
// support can be faked
@@ -55,8 +55,8 @@ protected void onShow() {
5555

5656
NativeButton fetchDetailsButton = new NativeButton(
5757
"Fetch client details", event -> {
58-
UI.getCurrentOrThrow().getPage()
59-
.getExtendedClientDetails().refresh(updatedDetails -> {
58+
UI.getCurrentOrThrow().getPage().getExtendedClientDetails()
59+
.refresh(updatedDetails -> {
6060
displayDetails(updatedDetails, screenWidth,
6161
screenHeight, windowInnerWidth,
6262
windowInnerHeight, bodyElementWidth,

0 commit comments

Comments
 (0)