@@ -238,6 +238,7 @@ public class ApplicationConfiguration implements EntryPoint { | |||
* always end with a slash (/). | |||
*/ | |||
private String vaadinDirUrl; | |||
private String frontendUrl; | |||
private String serviceUrl; | |||
private String contextRootUrl; | |||
private int uiId; | |||
@@ -339,6 +340,16 @@ public class ApplicationConfiguration implements EntryPoint { | |||
return vaadinDirUrl; | |||
} | |||
/** | |||
* Gets the URL of the that the {@literal frontend://} protocol should | |||
* resolve to. | |||
* | |||
* @return the URL of the frontend protocol | |||
*/ | |||
public String getFrontendUrl() { | |||
return frontendUrl; | |||
} | |||
public void setAppId(String appId) { | |||
id = appId; | |||
} | |||
@@ -427,6 +438,8 @@ public class ApplicationConfiguration implements EntryPoint { | |||
.getConfigString(ApplicationConstants.CONTEXT_ROOT_URL); | |||
vaadinDirUrl = WidgetUtil.getAbsoluteUrl(jsoConfiguration | |||
.getConfigString(ApplicationConstants.VAADIN_DIR_URL)); | |||
frontendUrl = WidgetUtil.getAbsoluteUrl(jsoConfiguration | |||
.getConfigString(ApplicationConstants.FRONTEND_URL)); | |||
uiId = jsoConfiguration.getConfigInteger(UIConstants.UI_ID_PARAMETER) | |||
.intValue(); | |||
@@ -334,6 +334,13 @@ public class ApplicationConnection implements HasHandlers { | |||
protected String getContextRootUrl() { | |||
return getConfiguration().getContextRootUrl(); | |||
} | |||
@Override | |||
protected String getFrontendUrl() { | |||
String url = getConfiguration().getFrontendUrl(); | |||
assert url.endsWith("/"); | |||
return url; | |||
} | |||
}; | |||
public static class MultiStepDuration extends Duration { |
@@ -40,8 +40,10 @@ import com.vaadin.server.ClientConnector; | |||
* <li>Absolute URLs including protocol and host are used as is on the | |||
* client-side. | |||
* </ul> | |||
* Note that it is a good idea to use URLs starting with {@literal vaadin://} | |||
* and place all HTML imports inside {@literal VAADIN/bower_components}. Polymer | |||
* Note that you should (almost) always use URLs starting with | |||
* {@literal frontend://} so that the framework can resolve the files to either | |||
* {@literal VAADIN/es5} or {@literal VAADIN/es6} depending on if the browser | |||
* supports ES6 classes (most browers) or not (IE11 and Safari <= 9). Polymer | |||
* elements rely on importing dependencies using relative paths | |||
* {@literal ../../other-element/other-element.html}, which will not work if | |||
* they are installed in different locations. | |||
@@ -50,10 +52,10 @@ import com.vaadin.server.ClientConnector; | |||
* added at the same time. | |||
* <p> | |||
* Example: | |||
* <code>@HtmlImport("bower_components/paper-slider/paper-slider.html")</code> | |||
* on the class com.example.MyConnector would load the file | |||
* http://host.com/VAADIN/bower_components/paper-slider/paper-slider.html before | |||
* the {@code init()} method of the client side connector is invoked. | |||
* <code>@HtmlImport("frontend://paper-slider/paper-slider.html")</code> on the | |||
* class com.example.MyConnector would load the file | |||
* {@literal http://host.com/VAADIN/es[56]/paper-slider/paper-slider.html} | |||
* before the {@code init()} method of the client side connector is invoked. | |||
* | |||
* @author Vaadin Ltd | |||
* @since 8.0 |
@@ -84,7 +84,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { | |||
private String appId; | |||
private PushMode pushMode; | |||
private JsonObject applicationParameters; | |||
private VaadinUriResolver uriResolver; | |||
private BootstrapUriResolver uriResolver; | |||
private WidgetsetInfo widgetsetInfo; | |||
public BootstrapContext(VaadinResponse response, | |||
@@ -177,7 +177,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { | |||
return applicationParameters; | |||
} | |||
public VaadinUriResolver getUriResolver() { | |||
public BootstrapUriResolver getUriResolver() { | |||
if (uriResolver == null) { | |||
uriResolver = new BootstrapUriResolver(this); | |||
} | |||
@@ -186,8 +186,9 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { | |||
} | |||
} | |||
private class BootstrapUriResolver extends VaadinUriResolver { | |||
protected static class BootstrapUriResolver extends VaadinUriResolver { | |||
private final BootstrapContext context; | |||
private String frontendUrl; | |||
public BootstrapUriResolver(BootstrapContext bootstrapContext) { | |||
context = bootstrapContext; | |||
@@ -250,6 +251,28 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { | |||
assert root.endsWith("/"); | |||
return root; | |||
} | |||
@Override | |||
protected String getFrontendUrl() { | |||
if (frontendUrl == null) { | |||
DeploymentConfiguration configuration = context.getSession() | |||
.getConfiguration(); | |||
if (context.getSession().getBrowser().isEs6Supported()) { | |||
frontendUrl = configuration.getApplicationOrSystemProperty( | |||
ApplicationConstants.FRONTEND_URL_ES6, | |||
ApplicationConstants.FRONTEND_URL_ES6_DEFAULT_VALUE); | |||
} else { | |||
frontendUrl = configuration.getApplicationOrSystemProperty( | |||
ApplicationConstants.FRONTEND_URL_ES5, | |||
ApplicationConstants.FRONTEND_URL_ES5_DEFAULT_VALUE); | |||
} | |||
if (!frontendUrl.endsWith("/")) { | |||
frontendUrl += "/"; | |||
} | |||
} | |||
return frontendUrl; | |||
} | |||
} | |||
@Override | |||
@@ -708,6 +731,8 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { | |||
String vaadinDir = vaadinService.getStaticFileLocation(request) | |||
+ "/VAADIN/"; | |||
appConfig.put(ApplicationConstants.VAADIN_DIR_URL, vaadinDir); | |||
appConfig.put(ApplicationConstants.FRONTEND_URL, | |||
context.getUriResolver().getFrontendUrl()); | |||
if (!session.getConfiguration().isProductionMode()) { | |||
appConfig.put("debug", true); |
@@ -538,4 +538,18 @@ public class WebBrowser implements Serializable { | |||
return browserDetails.isTooOldToFunctionProperly(); | |||
} | |||
/** | |||
* Checks if the browser supports ECMAScript 6, based on the user agent. | |||
* | |||
* @return <code>true</code> if the browser supports ES6, <code>false</code> | |||
* otherwise. | |||
*/ | |||
public boolean isEs6Supported() { | |||
if (browserDetails == null) { | |||
// Don't know, so assume no | |||
return false; | |||
} | |||
return browserDetails.isEs6Supported(); | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* Copyright 2000-2016 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.server; | |||
import java.util.Properties; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.mockito.Mockito; | |||
import com.vaadin.server.BootstrapHandler.BootstrapContext; | |||
import com.vaadin.server.BootstrapHandler.BootstrapUriResolver; | |||
public class BootstrapHandlerTest { | |||
private static final String VAADIN_URL = "http://host/VAADIN/"; | |||
public static class ES5Browser extends WebBrowser { | |||
@Override | |||
public boolean isEs6Supported() { | |||
return false; | |||
} | |||
} | |||
public static class ES6Browser extends WebBrowser { | |||
@Override | |||
public boolean isEs6Supported() { | |||
return true; | |||
} | |||
} | |||
@Test | |||
public void resolveFrontendES5() { | |||
testResolveFrontEnd("frontend://foobar.html", | |||
"http://host/VAADIN/frontend/es5/foobar.html", | |||
new ES5Browser()); | |||
} | |||
@Test | |||
public void resolveFrontendES6() { | |||
testResolveFrontEnd("frontend://foobar.html", | |||
"http://host/VAADIN/frontend/es6/foobar.html", | |||
new ES6Browser()); | |||
} | |||
@Test | |||
public void resolveFrontendES5CustomUrl() { | |||
Properties properties = new Properties(); | |||
properties.setProperty("frontend.url.es5", | |||
"https://cdn.somewhere.com/5"); | |||
testResolveFrontEnd("frontend://foobar.html", | |||
"https://cdn.somewhere.com/5/foobar.html", new ES5Browser(), | |||
properties); | |||
} | |||
@Test | |||
public void resolveFrontendES6CustomUrl() { | |||
Properties properties = new Properties(); | |||
properties.setProperty("frontend.url.es6", | |||
"https://cdn.somewhere.com/6"); | |||
testResolveFrontEnd("frontend://foobar.html", | |||
"https://cdn.somewhere.com/6/foobar.html", new ES6Browser(), | |||
properties); | |||
} | |||
private static void testResolveFrontEnd(String frontendUrl, | |||
String expectedUrl, WebBrowser browser) { | |||
testResolveFrontEnd(frontendUrl, expectedUrl, browser, | |||
new Properties()); | |||
} | |||
@SuppressWarnings("deprecation") | |||
private static void testResolveFrontEnd(String frontendUrl, | |||
String expectedUrl, WebBrowser browser, | |||
Properties customProperties) { | |||
BootstrapContext context = Mockito.mock(BootstrapContext.class); | |||
BootstrapUriResolver resolver = new BootstrapUriResolver(context) { | |||
@Override | |||
protected String getVaadinDirUrl() { | |||
return VAADIN_URL; | |||
} | |||
}; | |||
VaadinSession session = Mockito.mock(VaadinSession.class); | |||
Mockito.when(context.getSession()).thenReturn(session); | |||
DeploymentConfiguration configuration = new DefaultDeploymentConfiguration( | |||
BootstrapHandlerTest.class, customProperties); | |||
Mockito.when(session.getBrowser()).thenReturn(browser); | |||
Mockito.when(session.getConfiguration()).thenReturn(configuration); | |||
Assert.assertEquals(expectedUrl, | |||
resolver.resolveVaadinUri(frontendUrl)); | |||
} | |||
} |
@@ -45,6 +45,8 @@ public class ApplicationConstants implements Serializable { | |||
public static final String PUBLISHED_PROTOCOL_NAME = "published"; | |||
public static final String PUBLISHED_PROTOCOL_PREFIX = PUBLISHED_PROTOCOL_NAME | |||
+ "://"; | |||
public static final String FRONTEND_PROTOCOL_PREFIX = "frontend://"; | |||
/** | |||
* Prefix used for theme resource URLs | |||
* | |||
@@ -97,6 +99,16 @@ public class ApplicationConstants implements Serializable { | |||
*/ | |||
public static final String VAADIN_DIR_URL = "vaadinDir"; | |||
/** | |||
* Configuration parameter giving the (in some cases relative) URL to the | |||
* frontend resource folder from where frontend files are served (this is | |||
* what frontend:// should resolve to). | |||
* <p> | |||
* By default, this is "vaadin://es6" ("vaadin://es5" for browsers which | |||
* does not support ES6). | |||
*/ | |||
public static final String FRONTEND_URL = "frontendUrl"; | |||
/** | |||
* The name of the javascript containing the bootstrap code. The file is | |||
* located in the VAADIN directory. | |||
@@ -184,4 +196,30 @@ public class ApplicationConstants implements Serializable { | |||
*/ | |||
public static final String CONTENT_TYPE_TEXT_HTML_UTF_8 = "text/html; charset=utf-8"; | |||
/** | |||
* Configuration name for the {@literal frontend://} URL when using a | |||
* browser which is not ES6 compatible (i.e. IE11 or Safari 9). | |||
*/ | |||
public static final String FRONTEND_URL_ES5 = "frontend.url.es5"; | |||
/** | |||
* Configuration name for the {@literal frontend://} URL when using an ES6 | |||
* compatible browser. | |||
*/ | |||
public static final String FRONTEND_URL_ES6 = "frontend.url.es6"; | |||
/** | |||
* Default value of the configuration of the build URL of ES6 web | |||
* components. | |||
*/ | |||
public static final String FRONTEND_URL_ES6_DEFAULT_VALUE = VAADIN_PROTOCOL_PREFIX | |||
+ "frontend/es6/"; | |||
/** | |||
* Default value of the configuration of the build URL of ES5 web | |||
* components. | |||
*/ | |||
public static final String FRONTEND_URL_ES5_DEFAULT_VALUE = VAADIN_PROTOCOL_PREFIX | |||
+ "frontend/es5/"; | |||
} |
@@ -149,7 +149,8 @@ public class VBrowserDetails implements Serializable { | |||
parseVersionString(tmp); | |||
} | |||
} else if (isTrident) { | |||
// See https://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx#TriToken | |||
// See | |||
// https://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx#TriToken | |||
setIEMode((int) browserEngineVersion + 4); | |||
} else { | |||
String ieVersionString = userAgent | |||
@@ -585,4 +586,23 @@ public class VBrowserDetails implements Serializable { | |||
return false; | |||
} | |||
public boolean isEs6Supported() { | |||
if (isTooOldToFunctionProperly()) { | |||
return false; | |||
} | |||
// assumes evergreen browsers support ES6 | |||
if (isChrome() || isFirefox() || isOpera() || isEdge()) { | |||
return true; | |||
} | |||
// Safari > 9 | |||
if (isSafari() && getBrowserMajorVersion() > 9) { | |||
return true; | |||
} | |||
// IE11 and Safari 9 | |||
return false; | |||
} | |||
} |
@@ -45,6 +45,9 @@ public abstract class VaadinUriResolver implements Serializable { | |||
* RequestHandler} instances.</li> | |||
* <li><code>vaadin://</code> - resolves to the location of static resouces | |||
* in the VAADIN directory</li> | |||
* <li><code>frontend://</code> - resolves to the location of frontend | |||
* (Bower and similar) resources, which might vary depending on the used | |||
* browser</li> | |||
* </ul> | |||
* Any other URI protocols, such as <code>http://</code> or | |||
* <code>https://</code> are passed through this method unmodified. | |||
@@ -58,6 +61,13 @@ public abstract class VaadinUriResolver implements Serializable { | |||
if (vaadinUri == null) { | |||
return null; | |||
} | |||
if (vaadinUri | |||
.startsWith(ApplicationConstants.FRONTEND_PROTOCOL_PREFIX)) { | |||
final String frontendUrl = getFrontendUrl(); | |||
vaadinUri = frontendUrl + vaadinUri.substring( | |||
ApplicationConstants.FRONTEND_PROTOCOL_PREFIX.length()); | |||
} | |||
if (vaadinUri.startsWith(ApplicationConstants.THEME_PROTOCOL_PREFIX)) { | |||
final String themeUri = getThemeUri(); | |||
vaadinUri = themeUri + vaadinUri.substring(7); | |||
@@ -172,4 +182,13 @@ public abstract class VaadinUriResolver implements Serializable { | |||
*/ | |||
protected abstract String encodeQueryStringParameterValue( | |||
String parameterValue); | |||
/** | |||
* Returns the URL pointing to the folder containing frontend files, either | |||
* for ES5 (if browser does not support ES6) or ES6 (most browsers). | |||
* | |||
* @return the absolute or relative URL to the frontend files, ending with a | |||
* slash '/' | |||
*/ | |||
protected abstract String getFrontendUrl(); | |||
} |
@@ -0,0 +1,35 @@ | |||
/* | |||
/* | |||
* Copyright 2000-2016 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.tests.resources; | |||
import com.vaadin.annotations.JavaScript; | |||
import com.vaadin.annotations.Widgetset; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
@JavaScript("frontend://logFilename.js") | |||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||
public class FrontendInitialResourceUI extends AbstractTestUIWithLog { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
getPage().getJavaScript() | |||
.execute("document.body.innerHTML=window.jsFile;\n"); | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* Copyright 2000-2016 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.tests.resources; | |||
import com.vaadin.annotations.JavaScript; | |||
import com.vaadin.annotations.Widgetset; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.ui.Button; | |||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||
public class FrontendLaterLoadedResourceUI extends AbstractTestUIWithLog { | |||
@JavaScript("frontend://logFilename.js") | |||
public static class MyButton extends Button { | |||
} | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
Button b = new MyButton(); | |||
b.addClickListener(e -> { | |||
getPage().getJavaScript() | |||
.execute("document.body.innerHTML=window.jsFile;\n"); | |||
}); | |||
addComponent(b); | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
window.jsFile = "/VAADIN/frontend/es5/logFilename.js"; |
@@ -0,0 +1 @@ | |||
window.jsFile = "/VAADIN/frontend/es6/logFilename.js"; |
@@ -0,0 +1,43 @@ | |||
/* | |||
* Copyright 2000-2016 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.tests.resources; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import com.vaadin.testbench.parallel.BrowserUtil; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class FrontendInitialResourceUITest extends MultiBrowserTest { | |||
@Test | |||
public void correctEs5Es6FileImportedThroughFrontend() { | |||
openTestURL(); | |||
String es; | |||
if (BrowserUtil.isIE(getDesiredCapabilities()) | |||
|| BrowserUtil.isPhantomJS(getDesiredCapabilities())) { | |||
es = "es5"; | |||
} else { | |||
es = "es6"; | |||
} | |||
testBench().disableWaitForVaadin(); // For some reason needed by IE11 | |||
Assert.assertEquals("/VAADIN/frontend/" + es + "/logFilename.js", | |||
findElement(By.tagName("body")).getText()); | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
/* | |||
* Copyright 2000-2016 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.tests.resources; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.testbench.parallel.BrowserUtil; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class FrontendLaterLoadedResourceUITest extends MultiBrowserTest { | |||
@Test | |||
public void correctEs5Es6FileImportedThroughFrontend() { | |||
openTestURL(); | |||
$(ButtonElement.class).first().click(); | |||
String es; | |||
if (BrowserUtil.isIE(getDesiredCapabilities()) | |||
|| BrowserUtil.isPhantomJS(getDesiredCapabilities())) { | |||
es = "es5"; | |||
} else { | |||
es = "es6"; | |||
} | |||
testBench().disableWaitForVaadin(); // For some reason needed by IE11 | |||
Assert.assertEquals("/VAADIN/frontend/" + es + "/logFilename.js", | |||
findElement(By.tagName("body")).getText()); | |||
} | |||
} |