Skip to content

Commit 4dd166d

Browse files
committed
Make browser details collection available before init request completes
The browser details collection function getBrowserDetailsParameters was previously defined in FlowBootstrap.js, which was loaded after the init request. This caused the function to be undefined when Flow.ts tried to collect browser details to send with the ?v-r=init request. This change moves the browser details collection logic from FlowBootstrap.js into Flow.ts as a private method collectBrowserDetails(), and registers it as window.Vaadin.Flow.getBrowserDetailsParameters in the Flow constructor. This ensures the function is available when needed during the init request, while maintaining backward compatibility for code that calls the function via the global window object. The TypeScript implementation uses ($wnd as any) casts to access window properties like screen, document, navigator, etc., since the $wnd type doesn't include all browser DOM APIs. Values are stringified before returning to match the original JavaScript implementation. Also adds integration test to verify browser details are available immediately on page load without user interaction.
1 parent c9b5e1c commit 4dd166d

File tree

4 files changed

+127
-107
lines changed

4 files changed

+127
-107
lines changed

flow-client/src/main/frontend/Flow.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ export class Flow {
114114
isActive: () => this.isActive
115115
}
116116
};
117+
// Set browser details collection function as global for use by refresh()
118+
($wnd.Vaadin.Flow as any).getBrowserDetailsParameters = this.collectBrowserDetails.bind(this);
117119

118120
// Regular expression used to remove the app-context
119121
const elm = document.head.querySelector('base');
@@ -421,7 +423,7 @@ export class Flow {
421423
const httpRequest = xhr as any;
422424

423425
// Collect browser details to send with init request as JSON
424-
const browserDetails = ($wnd.Vaadin.Flow as any).getBrowserDetailsParameters();
426+
const browserDetails = this.collectBrowserDetails();
425427
const browserDetailsParam = browserDetails
426428
? `&v-browserDetails=${encodeURIComponent(JSON.stringify(browserDetails))}`
427429
: '';
@@ -453,6 +455,95 @@ export class Flow {
453455
});
454456
}
455457

458+
// Collects browser details parameters
459+
private collectBrowserDetails(): Record<string, string> {
460+
const params: Record<string, any> = {};
461+
462+
/* Screen height and width */
463+
params['v-sh'] = ($wnd as any).screen.height;
464+
params['v-sw'] = ($wnd as any).screen.width;
465+
/* Browser window dimensions */
466+
params['v-wh'] = ($wnd as any).innerHeight;
467+
params['v-ww'] = ($wnd as any).innerWidth;
468+
/* Body element dimensions */
469+
params['v-bh'] = ($wnd as any).document.body.clientHeight;
470+
params['v-bw'] = ($wnd as any).document.body.clientWidth;
471+
472+
/* Current time */
473+
const date = new Date();
474+
params['v-curdate'] = date.getTime();
475+
476+
/* Current timezone offset (including DST shift) */
477+
const tzo1 = date.getTimezoneOffset();
478+
479+
/* Compare the current tz offset with the first offset from the end
480+
of the year that differs --- if less that, we are in DST, otherwise
481+
we are in normal time */
482+
let dstDiff = 0;
483+
let rawTzo = tzo1;
484+
for (let m = 12; m > 0; m -= 1) {
485+
date.setUTCMonth(m);
486+
const tzo2 = date.getTimezoneOffset();
487+
if (tzo1 !== tzo2) {
488+
dstDiff = tzo1 > tzo2 ? tzo1 - tzo2 : tzo2 - tzo1;
489+
rawTzo = tzo1 > tzo2 ? tzo1 : tzo2;
490+
break;
491+
}
492+
}
493+
494+
/* Time zone offset */
495+
params['v-tzo'] = tzo1;
496+
497+
/* DST difference */
498+
params['v-dstd'] = dstDiff;
499+
500+
/* Time zone offset without DST */
501+
params['v-rtzo'] = rawTzo;
502+
503+
/* DST in effect? */
504+
params['v-dston'] = tzo1 !== rawTzo;
505+
506+
/* Time zone id (if available) */
507+
try {
508+
params['v-tzid'] = Intl.DateTimeFormat().resolvedOptions().timeZone;
509+
} catch (err) {
510+
params['v-tzid'] = '';
511+
}
512+
513+
/* Window name */
514+
if (($wnd as any).name) {
515+
params['v-wn'] = ($wnd as any).name;
516+
}
517+
518+
/* Detect touch device support */
519+
let supportsTouch = false;
520+
try {
521+
($wnd as any).document.createEvent('TouchEvent');
522+
supportsTouch = true;
523+
} catch (e) {
524+
/* Chrome and IE10 touch detection */
525+
supportsTouch = 'ontouchstart' in $wnd || typeof ($wnd as any).navigator.msMaxTouchPoints !== 'undefined';
526+
}
527+
params['v-td'] = supportsTouch;
528+
529+
/* Device Pixel Ratio */
530+
params['v-pr'] = ($wnd as any).devicePixelRatio;
531+
532+
if (($wnd as any).navigator.platform) {
533+
params['v-np'] = ($wnd as any).navigator.platform;
534+
}
535+
536+
/* Stringify each value (they are parsed on the server side) */
537+
const stringParams: Record<string, string> = {};
538+
Object.keys(params).forEach((key) => {
539+
const value = params[key];
540+
if (typeof value !== 'undefined') {
541+
stringParams[key] = value.toString();
542+
}
543+
});
544+
return stringParams;
545+
}
546+
456547
// Create shared connection state store and connection indicator
457548
private addConnectionIndicator() {
458549
// add connection indicator to DOM

flow-client/src/main/frontend/FlowBootstrap.js

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -187,92 +187,6 @@ Please submit an issue to https://github.com/vaadin/flow-components/issues/new/c
187187
ws.pendingApps = null;
188188
}
189189
};
190-
window.Vaadin.Flow.getBrowserDetailsParameters = function () {
191-
var params = {};
192-
193-
/* Screen height and width */
194-
params['v-sh'] = window.screen.height;
195-
params['v-sw'] = window.screen.width;
196-
/* Browser window dimensions */
197-
params['v-wh'] = window.innerHeight;
198-
params['v-ww'] = window.innerWidth;
199-
/* Body element dimensions */
200-
params['v-bh'] = document.body.clientHeight;
201-
params['v-bw'] = document.body.clientWidth;
202-
203-
/* Current time */
204-
var date = new Date();
205-
params['v-curdate'] = date.getTime();
206-
207-
/* Current timezone offset (including DST shift) */
208-
var tzo1 = date.getTimezoneOffset();
209-
210-
/* Compare the current tz offset with the first offset from the end
211-
of the year that differs --- if less that, we are in DST, otherwise
212-
we are in normal time */
213-
var dstDiff = 0;
214-
var rawTzo = tzo1;
215-
for (var m = 12; m > 0; m--) {
216-
date.setUTCMonth(m);
217-
var tzo2 = date.getTimezoneOffset();
218-
if (tzo1 != tzo2) {
219-
dstDiff = tzo1 > tzo2 ? tzo1 - tzo2 : tzo2 - tzo1;
220-
rawTzo = tzo1 > tzo2 ? tzo1 : tzo2;
221-
break;
222-
}
223-
}
224-
225-
/* Time zone offset */
226-
params['v-tzo'] = tzo1;
227-
228-
/* DST difference */
229-
params['v-dstd'] = dstDiff;
230-
231-
/* Time zone offset without DST */
232-
params['v-rtzo'] = rawTzo;
233-
234-
/* DST in effect? */
235-
params['v-dston'] = tzo1 != rawTzo;
236-
237-
/* Time zone id (if available) */
238-
try {
239-
params['v-tzid'] = Intl.DateTimeFormat().resolvedOptions().timeZone;
240-
} catch (err) {
241-
params['v-tzid'] = '';
242-
}
243-
244-
/* Window name */
245-
if (window.name) {
246-
params['v-wn'] = window.name;
247-
}
248-
249-
/* Detect touch device support */
250-
var supportsTouch = false;
251-
try {
252-
document.createEvent('TouchEvent');
253-
supportsTouch = true;
254-
} catch (e) {
255-
/* Chrome and IE10 touch detection */
256-
supportsTouch = 'ontouchstart' in window || typeof navigator.msMaxTouchPoints !== 'undefined';
257-
}
258-
params['v-td'] = supportsTouch;
259-
260-
/* Device Pixel Ratio */
261-
params['v-pr'] = window.devicePixelRatio;
262-
263-
if (navigator.platform) {
264-
params['v-np'] = navigator.platform;
265-
}
266-
267-
/* Stringify each value (they are parsed on the server side) */
268-
Object.keys(params).forEach(function (key) {
269-
var value = params[key];
270-
if (typeof value !== 'undefined') {
271-
params[key] = value.toString();
272-
}
273-
});
274-
return params;
275-
};
276190
}
277191

278192
log('Flow bootstrap loaded');

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

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.vaadin.flow.uitest.ui;
1717

18+
import com.vaadin.flow.component.UI;
1819
import com.vaadin.flow.component.html.Div;
1920
import com.vaadin.flow.component.html.NativeButton;
2021
import com.vaadin.flow.component.page.ExtendedClientDetails;
@@ -36,15 +37,11 @@ protected void onShow() {
3637
Div touchDevice = createDiv("td");
3738

3839
// Display initial values immediately
39-
getUI().ifPresent(ui -> {
40-
ExtendedClientDetails details = ui.getPage()
41-
.getExtendedClientDetails();
42-
if (details != null) {
43-
displayDetails(details, screenWidth, screenHeight,
44-
windowInnerWidth, windowInnerHeight, bodyElementWidth,
45-
bodyElementHeight, devicePixelRatio, touchDevice);
46-
}
47-
});
40+
ExtendedClientDetails details = UI.getCurrentOrThrow().getPage()
41+
.getExtendedClientDetails();
42+
displayDetails(details, screenWidth, screenHeight,
43+
windowInnerWidth, windowInnerHeight, bodyElementWidth,
44+
bodyElementHeight, devicePixelRatio, touchDevice);
4845

4946
// the sizing values cannot be set with JS but pixel ratio and touch
5047
// support can be faked
@@ -58,17 +55,15 @@ protected void onShow() {
5855

5956
NativeButton fetchDetailsButton = new NativeButton(
6057
"Fetch client details", event -> {
61-
getUI().ifPresent(ui -> {
62-
ExtendedClientDetails details = ui.getPage()
63-
.getExtendedClientDetails();
64-
details.refresh(updatedDetails -> {
65-
displayDetails(updatedDetails, screenWidth,
66-
screenHeight, windowInnerWidth,
67-
windowInnerHeight, bodyElementWidth,
68-
bodyElementHeight, devicePixelRatio,
69-
touchDevice);
70-
});
71-
});
58+
UI.getCurrentOrThrow().getPage()
59+
.getExtendedClientDetails().refresh(updatedDetails -> {
60+
displayDetails(updatedDetails, screenWidth,
61+
screenHeight, windowInnerWidth,
62+
windowInnerHeight, bodyElementWidth,
63+
bodyElementHeight, devicePixelRatio,
64+
touchDevice);
65+
66+
});
7267
});
7368
fetchDetailsButton.setId("fetch-values");
7469

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@
2323

2424
public class ExtendedClientDetailsIT extends ChromeBrowserTest {
2525

26+
@Test
27+
public void testExtendedClientDetails_availableImmediately() {
28+
open();
29+
30+
// Values should be available immediately without clicking any button
31+
verifyTextMatchesJSExecution("sh", "window.screen.height");
32+
verifyTextMatchesJSExecution("sw", "window.screen.width");
33+
verifyTextMatchesJSExecution("wh", "window.innerHeight");
34+
verifyTextMatchesJSExecution("ww", "window.innerWidth");
35+
verifyTextMatchesJSExecution("bh", "document.body.clientHeight");
36+
verifyTextMatchesJSExecution("bw", "document.body.clientWidth");
37+
try {
38+
Double.parseDouble($(TestBenchElement.class).id("pr").getText());
39+
} catch (NumberFormatException nfe) {
40+
Assert.fail("Could not parse reported device pixel ratio");
41+
}
42+
Assert.assertTrue("false".equalsIgnoreCase(
43+
$(TestBenchElement.class).id("td").getText()));
44+
}
45+
2646
@Test
2747
public void testExtendedClientDetails_reportsSomething() {
2848
open();

0 commit comments

Comments
 (0)