Browse Source

Pass timezone ID from browser to server. (#10041)

Resolves #7911.
Prerequisite for #10033.
tags/8.2.0.alpha2
Piotr Wilkin 6 years ago
parent
commit
bcf58e2145

+ 19
- 1
server/src/main/java/com/vaadin/server/WebBrowser.java View File

@@ -42,6 +42,7 @@ public class WebBrowser implements Serializable {
private int rawTimezoneOffset = 0;
private int dstSavings;
private boolean dstInEffect;
private String timeZoneId;
private boolean touchDevice;

private VBrowserDetails browserDetails;
@@ -353,6 +354,17 @@ public class WebBrowser implements Serializable {
return timezoneOffset;
}

/**
* Returns the TimeZone Id (like "Europe/Helsinki") provided by the browser
* (if the browser supports this feature).
*
* @return the TimeZone Id if provided by the browser, null otherwise.
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/resolvedOptions">Intl.DateTimeFormat.prototype.resolvedOptions()</a>
*/
public String getTimeZoneId() {
return timeZoneId;
}

/**
* Returns the browser-reported TimeZone offset in milliseconds from GMT
* ignoring possible daylight saving adjustments that may be in effect in
@@ -450,7 +462,7 @@ public class WebBrowser implements Serializable {
* @param touchDevice
*/
void updateClientSideDetails(String sw, String sh, String tzo, String rtzo,
String dstSavings, String dstInEffect, String curDate,
String dstSavings, String dstInEffect, String tzId, String curDate,
boolean touchDevice) {
if (sw != null) {
try {
@@ -487,6 +499,11 @@ public class WebBrowser implements Serializable {
if (dstInEffect != null) {
this.dstInEffect = Boolean.parseBoolean(dstInEffect);
}
if (tzId == null || "undefined".equals(tzId)) {
this.timeZoneId = null;
} else {
this.timeZoneId = tzId;
}
if (curDate != null) {
try {
long curTime = Long.parseLong(curDate);
@@ -525,6 +542,7 @@ public class WebBrowser implements Serializable {
request.getParameter("v-rtzo"),
request.getParameter("v-dstd"),
request.getParameter("v-dston"),
request.getParameter("v-tzid"),
request.getParameter("v-curdate"),
request.getParameter("v-td") != null);
}

+ 371
- 363
server/src/main/resources/VAADIN/vaadinBootstrap.js View File

@@ -1,369 +1,377 @@
(function() {
var apps = {};
var themesLoaded = {};
var widgetsets = {};
(function () {
var apps = {};
var themesLoaded = {};
var widgetsets = {};
var log;
if (typeof console === "undefined" || !window.location.search.match(/[&?]debug(&|$)/)) {
//If no console.log present, just use a no-op
log = function() {};
//If no console.log present, just use a no-op
log = function () {
};
} else if (typeof console.log === "function") {
//If it's a function, use it with apply
log = function() {
console.log.apply(console, arguments);
};
//If it's a function, use it with apply
log = function () {
console.log.apply(console, arguments);
};
} else {
//In IE, its a native function for which apply is not defined, but it works without a proper 'this' reference
log = console.log;
//In IE, its a native function for which apply is not defined, but it works without a proper 'this' reference
log = console.log;
}
var loadTheme = function(url, version) {
if(!themesLoaded[url]) {
log("loadTheme", url, version);
var href = url + '/styles.css';
if (version) {
href += '?v=' + version;
}
var stylesheet = document.createElement('link');
stylesheet.setAttribute('rel', 'stylesheet');
stylesheet.setAttribute('type', 'text/css');
stylesheet.setAttribute('href', href);
document.getElementsByTagName('head')[0].appendChild(stylesheet);
themesLoaded[url] = true;
}
};
var isWidgetsetLoaded = function(widgetset) {
var className = widgetset.replace(/\./g, "_");
return (typeof window[className]) != "undefined";
};
var loadWidgetset = function(url, widgetset, ready) {
if (widgetsets[widgetset]) {
return;
}
log("load widgetset", url, widgetset);
setTimeout(function() {
if (!isWidgetsetLoaded(widgetset)) {
if (ready) {
alert("Failed to load the widgetset: " + url);
} else {
if (window.confirm("Failed to load the widgetset. If using CDN for the widgetset, it is possible that compiling it takes up to a few minutes. Would you like to try again?")) {
window[widgetset] = undefined;
window.location.reload(false);
} else {
alert("Failed to load the widgetset: " + url);
}
}
}
}, 15000);
var scriptTag = document.createElement('script');
scriptTag.setAttribute('type', 'text/javascript');
scriptTag.setAttribute('src', url);
document.getElementsByTagName('head')[0].appendChild(scriptTag);
widgetsets[widgetset] = {
pendingApps: []
};
};

var isInitializedInDom = function(appId) {
var appDiv = document.getElementById(appId);
if (!appDiv) {
return false;
}
for ( var i = 0; i < appDiv.childElementCount; i++) {
var className = appDiv.childNodes[i].className;
// If the app div contains a child with the class
// "v-app-loading" we have only received the HTML
// but not yet started the widget set
// (UIConnector removes the v-app-loading div).
if (className && className.indexOf("v-app-loading") != -1) {
return false;
}
}
return true;
};

window.vaadin = window.vaadin || {
initApplication: function(appId, config) {
var testbenchId = appId.replace(/-\d+$/, '');
if (apps[appId]) {
if (window.vaadin && window.vaadin.clients && window.vaadin.clients[testbenchId] && window.vaadin.clients[testbenchId].initializing) {
throw "Application " + appId + " is already being initialized";
}
if (isInitializedInDom(appId)) {
throw "Application " + appId + " already initialized";
}
}

log("init application", appId, config);
window.vaadin.clients[testbenchId] = {
isActive: function() {
return true;
},
initializing: true
};
var getConfig = function(name) {
var value = config[name];
return value;
};
var fetchRootConfig = function(callback) {
log('Fetching root config');
var url = getConfig('browserDetailsUrl');
if (!url) {
// No special url defined, use the same URL that loaded this page (without the fragment)
url = window.location.href.replace(/#.*/,'');
}
// Timestamp to avoid caching
url += ((/\?/).test(url) ? "&" : "?") + "v-" + (new Date()).getTime();
var params = "v-browserDetails=1";
var rootId = getConfig("v-rootId");
if (rootId !== undefined) {
params += "&v-rootId=" + rootId;
}

// Tell the UI what theme it is configured to use
var theme = getConfig('theme');
if (theme !== undefined) {
params += '&theme=' + encodeURIComponent(theme);
}
params += "&v-appId=" + appId;
var extraParams = getConfig('extraParams')
if (extraParams !== undefined) {
params += extraParams;
}
params += '&' + vaadin.getBrowserDetailsParameters(appId, getConfig('sendUrlsAsParameters'));
var r;
try {
r = new XMLHttpRequest();
} catch (e) {
r = new ActiveXObject("MSXML2.XMLHTTP.3.0");
}
r.open('POST', url, true);
r.onreadystatechange = function (aEvt) {
if (r.readyState == 4) {
// Save responseStatus so as Offline Applications know what happened
// when loading root configuration from server, and depending on the
// error status display an error message or the offline UI.
config.rootResponseStatus = r.status;
config.rootResponseText = r.responseText;

var text = r.responseText;
if (r.status == 200){
log("Got root config response", text);
var updatedConfig = JSON.parse(text);
// Copy new properties to the config object
for (var property in updatedConfig) {
if (updatedConfig.hasOwnProperty(property)) {
config[property] = updatedConfig[property];
}
}
// Try bootstrapping again, this time without fetching missing info
bootstrapApp(false);
} else {
log('Error', r.statusText, text);
//Let TB waitForVaadin work again
delete window.vaadin.clients[testbenchId];
// Show the error in the app's div
var appDiv = document.getElementById(appId);
appDiv.innerHTML = text;
appDiv.style['overflow'] = 'auto';
}

// Run the fetchRootConfig callback if present.
callback && callback(r);
}
};
// send parameters as POST data
r.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
r.send(params);
log('sending request to ', url);
};
//Export public data
var app = {
getConfig: getConfig,
// Used when the app was started in offline, so as it is possible
// to defer root configuration loading until network is available.
fetchRootConfig: fetchRootConfig
};
apps[appId] = app;
if (!window.name) {
window.name = appId + '-' + Math.random();
}
var bootstrapApp = function(mayDefer) {
var vaadinDir = getConfig('vaadinDir');
var versionInfo = getConfig('versionInfo');
var themeUri = vaadinDir + 'themes/' + getConfig('theme');
loadTheme(themeUri, versionInfo && versionInfo['vaadinVersion']);
var widgetset = getConfig('widgetset');
var widgetsetUrl = getConfig('widgetsetUrl');
if (!widgetsetUrl) {
widgetsetUrl = vaadinDir + 'widgetsets/' + widgetset + "/" + widgetset + ".nocache.js?" + new Date().getTime();
}
var widgetsetReady = getConfig('widgetsetReady');
loadWidgetset(widgetsetUrl, widgetset, widgetsetReady);
if (getConfig('uidl') === undefined) {
if (mayDefer) {
fetchRootConfig();
} else {
throw "May not defer bootstrap any more";
}
} else {
if (widgetsets[widgetset].callback) {
log("Starting from bootstrap", appId);
widgetsets[widgetset].callback(appId);
} else {
log("Setting pending startup", appId);
widgetsets[widgetset].pendingApps.push(appId);
}
}
};
bootstrapApp(true);

if (getConfig("debug")) {
// TODO debug state is now global for the entire page, but should somehow only be set for the current application
window.vaadin.debug = true;
}
return app;
},
clients: {},
getAppIds: function() {
var ids = [ ];
for (var id in apps) {
if (apps.hasOwnProperty(id)) {
ids.push(id);
}
}
return ids;
},
getApp: function(appId) {
return apps[appId];
},
loadTheme: loadTheme,
registerWidgetset: function(widgetset, callback) {
log("Widgetset registered", widgetset);
var ws = widgetsets[widgetset];
if (ws && ws.pendingApps) {
ws.callback = callback;
for(var i = 0; i < ws.pendingApps.length; i++) {
var appId = ws.pendingApps[i];
log("Starting from register widgetset", appId);
callback(appId);
}
ws.pendingApps = null;
}
},
getBrowserDetailsParameters: function(parentElementId, sendUrlsAsParameters) {
// Screen height and width
var params = 'v-sh=' + window.screen.height;
params += '&v-sw=' + window.screen.width;
// Window height and width
var cw = 0;
var ch = 0;
if(typeof(window.innerWidth) == 'number') {
// Modern browsers
cw = window.innerWidth;
ch = window.innerHeight;
} else {
// IE 8
cw = document.documentElement.clientWidth;
ch = document.documentElement.clientHeight;
}
params += '&v-cw=' + cw + '&v-ch=' + ch;

var d = new Date();
params += '&v-curdate=' + d.getTime();
var tzo1 = d.getTimezoneOffset(); // current offset
var dstDiff = 0;
var rtzo = tzo1;
for (var m=12;m>0;m--) {
d.setUTCMonth(m);
var tzo2 = d.getTimezoneOffset();
if (tzo1 != tzo2) {
dstDiff = (tzo1 > tzo2 ? tzo1-tzo2 : tzo2-tzo1); // offset w/o DST
rtzo = (tzo1 > tzo2 ? tzo1 : tzo2); // offset w/o DST
break;
}
}

// Time zone offset
params += '&v-tzo=' + tzo1;
// DST difference
params += '&v-dstd=' + dstDiff;
// Raw time zone offset
params += '&v-rtzo=' + rtzo;
// DST in effect?
params += '&v-dston=' + (tzo1 != rtzo);
var pe = document.getElementById(parentElementId);
if (pe) {
params += '&v-vw=' + pe.offsetWidth;
params += '&v-vh=' + pe.offsetHeight;
}
// Location
if (sendUrlsAsParameters !== false) {
params += '&v-loc=' + encodeURIComponent(location.href);
}

// Window name
if (window.name) {
params += '&v-wn=' + encodeURIComponent(window.name);
}
// Detect touch device support
var supportsTouch = false;
try {
document.createEvent("TouchEvent");
supportsTouch = true;
} catch (e) {
// Chrome and IE10 touch detection
supportsTouch = 'ontouchstart' in window
|| navigator.msMaxTouchPoints;
}

if (supportsTouch) {
params += "&v-td=1";
}
return params;
}
};
log('Vaadin bootstrap loaded');

var loadTheme = function (url, version) {
if (!themesLoaded[url]) {
log("loadTheme", url, version);

var href = url + '/styles.css';
if (version) {
href += '?v=' + version;
}

var stylesheet = document.createElement('link');
stylesheet.setAttribute('rel', 'stylesheet');
stylesheet.setAttribute('type', 'text/css');
stylesheet.setAttribute('href', href);
document.getElementsByTagName('head')[0].appendChild(stylesheet);
themesLoaded[url] = true;
}
};

var isWidgetsetLoaded = function (widgetset) {
var className = widgetset.replace(/\./g, "_");
return (typeof window[className]) != "undefined";
};

var loadWidgetset = function (url, widgetset, ready) {
if (widgetsets[widgetset]) {
return;
}
log("load widgetset", url, widgetset);
setTimeout(function () {
if (!isWidgetsetLoaded(widgetset)) {
if (ready) {
alert("Failed to load the widgetset: " + url);
} else {
if (window.confirm("Failed to load the widgetset. If using CDN for the widgetset, it is possible that compiling it takes up to a few minutes. Would you like to try again?")) {
window[widgetset] = undefined;
window.location.reload(false);
} else {
alert("Failed to load the widgetset: " + url);
}
}
}
}, 15000);

var scriptTag = document.createElement('script');
scriptTag.setAttribute('type', 'text/javascript');
scriptTag.setAttribute('src', url);
document.getElementsByTagName('head')[0].appendChild(scriptTag);

widgetsets[widgetset] = {
pendingApps: []
};
};

var isInitializedInDom = function (appId) {
var appDiv = document.getElementById(appId);
if (!appDiv) {
return false;
}
for (var i = 0; i < appDiv.childElementCount; i++) {
var className = appDiv.childNodes[i].className;
// If the app div contains a child with the class
// "v-app-loading" we have only received the HTML
// but not yet started the widget set
// (UIConnector removes the v-app-loading div).
if (className && className.indexOf("v-app-loading") != -1) {
return false;
}
}
return true;
};

window.vaadin = window.vaadin || {
initApplication: function (appId, config) {
var testbenchId = appId.replace(/-\d+$/, '');

if (apps[appId]) {
if (window.vaadin && window.vaadin.clients && window.vaadin.clients[testbenchId] && window.vaadin.clients[testbenchId].initializing) {
throw "Application " + appId + " is already being initialized";
}
if (isInitializedInDom(appId)) {
throw "Application " + appId + " already initialized";
}
}

log("init application", appId, config);

window.vaadin.clients[testbenchId] = {
isActive: function () {
return true;
},
initializing: true
};

var getConfig = function (name) {
var value = config[name];
return value;
};

var fetchRootConfig = function (callback) {
log('Fetching root config');
var url = getConfig('browserDetailsUrl');
if (!url) {
// No special url defined, use the same URL that loaded this page (without the fragment)
url = window.location.href.replace(/#.*/, '');
}
// Timestamp to avoid caching
url += ((/\?/).test(url) ? "&" : "?") + "v-" + (new Date()).getTime();

var params = "v-browserDetails=1";
var rootId = getConfig("v-rootId");
if (rootId !== undefined) {
params += "&v-rootId=" + rootId;
}

// Tell the UI what theme it is configured to use
var theme = getConfig('theme');
if (theme !== undefined) {
params += '&theme=' + encodeURIComponent(theme);
}

params += "&v-appId=" + appId;

var extraParams = getConfig('extraParams')
if (extraParams !== undefined) {
params += extraParams;
}

params += '&' + vaadin.getBrowserDetailsParameters(appId, getConfig('sendUrlsAsParameters'));

var r;
try {
r = new XMLHttpRequest();
} catch (e) {
r = new ActiveXObject("MSXML2.XMLHTTP.3.0");
}
r.open('POST', url, true);
r.onreadystatechange = function (aEvt) {
if (r.readyState == 4) {
// Save responseStatus so as Offline Applications know what happened
// when loading root configuration from server, and depending on the
// error status display an error message or the offline UI.
config.rootResponseStatus = r.status;
config.rootResponseText = r.responseText;

var text = r.responseText;
if (r.status == 200) {
log("Got root config response", text);
var updatedConfig = JSON.parse(text);

// Copy new properties to the config object
for (var property in updatedConfig) {
if (updatedConfig.hasOwnProperty(property)) {
config[property] = updatedConfig[property];
}
}

// Try bootstrapping again, this time without fetching missing info
bootstrapApp(false);
} else {
log('Error', r.statusText, text);

//Let TB waitForVaadin work again
delete window.vaadin.clients[testbenchId];

// Show the error in the app's div
var appDiv = document.getElementById(appId);
appDiv.innerHTML = text;
appDiv.style['overflow'] = 'auto';
}

// Run the fetchRootConfig callback if present.
callback && callback(r);
}
};
// send parameters as POST data
r.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
r.send(params);

log('sending request to ', url);
};

//Export public data
var app = {
getConfig: getConfig,
// Used when the app was started in offline, so as it is possible
// to defer root configuration loading until network is available.
fetchRootConfig: fetchRootConfig
};
apps[appId] = app;

if (!window.name) {
window.name = appId + '-' + Math.random();
}

var bootstrapApp = function (mayDefer) {
var vaadinDir = getConfig('vaadinDir');

var versionInfo = getConfig('versionInfo');

var themeUri = vaadinDir + 'themes/' + getConfig('theme');
loadTheme(themeUri, versionInfo && versionInfo['vaadinVersion']);

var widgetset = getConfig('widgetset');
var widgetsetUrl = getConfig('widgetsetUrl');
if (!widgetsetUrl) {
widgetsetUrl = vaadinDir + 'widgetsets/' + widgetset + "/" + widgetset + ".nocache.js?" + new Date().getTime();
}
var widgetsetReady = getConfig('widgetsetReady');
loadWidgetset(widgetsetUrl, widgetset, widgetsetReady);

if (getConfig('uidl') === undefined) {
if (mayDefer) {
fetchRootConfig();
} else {
throw "May not defer bootstrap any more";
}
} else {
if (widgetsets[widgetset].callback) {
log("Starting from bootstrap", appId);
widgetsets[widgetset].callback(appId);
} else {
log("Setting pending startup", appId);
widgetsets[widgetset].pendingApps.push(appId);
}
}
};
bootstrapApp(true);

if (getConfig("debug")) {
// TODO debug state is now global for the entire page, but should somehow only be set for the current application
window.vaadin.debug = true;
}

return app;
},
clients: {},
getAppIds: function () {
var ids = [];
for (var id in apps) {
if (apps.hasOwnProperty(id)) {
ids.push(id);
}
}
return ids;
},
getApp: function (appId) {
return apps[appId];
},
loadTheme: loadTheme,
registerWidgetset: function (widgetset, callback) {
log("Widgetset registered", widgetset);
var ws = widgetsets[widgetset];
if (ws && ws.pendingApps) {
ws.callback = callback;
for (var i = 0; i < ws.pendingApps.length; i++) {
var appId = ws.pendingApps[i];
log("Starting from register widgetset", appId);
callback(appId);
}
ws.pendingApps = null;
}
},
getBrowserDetailsParameters: function (parentElementId, sendUrlsAsParameters) {
// Screen height and width
var params = 'v-sh=' + window.screen.height;
params += '&v-sw=' + window.screen.width;

// Window height and width
var cw = 0;
var ch = 0;
if (typeof(window.innerWidth) == 'number') {
// Modern browsers
cw = window.innerWidth;
ch = window.innerHeight;
} else {
// IE 8
cw = document.documentElement.clientWidth;
ch = document.documentElement.clientHeight;
}
params += '&v-cw=' + cw + '&v-ch=' + ch;


var d = new Date();

params += '&v-curdate=' + d.getTime();

var tzo1 = d.getTimezoneOffset(); // current offset
var dstDiff = 0;
var rtzo = tzo1;

for (var m = 12; m > 0; m--) {
d.setUTCMonth(m);
var tzo2 = d.getTimezoneOffset();
if (tzo1 != tzo2) {
dstDiff = (tzo1 > tzo2 ? tzo1 - tzo2 : tzo2 - tzo1); // offset w/o DST
rtzo = (tzo1 > tzo2 ? tzo1 : tzo2); // offset w/o DST
break;
}
}

// Time zone offset
params += '&v-tzo=' + tzo1;

// DST difference
params += '&v-dstd=' + dstDiff;

// Raw time zone offset
params += '&v-rtzo=' + rtzo;

// DST in effect?
params += '&v-dston=' + (tzo1 != rtzo);

// Time zone id (if available)
try {
params += '&v-tzid=' + encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone);
} catch (err) {

}

var pe = document.getElementById(parentElementId);
if (pe) {
params += '&v-vw=' + pe.offsetWidth;
params += '&v-vh=' + pe.offsetHeight;
}

// Location
if (sendUrlsAsParameters !== false) {
params += '&v-loc=' + encodeURIComponent(location.href);
}

// Window name
if (window.name) {
params += '&v-wn=' + encodeURIComponent(window.name);
}

// Detect touch device support
var supportsTouch = false;
try {
document.createEvent("TouchEvent");
supportsTouch = true;
} catch (e) {
// Chrome and IE10 touch detection
supportsTouch = 'ontouchstart' in window
|| navigator.msMaxTouchPoints;
}

if (supportsTouch) {
params += "&v-td=1";
}

return params;
}
};

log('Vaadin bootstrap loaded');
})();

Loading…
Cancel
Save