diff --git a/drivers.xml b/drivers.xml index dc2f7471214..7085e54ae2c 100644 --- a/drivers.xml +++ b/drivers.xml @@ -2,10 +2,10 @@ - + - https://chromedriver.storage.googleapis.com/77.0.3865.40/chromedriver_win32.zip - c367947ec475e436e34f3f9c4864ae3659867c6e + https://chromedriver.storage.googleapis.com/78.0.3904.70/chromedriver_win32.zip + 26705e683632c6e1f9c62179b6143b409f4d7681 sha1 @@ -13,10 +13,10 @@ - + - https://chromedriver.storage.googleapis.com/77.0.3865.40/chromedriver_linux64.zip - 1776fe8449489fa98e4fe80255ed3874f94d0dee + https://chromedriver.storage.googleapis.com/78.0.3904.70/chromedriver_linux64.zip + 63e13e5f0df96af1cc3a2006c00b2f1871b62caf sha1 @@ -24,10 +24,10 @@ - + - https://chromedriver.storage.googleapis.com/77.0.3865.40/chromedriver_mac64.zip - bc5d502044c86fbba20614df317cc2194d19aa86 + https://chromedriver.storage.googleapis.com/78.0.3904.70/chromedriver_mac64.zip + ce06a7d3a9d18b3d054d3b4c3c32d2cd74e81a91 sha1 diff --git a/flow-bom/pom.xml b/flow-bom/pom.xml index 34490defb70..dd5e0a0fe55 100644 --- a/flow-bom/pom.xml +++ b/flow-bom/pom.xml @@ -38,6 +38,11 @@ flow-html-components ${project.version} + + com.vaadin + flow-server-production-mode + ${project.version} + com.vaadin flow-html-components-testbench diff --git a/flow-client/pom.xml b/flow-client/pom.xml index 2d8594ce46a..17010a1343c 100644 --- a/flow-client/pom.xml +++ b/flow-client/pom.xml @@ -194,6 +194,7 @@ FF38 300 + -Xmx512m -Dvaadin.enableDevServer=false diff --git a/flow-client/src/main/java/com/vaadin/client/ResourceLoader.java b/flow-client/src/main/java/com/vaadin/client/ResourceLoader.java index 365ad034a63..2248bc78a9b 100644 --- a/flow-client/src/main/java/com/vaadin/client/ResourceLoader.java +++ b/flow-client/src/main/java/com/vaadin/client/ResourceLoader.java @@ -771,13 +771,14 @@ private static native void runPromiseExpression(String expression, /*-{ try { var promise = promiseSupplier.@java.util.function.Supplier::get(*)(); - if ( !(promise instanceof Promise )){ + if ( !(promise instanceof $wnd.Promise )){ throw new Error('The expression "'+expression+'" result is not a Promise.'); } promise.then( function(result) { onSuccess.@java.lang.Runnable::run(*)(); } , - function(error) { onError.@java.lang.Runnable::run(*)(); } ); + function(error) { console.error(error); onError.@java.lang.Runnable::run(*)(); } ); } catch(error) { + console.error(error); onError.@java.lang.Runnable::run(*)(); } }-*/; diff --git a/flow-client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java b/flow-client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java index 36dd41e37f8..93d8a1d174c 100644 --- a/flow-client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java +++ b/flow-client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java @@ -24,6 +24,7 @@ import com.vaadin.client.Console; import com.vaadin.client.Registry; +import com.vaadin.client.UILifecycle; import com.vaadin.client.UILifecycle.UIState; import com.vaadin.client.WidgetUtil; import com.vaadin.client.communication.AtmospherePushConnection.AtmosphereResponse; @@ -297,7 +298,10 @@ protected void updateDialog() { */ protected final void giveUp() { reconnectionCause = null; - endRequest(); + + if (registry.getRequestResponseTracker().hasActiveRequest()) { + endRequest(); + } stopDialogTimer(); if (!isDialogVisible()) { @@ -468,7 +472,12 @@ protected void handleUnauthorized(XhrConnectionError xhrConnectionError) { private void stopApplication() { // Consider application not running any more and prevent all // future requests - registry.getUILifecycle().setState(UIState.TERMINATED); + + UILifecycle uiLifecycle = registry.getUILifecycle(); + + if (uiLifecycle.getState() != UIState.TERMINATED) { + uiLifecycle.setState(UIState.TERMINATED); + } } private void handleUnrecoverableCommunicationError(String details, diff --git a/flow-client/src/main/java/com/vaadin/client/communication/MessageHandler.java b/flow-client/src/main/java/com/vaadin/client/communication/MessageHandler.java index be0e0491102..15725cd1a82 100644 --- a/flow-client/src/main/java/com/vaadin/client/communication/MessageHandler.java +++ b/flow-client/src/main/java/com/vaadin/client/communication/MessageHandler.java @@ -18,6 +18,7 @@ import com.google.gwt.core.client.Duration; import com.google.gwt.core.client.Scheduler; import com.google.gwt.user.client.Timer; + import com.vaadin.client.Command; import com.vaadin.client.Console; import com.vaadin.client.DependencyLoader; @@ -190,8 +191,15 @@ public void handleMessage(final ValueMap json) { "The json to handle cannot be null"); } if (getServerId(json) == -1) { - Console.error("Response didn't contain a server id. " - + "Please verify that the server is up-to-date and that the response data has not been modified in transmission."); + + ValueMap meta = json.getValueMap("meta"); + + // Log the error only if session didn't expire. + if (meta == null + || !meta.containsKey(JsonConstants.META_SESSION_EXPIRED)) { + Console.error("Response didn't contain a server id. " + + "Please verify that the server is up-to-date and that the response data has not been modified in transmission."); + } } UIState state = registry.getUILifecycle().getState(); @@ -502,7 +510,7 @@ private boolean isResynchronize(ValueMap json) { private boolean isResponse(ValueMap json) { ValueMap meta = json.getValueMap("meta"); - if (meta == null || !meta.containsKey("async")) { + if (meta == null || !meta.containsKey(JsonConstants.META_ASYNC)) { return true; } return false; diff --git a/flow-client/src/main/java/com/vaadin/client/communication/ServerConnector.java b/flow-client/src/main/java/com/vaadin/client/communication/ServerConnector.java index f2ee8a09121..6b5032159c2 100644 --- a/flow-client/src/main/java/com/vaadin/client/communication/ServerConnector.java +++ b/flow-client/src/main/java/com/vaadin/client/communication/ServerConnector.java @@ -122,15 +122,21 @@ public void sendEventMessage(int nodeId, String eventType, * the event handler method name to execute on the server side * @param argsArray * the arguments array for the method + * @param promiseId + * the promise id to use for getting the result back, or -1 if no + * result is expected */ public void sendTemplateEventMessage(StateNode node, String methodName, - JsonArray argsArray) { + JsonArray argsArray, int promiseId) { JsonObject message = Json.createObject(); message.put(JsonConstants.RPC_TYPE, JsonConstants.RPC_PUBLISHED_SERVER_EVENT_HANDLER); message.put(JsonConstants.RPC_NODE, node.getId()); message.put(JsonConstants.RPC_TEMPLATE_EVENT_METHOD_NAME, methodName); message.put(JsonConstants.RPC_TEMPLATE_EVENT_ARGS, argsArray); + if (promiseId != -1) { + message.put(JsonConstants.RPC_TEMPLATE_EVENT_PROMISE, promiseId); + } sendMessage(message); } diff --git a/flow-client/src/main/java/com/vaadin/client/flow/StateTree.java b/flow-client/src/main/java/com/vaadin/client/flow/StateTree.java index 2b8c7b05f97..6f99e8592da 100644 --- a/flow-client/src/main/java/com/vaadin/client/flow/StateTree.java +++ b/flow-client/src/main/java/com/vaadin/client/flow/StateTree.java @@ -245,13 +245,16 @@ public void sendNodePropertySyncToServer(MapProperty property) { * the method name * @param argsArray * the arguments array for the method + * @param promiseId + * the promise id to use for getting the result back, or -1 if no + * result is expected */ public void sendTemplateEventToServer(StateNode node, String methodName, - JsArray argsArray) { + JsArray argsArray, int promiseId) { if (isValidNode(node)) { JsonArray array = WidgetUtil.crazyJsCast(argsArray); registry.getServerConnector().sendTemplateEventMessage(node, - methodName, array); + methodName, array, promiseId); } } diff --git a/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventHandlerBinder.java b/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventHandlerBinder.java index ca65292f96f..55dca50815a 100644 --- a/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventHandlerBinder.java +++ b/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventHandlerBinder.java @@ -52,7 +52,7 @@ private ServerEventHandlerBinder() { public static EventRemover bindServerEventHandlerNames(Element element, StateNode node) { return bindServerEventHandlerNames(() -> ServerEventObject.get(element), - node, NodeFeatures.CLIENT_DELEGATE_HANDLERS); + node, NodeFeatures.CLIENT_DELEGATE_HANDLERS, true); } /** @@ -67,11 +67,15 @@ public static EventRemover bindServerEventHandlerNames(Element element, * the state node containing the feature * @param featureId * the feature id which contains event handler methods + * @param returnValue + * true if the handler should return a promise that + * will reflect the server-side result; false to not + * return any value * @return a handle which can be used to remove the listener for the feature */ public static EventRemover bindServerEventHandlerNames( Supplier objectProvider, StateNode node, - int featureId) { + int featureId, boolean returnValue) { NodeList serverEventHandlerNamesList = node.getList(featureId); if (serverEventHandlerNamesList.length() > 0) { @@ -80,7 +84,7 @@ public static EventRemover bindServerEventHandlerNames( for (int i = 0; i < serverEventHandlerNamesList.length(); i++) { String serverEventHandlerName = (String) serverEventHandlerNamesList .get(i); - object.defineMethod(serverEventHandlerName, node); + object.defineMethod(serverEventHandlerName, node, returnValue); } } @@ -94,7 +98,8 @@ public static EventRemover bindServerEventHandlerNames( JsArray add = e.getAdd(); for (int i = 0; i < add.length(); i++) { - serverObject.defineMethod((String) add.get(i), node); + serverObject.defineMethod((String) add.get(i), node, + returnValue); } }); } diff --git a/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventObject.java b/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventObject.java index 4985d08c54d..e30cbe82d12 100644 --- a/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventObject.java +++ b/flow-client/src/main/java/com/vaadin/client/flow/binding/ServerEventObject.java @@ -15,7 +15,6 @@ */ package com.vaadin.client.flow.binding; - import jsinterop.annotations.JsFunction; import com.google.gwt.core.client.JavaScriptObject; @@ -28,6 +27,7 @@ import com.vaadin.client.flow.collection.JsMap; import com.vaadin.client.flow.util.NativeFunction; import com.vaadin.flow.internal.nodefeature.NodeFeatures; +import com.vaadin.flow.shared.JsonConstants; import elemental.dom.Element; import elemental.dom.Node; @@ -46,6 +46,8 @@ public final class ServerEventObject extends JavaScriptObject { private static final String NODE_ID = "nodeId"; private static final String EVENT_PREFIX = "event"; + private static final String PROMISE_CALLBACK_NAME = JsonConstants.RPC_PROMISE_CALLBACK_NAME; + /** * Callback interface for an event data expression parsed using new * Function() in JavaScript. @@ -76,6 +78,31 @@ private interface ServerEventDataExpression { protected ServerEventObject() { } + private native void initPromiseHandler() + /*-{ + var name = @ServerEventObject::PROMISE_CALLBACK_NAME + // Use defineProperty to make it non-enumerable + Object.defineProperty(this, name, { + value: function(promiseId, success, value) { + var promise = this[name].promises[promiseId]; + + // undefined if client-side node was recreated after execution was scheduled + if (promise !== undefined) { + delete this[name].promises[promiseId]; + + if (success) { + // Resolve + promise[0](value); + } else { + // Reject + promise[1](Error("Something went wrong. Check server-side logs for more information.")); + } + } + } + }); + this[name].promises = []; + }-*/; + /** * Defines a method with the given name to be a callback to the server for * the given state node. @@ -88,8 +115,13 @@ protected ServerEventObject() { * @param node * the node to use as an identifier when sending an event to the * server + * @param returnPromise + * true if the handler should return a promise that + * will reflect the server-side result; false to not + * return any value */ - public native void defineMethod(String methodName, StateNode node) + public native void defineMethod(String methodName, StateNode node, + boolean returnPromise) /*-{ this[methodName] = $entry(function(eventParameter) { var prototype = Object.getPrototypeOf(this); @@ -102,7 +134,24 @@ public native void defineMethod(String methodName, StateNode node) if(args === null) { args = Array.prototype.slice.call(arguments); } - tree.@com.vaadin.client.flow.StateTree::sendTemplateEventToServer(*)(node, methodName, args); + + var returnValue; + var promiseId = -1; + + if (returnPromise) { + var promises = this[@ServerEventObject::PROMISE_CALLBACK_NAME].promises; + + promiseId = promises.length; + + returnValue = new Promise(function(resolve, reject) { + // Store each callback for later use + promises[promiseId] = [resolve, reject]; + }); + } + + tree.@com.vaadin.client.flow.StateTree::sendTemplateEventToServer(*)(node, methodName, args, promiseId); + + return returnValue; }); }-*/; @@ -230,6 +279,7 @@ public static ServerEventObject get(Element element) { .crazyJsoCast(WidgetUtil.getJsProperty(element, "$server")); if (serverObject == null) { serverObject = (ServerEventObject) JavaScriptObject.createObject(); + serverObject.initPromiseHandler(); WidgetUtil.setJsProperty(element, "$server", serverObject); } return serverObject; diff --git a/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java b/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java index 776a42ec72b..4eea8b15b8b 100644 --- a/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java +++ b/flow-client/src/main/java/com/vaadin/client/flow/binding/SimpleElementBindingStrategy.java @@ -19,8 +19,11 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import jsinterop.annotations.JsFunction; + import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.Scheduler; + import com.vaadin.client.Command; import com.vaadin.client.Console; import com.vaadin.client.ExistingElementMap; @@ -62,7 +65,6 @@ import elemental.json.JsonObject; import elemental.json.JsonType; import elemental.json.JsonValue; -import jsinterop.annotations.JsFunction; /** * Binding strategy for a simple (not template) {@link Element} node. @@ -257,8 +259,14 @@ private void scheduleInitialExecution(StateNode stateNode) { * command execution */ Reactive.addPostFlushListener( - () -> Scheduler.get().scheduleDeferred(() -> stateNode - .getNodeData(InitialPropertyUpdate.class).execute())); + () -> Scheduler.get().scheduleDeferred(() -> { + InitialPropertyUpdate propertyUpdate = stateNode + .getNodeData(InitialPropertyUpdate.class); + // cleared if handlePropertiesChanged has already happened + if (propertyUpdate != null) { + propertyUpdate.execute(); + } + })); } private native void bindPolymerModelProperties(StateNode node, @@ -284,9 +292,9 @@ private native void bindPolymerModelProperties(StateNode node, private native void hookUpPolymerElement(StateNode node, Element element) /*-{ var self = this; - + var originalPropertiesChanged = element._propertiesChanged; - + if (originalPropertiesChanged) { element._propertiesChanged = function (currentProps, changedProps, oldProps) { $entry(function () { @@ -295,16 +303,16 @@ private native void hookUpPolymerElement(StateNode node, Element element) originalPropertiesChanged.apply(this, arguments); }; } - - + + var tree = node.@com.vaadin.client.flow.StateNode::getTree()(); - + var originalReady = element.ready; - + element.ready = function (){ originalReady.apply(this, arguments); @com.vaadin.client.PolymerUtils::fireReadyEvent(*)(element); - + // The _propertiesChanged method which is replaced above for the element // doesn't do anything for items in dom-repeat. // Instead it's called with some meaningful info for the dom-repeat element. @@ -313,7 +321,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) // which changes this method for any dom-repeat instance. var replaceDomRepeatPropertyChange = function(){ var domRepeat = element.root.querySelector('dom-repeat'); - + if ( domRepeat ){ // If the dom-repeat element is in the DOM then // this method should not be executed anymore. The logic below will replace @@ -327,12 +335,12 @@ private native void hookUpPolymerElement(StateNode node, Element element) // if dom-repeat is found => replace _propertiesChanged method in the prototype and mark it as replaced. if ( !domRepeat.constructor.prototype.$propChangedModified){ domRepeat.constructor.prototype.$propChangedModified = true; - + var changed = domRepeat.constructor.prototype._propertiesChanged; - + domRepeat.constructor.prototype._propertiesChanged = function(currentProps, changedProps, oldProps){ changed.apply(this, arguments); - + var props = Object.getOwnPropertyNames(changedProps); var items = "items."; var i; @@ -353,7 +361,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) if( currentPropsItem && currentPropsItem.nodeId ){ var nodeId = currentPropsItem.nodeId; var value = currentPropsItem[propertyName]; - + // this is an attempt to find the template element // which is not available as a context in the protype method var host = this.__dataHost; @@ -364,7 +372,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) while( !host.localName || host.__dataHost ){ host = host.__dataHost; } - + $entry(function () { @SimpleElementBindingStrategy::handleListItemPropertyChange(*)(nodeId, host, propertyName, value, tree); })(); @@ -375,7 +383,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) }; } }; - + // dom-repeat doesn't have to be in DOM even if template has it // such situation happens if there is dom-if e.g. which evaluates to false initially. // in this case dom-repeat is not yet in the DOM tree until dom-if becomes true @@ -390,7 +398,7 @@ private native void hookUpPolymerElement(StateNode node, Element element) element.addEventListener('dom-change',replaceDomRepeatPropertyChange); } } - + }-*/; private static void handleListItemPropertyChange(double nodeId, @@ -1379,7 +1387,7 @@ private EventRemover bindClassList(Element element, StateNode node) { private EventRemover bindPolymerEventHandlerNames(BindingContext context) { return ServerEventHandlerBinder.bindServerEventHandlerNames( () -> WidgetUtil.crazyJsoCast(context.htmlNode), context.node, - NodeFeatures.POLYMER_SERVER_EVENT_HANDLERS); + NodeFeatures.POLYMER_SERVER_EVENT_HANDLERS, false); } private EventRemover bindClientCallableMethods(BindingContext context) { diff --git a/flow-client/src/test-gwt/java/com/vaadin/client/ClientEngineTestBase.java b/flow-client/src/test-gwt/java/com/vaadin/client/ClientEngineTestBase.java index 7f6d878801c..984c371c123 100644 --- a/flow-client/src/test-gwt/java/com/vaadin/client/ClientEngineTestBase.java +++ b/flow-client/src/test-gwt/java/com/vaadin/client/ClientEngineTestBase.java @@ -10,7 +10,7 @@ public abstract class ClientEngineTestBase extends GWTTestCase { @Override protected void gwtSetUp() throws Exception { - installCollectionsPolyfill(); + installPolyfills(); super.gwtSetUp(); } @@ -19,7 +19,7 @@ public String getModuleName() { return "com.vaadin.ClientEngineXSI"; } - private static native void installCollectionsPolyfill() + private static native void installPolyfills() /*-{ // Remove broken HtmlUnit versions (polyfill checks window, but installs to $wnd) delete window.Set; @@ -34,6 +34,13 @@ private static native void installCollectionsPolyfill() 0}function z(){return k(this._itp,this._keys)}function l(){return k(this._itp,this._values)}function A(){return k(this._itp,this._keys,this._values)}function B(){return k(this._itp,this._values,this._values)}function k(a,c,b){var g=[0],e=!1;a.push(g);return{next:function(){var f,d=g[0];!e&&dn;n++)r(e,e._deferreds[n]);e._deferreds=null}function c(e,n){var t=!1;try{e(function(e){t||(t=!0,i(n,e))},function(e){t||(t=!0,f(n,e))})}catch(o){if(t)return;t=!0,f(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,n){var o=new this.constructor(t);return r(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,n,o)),o},o.prototype["finally"]=e,o.all=function(e){return new o(function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,function(n){r(e,n)},o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(t,r){if(!n(e))return r(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)o.resolve(e[i]).then(t,r)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=o}); }-*/; } diff --git a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtEventHandlerTest.java b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtEventHandlerTest.java index 955f987e479..164fca9b702 100644 --- a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtEventHandlerTest.java +++ b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtEventHandlerTest.java @@ -56,6 +56,7 @@ public class GwtEventHandlerTest extends ClientEngineTestBase { private Map> serverMethods = new HashMap<>(); private Map serverRpcNodes = new HashMap<>(); + private Map serverPromiseIds = new HashMap<>(); @Override protected void gwtSetUp() throws Exception { @@ -72,9 +73,10 @@ protected void gwtSetUp() throws Exception { @Override public void sendTemplateEventToServer(StateNode node, - String methodName, JsArray argValues) { + String methodName, JsArray argValues, int promiseId) { serverMethods.put(methodName, argValues); serverRpcNodes.put(methodName, node); + serverPromiseIds.put(methodName, Integer.valueOf(promiseId)); } }; @@ -91,6 +93,77 @@ public void testNoServerEventHandler_nothingInDom() { assertNull(WidgetUtil.getJsProperty(element, "$server")); } + public void testClientCallablePromises() { + String methodName = "publishedMethod"; + + node.getList(NodeFeatures.CLIENT_DELEGATE_HANDLERS).add(0, methodName); + Binder.bind(node, element); + Reactive.flush(); + ServerEventObject serverObject = ServerEventObject.get(element); + + NativeFunction publishedMethod = new NativeFunction( + "return this." + methodName + "()"); + Object promise0 = publishedMethod.apply(serverObject, + JsCollections.array()); + + assertNotNull(promise0); + assertEquals(Integer.valueOf(0), serverPromiseIds.get(methodName)); + assertTrue(hasPromise(element, 0)); + + Object promise1 = publishedMethod.apply(serverObject, + JsCollections.array()); + assertEquals(Integer.valueOf(1), serverPromiseIds.get(methodName)); + assertTrue(hasPromise(element, 1)); + + addThen(promise0, value -> { + assertEquals("promise0", value); + assertFalse("Promise handlers should be cleared", + hasPromise(element, 0)); + + completePromise(element, 1, false, null); + }); + + addCatch(promise1, message -> { + assertEquals( + "Error: Something went wrong. Check server-side logs for more information.", + message); + assertFalse("Promise handlers should be cleared", + hasPromise(element, 1)); + + finishTest(); + }); + + completePromise(element, 0, true, "promise0"); + + delayTestFinish(100); + } + + private static native void addThen(Object promise, Consumer callback) + /*-{ + promise.then($entry(function(value) { + callback.@Consumer::accept(*)(value); + })); + }-*/; + + private static native void addCatch(Object promise, + Consumer callback) + /*-{ + promise['catch']($entry(function(value) { + callback.@Consumer::accept(*)(""+value); + })); + }-*/; + + private static native void completePromise(Element element, int promiseId, + boolean success, String value) + /*-{ + element.$server[@ServerEventObject::PROMISE_CALLBACK_NAME](promiseId, success, value); + }-*/; + + private static native boolean hasPromise(Element element, int promiseId) + /*-{ + return promiseId in element.$server[@ServerEventObject::PROMISE_CALLBACK_NAME].promises; + }-*/; + public void testClientCallableMethodInDom() { assertServerEventHandlerMethodInDom( NodeFeatures.CLIENT_DELEGATE_HANDLERS, @@ -145,6 +218,7 @@ public void testPolymerMockedEventHandler() { assertEquals(methodName, serverMethods.keySet().iterator().next()); assertEquals(0, serverMethods.get(methodName).length()); assertEquals(node, serverRpcNodes.get(methodName)); + assertEquals(Integer.valueOf(-1), serverPromiseIds.get(methodName)); } public void testPolymerMockedEventHandlerWithEventData() { @@ -219,7 +293,7 @@ public void testPolymerMockedEventHandlerWithDefaultImplementation() { * Add a function to the element prototype ("default" function) for * {@code methodName} that adds the second argument to the event (first * argument) as a result - * + * * @param element * Element to add "default" method to * @param methodName @@ -328,7 +402,7 @@ public void testEventHandlerModelItemSingleItem() { /** * Add get functionality to element if not defined. Add the key value pair * to property object or create object if not available. - * + * * @param node * Target node * @param property diff --git a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtMultipleBindingTest.java b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtMultipleBindingTest.java index 2a128056d73..0cfe8dca2a3 100644 --- a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtMultipleBindingTest.java +++ b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtMultipleBindingTest.java @@ -97,7 +97,7 @@ protected void gwtSetUp() throws Exception { tree = new StateTree(registry) { @Override public void sendTemplateEventToServer(StateNode node, - String methodName, JsArray argValues) { + String methodName, JsArray argValues, int promiseId) { } }; diff --git a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtStateTreeTest.java b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtStateTreeTest.java index b8c97eec824..400874fd9f8 100644 --- a/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtStateTreeTest.java +++ b/flow-client/src/test-gwt/java/com/vaadin/client/flow/GwtStateTreeTest.java @@ -16,6 +16,7 @@ package com.vaadin.client.flow; import com.google.gwt.core.client.JavaScriptObject; + import com.vaadin.client.ClientEngineTestBase; import com.vaadin.client.InitialPropertiesHandler; import com.vaadin.client.Registry; @@ -50,7 +51,7 @@ private TestServerConnector(Registry registry) { @Override public void sendTemplateEventMessage(StateNode node, String methodName, - JsonArray array) { + JsonArray array, int promiseId) { this.node = node; this.methodName = methodName; args = array; @@ -79,7 +80,7 @@ public void testSendTemplateEventToServer_delegateToServerConnector() { StateNode node = new StateNode(0, tree); tree.registerNode(node); JsArray array = getArgArray(); - tree.sendTemplateEventToServer(node, "foo", array); + tree.sendTemplateEventToServer(node, "foo", array, -1); JsonObject object = Json.createObject(); object.put("key", "value"); @@ -104,7 +105,7 @@ public void testDeferredTemplateMessage_isIgnored() { tree.registerNode(node); Reactive.addPostFlushListener(() -> { - tree.sendTemplateEventToServer(node, "click", null); + tree.sendTemplateEventToServer(node, "click", null, -1); TestServerConnector serverConnector = (TestServerConnector) registry .getServerConnector(); assertNull( diff --git a/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java b/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java index fbb325d0f02..6263c2a3378 100644 --- a/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java +++ b/flow-data/src/main/java/com/vaadin/flow/data/binder/Binder.java @@ -566,9 +566,11 @@ default BindingBuilder withNullRepresentation( TARGET nullRepresentation) { return withConverter( fieldValue -> Objects.equals(fieldValue, nullRepresentation) - ? null : fieldValue, + ? null + : fieldValue, modelValue -> Objects.isNull(modelValue) - ? nullRepresentation : modelValue); + ? nullRepresentation + : modelValue); } /** @@ -2503,7 +2505,8 @@ private Converter createNullRepresentationA Converter nullRepresentationConverter = Converter .from(fieldValue -> fieldValue, modelValue -> Objects.isNull(modelValue) - ? field.getEmptyValue() : modelValue, + ? field.getEmptyValue() + : modelValue, Throwable::getMessage); ConverterDelegate converter = new ConverterDelegate<>( nullRepresentationConverter); @@ -2882,6 +2885,7 @@ protected void removeBindingInternal(Binding binding) { if (bindings.remove(binding)) { boundProperties.entrySet() .removeIf(entry -> entry.getValue().equals(binding)); + changedBindings.remove(binding); } } diff --git a/flow-data/src/main/java/com/vaadin/flow/data/provider/AbstractDataProvider.java b/flow-data/src/main/java/com/vaadin/flow/data/provider/AbstractDataProvider.java index 2b42f40ae55..93d66fe7e14 100644 --- a/flow-data/src/main/java/com/vaadin/flow/data/provider/AbstractDataProvider.java +++ b/flow-data/src/main/java/com/vaadin/flow/data/provider/AbstractDataProvider.java @@ -45,7 +45,16 @@ public abstract class AbstractDataProvider implements DataProvider { @Override public Registration addDataProviderListener( DataProviderListener listener) { - return addListener(DataChangeEvent.class, listener::onDataChange); + // Using an anonymous class instead of lambda or method reference to prevent potential + // self reference serialization issues when clients holds a reference + // to the Registration instance returned by this method + SerializableConsumer consumer = new SerializableConsumer() { + @Override + public void accept(DataChangeEvent dataChangeEvent) { + listener.onDataChange(dataChangeEvent); + } + }; + return addListener(DataChangeEvent.class, consumer); } @Override diff --git a/flow-data/src/test/java/com/vaadin/flow/data/binder/BinderTest.java b/flow-data/src/test/java/com/vaadin/flow/data/binder/BinderTest.java index 2c8258e015d..e953d78e729 100644 --- a/flow-data/src/test/java/com/vaadin/flow/data/binder/BinderTest.java +++ b/flow-data/src/test/java/com/vaadin/flow/data/binder/BinderTest.java @@ -121,6 +121,23 @@ public void bindNullBean_FieldsAreCleared() { assertEquals("Age field not empty", "", ageField.getValue()); } + @Test + public void removeInvalidBinding_validateDoesNotThrow() { + binder.forField(nameField).bind(Person::getFirstName, + Person::setFirstName); + Binding ageBinding = binder.forField(ageField) + .withConverter(new StringToIntegerConverter("")) + .bind(Person::getAge, Person::setAge); + binder.withValidator(bean -> true, ""); + binder.setBean(item); + + ageField.setValue("foo"); + + binder.removeBinding(ageBinding); + + binder.validate(); + } + @Test public void clearForReadBean_boundFieldsAreCleared() { binder.forField(nameField).bind(Person::getFirstName, @@ -451,8 +468,9 @@ public void beanBinder_withConverter_nullRepresentationIsNotDisabled() { String customNullPointerRepresentation = "foo"; Binder binder = new Binder<>(Person.class); binder.forField(nameField) - .withConverter(value -> value, value -> value == null - ? customNullPointerRepresentation : value) + .withConverter(value -> value, + value -> value == null ? customNullPointerRepresentation + : value) .bind("firstName"); Person person = new Person(); @@ -1345,7 +1363,6 @@ public void nullRejetingField_nullValue_wrappedExceptionMentionsNullRepresentati binder.readBean(new AtomicReference<>()); } - @Test public void nullRejetingField_otherRejectedValue_originalExceptionIsThrown() { TestTextField field = createNullRejectingFieldWithEmptyValue(""); diff --git a/flow-data/src/test/java/com/vaadin/flow/tests/data/DataSerializableTest.java b/flow-data/src/test/java/com/vaadin/flow/tests/data/DataSerializableTest.java index b5cedc9cb03..47d28c02de4 100644 --- a/flow-data/src/test/java/com/vaadin/flow/tests/data/DataSerializableTest.java +++ b/flow-data/src/test/java/com/vaadin/flow/tests/data/DataSerializableTest.java @@ -1,6 +1,58 @@ package com.vaadin.flow.tests.data; +import java.io.Serializable; +import java.util.Collections; + +import com.vaadin.flow.data.binder.HasDataProvider; +import com.vaadin.flow.data.provider.DataProvider; +import com.vaadin.flow.data.provider.ListDataProvider; +import com.vaadin.flow.shared.Registration; import com.vaadin.flow.testutil.ClassesSerializableTest; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; public class DataSerializableTest extends ClassesSerializableTest { + + /* + * AbstractDataProvider.addDataProviderListener may return a Registration instance + * that is not deserializable due to self references. + * This happens for example if the dataprovider, member of a component, + * is used to add a com.vaadin.flow.data.provider.DataProviderListener + * into an inner component; the resulting Registration handles a reference + * to the dataprovider itself that is already referenced by the outer component + */ + @Test + public void selfReferenceSerialization() throws Throwable { + Outer outer = new Outer(); + Outer out = serializeAndDeserialize(outer); + assertNotNull(out); + } + + static class Inner implements HasDataProvider, Serializable { + + private Registration registration; + + @Override + public void setDataProvider(DataProvider dataProvider) { + if (registration != null) { + registration.remove(); + } + registration = dataProvider.addDataProviderListener(event -> onDataProviderChange()); + } + + void onDataProviderChange() { + + } + + } + + static class Outer implements Serializable { + private final ListDataProvider dataProvider = new ListDataProvider<>(Collections.emptyList()); + private final Inner inner = new Inner(); + + public Outer() { + inner.setDataProvider(dataProvider); + } + } } diff --git a/flow-html-components-testbench/src/main/java/com/vaadin/flow/component/html/testbench/PreElement.java b/flow-html-components-testbench/src/main/java/com/vaadin/flow/component/html/testbench/PreElement.java new file mode 100644 index 00000000000..de0568e8e09 --- /dev/null +++ b/flow-html-components-testbench/src/main/java/com/vaadin/flow/component/html/testbench/PreElement.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2018 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.component.html.testbench; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.elementsbase.Element; + +/** + * A TestBench element representing a <pre> element. + * + */ +@Element("pre") +public class PreElement extends TestBenchElement { + +} diff --git a/flow-html-components/src/main/java/com/vaadin/flow/component/html/Pre.java b/flow-html-components/src/main/java/com/vaadin/flow/component/html/Pre.java new file mode 100644 index 00000000000..5de9b817b9a --- /dev/null +++ b/flow-html-components/src/main/java/com/vaadin/flow/component/html/Pre.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2018 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.component.html; + +import com.vaadin.flow.component.ClickNotifier; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HtmlContainer; +import com.vaadin.flow.component.Tag; + +/** + * Component representing a <pre> element. + * + * @author Vaadin Ltd + */ +@Tag(Tag.PRE) +public class Pre extends HtmlContainer + implements ClickNotifier
 {
+
+    /**
+     * Creates a new empty preformatted text block.
+     */
+    public Pre() {
+        super();
+    }
+
+    /**
+     * Creates a new preformatted text block with the given child components.
+     *
+     * @param components
+     *            the child components
+     */
+    public Pre(Component... components) {
+        super(components);
+    }
+
+    /**
+     * Creates a new paragraph with the given text.
+     *
+     * @param text
+     *            the text
+     */
+    public Pre(String text) {
+        super();
+        setText(text);
+    }
+}
diff --git a/flow-html-components/src/test/java/com/vaadin/flow/component/html/PreTest.java b/flow-html-components/src/test/java/com/vaadin/flow/component/html/PreTest.java
new file mode 100644
index 00000000000..b544dada6b1
--- /dev/null
+++ b/flow-html-components/src/test/java/com/vaadin/flow/component/html/PreTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2018 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.component.html;
+
+public class PreTest extends ComponentTest {
+    // Actual test methods in super class
+
+    @Override
+    protected void addProperties() {
+        // Component defines no new properties
+    }
+
+}
diff --git a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/common/FlowPluginFrontendUtils.java b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/common/FlowPluginFrontendUtils.java
index 37f8f83502e..6cf2fa8dc64 100644
--- a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/common/FlowPluginFrontendUtils.java
+++ b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/common/FlowPluginFrontendUtils.java
@@ -38,7 +38,7 @@ public class FlowPluginFrontendUtils {
      * Additionally include compile-time-only dependencies matching the pattern.
      */
     private static final String INCLUDE_FROM_COMPILE_DEPS_REGEX =
-            ".*(/|\\\\)portlet-api-.+jar$";
+            ".*(/|\\\\)(portlet-api|javax\\.servlet-api)-.+jar$";
 
     private FlowPluginFrontendUtils() {
     }
diff --git a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java
index 087f2d22698..8016704a48e 100644
--- a/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java
+++ b/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java
@@ -129,6 +129,9 @@ public class BuildFrontendMojo extends FlowModeAbstractMojo {
             + Constants.LOCAL_FRONTEND_RESOURCES_PATH)
     protected File frontendResourcesDirectory;
 
+    @Parameter
+    private String polymerVersion;
+
     /**
      * Whether to use byte code scanner strategy to discover frontend
      * components.
@@ -192,7 +195,7 @@ private void runNodeUpdater() throws ExecutionFailedException {
                         .withConnectJavaSourceFolder(javaSourceFolder)
                         .withConnectGeneratedOpenApiJson(openApiJsonFile)
                         .withConnectClientTsApiFolder(generatedTsFolder)
-                        .build().execute();
+                        .withPolymerVersion(polymerVersion).build().execute();
     }
 
     private void runWebpack() {
diff --git a/flow-migration/src/test/java/com/vaadin/flow/migration/MigrationTest.java b/flow-migration/src/test/java/com/vaadin/flow/migration/MigrationTest.java
index 68b6f53d6d7..adb229ddebb 100644
--- a/flow-migration/src/test/java/com/vaadin/flow/migration/MigrationTest.java
+++ b/flow-migration/src/test/java/com/vaadin/flow/migration/MigrationTest.java
@@ -130,12 +130,12 @@ public void migratePassesHappyPath()
                 Paths.get(sourcesFolder.getPath(), "foobar").toFile());
 
         // Expected execution calls:
-        // 1 - npm install polymer-modulizer
+        // 1 - npm --no-update-notifier install polymer-modulizer
         // 2 - node {tempFolder} i -F --confid.interactive=false -S polymer#2.8.0
-        // 3 - npm i
+        // 3 - npm --no-update-notifier i
         // 4 - node node_modules/polymer-modulizer/bin/modulizer.js --force --out , --import-style=name
 
-        LinkedList excecuteExpectations = Stream.of(3, 7, 2, 6)
+        LinkedList excecuteExpectations = Stream.of(4, 7, 3, 6)
                 .collect(Collectors.toCollection(LinkedList::new));
         
         Migration migration = new Migration(configuration) {
diff --git a/flow-server/src/main/java/com/vaadin/flow/component/ClientCallable.java b/flow-server/src/main/java/com/vaadin/flow/component/ClientCallable.java
index 7d59c8174a2..d08d4794602 100644
--- a/flow-server/src/main/java/com/vaadin/flow/component/ClientCallable.java
+++ b/flow-server/src/main/java/com/vaadin/flow/component/ClientCallable.java
@@ -25,7 +25,9 @@
 
 /**
  * Publishes the annotated method so it can be invoked from the client side
- * using the notation this.$server.method() in template methods.
+ * using the notation this.$server.method(). The method will return
+ * a Promise which will be resolved with either the return value from the server
+ * or a generic rejection if the server-side method throws an exception.
  *
  * @author Vaadin Ltd
  * @since 1.0
diff --git a/flow-server/src/main/java/com/vaadin/flow/component/HasComponents.java b/flow-server/src/main/java/com/vaadin/flow/component/HasComponents.java
index c534aff8980..73d1eb6a356 100644
--- a/flow-server/src/main/java/com/vaadin/flow/component/HasComponents.java
+++ b/flow-server/src/main/java/com/vaadin/flow/component/HasComponents.java
@@ -55,10 +55,10 @@ default void add(Component... components) {
     }
 
     /**
-     * Add the given text as children of this component.
+     * Add the given text as a child of this component.
      *
      * @param text
-     *            the text to add
+     *            the text to add, not null
      */
     default void add(String text) {
         add(new Text(text));
diff --git a/flow-server/src/main/java/com/vaadin/flow/component/Text.java b/flow-server/src/main/java/com/vaadin/flow/component/Text.java
index 9b65f919c32..f9481fcd1e4 100644
--- a/flow-server/src/main/java/com/vaadin/flow/component/Text.java
+++ b/flow-server/src/main/java/com/vaadin/flow/component/Text.java
@@ -29,7 +29,7 @@ public class Text extends Component implements HasText {
      * Creates an instance using the given text.
      *
      * @param text
-     *            the text to show
+     *            the text to show, not null
      */
     public Text(String text) {
         super(Element.createText(text));
diff --git a/flow-server/src/main/java/com/vaadin/flow/component/page/Page.java b/flow-server/src/main/java/com/vaadin/flow/component/page/Page.java
index 997e39943dc..b9c75d80fab 100644
--- a/flow-server/src/main/java/com/vaadin/flow/component/page/Page.java
+++ b/flow-server/src/main/java/com/vaadin/flow/component/page/Page.java
@@ -414,9 +414,9 @@ public ExecutionCanceler executeJavaScript(String expression,
     /**
      * Asynchronously runs the given JavaScript expression in the browser.
      * 

- * It is possible to get access to the return value of the execution by - * registering a handler with the returned pending result. If no handler is - * registered, the return value will be ignored. + * The returned PendingJavaScriptResult can be used to retrieve + * any return value from the JavaScript expression. If no + * return value handler is registered, the return value will be ignored. *

* The given parameters will be available to the expression as variables * named $0, $1, and so on. Supported parameter @@ -550,7 +550,7 @@ public void open(String url) { * @param windowName * the name of the window. */ - private void open(String url, String windowName) { + public void open(String url, String windowName) { executeJavaScript("window.open($0, $1)", url, windowName); } diff --git a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/BundleParser.java b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/BundleParser.java index 58cb21abb0b..a145e7e6d78 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/BundleParser.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/BundleParser.java @@ -15,12 +15,14 @@ */ package com.vaadin.flow.component.polymertemplate; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +32,7 @@ import elemental.json.JsonArray; import elemental.json.JsonObject; import elemental.json.JsonType; + import static elemental.json.JsonType.ARRAY; import static elemental.json.JsonType.OBJECT; import static elemental.json.JsonType.STRING; @@ -79,13 +82,19 @@ public final class BundleParser { * end character with ;} e.g. ';} */ private static final Pattern TEMPLATE_PATTERN = Pattern.compile( - "get[\\s]*template\\(\\)[\\s]*\\{[\\s]*return[\\s]*html([\\`|\\'|\\\"])([\\s\\S]*)\\1;[\\s]*\\}"); + "get[\\s]*template\\(\\)[\\s]*\\{[\\s]*return[\\s]*html([\\`\\'\\\"])([\\s\\S]*)\\1;[\\s]*\\}"); + + private static final Pattern NO_TEMPLATE_PATTERN = Pattern.compile( + "innerHTML[\\s]*=[\\s]*([\\`\\'\\\"])([\\s]* !node.equals(templateDocument.body())) + .filter(node -> !node.equals(body)) .forEach(template::appendChild); return template; } + private static Element tryParsePolymer2(Document templateDocument, + Matcher noTemplateMatcher) { + while (noTemplateMatcher.find() + && noTemplateMatcher.groupCount() == 2) { + String group = noTemplateMatcher.group(2); + LOGGER.trace( + "Found Polymer 2 style insertion as a Polymer 3 template content {}", + group); + + templateDocument = Jsoup.parse(group); + LOGGER.trace("The parsed template document was {}", + templateDocument); + Optional domModule = JsoupUtils + .getDomModule(templateDocument, null); + if (!domModule.isPresent()) { + continue; + } + JsoupUtils.removeCommentsRecursively(domModule.get()); + Elements templates = domModule.get() + .getElementsByTag(TEMPLATE_TAG_NAME); + if (templates.isEmpty()) { + continue; + } + return templates.get(0); + } + return null; + } + // From the statistics json recursively go through all chunks and modules to // find the first module whose name matches the file name private static String getSourceFromObject(JsonObject module, @@ -211,10 +259,11 @@ && validKey(module, SOURCE, STRING)) { .replaceFirst("^frontend://", "."); // For polymer templates inside add-ons we will not find the sources - // using ./ as the actual path contains "node_modules/@vaadin/flow-frontend/" instead of "./" + // using ./ as the actual path contains + // "node_modules/@vaadin/flow-frontend/" instead of "./" if (name.contains(FrontendUtils.FLOW_NPM_PACKAGE_NAME)) { - alternativeFileName = alternativeFileName - .replaceFirst("\\./", ""); + alternativeFileName = alternativeFileName.replaceFirst("\\./", + ""); } // Remove query-string used by webpack modules like babel (e.g @@ -247,4 +296,18 @@ private static boolean validKey(JsonObject o, String k, JsonType t) { && o.get(k).getType().equals(t); return validKey && (!t.equals(STRING) || !o.getString(k).isEmpty()); } + + /** + * Removes comments (block comments and line comments) from the JS code. + *

+ * Note that this is not really a correct way to do this: this will remove + * comments also if they are inside strings. But this is not important here + * in this class since we care only about import statements where this is + * fine. + * + * @return the code with removed comments + */ + private static String removeComments(String content) { + return COMMENTS_PATTERN.matcher(content).replaceAll(""); + } } diff --git a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/DefaultTemplateParser.java b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/DefaultTemplateParser.java index 8b95566188e..d94314a7554 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/DefaultTemplateParser.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/DefaultTemplateParser.java @@ -25,10 +25,8 @@ import java.util.stream.Collectors; import org.jsoup.Jsoup; -import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import org.jsoup.nodes.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -141,13 +139,13 @@ private static Element parseHtmlImport(InputStream content, String path, try { Document parsedDocument = Jsoup.parse(content, StandardCharsets.UTF_8.name(), ""); - Optional optionalDomModule = getDomModule(parsedDocument, - tag); + Optional optionalDomModule = JsoupUtils + .getDomModule(parsedDocument, tag); if (!optionalDomModule.isPresent()) { return null; } Element domModule = optionalDomModule.get(); - removeCommentsRecursively(domModule); + JsoupUtils.removeCommentsRecursively(domModule); return domModule; } catch (IOException exception) { throw new RuntimeException(String.format( @@ -156,24 +154,6 @@ private static Element parseHtmlImport(InputStream content, String path, } } - private static Optional getDomModule(Element parent, String id) { - return parent.getElementsByTag("dom-module").stream() - .filter(element -> id.equals(element.id())).findFirst(); - } - - private static void removeCommentsRecursively(Node node) { - int i = 0; - while (i < node.childNodeSize()) { - Node child = node.childNode(i); - if (child instanceof Comment) { - child.remove(); - } else { - removeCommentsRecursively(child); - i++; - } - } - } - private Logger getLogger() { return LoggerFactory.getLogger(DefaultTemplateParser.class.getName()); } diff --git a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/IdMapper.java b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/IdMapper.java index 17c77873e8e..43b3add8540 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/IdMapper.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/IdMapper.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.Optional; import java.util.function.Consumer; -import java.util.stream.Stream; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.Tag; @@ -74,25 +73,7 @@ public IdMapper(AbstractTemplate template) { */ public void mapComponentOrElement(Field field, String id, String tag, Consumer beforeComponentInject) { - Element element = getElementById(id).orElse(null); - - if (element == null) { - injectClientSideElement(tag, id, field, beforeComponentInject); - } else { - injectServerSideElement(element, field, beforeComponentInject); - } - } - - private void injectServerSideElement(Element element, Field field, - Consumer beforeComponentInject) { - if (getElement().equals(element)) { - throw new IllegalArgumentException( - "Cannot map the root element of the template. " - + "This is always mapped to the template instance itself (" - + getContainerClass().getName() + ')'); - } else if (element != null) { - injectTemplateElement(element, field, beforeComponentInject); - } + injectClientSideElement(tag, id, field, beforeComponentInject); } private Class getContainerClass() { @@ -132,20 +113,6 @@ private Element getElement() { return template.getElement(); } - private Optional getElementById(String id) { - return getOrCreateShadowRoot().getChildren() - .flatMap(this::flattenChildren) - .filter(element -> id.equals(element.getAttribute("id"))) - .findFirst(); - } - - private Stream flattenChildren(Element node) { - if (node.getChildCount() > 0) { - return node.getChildren().flatMap(this::flattenChildren); - } - return Stream.of(node); - } - /** * Attaches a child element with the given {@code tagName} and {@code id} to * an existing dom element on the client side with matching data. diff --git a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/JsoupUtils.java b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/JsoupUtils.java new file mode 100644 index 00000000000..1ae6456be56 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/JsoupUtils.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2018 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.component.polymertemplate; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; + +/** + * Utilities for JSOUP DOM manipulations. + * + * @author Vaadin Ltd + * + */ +final class JsoupUtils { + + private JsoupUtils() { + // Utility class + } + + /** + * Removes all comments from the {@code node} tree. + * + * @param node + * a Jsoup node + */ + static void removeCommentsRecursively(Node node) { + int i = 0; + while (i < node.childNodeSize()) { + Node child = node.childNode(i); + if (child instanceof Comment) { + child.remove(); + } else { + removeCommentsRecursively(child); + i++; + } + } + } + + /** + * Finds {@code "dom-module"} element inside the {@code parent}. + *

+ * If {@code id} is provided then {@code "dom-module"} element is searched + * with the given {@code id} value. + * + * @param parent + * the parent element + * @param id + * optional id attribute value to search {@code "dom-module"} + * element, may be {@code null} + * @return + */ + static Optional getDomModule(Element parent, String id) { + Stream stream = parent.getElementsByTag("dom-module").stream(); + if (id != null) { + stream = stream.filter(element -> id.equals(element.id())); + } + return stream.findFirst(); + } + +} diff --git a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java index ca3e490fa4a..ebda6011976 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/NpmTemplateParser.java @@ -122,14 +122,16 @@ public TemplateData getTemplateContent( } if (chosenDep != null) { - // Template needs to be wrapped in an element with id, to look - // like a P2 template - Element parent = new Element(tag); - parent.attr("id", tag); Element templateElement = BundleParser.parseTemplateElement( chosenDep.getFirst().getUrl(), chosenDep.getSecond()); - templateElement.appendTo(parent); + if (!JsoupUtils.getDomModule(templateElement, null).isPresent()) { + // Template needs to be wrapped in an element with id, to look + // like a P2 template + Element parent = new Element(tag); + parent.attr("id", tag); + templateElement.appendTo(parent); + } return new TemplateData(chosenDep.getFirst().getUrl(), templateElement); diff --git a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/TemplateInitializer.java b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/TemplateInitializer.java index d4ec693db02..6e222b1d1b8 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/TemplateInitializer.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/TemplateInitializer.java @@ -116,8 +116,6 @@ private void doRequestAttachCustomElement(String id, String tag, if (idMapper.isMapped(id)) { return; } - // make sure that shadow root is available - idMapper.getOrCreateShadowRoot(); Element element = new Element(tag); VirtualChildrenList list = getElement().getNode() diff --git a/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentWrapper.java b/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentWrapper.java index b5e4698aaa7..5fad098d6e9 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentWrapper.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/webcomponent/WebComponentWrapper.java @@ -119,7 +119,8 @@ public void disconnected() { if (event.getSource().getInternals() .getLastHeartbeatTimestamp() - disconnect > timeout) { - this.getElement().removeFromParent(); + Element element = this.getElement(); + element.getParent().removeVirtualChild(element); } }); } diff --git a/flow-server/src/main/java/com/vaadin/flow/dom/Element.java b/flow-server/src/main/java/com/vaadin/flow/dom/Element.java index 2095be41423..132f4a24647 100644 --- a/flow-server/src/main/java/com/vaadin/flow/dom/Element.java +++ b/flow-server/src/main/java/com/vaadin/flow/dom/Element.java @@ -220,7 +220,7 @@ public Stream getChildren() { * Creates a text node with the given text. * * @param text - * the text in the node + * the text in the node, not null * @return an element representing the text node */ public static Element createText(String text) { @@ -1669,9 +1669,10 @@ public void executeJavaScript(String expression, // When updating JavaDocs here, keep in sync with Page.executeJavaScript /** * Asynchronously runs the given JavaScript expression in the browser in the - * context of this element. It is possible to get access to the return value - * of the execution by registering a handler with the returned pending - * result. If no handler is registered, the return value will be ignored. + * context of this element. The returned + * PendingJavaScriptResult can be used to retrieve any + * return value from the JavaScript expression. If no return + * value handler is registered, the return value will be ignored. *

* This element will be available to the expression as this. * The given parameters will be available as variables named diff --git a/flow-server/src/main/java/com/vaadin/flow/dom/Node.java b/flow-server/src/main/java/com/vaadin/flow/dom/Node.java index 4fdf1f3b44b..366358ab85c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/dom/Node.java +++ b/flow-server/src/main/java/com/vaadin/flow/dom/Node.java @@ -180,6 +180,50 @@ public N appendVirtualChild(Element... children) { return getSelf(); } + /** + * Removes the given children that have been attached as the virtual + * children of this element. + *

+ * The virtual child is not really a child of the DOM element. The + * client-side counterpart is created in the memory but it's not attached to + * the DOM tree. The resulting element is referenced via the server side + * {@link Element} in JS function call as usual. * + * + * @param children + * the element(s) to remove + * @return this element + */ + /* + * The use case for removing virtual children is when exported Flow web + * components are detached from their parent due to missing heart beats + + * timeout. + */ + public N removeVirtualChild(Element... children) { + if (children == null) { + throw new IllegalArgumentException( + THE_CHILDREN_ARRAY_CANNOT_BE_NULL); + } + + if (getNode().hasFeature(VirtualChildrenList.class)) { + VirtualChildrenList childrenList = getNode() + .getFeature(VirtualChildrenList.class); + for (Element child : children) { + if (child == null) { + throw new IllegalArgumentException( + "Element to remove must not be null"); + } + int index = childrenList.indexOf(child.getNode()); + if (index == -1) { + throw new IllegalArgumentException( + "Trying to detach a virtual child element from parent that does not have it."); + } + childrenList.remove(index); + } + } + + return getSelf(); + } + /** * Gets whether this element is a virtual child of its parent. * diff --git a/flow-server/src/main/java/com/vaadin/flow/dom/impl/AbstractNodeStateProvider.java b/flow-server/src/main/java/com/vaadin/flow/dom/impl/AbstractNodeStateProvider.java index ffba5ec92c7..d2171d6cbf9 100644 --- a/flow-server/src/main/java/com/vaadin/flow/dom/impl/AbstractNodeStateProvider.java +++ b/flow-server/src/main/java/com/vaadin/flow/dom/impl/AbstractNodeStateProvider.java @@ -120,10 +120,9 @@ public void removeChild(StateNode node, Element child) { ElementChildrenList childrenFeature = getChildrenFeature(node); int pos = childrenFeature.indexOf(child.getNode()); if (pos == -1) { - throw new IllegalArgumentException("Not in the list"); + throw new IllegalArgumentException("Trying to detach an element from parent that does not have it."); } childrenFeature.remove(pos); - } @Override diff --git a/flow-server/src/main/java/com/vaadin/flow/dom/impl/BasicTextElementStateProvider.java b/flow-server/src/main/java/com/vaadin/flow/dom/impl/BasicTextElementStateProvider.java index 1ba8a83b968..2cb208186c1 100644 --- a/flow-server/src/main/java/com/vaadin/flow/dom/impl/BasicTextElementStateProvider.java +++ b/flow-server/src/main/java/com/vaadin/flow/dom/impl/BasicTextElementStateProvider.java @@ -42,7 +42,7 @@ private BasicTextElementStateProvider() { * Creates a compatible text state node using the given text. * * @param text - * the text to use + * the text to use, not null * @return a initialized and compatible state node */ public static StateNode createStateNode(String text) { diff --git a/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java index 14409ed7441..fbcc296817a 100644 --- a/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java @@ -129,8 +129,8 @@ default boolean isClientSideMode() { boolean isSendUrlsAsParameters(); /** - * Returns whether a session should be closed when all its open UIs have - * been idle for longer than its configured maximum inactivity time. + * Returns whether a Vaadin session should be closed when all its open UIs + * have been idle for longer than its configured maximum inactivity time. *

* A UI is idle if it is open on the client side but has no activity other * than heartbeat requests. If {@code isCloseIdleSessions() == false}, @@ -141,8 +141,8 @@ default boolean isClientSideMode() { * @see WrappedSession#getMaxInactiveInterval() * * - * @return True if UIs and sessions receiving only heartbeat requests are - * eventually closed; false if heartbeat requests extend UI and + * @return True if UIs and Vaadin sessions receiving only heartbeat requests + * are eventually closed; false if heartbeat requests extend UI and * session lifetime indefinitely. */ boolean isCloseIdleSessions(); @@ -395,20 +395,41 @@ default List getPolyfills() { * @return true if dev server should be used */ default boolean enableDevServer() { - return getBooleanProperty( - Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, true); + return getBooleanProperty(Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, + true); } /** - * Get if the dev server should be reused on each reload. - * True by default, set it to false in tests so as dev server - * is not kept as a daemon after the test. + * Get if the dev server should be reused on each reload. True by default, + * set it to false in tests so as dev server is not kept as a daemon after + * the test. * * @return true if dev server should be reused */ default boolean reuseDevServer() { - return getBooleanProperty( - Constants.SERVLET_PARAMETER_REUSE_DEV_SERVER, true); + return getBooleanProperty(Constants.SERVLET_PARAMETER_REUSE_DEV_SERVER, + true); + } + + /** + * Get if the stats.json file should be retrieved from an external service or + * through the classpath. + * + * @return true if stats.json is served from an external location + */ + default boolean isStatsExternal() { + return getBooleanProperty(Constants.EXTERNAL_STATS_FILE, false); + } + + /** + * Get the url from where stats.json should be retrieved from. + * If not given this will default to '/vaadin-static/VAADIN/config/stats.json' + * + * @return external stats.json location + */ + default String getExternalStatsUrl() { + return getStringProperty(Constants.EXTERNAL_STATS_URL, + Constants.DEFAULT_EXTERNAL_STATS_URL); } /** diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/JsonCodec.java b/flow-server/src/main/java/com/vaadin/flow/internal/JsonCodec.java index 2d841c168b1..5e9cfe3b871 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/JsonCodec.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/JsonCodec.java @@ -80,6 +80,8 @@ private JsonCodec() { * @return the value encoded as JSON */ public static JsonValue encodeWithTypeInfo(Object value) { + assert value == null || canEncodeWithTypeInfo(value.getClass()); + if (value instanceof Component) { return encodeNode(((Component) value).getElement()); } else if (value instanceof Node) { @@ -119,7 +121,7 @@ private static JsonArray wrapComplexValue(int typeId, JsonValue... values) { /** * Helper for checking whether the type is supported by - * {@link #encodeWithoutTypeInfo(Object)}. Supported values types are + * {@link #encodeWithoutTypeInfo(Object)}. Supported value types are * {@link String}, {@link Integer}, {@link Double}, {@link Boolean}, * {@link JsonValue}. * @@ -134,6 +136,23 @@ public static boolean canEncodeWithoutTypeInfo(Class type) { || JsonValue.class.isAssignableFrom(type); } + /** + * Helper for checking whether the type is supported by + * {@link #encodeWithTypeInfo(Object)}. Supported values types are + * {@link Node}, {@link Component}, {@link ReturnChannelRegistration} and + * anything accepted by {@link #canEncodeWithoutTypeInfo(Class)}. + * + * @param type + * the type to check + * @return whether the type can be encoded + */ + public static boolean canEncodeWithTypeInfo(Class type) { + return canEncodeWithoutTypeInfo(type) + || Node.class.isAssignableFrom(type) + || Component.class.isAssignableFrom(type) + || ReturnChannelRegistration.class.isAssignableFrom(type); + } + /** * Encodes a "primitive" value or a constant pool reference to JSON. This * methods supports {@link ConstantPoolKey} in addition to the types @@ -168,6 +187,9 @@ public static JsonValue encodeWithoutTypeInfo(Object value) { if (value == null) { return Json.createNull(); } + + assert canEncodeWithoutTypeInfo(value.getClass()); + Class type = value.getClass(); if (String.class.equals(value.getClass())) { return Json.create((String) value); diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/AbstractServerHandlers.java b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/AbstractServerHandlers.java index 023a66a800f..48a277cdedb 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/AbstractServerHandlers.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/AbstractServerHandlers.java @@ -166,15 +166,7 @@ protected void collectHandlerMethods(Class clazz, */ protected void addHandlerMethod(Method method, Collection methods) { ensureSupportedParameterTypes(method); - if (!void.class.equals(method.getReturnType())) { - String msg = String.format(Locale.ENGLISH, - "Only void handler methods are supported. " - + "Component '%s' has method '%s' annotated with '%s' whose return type is not void but %s", - method.getDeclaringClass().getName(), method.getName(), - getHandlerAnnotation().getName(), - method.getReturnType().getSimpleName()); - throw new IllegalStateException(msg); - } + ensureSupportedReturnType(method); Optional> checkedException = Stream .of(method.getExceptionTypes()) .filter(ReflectTools::isCheckedException).findFirst(); @@ -191,6 +183,24 @@ protected void addHandlerMethod(Method method, Collection methods) { methods.add(method); } + /** + * Validate return type support for given method. + * + * @param method + * method to check return type for + */ + protected void ensureSupportedReturnType(Method method) { + if (!void.class.equals(method.getReturnType())) { + String msg = String.format(Locale.ENGLISH, + "Only void handler methods are supported. " + + "Component '%s' has method '%s' annotated with '%s' whose return type is not void but \"%s\"", + method.getDeclaringClass().getName(), method.getName(), + getHandlerAnnotation().getName(), + method.getReturnType().getSimpleName()); + throw new IllegalStateException(msg); + } + } + /** * Gets the annotation which is used to mark methods as handlers. * diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ClientCallableHandlers.java b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ClientCallableHandlers.java index c96f14c51bc..d34ae0a4368 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ClientCallableHandlers.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/ClientCallableHandlers.java @@ -16,10 +16,13 @@ package com.vaadin.flow.internal.nodefeature; import java.lang.reflect.Method; +import java.util.Locale; import com.vaadin.flow.component.ClientCallable; import com.vaadin.flow.component.Component; import com.vaadin.flow.dom.DisabledUpdateMode; +import com.vaadin.flow.internal.JsonCodec; +import com.vaadin.flow.internal.ReflectTools; import com.vaadin.flow.internal.StateNode; /** @@ -53,6 +56,25 @@ protected void ensureSupportedParameterTypes(Method method) { // limit supported types } + @Override + protected void ensureSupportedReturnType(Method method) { + Class returnType = method.getReturnType(); + if (returnType.isPrimitive()) { + returnType = ReflectTools.convertPrimitiveType(returnType); + } + + if (!void.class.equals(returnType) + && !JsonCodec.canEncodeWithTypeInfo(returnType)) { + String msg = String.format(Locale.ENGLISH, + "Only return types that can be used as Element.executeJs parameters are supported. " + + "Component '%s' has method '%s' annotated with '%s' whose return type is \"%s\"", + method.getDeclaringClass().getName(), method.getName(), + getHandlerAnnotation().getName(), + method.getReturnType().getSimpleName()); + throw new IllegalStateException(msg); + } + } + @Override protected DisabledUpdateMode getUpdateMode(Method method) { return method.getAnnotation(getHandlerAnnotation()).value(); diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/TextNodeMap.java b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/TextNodeMap.java index 673035ace75..9539f4273b8 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/TextNodeMap.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/TextNodeMap.java @@ -44,7 +44,7 @@ protected String getKey() { * Sets the text of this node. * * @param text - * the text, not null + * the text, not null */ public void setText(String text) { assert text != null; diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/VirtualChildrenList.java b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/VirtualChildrenList.java index 619140944f3..2aa0a10961e 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/VirtualChildrenList.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/VirtualChildrenList.java @@ -154,8 +154,17 @@ public Iterator iterator() { } @Override - protected StateNode remove(int index) { - throw new UnsupportedOperationException(); + public int indexOf(StateNode node) { + return super.indexOf(node); + } + + @Override + public StateNode remove(int index) { + // removing the payload in case the element is reused + get(index).getFeature(ElementData.class).remove(NodeProperties.PAYLOAD); + + // this should not omit a node change to client side. + return super.remove(index); } @Override diff --git a/flow-server/src/main/java/com/vaadin/flow/router/RouterLink.java b/flow-server/src/main/java/com/vaadin/flow/router/RouterLink.java index 3d2e144376e..aa08009674b 100644 --- a/flow-server/src/main/java/com/vaadin/flow/router/RouterLink.java +++ b/flow-server/src/main/java/com/vaadin/flow/router/RouterLink.java @@ -183,6 +183,39 @@ public > void setRoute( updateHref(url); } + /** + * Set the navigation target for this link. + * + * @param navigationTarget + * navigation target + */ + public void setRoute(Class navigationTarget) { + validateRouteParameters(getRouter(), navigationTarget); + String url = RouteConfiguration.forRegistry(getRouter().getRegistry()) + .getUrl(navigationTarget); + updateHref(url); + } + + /** + * Set the navigation target for this link. + * + * @param navigationTarget + * navigation target + * @param parameter + * url parameter for navigation target + * @param + * url parameter type + * @param + * navigation target type + */ + public > void setRoute( + Class navigationTarget, T parameter) { + validateRouteParameters(getRouter(), navigationTarget); + String url = RouteConfiguration.forRegistry(getRouter().getRegistry()) + .getUrl(navigationTarget, parameter); + updateHref(url); + } + private void validateRouteParameters(Router router, Class navigationTarget) { if (router == null) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java index f947316e00b..45509751120 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java @@ -44,6 +44,8 @@ public final class Constants implements Serializable { public static final String CONNECT_APPLICATION_PROPERTIES_TOKEN = "connect.applicationProperties"; public static final String CONNECT_OPEN_API_FILE_TOKEN = "connect.openApiFile"; public static final String CONNECT_GENERATED_TS_DIR_TOKEN = "connect.generated"; + public static final String EXTERNAL_STATS_FILE_TOKEN = "externalStatsFile"; + public static final String EXTERNAL_STATS_URL_TOKEN = "externalStatsUrl"; /** * enable it if your project is a Polymer 2.0 one, should be removed in V15 @@ -217,11 +219,15 @@ public final class Constants implements Serializable { /** * Boolean parameter for enabling/disabling bytecode scanning in dev mode. - * If enabled, entry points are scanned for reachable frontend resources. - * If disabled, all classes on the classpath are scanned. + * If enabled, entry points are scanned for reachable frontend resources. If + * disabled, all classes on the classpath are scanned. */ public static final String SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE = "devmode.optimizeBundle"; + /** + * Configuration parameter name for polymer version. + */ + public static final String SERVLET_PARAMETER_DEVMODE_POLYMER_VERSION = "devmode.polymer.version"; /** * The path used in the vaadin servlet for handling static resources. @@ -270,6 +276,20 @@ public final class Constants implements Serializable { public static final int SHOULD_WORK_NPM_MAJOR_VERSION = 5; public static final int SHOULD_WORK_NPM_MINOR_VERSION = 5; + /** + * Property boolean for marking stats.json to be fetched from external + * location. + */ + public static final String EXTERNAL_STATS_FILE = "external.stats.file"; + /** + * Property String for external stats.json location url. + */ + public static final String EXTERNAL_STATS_URL = "external.stats.url"; + /** + * Default location to look for the external stats.json. + */ + public static final String DEFAULT_EXTERNAL_STATS_URL = "/vaadin-static/VAADIN/config/stats.json"; + private Constants() { // prevent instantiation constants class only } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java index a733414c6b0..f6f6bcea211 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java @@ -16,13 +16,11 @@ package com.vaadin.flow.server; -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.io.UncheckedIOException; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -49,6 +47,10 @@ import static com.vaadin.flow.server.Constants.CONNECT_GENERATED_TS_DIR_TOKEN; import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; import static com.vaadin.flow.server.Constants.CONNECT_OPEN_API_FILE_TOKEN; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_FILE; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_FILE_TOKEN; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_URL; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_URL_TOKEN; import static com.vaadin.flow.server.Constants.FRONTEND_TOKEN; import static com.vaadin.flow.server.Constants.NPM_TOKEN; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_CLIENT_SIDE_MODE; @@ -102,18 +104,16 @@ private DeploymentConfigurationFactory() { * * @param systemPropertyBaseClass * the class to look for properties defined with annotations - * @param servletConfig + * @param vaadinConfig * the config to get the rest of the properties from * @return {@link DeploymentConfiguration} instance - * @throws ServletException - * if construction of the {@link Properties} for the parameters - * fails + * @throws VaadinConfigurationException thrown if property construction fails */ public static DeploymentConfiguration createDeploymentConfiguration( - Class systemPropertyBaseClass, ServletConfig servletConfig) - throws ServletException { + Class systemPropertyBaseClass, VaadinConfig vaadinConfig) + throws VaadinConfigurationException { return new DefaultDeploymentConfiguration(systemPropertyBaseClass, - createInitParameters(systemPropertyBaseClass, servletConfig)); + createInitParameters(systemPropertyBaseClass, vaadinConfig)); } /** @@ -123,18 +123,16 @@ public static DeploymentConfiguration createDeploymentConfiguration( * * @param systemPropertyBaseClass * the class to look for properties defined with annotations - * @param servletConfig + * @param vaadinConfig * the config to get the rest of the properties from * @return {@link DeploymentConfiguration} instance - * @throws ServletException - * if construction of the {@link Properties} for the parameters - * fails + * @throws VaadinConfigurationException thrown if property construction fails */ public static DeploymentConfiguration createPropertyDeploymentConfiguration( - Class systemPropertyBaseClass, ServletConfig servletConfig) - throws ServletException { + Class systemPropertyBaseClass, VaadinConfig vaadinConfig) + throws VaadinConfigurationException { return new PropertyDeploymentConfiguration(systemPropertyBaseClass, - createInitParameters(systemPropertyBaseClass, servletConfig)); + createInitParameters(systemPropertyBaseClass, vaadinConfig)); } /** @@ -142,35 +140,33 @@ public static DeploymentConfiguration createPropertyDeploymentConfiguration( * in current application. * * @param systemPropertyBaseClass - * the class to look for properties defined with annotations - * @param servletConfig - * the config to get the rest of the properties from + * the class to look for properties defined with annotations + * @param vaadinConfig + * the config to get the rest of the properties from * @return {@link Properties} instance - * @throws ServletException - * if construction of the {@link Properties} for the parameters - * fails + * @throws VaadinConfigurationException thrown if property construction fails */ protected static Properties createInitParameters( - Class systemPropertyBaseClass, ServletConfig servletConfig) - throws ServletException { + Class systemPropertyBaseClass, VaadinConfig vaadinConfig) + throws VaadinConfigurationException { Properties initParameters = new Properties(); readUiFromEnclosingClass(systemPropertyBaseClass, initParameters); readConfigurationAnnotation(systemPropertyBaseClass, initParameters); // Read default parameters from server.xml - final ServletContext context = servletConfig.getServletContext(); - for (final Enumeration e = context.getInitParameterNames(); e + final VaadinContext context = vaadinConfig.getVaadinContext(); + for (final Enumeration e = context.getContextParameterNames(); e .hasMoreElements();) { final String name = e.nextElement(); - initParameters.setProperty(name, context.getInitParameter(name)); + initParameters.setProperty(name, context.getContextParameter(name)); } // Override with application config from web.xml - for (final Enumeration e = servletConfig - .getInitParameterNames(); e.hasMoreElements();) { + for (final Enumeration e = vaadinConfig + .getConfigParameterNames(); e.hasMoreElements(); ) { final String name = e.nextElement(); - initParameters.setProperty(name, - servletConfig.getInitParameter(name)); + initParameters + .setProperty(name, vaadinConfig.getConfigParameter(name)); } readBuildInfo(initParameters); @@ -184,6 +180,24 @@ private static void readBuildInfo(Properties initParameters) { // already set. if (json != null) { JsonObject buildInfo = JsonUtil.parse(json); + if (buildInfo.hasKey(EXTERNAL_STATS_FILE_TOKEN) || buildInfo + .hasKey(EXTERNAL_STATS_URL_TOKEN)) { + // If external stats file is flagged then we should always run in + // npm production mode. + initParameters.setProperty(SERVLET_PARAMETER_PRODUCTION_MODE, + Boolean.toString(true)); + initParameters.setProperty(SERVLET_PARAMETER_COMPATIBILITY_MODE, + Boolean.toString(false)); + initParameters.setProperty(SERVLET_PARAMETER_ENABLE_DEV_SERVER, + Boolean.toString(false)); + initParameters.setProperty(EXTERNAL_STATS_FILE, + Boolean.toString(true)); + if (buildInfo.hasKey(EXTERNAL_STATS_URL_TOKEN)) { + initParameters.setProperty(EXTERNAL_STATS_URL, + buildInfo.getString(EXTERNAL_STATS_URL_TOKEN)); + } + return; + } if (buildInfo.hasKey(SERVLET_PARAMETER_PRODUCTION_MODE)) { initParameters.setProperty(SERVLET_PARAMETER_PRODUCTION_MODE, String.valueOf(buildInfo.getBoolean( @@ -293,7 +307,7 @@ private static String getTokenFileContents(Properties initParameters) { try { json = getResourceFromFile(initParameters); if (json == null) { - json = getResourceFromClassloader(initParameters); + json = getResourceFromClassloader(); } } catch (IOException e) { throw new UncheckedIOException(e); @@ -316,7 +330,7 @@ private static String getResourceFromFile(Properties initParameters) return json; } - private static String getResourceFromClassloader(Properties initParameters) + private static String getResourceFromClassloader() throws IOException { String json = null; // token file is in the class-path of the application @@ -329,10 +343,11 @@ private static String getResourceFromClassloader(Properties initParameters) URL resource = resources.stream() .filter(url -> !url.getPath().endsWith("jar!/" + tokenResource)) .findFirst().orElse(null); - if (resource == null && isProductionMode(initParameters)) { - // For no non jar build info, in production mode check jar files - // for production mode jar. - json = getProductionModeResource(resources); + if (resource == null && !resources.isEmpty()) { + // For no non jar build info, in production mode check for + // webpack.generated.json if it's in a jar in a jar then accept + // single jar flow-build-info. + json = getPossibleJarResource(resources); } else if (resource != null) { json = FrontendUtils.streamToString(resource.openStream()); } @@ -366,30 +381,39 @@ private static boolean isProductionMode(Properties initParameters) { } /** - * Check resources for a production mode resource if all resources were - * from JAR files as we may be running from a JAR and add-ons are not - * packaged in productionMode. + * Check if the webpack.generated.js resources is inside 2 jars + * (flow-server.jar and application.jar) if this is the case then we can + * accept a build info file from inside jar with a single jar in the path. * * @param resources * flow-build-info url resource files - * @return production mode flow-build-info string + * @return flow-build-info json string or null if no applicable files found * @throws IOException * exception reading stream */ - private static String getProductionModeResource(List resources) + private static String getPossibleJarResource(List resources) throws IOException { - for (URL resource : resources) { - String json = FrontendUtils.streamToString(resource.openStream()); - if (json != null && !json.contains(NPM_TOKEN) && !json - .contains(FRONTEND_TOKEN) && json - .contains("\"productionMode\": true")) { - return json; + URL webpackGenerated = DeploymentConfiguration.class.getClassLoader() + .getResource(FrontendUtils.WEBPACK_GENERATED); + // If jar!/ exists 2 times for webpack.generated.json then we are + // running from a jar + if (countInstances(webpackGenerated.getPath(), "jar!/") >= 2) { + for (URL resource : resources) { + // As we now know that we are running from a jar we can accept a + // build info with a single jar in the path + if (countInstances(resource.getPath(), "jar!/") == 1) { + return FrontendUtils.streamToString(resource.openStream()); + } } } // No applicable resources found. return null; } + private static int countInstances(String input, String value) { + return input.split(value, -1).length - 1; + } + /** * Verify that given folder actually exists on the system if we are not in * production mode. @@ -468,9 +492,21 @@ private static void readUiFromEnclosingClass( } } + /** + * Read the VaadinServletConfiguration annotation for initialization name + * value pairs and add them to the intial properties object. + * + * @param systemPropertyBaseClass + * base class for constructing the configuration + * @param initParameters + * current initParameters object + * @throws VaadinConfigurationException + * exception thrown for failure in invoking method on configuration + * annotation + */ private static void readConfigurationAnnotation( Class systemPropertyBaseClass, Properties initParameters) - throws ServletException { + throws VaadinConfigurationException { Optional optionalConfigAnnotation = AnnotationReader .getAnnotationFor(systemPropertyBaseClass, VaadinServletConfiguration.class); @@ -500,14 +536,12 @@ private static void readConfigurationAnnotation( } initParameters.setProperty(name.value(), stringValue); - } catch (Exception e) { + } catch (IllegalAccessException | InvocationTargetException e) { // This should never happen - throw new ServletException( + throw new VaadinConfigurationException( "Could not read @VaadinServletConfiguration value " - + method.getName(), - e); + + method.getName(), e); } } - } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java index 2cc4eab0a78..64a416e7d29 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Serializable; import java.io.UncheckedIOException; import java.lang.management.ManagementFactory; import java.net.HttpURLConnection; @@ -69,7 +68,7 @@ * * @since 2.0 */ -public final class DevModeHandler implements Serializable { +public final class DevModeHandler { private static final AtomicReference atomicHandler = new AtomicReference<>(); @@ -105,9 +104,9 @@ public final class DevModeHandler implements Serializable { public static final String WEBPACK_SERVER = "node_modules/webpack-dev-server/bin/webpack-dev-server.js"; private int port; - private transient Process webpackProcess; + private Process webpackProcess; private final boolean reuseDevServer; - private transient DevServerWatchDog watchDog; + private DevServerWatchDog watchDog; private StringBuilder cumulativeOutput = new StringBuilder(); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/ExecutionFailedException.java b/flow-server/src/main/java/com/vaadin/flow/server/ExecutionFailedException.java index b0f1e64c0c6..644450513bf 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/ExecutionFailedException.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/ExecutionFailedException.java @@ -21,7 +21,6 @@ * @author Vaadin Ltd * @since 2.0 * - * @see FallibleCommand */ public class ExecutionFailedException extends Exception { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/FallibleCommand.java b/flow-server/src/main/java/com/vaadin/flow/server/FallibleCommand.java index 495a955f8a0..68409daf099 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/FallibleCommand.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/FallibleCommand.java @@ -22,7 +22,10 @@ * * @author Vaadin Ltd * @since 2.0 + * @deprecated this command is an internal command and is not supposed to be + * used in application code */ +@Deprecated public interface FallibleCommand extends Serializable { /** diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfig.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfig.java new file mode 100644 index 00000000000..d816efa414d --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfig.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2019 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.server; + +import java.io.Serializable; +import java.util.Enumeration; + +/** + * Configuration in which {@link VaadinService} is running. + * This is a wrapper for Config objects for instance ServletConfig + * and PortletConfig. + * + * @since + */ +public interface VaadinConfig extends Serializable { + + /** + * Get the VaadinContext for this configuration. + * + * @return VaadinContext object for this VaadinConfiguration + */ + VaadinContext getVaadinContext(); + + /** + * Returns the names of the initialization parameters as an + * Enumeration, or an empty Enumeration if there + * are o initialization parameters. + * + * @return initialization parameters as a Enumeration + */ + Enumeration getConfigParameterNames(); + + /** + * Returns the value for the requested parameter, or null if + * the parameter does not exist. + * + * @param name + * name of the parameter whose value is requested + * @return parameter value as String or null for + * no parameter + */ + String getConfigParameter(String name); +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java new file mode 100644 index 00000000000..b2178b3d116 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2019 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.server; + +/** + * Exception thrown for failures in the generation of a deployment configuration + * object. + */ +public class VaadinConfigurationException extends Exception { + + /** + * Exception constructor. + * + * @param message + * exception message + * @param exception + * exception cause + */ + public VaadinConfigurationException(String message, Exception exception) { + super(message, exception); + } +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinContext.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinContext.java index 9d4cfc05b77..6ccb244fcc2 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinContext.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinContext.java @@ -16,15 +16,17 @@ package com.vaadin.flow.server; import java.io.Serializable; +import java.util.Enumeration; import java.util.function.Supplier; /** * Context in which {@link VaadinService} is running. * - * It is used to store service-scoped attributes. + * This is used to store service-scoped attributes and also works as a wrapper + * for context objects with properties e.g. ServletContext and + * PortletContext * - * @author miki - * @since 14.0.0 + * @since 2.0.0 */ public interface VaadinContext extends Serializable { @@ -73,4 +75,24 @@ default T getAttribute(Class type) { * @see #setAttribute(Object) for setting attributes. */ void removeAttribute(Class clazz); + + /** + * Returns the names of the initialization parameters as an + * Enumeration, or an empty Enumeration if there + * are o initialization parameters. + * + * @return initialization parameters as a Enumeration + */ + Enumeration getContextParameterNames(); + + /** + * Returns the value for the requested parameter, or null if + * the parameter does not exist. + * + * @param name + * name of the parameter whose value is requested + * @return parameter value as String or null for + * no parameter + */ + String getContextParameter(String name); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java index 9a9e23e8346..fcb9f55d384 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinService.java @@ -1786,14 +1786,21 @@ private static String wrapJsonForClient(JsonObject json) { /** * Creates the JSON to send to the client when the session has expired. * + * @param async + * a boolean indicating whether the message is sent synchronously + * or asynchronously. * @return the JSON used to inform the client about a session expiration, as * a string */ - public static String createSessionExpiredJSON() { + public static String createSessionExpiredJSON(boolean async) { JsonObject json = Json.createObject(); JsonObject meta = Json.createObject(); json.put("meta", meta); + if (async) { + meta.put(JsonConstants.META_ASYNC, true); + } + meta.put(JsonConstants.META_SESSION_EXPIRED, true); return wrapJsonForClient(json); } @@ -1801,14 +1808,17 @@ public static String createSessionExpiredJSON() { /** * Creates the JSON to send to the client when the UI cannot be found. * + * @param async + * a boolean indicating whether the message is sent synchronously + * or asynchronously. * @return the JSON used to inform the client that the UI cannot be found, * as a string */ - public static String createUINotFoundJSON() { + public static String createUINotFoundJSON(boolean async) { // Session Expired is technically not really the correct thing as // the session exists but the requested UI does not. Still we want // to handle it the same way on the client side. - return createSessionExpiredJSON(); + return createSessionExpiredJSON(async); } private static void putValueOrJsonNull(JsonObject json, String key, diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java index 3a29070db1c..d2aba277bdd 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -50,7 +51,6 @@ public class VaadinServlet extends HttpServlet { private VaadinServletService servletService; private StaticFileHandler staticFileHandler; - private DevModeHandler devmodeHandler; private WebJarServer webJarServer; /** @@ -81,7 +81,6 @@ public void init(ServletConfig servletConfig) throws ServletException { if (deploymentConfiguration.areWebJarsEnabled()) { webJarServer = new WebJarServer(deploymentConfiguration); } - devmodeHandler = DevModeHandler.getDevModeHandler(); // Sets current service even though there are no request and response servletService.setCurrentInstances(null, null); @@ -140,16 +139,17 @@ public static VaadinServlet getCurrent() { * frameworks. * * @return the created deployment configuration - * - * @throws ServletException - * if construction of the {@link Properties} for - * {@link DeploymentConfigurationFactory#createInitParameters(Class, ServletConfig)} - * fails */ protected DeploymentConfiguration createDeploymentConfiguration() throws ServletException { - return createDeploymentConfiguration(DeploymentConfigurationFactory - .createInitParameters(getClass(), getServletConfig())); + try { + return createDeploymentConfiguration(DeploymentConfigurationFactory + .createInitParameters(getClass(), + new VaadinServletConfig(getServletConfig()))); + } catch (VaadinConfigurationException e) { + throw new ServletException( + "Failed to construct DeploymentConfiguration.", e); + } } /** @@ -274,9 +274,10 @@ protected void service(HttpServletRequest request, */ protected boolean serveStaticOrWebJarRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { + DevModeHandler handler = DevModeHandler.getDevModeHandler(); - if (devmodeHandler != null && devmodeHandler.isDevModeRequest(request) - && devmodeHandler.serveDevModeRequest(request, response)) { + if (handler != null && handler.isDevModeRequest(request) + && handler.serveDevModeRequest(request, response)) { return true; } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfig.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfig.java new file mode 100644 index 00000000000..1d175936734 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfig.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2019 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.server; + +import javax.servlet.ServletConfig; +import java.util.Enumeration; +import java.util.Objects; + +/** + * {@link VaadinConfig} implementation for Servlets. + * + * @since + */ +public class VaadinServletConfig implements VaadinConfig { + + private transient ServletConfig config; + + /** + * Vaadin servlet configuration wrapper constructor. + * + * @param config + * servlet configuration object, not null + */ + public VaadinServletConfig(ServletConfig config) { + Objects.requireNonNull(config, "VaadinServletConfig requires the ServletConfig object"); + this.config = config; + } + + /** + * Ensures there is a valid instance of {@link ServletConfig}. + */ + private void ensureServletConfig() { + if (config == null && VaadinService + .getCurrent() instanceof VaadinServletService) { + config = ((VaadinServletService) VaadinService.getCurrent()) + .getServlet().getServletConfig(); + } else if (config == null) { + throw new IllegalStateException( + "The underlying ServletContext of VaadinServletContext is null and there is no VaadinServletService to obtain it from."); + } + } + + @Override + public VaadinContext getVaadinContext() { + ensureServletConfig(); + return new VaadinServletContext(config.getServletContext()); + } + + @Override + public Enumeration getConfigParameterNames() { + ensureServletConfig(); + return config.getInitParameterNames(); + } + + @Override + public String getConfigParameter(String name) { + ensureServletConfig(); + return config.getInitParameter(name); + } +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletContext.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletContext.java index b0dc0178b3f..7bda9b4822f 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletContext.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletContext.java @@ -16,12 +16,13 @@ package com.vaadin.flow.server; import javax.servlet.ServletContext; +import java.util.Enumeration; import java.util.function.Supplier; /** * {@link VaadinContext} that goes with {@link VaadinServletService}. - * @author miki - * @since 14.0.0 + * + * @since 2.0.0 */ public class VaadinServletContext implements VaadinContext { @@ -80,4 +81,16 @@ public void removeAttribute(Class clazz) { context.removeAttribute(clazz.getName()); } + @Override + public Enumeration getContextParameterNames() { + ensureServletContext(); + return context.getInitParameterNames(); + } + + @Override + public String getContextParameter(String name) { + ensureServletContext(); + return context.getInitParameter(name); + } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/MetadataWriter.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/MetadataWriter.java index a91947cad96..0a459641f02 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/MetadataWriter.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/MetadataWriter.java @@ -20,6 +20,8 @@ import com.vaadin.flow.component.UI; import com.vaadin.flow.server.SystemMessages; +import com.vaadin.flow.server.VaadinSessionState; +import com.vaadin.flow.shared.JsonConstants; import elemental.json.Json; import elemental.json.JsonObject; @@ -59,7 +61,12 @@ public JsonObject createMetadata(UI ui, boolean repaintAll, boolean async, } if (async) { - meta.put("async", true); + meta.put(JsonConstants.META_ASYNC, true); + } + + VaadinSessionState state = ui.getSession().getState(); + if (state != null && state.compareTo(VaadinSessionState.CLOSING) >= 0) { + meta.put(JsonConstants.META_SESSION_EXPIRED, true); } // meta instruction for client to enable auto-forward to diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java index c4477bb182f..2d7d0600a9f 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java @@ -70,10 +70,10 @@ private interface PushEventCallback { * open by calling resource.suspend(). If there is a pending push, send it * now. */ - private final PushEventCallback establishCallback = ( resource, ui) -> { - getLogger().debug( - "New push connection for resource {} with transport {}", - resource.uuid(), resource.transport() ); + private final PushEventCallback establishCallback = (resource, ui) -> { + getLogger().debug( + "New push connection for resource {} with transport {}", + resource.uuid(), resource.transport()); resource.getResponse().setContentType("text/plain; charset=UTF-8"); @@ -105,8 +105,7 @@ private interface PushEventCallback { * respond to the request directly.) */ private final PushEventCallback receiveCallback = (resource, ui) -> { - getLogger().debug("Received message from resource {}", - resource.uuid()); + getLogger().debug("Received message from resource {}", resource.uuid()); AtmosphereRequest req = resource.getRequest(); @@ -137,8 +136,7 @@ private interface PushEventCallback { // Refresh on client side sendRefreshAndDisconnect(resource); } catch (InvalidUIDLSecurityKeyException e) { - getLogger().warn( - "Invalid security key received from {}", + getLogger().warn("Invalid security key received from {}", resource.getRequest().getRemoteHost()); // Refresh on client side sendRefreshAndDisconnect(resource); @@ -200,7 +198,7 @@ private void callWithUi(final AtmosphereResource resource, assert VaadinSession.getCurrent() == session; } catch (SessionExpiredException e) { sendNotificationAndDisconnect(resource, - VaadinService.createSessionExpiredJSON()); + VaadinService.createSessionExpiredJSON(true)); return; } @@ -212,7 +210,7 @@ private void callWithUi(final AtmosphereResource resource, if (ui == null) { sendNotificationAndDisconnect(resource, - VaadinService.createUINotFoundJSON()); + VaadinService.createUINotFoundJSON(true)); } else { callback.run(resource, ui); } @@ -244,8 +242,7 @@ private void callWithUi(final AtmosphereResource resource, try { session.unlock(); } catch (Exception e) { - getLogger().warn( - "Error while unlocking session", e); + getLogger().warn("Error while unlocking session", e); // can't call ErrorHandler, we (hopefully) don't have a lock } } @@ -280,8 +277,7 @@ private static AtmospherePushConnection getConnectionForUI(UI ui) { void connectionLost(AtmosphereResourceEvent event) { if (event == null) { - getLogger().error( - "Could not get event. This should never happen."); + getLogger().error("Could not get event. This should never happen."); return; } // We don't want to use callWithUi here, as it assumes there's a client @@ -290,8 +286,6 @@ void connectionLost(AtmosphereResourceEvent event) { AtmosphereResource resource = event.getResource(); if (resource == null) { - getLogger().error( - "Could not get resource. This should never happen."); return; } VaadinServletRequest vaadinRequest = new VaadinServletRequest( @@ -330,8 +324,8 @@ void connectionLost(AtmosphereResourceEvent event) { ui = findUiUsingResource(resource, session.getUIs()); if (ui == null) { - getLogger().debug( - "Could not get UI. This should never happen," + getLogger() + .debug("Could not get UI. This should never happen," + " except when reloading in Firefox and Chrome -" + " see http://dev.vaadin.com/ticket/14251."); return; @@ -358,8 +352,7 @@ void connectionLost(AtmosphereResourceEvent event) { * The client is expected to close the connection after push * mode has been set to disabled. */ - getLogger().debug( - "Connection closed for resource {}", id); + getLogger().debug("Connection closed for resource {}", id); } else { /* * Unexpected cancel, e.g. if the user closes the browser @@ -379,8 +372,7 @@ void connectionLost(AtmosphereResourceEvent event) { try { session.unlock(); } catch (Exception e) { - getLogger().warn("Error while unlocking session", - e); + getLogger().warn("Error while unlocking session", e); // can't call ErrorHandler, we (hopefully) don't have a lock } } @@ -441,8 +433,8 @@ private static void sendNotificationAndDisconnect( resource.getResponse().getWriter().write(notificationJson); resource.resume(); } catch (Exception e) { - getLogger().trace( - "Failed to send critical notification to client", e); + getLogger().trace("Failed to send critical notification to client", + e); } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java index e875123b396..c8507195ed3 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java @@ -35,6 +35,7 @@ import com.vaadin.flow.component.UI; import com.vaadin.flow.internal.MessageDigestUtil; +import com.vaadin.flow.internal.StateNode; import com.vaadin.flow.server.ErrorEvent; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinService; @@ -208,6 +209,20 @@ public InvalidUIDLSecurityKeyException() { } } + /** + * Exception thrown then the client side resynchronization is required. + */ + public static class ResynchronizationRequiredException + extends RuntimeException { + + /** + * Default constructor for the exception. + */ + public ResynchronizationRequiredException() { + super(); + } + } + /** * Reads JSON containing zero or more serialized RPC calls (including legacy * variable changes) and executes the calls. @@ -302,10 +317,17 @@ public void handleRpc(UI ui, Reader reader, VaadinRequest request) } if (rpcRequest.isResynchronize()) { - // FIXME Implement - throw new UnsupportedOperationException("FIXME: Implement resync"); + getLogger().warn("Resynchronizing UI by client's request. Under " + + "normal operations this should not happen and may " + + "indicate a bug in Vaadin platform. If you see this " + + "message regularly please open a bug report at " + + "https://github.com/vaadin/flow/issues"); + ui.getInternals().getStateTree().getRootNode() + .visitNodeTree(StateNode::markAsDirty); + // Signal by exception instead of return value to keep the method + // signature for source and binary compatibility + throw new ResynchronizationRequiredException(); } - } /** diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java index db5b2270270..8479ce31f94 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java @@ -36,6 +36,7 @@ import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.VaadinSession; import com.vaadin.flow.server.communication.ServerRpcHandler.InvalidUIDLSecurityKeyException; +import com.vaadin.flow.server.communication.ServerRpcHandler.ResynchronizationRequiredException; import com.vaadin.flow.shared.JsonConstants; import elemental.json.JsonException; @@ -77,7 +78,8 @@ public boolean synchronizedHandleRequest(VaadinSession session, if (uI == null) { // This should not happen but it will if the UI has been closed. We // really don't want to see it in the server logs though - commitJsonResponse(response, VaadinService.createUINotFoundJSON()); + commitJsonResponse(response, + VaadinService.createUINotFoundJSON(false)); return true; } @@ -85,8 +87,7 @@ public boolean synchronizedHandleRequest(VaadinSession session, try { getRpcHandler(session).handleRpc(uI, request.getReader(), request); - - writeUidl(uI, stringWriter); + writeUidl(uI, stringWriter, false); } catch (JsonException e) { getLogger().error("Error writing JSON to response", e); // Refresh on client side @@ -98,6 +99,9 @@ public boolean synchronizedHandleRequest(VaadinSession session, // Refresh on client side writeRefresh(response); return true; + } catch (ResynchronizationRequiredException e) { // NOSONAR + // Resync on the client side + writeUidl(uI, stringWriter, true); } finally { stringWriter.close(); } @@ -112,8 +116,9 @@ private void writeRefresh(VaadinResponse response) throws IOException { commitJsonResponse(response, json); } - private static void writeUidl(UI ui, Writer writer) throws IOException { - JsonObject uidl = new UidlWriter().createUidl(ui, false); + private static void writeUidl(UI ui, Writer writer, boolean resync) + throws IOException { + JsonObject uidl = new UidlWriter().createUidl(ui, false, resync); // some dirt to prevent cross site scripting String responseString = "for(;;);[" + uidl.toJson() + "]"; @@ -140,7 +145,7 @@ public boolean handleSessionExpired(VaadinRequest request, VaadinService service = request.getService(); service.writeUncachedStringResponse(response, JsonConstants.JSON_CONTENT_TYPE, - VaadinService.createSessionExpiredJSON()); + VaadinService.createSessionExpiredJSON(false)); return true; } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlWriter.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlWriter.java index 4eb6a62bff3..e761fa2838a 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlWriter.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlWriter.java @@ -141,10 +141,12 @@ public AbstractTheme getTheme() { * The {@link UI} whose changes to write * @param async * True if this message is sent by the server asynchronously, - * false if it is a response to a client message. + * false if it is a response to a client message + * @param resync + * True iff the client should be asked to resynchronize * @return JSON object containing the UIDL response */ - public JsonObject createUidl(UI ui, boolean async) { + public JsonObject createUidl(UI ui, boolean async, boolean resync) { JsonObject response = Json.createObject(); UIInternals uiInternals = ui.getInternals(); @@ -163,6 +165,9 @@ public JsonObject createUidl(UI ui, boolean async) { ? uiInternals.getServerSyncId() : -1; response.put(ApplicationConstants.SERVER_SYNC_ID, syncId); + if (resync) { + response.put(ApplicationConstants.RESYNCHRONIZE_ID, true); + } int nextClientToServerMessageId = uiInternals .getLastProcessedClientToServerId() + 1; response.put(ApplicationConstants.CLIENT_TO_SERVER_ID, @@ -206,6 +211,20 @@ public JsonObject createUidl(UI ui, boolean async) { return response; } + /** + * Creates a JSON object containing all pending changes to the given UI. + * + * @param ui + * The {@link UI} whose changes to write + * @param async + * True if this message is sent by the server asynchronously, + * false if it is a response to a client message. + * @return JSON object containing the UIDL response + */ + public JsonObject createUidl(UI ui, boolean async) { + return createUidl(ui, async, false); + } + private static void populateDependencies(JsonObject response, DependencyList dependencyList, ResolveContext context) { Collection pendingSendToClient = dependencyList diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandler.java index abe69f855a1..ef4dccf63ea 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandler.java @@ -15,11 +15,27 @@ */ package com.vaadin.flow.server.communication; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; + +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + import com.vaadin.flow.component.PushConfiguration; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.webcomponent.WebComponentUI; import com.vaadin.flow.internal.JsonUtils; import com.vaadin.flow.server.BootstrapHandler; +import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.ServletHelper; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinResponse; @@ -33,25 +49,10 @@ import elemental.json.Json; import elemental.json.JsonArray; import elemental.json.JsonObject; -import org.jsoup.nodes.Attribute; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Pattern; import static com.vaadin.flow.shared.ApplicationConstants.CONTENT_TYPE_TEXT_JAVASCRIPT_UTF_8; import static java.nio.charset.StandardCharsets.UTF_8; - /** * Bootstrap handler for WebComponent requests. * @@ -61,15 +62,16 @@ public class WebComponentBootstrapHandler extends BootstrapHandler { private static final String REQ_PARAM_URL = "url"; private static final String PATH_PREFIX = "/web-component/web-component"; - private static final Pattern PATH_PATTERN = - Pattern.compile(".*" + PATH_PREFIX + "-(ui|bootstrap)\\.(js|html)$"); + private static final Pattern PATH_PATTERN = Pattern + .compile(".*" + PATH_PREFIX + "-(ui|bootstrap)\\.(js|html)$"); private static class WebComponentBootstrapContext extends BootstrapContext { private WebComponentBootstrapContext(VaadinRequest request, - VaadinResponse response, UI ui, - Function callback) { - super(request, response, ui.getInternals().getSession(), ui, callback); + VaadinResponse response, UI ui, + Function callback) { + super(request, response, ui.getInternals().getSession(), ui, + callback); } @Override @@ -82,7 +84,8 @@ public Optional getPageConfigurationAnnotation( @Override protected Optional getTheme() { - Optional optionalTheme = getPageConfigurationAnnotation(Theme.class); + Optional optionalTheme = getPageConfigurationAnnotation( + Theme.class); return optionalTheme.map(ThemeDefinition::new); } } @@ -96,7 +99,9 @@ public WebComponentBootstrapHandler() { /** * Creates a new bootstrap handler, allowing to use custom page builder. - * @param pageBuilder Page builder to use. + * + * @param pageBuilder + * Page builder to use. */ protected WebComponentBootstrapHandler(PageBuilder pageBuilder) { super(pageBuilder); @@ -112,31 +117,36 @@ protected boolean canHandleRequest(VaadinRequest request) { } /** - * Returns the request's base url to use in constructing and initialising ui. - * @param request Request to the url for. + * Returns the request's base url to use in constructing and initialising + * ui. + * + * @param request + * Request to the url for. * @return Request's url. */ protected String getRequestUrl(VaadinRequest request) { - return ((VaadinServletRequest)request).getRequestURL().toString(); + return ((VaadinServletRequest) request).getRequestURL().toString(); } @Override - protected BootstrapContext createAndInitUI( - Class uiClass, VaadinRequest request, - VaadinResponse response, VaadinSession session) { + protected BootstrapContext createAndInitUI(Class uiClass, + VaadinRequest request, VaadinResponse response, + VaadinSession session) { + + if (!canHandleRequest(request)) { + throw new IllegalStateException( + "Unexpected request URL '" + getRequestUrl(request) + + "' in the bootstrap handler for web " + + "component UI which should handle path " + + PATH_PATTERN.toString()); + } + + final String serviceUrl = getServiceUrl(request, response); + BootstrapContext context = super.createAndInitUI(WebComponentUI.class, request, response, session); JsonObject config = context.getApplicationParameters(); - if(!canHandleRequest(request)) { - throw new IllegalStateException("Unexpected request URL '" - + getRequestUrl(request) + "' in the bootstrap handler for web " - + "component UI which should handle path " - + PATH_PATTERN.toString()); - } - - String serviceUrl = getServiceUrl(request); - String pushURL = context.getSession().getConfiguration().getPushURL(); if (pushURL == null) { pushURL = serviceUrl; @@ -169,26 +179,29 @@ protected BootstrapContext createAndInitUI( @Override protected BootstrapContext createBootstrapContext(VaadinRequest request, - VaadinResponse response, UI ui, Function callback) { - return new WebComponentBootstrapContext(request, response, ui, callback); + VaadinResponse response, UI ui, + Function callback) { + return new WebComponentBootstrapContext(request, response, ui, + callback); } - @Override - public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { - if (session.getService().getDeploymentConfiguration().isCompatibilityMode()) { + public boolean synchronizedHandleRequest(VaadinSession session, + VaadinRequest request, VaadinResponse response) throws IOException { + if (session.getService().getDeploymentConfiguration() + .isCompatibilityMode()) { return super.synchronizedHandleRequest(session, request, response); } else { // Find UI class Class uiClass = getUIClass(request); - BootstrapContext context = createAndInitUI(uiClass, request, response, - session); + BootstrapContext context = createAndInitUI(uiClass, request, + response, session); ServletHelper.setResponseNoCacheHeaders(response::setHeader, response::setDateHeader); - String serviceUrl = getServiceUrl(request); + String serviceUrl = getServiceUrl(request, response); Document document = getPageBuilder().getBootstrapPage(context); writeBootstrapPage(response, document.head(), serviceUrl); @@ -202,19 +215,20 @@ public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest re * JavaScript. Drops {@code } element. * * @param response - * {@link com.vaadin.flow.server.VaadinResponse} into which the - * script is written + * {@link com.vaadin.flow.server.VaadinResponse} into which the + * script is written * @param head - * head element of Vaadin Bootstrap page. The child elements are - * copied into the embedding page's head using JavaScript. + * head element of Vaadin Bootstrap page. The child elements are + * copied into the embedding page's head using JavaScript. * @param serviceUrl - * base path to use for the head elements' URLs + * base path to use for the head elements' URLs * @throws IOException - * if writing fails + * if writing fails */ - private void writeBootstrapPage( - VaadinResponse response, Element head, String serviceUrl) throws IOException { - writeBootstrapPage(CONTENT_TYPE_TEXT_JAVASCRIPT_UTF_8, response, head, serviceUrl); + private void writeBootstrapPage(VaadinResponse response, Element head, + String serviceUrl) throws IOException { + writeBootstrapPage(CONTENT_TYPE_TEXT_JAVASCRIPT_UTF_8, response, head, + serviceUrl); } /** @@ -223,30 +237,32 @@ private void writeBootstrapPage( * JavaScript. Drops {@code } element. * * @param contentType - * Content type of the response. + * Content type of the response. * @param response - * {@link com.vaadin.flow.server.VaadinResponse} into which the - * script is written + * {@link com.vaadin.flow.server.VaadinResponse} into which the + * script is written * @param head - * head element of Vaadin Bootstrap page. The child elements are - * copied into the embedding page's head using JavaScript. + * head element of Vaadin Bootstrap page. The child elements are + * copied into the embedding page's head using JavaScript. * @param serviceUrl - * base path to use for the head elements' URLs + * base path to use for the head elements' URLs * @throws IOException - * if writing fails + * if writing fails */ - protected void writeBootstrapPage(String contentType, VaadinResponse response, Element head, String serviceUrl) throws IOException { + protected void writeBootstrapPage(String contentType, + VaadinResponse response, Element head, String serviceUrl) + throws IOException { /* - The elements found in the head are reconstructed using JavaScript and - document.createElement(...). Since innerHTML and related methods - do not execute "))); - System.out.println(allElements); Assert.assertTrue( "index.js should be added to head for ES6 browsers. (deferred and type module)", diff --git a/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java b/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java index d1a6a354263..07d9bde184b 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java @@ -100,7 +100,7 @@ public void servletWithEnclosingUI_hasItsNameInConfig() throws Exception { DeploymentConfiguration config = DeploymentConfigurationFactory .createDeploymentConfiguration(servlet, - createServletConfigMock(servletConfigParams, + createVaadinConfigMock(servletConfigParams, Collections.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath()))); @@ -123,7 +123,7 @@ public void servletWithNoEnclosingUI_hasDefaultUiInConfig() defaultServletParams); DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createServletConfigMock( + .createDeploymentConfiguration(servlet, createVaadinConfigMock( servletConfigParams, emptyMap())); Class notUiClass = servlet.getEnclosingClass(); @@ -143,7 +143,7 @@ public void vaadinServletConfigurationRead() throws Exception { defaultServletParams); DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createServletConfigMock( + .createDeploymentConfiguration(servlet, createVaadinConfigMock( servletConfigParams, emptyMap())); assertTrue(String.format( @@ -170,7 +170,7 @@ public void servletConfigParametersOverrideVaadinParameters() Integer.toString(overridingHeartbeatIntervalValue)); DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createServletConfigMock( + .createDeploymentConfiguration(servlet, createVaadinConfigMock( servletConfigParams, emptyMap())); assertEquals( @@ -198,7 +198,7 @@ public void servletContextParametersOverrideVaadinParameters() Integer.toString(overridingHeartbeatIntervalValue)); DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createServletConfigMock( + .createDeploymentConfiguration(servlet, createVaadinConfigMock( emptyMap(), servletContextParams)); assertEquals( @@ -235,7 +235,7 @@ public void servletConfigParametersOverrideServletContextParameters() Integer.toString(servletContextHeartbeatIntervalValue)); DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createServletConfigMock( + .createDeploymentConfiguration(servlet, createVaadinConfigMock( servletConfigParams, servletContextParams)); assertEquals( @@ -266,7 +266,7 @@ public void should_throwIfCompatibilityModeIsFalseButNoTokenFile() DeploymentConfigurationFactory.createDeploymentConfiguration( VaadinServlet.class, - createServletConfigMock(Collections.singletonMap( + createVaadinConfigMock(Collections.singletonMap( Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE, Boolean.FALSE.toString()), emptyMap())); } @@ -285,7 +285,7 @@ public void shouldNotThrowIfCompatibilityModeIsFalse_noTokenFile_correctWebPackC FileUtils.writeLines(webPack, Arrays.asList("./webpack.generated.js")); DeploymentConfigurationFactory.createDeploymentConfiguration( - VaadinServlet.class, createServletConfigMock(map, emptyMap())); + VaadinServlet.class, createVaadinConfigMock(map, emptyMap())); } @Test @@ -309,14 +309,16 @@ public void shouldThrowIfCompatibilityModeIsFalse_noTokenFile_incorrectWebPackCo webPack.createNewFile(); DeploymentConfigurationFactory.createDeploymentConfiguration( - VaadinServlet.class, createServletConfigMock(map, emptyMap())); + VaadinServlet.class, createVaadinConfigMock(map, emptyMap())); } @Test public void should_readConfigurationFromTokenFile() throws Exception { FileUtils.writeLines(tokenFile, - Arrays.asList("{", "\"compatibilityMode\": false,", - "\"productionMode\": true", "}")); + Arrays.asList("{", + "\"compatibilityMode\": false,", + "\"productionMode\": true", + "}")); DeploymentConfiguration config = createConfig(Collections .singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); @@ -331,10 +333,13 @@ public void shouldThrow_tokenFileContainsNonExistingNpmFolderInDevMode() exception.expectMessage( String.format(DEV_FOLDER_MISSING_MESSAGE, "npm")); FileUtils.writeLines(tokenFile, - Arrays.asList("{", "\"compatibilityMode\": false,", - "\"productionMode\": false,", "\"npmFolder\": \"npm\",", + Arrays.asList("{", + "\"compatibilityMode\": false,", + "\"productionMode\": false,", + "\"npmFolder\": \"npm\",", "\"generatedFolder\": \"generated\",", - "\"frontendFolder\": \"frontend\"", "}")); + "\"frontendFolder\": \"frontend\"", + "}")); createConfig(Collections.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); @@ -347,9 +352,11 @@ public void shouldThrow_tokenFileContainsNonExistingFrontendFolderNoNpmFolder() exception.expectMessage( String.format(DEV_FOLDER_MISSING_MESSAGE, "frontend")); FileUtils.writeLines(tokenFile, - Arrays.asList("{", "\"compatibilityMode\": false,", + Arrays.asList("{", + "\"compatibilityMode\": false,", "\"productionMode\": false,", - "\"frontendFolder\": \"frontend\"", "}")); + "\"frontendFolder\": \"frontend\"", + "}")); createConfig(Collections.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); @@ -365,10 +372,12 @@ public void shouldThrow_tokenFileContainsNonExistingFrontendFolderOutsideNpmSubF String tempFolder = temporaryFolder.getRoot().getAbsolutePath() .replace("\\", "/"); FileUtils.writeLines(tokenFile, - Arrays.asList("{", "\"compatibilityMode\": false,", + Arrays.asList("{", + "\"compatibilityMode\": false,", "\"productionMode\": false,", "\"npmFolder\": \"" + tempFolder + "/npm\",", - "\"frontendFolder\": \"frontend\"", "}")); + "\"frontendFolder\": \"frontend\"", + "}")); createConfig(Collections.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); @@ -381,7 +390,8 @@ public void shouldNotThrow_tokenFileFrontendFolderInDevMode() String tempFolder = temporaryFolder.getRoot().getAbsolutePath() .replace("\\", "/"); FileUtils.writeLines(tokenFile, Arrays.asList("{", - "\"compatibilityMode\": false,", "\"productionMode\": false,", + "\"compatibilityMode\": false,", + "\"productionMode\": false,", "\"npmFolder\": \"" + tempFolder + "/npm\",", "\"frontendFolder\": \"" + tempFolder + "/npm/frontend\"", "}")); @@ -397,17 +407,72 @@ public void shouldNotThrow_tokenFileFoldersExist() throws Exception { String tempFolder = temporaryFolder.getRoot().getAbsolutePath() .replace("\\", "/"); FileUtils.writeLines(tokenFile, Arrays.asList("{", - "\"compatibilityMode\": false,", "\"productionMode\": false,", + "\"compatibilityMode\": false,", + "\"productionMode\": false,", "\"npmFolder\": \"" + tempFolder + "/npm\",", - "\"frontendFolder\": \"" + tempFolder + "/frontend\"", "}")); + "\"frontendFolder\": \"" + tempFolder + "/frontend\"", + "}")); createConfig(Collections.singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); } + @Test + public void externalStatsFileTrue_predefinedContext() throws Exception { + FileUtils.writeLines(tokenFile, Arrays.asList("{", + "\"externalStatsFile\": true", + "}")); + + DeploymentConfiguration config = createConfig(Collections + .singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); + + assertEquals(true, config.isProductionMode()); + assertEquals(false, config.isCompatibilityMode()); + assertEquals(false, config.enableDevServer()); + assertEquals(true, config.isStatsExternal()); + assertEquals(Constants.DEFAULT_EXTERNAL_STATS_URL, config.getExternalStatsUrl()); + } + + @Test + public void externalStatsUrlGiven_predefinedContext() throws Exception { + FileUtils.writeLines(tokenFile, Arrays.asList("{", + "\"externalStatsUrl\": \"http://my.server/static/stats.json\"", + "}")); + + DeploymentConfiguration config = createConfig(Collections + .singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); + + assertEquals(true, config.isProductionMode()); + assertEquals(false, config.isCompatibilityMode()); + assertEquals(false, config.enableDevServer()); + assertEquals(true, config.isStatsExternal()); + assertEquals("http://my.server/static/stats.json", config.getExternalStatsUrl()); + } + + @Test + public void externalStatsFileTrue_predefinedValuesAreNotOverridden() throws Exception { + // note that this situation shouldn't happen that the other settings + // would be against the external usage. + FileUtils.writeLines(tokenFile, Arrays.asList("{", + "\"compatibilityMode\": true,", + "\"enableDevServer\": true,", + "\"productionMode\": false,", + "\"externalStatsFile\": true", + "}")); + + DeploymentConfiguration config = createConfig(Collections + .singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); + + assertEquals(true, config.isProductionMode()); + assertEquals(false, config.isCompatibilityMode()); + assertEquals(false, config.enableDevServer()); + assertEquals(true, config.isStatsExternal()); + assertEquals(Constants.DEFAULT_EXTERNAL_STATS_URL, config.getExternalStatsUrl()); + } + @Test public void createInitParameters_fallbackChunkObjectIsInInitParams() - throws ServletException, IOException { + throws VaadinConfigurationException, IOException { ServletContext context = Mockito.mock(ServletContext.class); ServletConfig config = Mockito.mock(ServletConfig.class); Mockito.when(config.getServletContext()).thenReturn(context); @@ -431,7 +496,7 @@ public void createInitParameters_fallbackChunkObjectIsInInitParams() .thenReturn(tokenFile.getPath()); Properties properties = DeploymentConfigurationFactory - .createInitParameters(Object.class, config); + .createInitParameters(Object.class, new VaadinServletConfig(config)); Object object = properties .get(DeploymentConfigurationFactory.FALLBACK_CHUNK); @@ -454,7 +519,13 @@ public void createInitParameters_fallbackChunkObjectIsInInitParams() private DeploymentConfiguration createConfig(Map map) throws Exception { return DeploymentConfigurationFactory.createDeploymentConfiguration( - VaadinServlet.class, createServletConfigMock(map, emptyMap())); + VaadinServlet.class, createVaadinConfigMock(map, emptyMap())); + } + + private VaadinConfig createVaadinConfigMock( + Map servletConfigParameters, + Map servletContextParameters) throws Exception { + return new VaadinServletConfig(createServletConfigMock(servletConfigParameters,servletContextParameters)); } private ServletConfig createServletConfigMock( diff --git a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigTest.java b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigTest.java new file mode 100644 index 00000000000..e9651b681d5 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigTest.java @@ -0,0 +1,87 @@ +package com.vaadin.flow.server; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Test VaadinServletConfig property handling and function with VaadinContext. + */ +public class VaadinServletConfigTest { + + private VaadinServletConfig config; + + private ServletContext servletContext; + private final Map attributeMap = new HashMap<>(); + private Map properties; + + @Before + public void setup() { + ServletConfig servletConfig = Mockito.mock(ServletConfig.class); + servletContext = Mockito.mock(ServletContext.class); + + Mockito.when(servletConfig.getServletContext()).thenReturn(servletContext); + + Mockito.when(servletContext.getAttribute(Mockito.anyString())) + .then(invocationOnMock -> attributeMap + .get(invocationOnMock.getArguments()[0].toString())); + Mockito.doAnswer(invocationOnMock -> attributeMap + .put(invocationOnMock.getArguments()[0].toString(), + invocationOnMock.getArguments()[1])) + .when(servletContext) + .setAttribute(Mockito.anyString(), Mockito.any()); + + properties = new HashMap<>(); + properties.put(Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE, "false"); + properties.put(Constants.SERVLET_PARAMETER_PRODUCTION_MODE, "true"); + properties.put(Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, "false"); + + Mockito.when(servletConfig.getInitParameterNames()) + .thenReturn(Collections.enumeration(properties.keySet())); + Mockito.when(servletConfig.getInitParameter(Mockito.anyString())) + .then(invocation -> properties + .get(invocation.getArguments()[0])); + config = new VaadinServletConfig(servletConfig); + } + + @Test + public void getPropertyNames_returnsExpectedProperties() { + List list = Collections.list(config.getConfigParameterNames()); + Assert.assertEquals( + "Context should return only keys defined in ServletContext", + properties.size(), list.size()); + for (String key : properties.keySet()) { + Assert.assertEquals(String.format( + "Value should be same from context for key '%s'", key), + properties.get(key), config.getConfigParameter(key)); + } + } + + + @Test + public void vaadinContextThroughConfig_setAndGetAttribute() { + String value = "my-attribute"; + config.getVaadinContext().setAttribute(value); + String result = config.getVaadinContext().getAttribute(String.class); + Assert.assertEquals(value, result); + // overwrite + String newValue = "this is a new value"; + config.getVaadinContext().setAttribute(newValue); + result = config.getVaadinContext().getAttribute(String.class); + Assert.assertEquals(newValue, result); + // now the provider should not be called, so value should be still there + result = config.getVaadinContext().getAttribute(String.class, + () -> { + throw new AssertionError("Should not be called"); + }); + Assert.assertEquals(newValue, result); + } +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletContextTest.java b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletContextTest.java index 3c17bd9aa4d..be54db257aa 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletContextTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletContextTest.java @@ -1,17 +1,20 @@ package com.vaadin.flow.server; +import javax.servlet.ServletContext; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import javax.servlet.ServletContext; -import java.util.HashMap; -import java.util.Map; - /** - * @author miki - * @since 2019-05-27 + * Tests for VaadinServletContext attribute storage and property delegation. + * + * @since 2.0.0 */ public class VaadinServletContextTest { @@ -22,6 +25,7 @@ private static String testAttributeProvider() { private VaadinServletContext context; private final Map attributeMap = new HashMap<>(); + private Map properties; @Before public void setup() { @@ -31,7 +35,17 @@ public void setup() { invocationOnMock.getArguments()[0].toString(), invocationOnMock.getArguments()[1] )).when(servletContext).setAttribute(Mockito.anyString(), Mockito.any()); - + + properties = new HashMap<>(); + properties.put(Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE, "false"); + properties.put(Constants.SERVLET_PARAMETER_PRODUCTION_MODE, "true"); + properties.put(Constants.SERVLET_PARAMETER_ENABLE_DEV_SERVER, "false"); + + Mockito.when(servletContext.getInitParameterNames()) + .thenReturn(Collections.enumeration(properties.keySet())); + Mockito.when(servletContext.getInitParameter(Mockito.anyString())) + .then(invocation -> properties + .get(invocation.getArguments()[0])); context = new VaadinServletContext(servletContext); } @@ -76,4 +90,17 @@ public void setAndGetAttribute() { }); Assert.assertEquals(newValue, result); } + + @Test + public void getPropertyNames_returnsExpectedProperties() { + List list = Collections.list(context.getContextParameterNames()); + Assert.assertEquals( + "Context should return only keys defined in ServletContext", + properties.size(), list.size()); + for (String key : properties.keySet()) { + Assert.assertEquals(String.format( + "Value should be same from context for key '%s'", key), + properties.get(key), context.getContextParameter(key)); + } + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/CommunicationUtil.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/CommunicationUtil.java new file mode 100644 index 00000000000..22dc134e149 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/CommunicationUtil.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2019 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.server.communication; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.mockito.internal.verification.VerificationModeFactory; + +class CommunicationUtil { + + static String getStringWhenWriteBytesOffsetLength(OutputStream outputStream) + throws IOException { + ArgumentCaptor contentArg = ArgumentCaptor + .forClass(byte[].class); + ArgumentCaptor offsetArg = ArgumentCaptor.forClass(int.class); + ArgumentCaptor lengthArg = ArgumentCaptor.forClass(int.class); + + Mockito.verify(outputStream, VerificationModeFactory.atLeastOnce()) + .write(contentArg.capture(), offsetArg.capture(), + lengthArg.capture()); + + List offsetValues = offsetArg.getAllValues(); + List lengthValues = lengthArg.getAllValues(); + + AtomicInteger i = new AtomicInteger(); + + return contentArg.getAllValues().stream().map(bytes -> { + return new String(bytes, offsetValues.get(i.get()), + lengthValues.get(i.getAndIncrement())); + }).collect(Collectors.joining()); + } + + static String getStringWhenWriteString(OutputStream outputStream) + throws IOException { + ArgumentCaptor contentArg = ArgumentCaptor + .forClass(byte[].class); + + Mockito.verify(outputStream, VerificationModeFactory.atLeastOnce()) + .write(contentArg.capture()); + + return contentArg.getAllValues().stream() + .map(bytes -> new String(bytes)).collect(Collectors.joining()); + } + + static String getStringWhenWriteString(PrintWriter printWriter) { + ArgumentCaptor contentArg = ArgumentCaptor + .forClass(String.class); + + Mockito.verify(printWriter, VerificationModeFactory.atLeastOnce()) + .write(contentArg.capture()); + + return String.join("", contentArg.getAllValues()); + } + +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/MetadataWriterTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/MetadataWriterTest.java index 3be9e8c822d..ee1ccfee959 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/MetadataWriterTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/MetadataWriterTest.java @@ -15,9 +15,6 @@ */ package com.vaadin.flow.server.communication; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.io.IOException; import org.junit.Assert; @@ -28,11 +25,14 @@ import com.vaadin.flow.component.UI; import com.vaadin.flow.server.SystemMessages; import com.vaadin.flow.server.VaadinSession; +import com.vaadin.flow.server.VaadinSessionState; import com.vaadin.flow.server.WrappedSession; -import com.vaadin.flow.server.communication.MetadataWriter; import elemental.json.JsonObject; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class MetadataWriterTest { private UI ui; @@ -55,33 +55,25 @@ private void disableSessionExpirationMessages(SystemMessages messages) { @Test public void writeAsyncTag() throws Exception { - JsonObject meta = new MetadataWriter().createMetadata(ui, false, true, - messages); - Assert.assertEquals("{\"async\":true}", meta.toJson()); + assertMetadataOutput(false, true, "{\"async\":true}"); } @Test public void writeRepaintTag() throws Exception { - JsonObject meta = new MetadataWriter().createMetadata(ui, true, false, - messages); - Assert.assertEquals("{\"repaintAll\":true}", meta.toJson()); + assertMetadataOutput(true, false, "{\"repaintAll\":true}"); } @Test public void writeRepaintAndAsyncTag() throws Exception { - JsonObject meta = new MetadataWriter().createMetadata(ui, true, true, - messages); - Assert.assertEquals("{\"repaintAll\":true,\"async\":true}", - meta.toJson()); + assertMetadataOutput(true, true, + "{\"repaintAll\":true,\"async\":true}"); } @Test public void writeRedirectWithExpiredSession() throws Exception { disableSessionExpirationMessages(messages); - JsonObject meta = new MetadataWriter().createMetadata(ui, false, false, - messages); - Assert.assertEquals("{}", meta.toJson()); + assertMetadataOutput(false, false, "{}"); } @Test @@ -91,11 +83,8 @@ public void writeRedirectWithActiveSession() throws Exception { disableSessionExpirationMessages(messages); - JsonObject meta = new MetadataWriter().createMetadata(ui, false, false, - messages); - Assert.assertEquals( - "{\"timedRedirect\":{\"interval\":15,\"url\":\"\"}}", - meta.toJson()); + assertMetadataOutput(false, false, + "{\"timedRedirect\":{\"interval\":15,\"url\":\"\"}}"); } @Test @@ -105,10 +94,36 @@ public void writeAsyncWithSystemMessages() throws IOException { disableSessionExpirationMessages(messages); - JsonObject meta = new MetadataWriter().createMetadata(ui, false, true, - messages); - Assert.assertEquals( - "{\"async\":true,\"timedRedirect\":{\"interval\":15,\"url\":\"\"}}", - meta.toJson()); + assertMetadataOutput(false, true, + "{\"async\":true,\"timedRedirect\":{\"interval\":15,\"url\":\"\"}}"); } + + @Test + public void writeSessionExpiredTag_sessionIsOpen() throws Exception { + Mockito.when(session.getState()).thenReturn(VaadinSessionState.OPEN); + assertMetadataOutput(false, false, "{}"); + } + + @Test + public void writeSessionExpiredTag_sessionIsClosing() throws Exception { + Mockito.when(session.getState()).thenReturn(VaadinSessionState.CLOSING); + assertMetadataOutput(false, false, "{\"sessionExpired\":true}"); + + Mockito.when(session.getState()).thenReturn(VaadinSessionState.CLOSED); + assertMetadataOutput(false, false, "{\"sessionExpired\":true}"); + } + + @Test + public void writeSessionExpiredTag_sessionIsClosed() throws Exception { + Mockito.when(session.getState()).thenReturn(VaadinSessionState.CLOSED); + assertMetadataOutput(false, false, "{\"sessionExpired\":true}"); + } + + private void assertMetadataOutput(boolean repaintAll, boolean async, + String expectedOutput) { + JsonObject meta = new MetadataWriter().createMetadata(ui, repaintAll, + async, messages); + Assert.assertEquals(expectedOutput, meta.toJson()); + } + } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java new file mode 100644 index 00000000000..135163a5817 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2019 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.server.communication; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Properties; + +import com.vaadin.flow.server.DefaultDeploymentConfiguration; +import com.vaadin.flow.server.VaadinServletService; +import org.atmosphere.cpr.AtmosphereRequest; +import org.atmosphere.cpr.AtmosphereResource; +import org.atmosphere.cpr.AtmosphereResponse; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class PushAtmosphereHandlerTest { + + private AtmosphereResource resource; + + private AtmosphereRequest request; + private AtmosphereResponse response; + private PrintWriter printWriter; + + private PushAtmosphereHandler atmosphereHandler; + + @Before + public void setup() throws IOException { + request = Mockito.mock(AtmosphereRequest.class); + response = Mockito.mock(AtmosphereResponse.class); + printWriter = Mockito.mock(PrintWriter.class); + Mockito.when(response.getWriter()).thenReturn(printWriter); + + resource = Mockito.mock(AtmosphereResource.class); + Mockito.when(resource.getRequest()).thenReturn(request); + Mockito.when(resource.getResponse()).thenReturn(response); + + VaadinServletService service = new VaadinServletService(null, + new DefaultDeploymentConfiguration(getClass(), + new Properties())); + + PushHandler handler = new PushHandler(service); + + atmosphereHandler = new PushAtmosphereHandler(); + atmosphereHandler.setPushHandler(handler); + } + + @Test + public void writeSessionExpiredAsyncGet() throws Exception { + writeSessionExpiredAsync("GET"); + } + + @Test + public void writeSessionExpiredAsyncPost() throws Exception { + writeSessionExpiredAsync("POST"); + } + + private void writeSessionExpiredAsync(String httpMethod) throws IOException { + Mockito.when(request.getMethod()).thenReturn(httpMethod); + + atmosphereHandler.onRequest(resource); + + String responseContent = CommunicationUtil + .getStringWhenWriteString(printWriter); + + // response shouldn't contain async + Assert.assertEquals("Invalid response", + "for(;;);[{\"meta\":{\"async\":true,\"sessionExpired\":true}}]", + responseContent); + } + +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java new file mode 100644 index 00000000000..ab22e580fa3 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java @@ -0,0 +1,78 @@ +package com.vaadin.flow.server.communication; + +import java.io.IOException; +import java.io.StringReader; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; + +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.internal.UIInternals; +import com.vaadin.flow.function.DeploymentConfiguration; +import com.vaadin.flow.internal.StateTree; +import com.vaadin.flow.server.VaadinRequest; +import com.vaadin.flow.server.VaadinService; +import com.vaadin.flow.server.VaadinSession; + +public class ServerRpcHandlerTest { + private VaadinRequest request; + private VaadinService service; + private VaadinSession session; + private UI ui; + private UIInternals uiInternals; + private StateTree uiTree; + final private String csrfToken = ""; + + private ServerRpcHandler serverRpcHandler; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void setup() { + request = Mockito.mock(VaadinRequest.class); + service = Mockito.mock(VaadinService.class); + session = Mockito.mock(VaadinSession.class); + ui = Mockito.mock(UI.class); + uiInternals = Mockito.mock(UIInternals.class); + + Mockito.when(request.getService()).thenReturn(service); + Mockito.when(session.getService()).thenReturn(service); + + Mockito.when(ui.getInternals()).thenReturn(uiInternals); + Mockito.when(ui.getSession()).thenReturn(session); + Mockito.when(ui.getCsrfToken()).thenReturn(csrfToken); + + DeploymentConfiguration deploymentConfiguration = Mockito + .mock(DeploymentConfiguration.class); + Mockito.when(service.getDeploymentConfiguration()) + .thenReturn(deploymentConfiguration); + + uiTree = new StateTree(uiInternals); + Mockito.when(uiInternals.getStateTree()).thenReturn(uiTree); + + serverRpcHandler = new ServerRpcHandler(); + } + + @Test + public void handleRpc_resynchronize_shouldResynchronizeClientAndMarksTreeDirty() + throws IOException, + ServerRpcHandler.InvalidUIDLSecurityKeyException { + // given + StringReader reader = new StringReader("{\"csrfToken\": \"" + csrfToken + + "\", \"rpc\":[], \"resynchronize\": true, \"clientId\":1}"); + uiTree.collectChanges(c -> { // clean tree + }); + thrown.expect(ServerRpcHandler.ResynchronizationRequiredException.class); + + // when + serverRpcHandler.handleRpc(ui, reader, request); + + // then + Assert.assertTrue(uiTree.hasDirtyNodes()); + } +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java new file mode 100644 index 00000000000..87d09a34b68 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2019 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.server.communication; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Properties; + +import com.vaadin.flow.server.DefaultDeploymentConfiguration; +import com.vaadin.flow.server.ServletHelper.RequestType; +import com.vaadin.flow.server.VaadinRequest; +import com.vaadin.flow.server.VaadinResponse; +import com.vaadin.flow.server.VaadinService; +import com.vaadin.flow.server.VaadinServletService; +import com.vaadin.flow.server.VaadinSession; +import com.vaadin.flow.shared.ApplicationConstants; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class UidlRequestHandlerTest { + + private VaadinRequest request; + private VaadinResponse response; + private OutputStream outputStream; + + private UidlRequestHandler handler; + + @Before + public void setup() throws IOException { + request = Mockito.mock(VaadinRequest.class); + response = Mockito.mock(VaadinResponse.class); + outputStream = Mockito.mock(OutputStream.class); + Mockito.when(response.getOutputStream()).thenReturn(outputStream); + + handler = new UidlRequestHandler(); + } + + @Test + public void writeSessionExpired() throws Exception { + + VaadinService service = new VaadinServletService(null, + new DefaultDeploymentConfiguration(getClass(), + new Properties())); + Mockito.when(request.getService()).thenReturn(service); + + Mockito.when(request + .getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER)) + .thenReturn(RequestType.UIDL.getIdentifier()); + + boolean result = handler.handleSessionExpired(request, response); + Assert.assertTrue("Result should be true", result); + + String responseContent = CommunicationUtil + .getStringWhenWriteBytesOffsetLength(outputStream); + + // response shouldn't contain async + Assert.assertEquals("Invalid response", + "for(;;);[{\"meta\":{\"sessionExpired\":true}}]", + responseContent); + } + + @Test + public void writeSessionExpired_whenUINotFound() throws IOException { + + VaadinService service = Mockito.mock(VaadinService.class); + VaadinSession session = Mockito.mock(VaadinSession.class); + Mockito.when(session.getService()).thenReturn(service); + + Mockito.when(service.findUI(request)).thenReturn(null); + + boolean result = handler.synchronizedHandleRequest(session, request, + response); + Assert.assertTrue("Result should be true", result); + + String responseContent = CommunicationUtil + .getStringWhenWriteString(outputStream); + + // response shouldn't contain async + Assert.assertEquals("Invalid response", + "for(;;);[{\"meta\":{\"sessionExpired\":true}}]", + responseContent); + } + +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlWriterTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlWriterTest.java index c40f294508b..e10218e892d 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlWriterTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlWriterTest.java @@ -352,6 +352,19 @@ public void parentViewDependenciesAreAddedFirst_npmMode() throws Exception { } } + @Test + public void resynchronizationRequested_responseFieldContainsResynchronize() + throws Exception { + UI ui = initializeUIForDependenciesTest(new TestUI()); + UidlWriter uidlWriter = new UidlWriter(); + + JsonObject response = uidlWriter.createUidl(ui, false, true); + assertTrue("Response contains resynchronize field", + response.hasKey(ApplicationConstants.RESYNCHRONIZE_ID)); + assertTrue("Response resynchronize field is set to true", + response.getBoolean(ApplicationConstants.RESYNCHRONIZE_ID)); + } + private void assertInlineDependencies(List inlineDependencies, String expectedPrefix) { assertThat("Should have an inline dependency", inlineDependencies, diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandlerTest.java new file mode 100644 index 00000000000..859f899dd4b --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/WebComponentBootstrapHandlerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2018 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.server.communication; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.hamcrest.CoreMatchers; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.flow.server.VaadinResponse; + +public class WebComponentBootstrapHandlerTest { + + @Test + public void writeBootstrapPage_skipMetaAndStyleHeaderElements() + throws IOException { + WebComponentBootstrapHandler handler = new WebComponentBootstrapHandler(); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + Element head = new Document("").normalise().head(); + Element meta = head.ownerDocument().createElement("meta"); + head.appendChild(meta); + meta.attr("http-equiv", "Content-Type"); + + Element style = head.ownerDocument().createElement("style"); + head.appendChild(style); + style.attr("type'", "text/css"); + style.text("body {height:100vh;width:100vw;margin:0;}"); + + Element script = head.ownerDocument().createElement("script"); + head.appendChild(script); + script.text("var i=1;"); + + VaadinResponse response = Mockito.mock(VaadinResponse.class); + Mockito.when(response.getOutputStream()).thenReturn(stream); + handler.writeBootstrapPage("", response, head, ""); + + String resultingScript = stream.toString(); + + Assert.assertThat(resultingScript, + CoreMatchers.containsString("var i=1;")); + Assert.assertThat(resultingScript, CoreMatchers.not(CoreMatchers + .containsString("body {height:100vh;width:100vw;margin:0;}"))); + Assert.assertThat(resultingScript, + CoreMatchers.not(CoreMatchers.containsString("http-equiv"))); + } +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/PublishedServerEventHandlerRpcHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/PublishedServerEventHandlerRpcHandlerTest.java index 3d1301577c0..4c149cc6a98 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/PublishedServerEventHandlerRpcHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/rpc/PublishedServerEventHandlerRpcHandlerTest.java @@ -15,6 +15,8 @@ */ package com.vaadin.flow.server.communication.rpc; +import java.util.List; + import net.jcip.annotations.NotThreadSafe; import org.jsoup.nodes.Element; import org.junit.After; @@ -29,6 +31,8 @@ import com.vaadin.flow.component.EventData; import com.vaadin.flow.component.Tag; import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.internal.PendingJavaScriptInvocation; +import com.vaadin.flow.component.internal.UIInternals.JavaScriptInvocation; import com.vaadin.flow.component.polymertemplate.EventHandler; import com.vaadin.flow.component.polymertemplate.PolymerTemplate; import com.vaadin.flow.component.polymertemplate.TemplateParser.TemplateData; @@ -37,6 +41,7 @@ import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.shared.JsonConstants; import com.vaadin.flow.templatemodel.TemplateModel; +import com.vaadin.tests.util.MockUI; import elemental.json.Json; import elemental.json.JsonArray; @@ -67,6 +72,15 @@ private void method() { isInvoked = true; } + @ClientCallable + private int compute(int input) { + if (input < 0) { + throw new ArithmeticException(); + } else { + return (int) Math.sqrt(input); + } + } + } @Tag(Tag.DIV) @@ -221,7 +235,7 @@ public void tearDown() { public void methodIsInvoked() { ComponentWithMethod component = new ComponentWithMethod(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method", Json.createArray()); + component.getClass(), "method", Json.createArray(), -1); Assert.assertTrue(component.isInvoked); } @@ -231,7 +245,7 @@ public void methodIsInvokedOnCompositeContent() { CompositeOfComponentWithMethod composite = new CompositeOfComponentWithMethod(); ComponentWithMethod component = composite.getContent(); PublishedServerEventHandlerRpcHandler.invokeMethod(composite, - composite.getClass(), "method", Json.createArray()); + composite.getClass(), "method", Json.createArray(), -1); Assert.assertTrue(component.isInvoked); } @@ -241,7 +255,7 @@ public void methodIsInvokectOnCompositeOfComposite() { CompositeOfComposite composite = new CompositeOfComposite(); ComponentWithMethod component = composite.getContent().getContent(); PublishedServerEventHandlerRpcHandler.invokeMethod(composite, - composite.getClass(), "method", Json.createArray()); + composite.getClass(), "method", Json.createArray(), -1); Assert.assertTrue(component.isInvoked); } @@ -254,7 +268,7 @@ public void methodWithDecoderParameters_convertableValues_methodIsInvoked() { DecoderParameters component = new DecoderParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method", params); + component.getClass(), "method", params, -1); Assert.assertTrue(component.isInvoked); } @@ -267,7 +281,7 @@ public void methodWithDecoderParameters_nonConvertableValues_methodIsInvoked() { DecoderParameters component = new DecoderParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method", params); + component.getClass(), "method", params, -1); } @Test(expected = IllegalArgumentException.class) @@ -276,21 +290,105 @@ public void methodWithoutArgs_argsProvided() { args.set(0, true); ComponentWithMethod component = new ComponentWithMethod(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method", args); + component.getClass(), "method", args, -1); + } + + @Test + public void promiseSuccess() { + int promiseId = 4; + + JsonArray args = Json.createArray(); + args.set(0, 36); + + ComponentWithMethod component = new ComponentWithMethod(); + MockUI ui = new MockUI(); + ui.add(component); + + // Get rid of attach invocations + ui.getInternals().getStateTree().runExecutionsBeforeClientResponse(); + ui.getInternals().dumpPendingJavaScriptInvocations(); + + PublishedServerEventHandlerRpcHandler.invokeMethod(component, + component.getClass(), "compute", args, promiseId); + + List pendingJavaScriptInvocations = ui + .dumpPendingJsInvocations(); + Assert.assertEquals(1, pendingJavaScriptInvocations.size()); + + JavaScriptInvocation invocation = pendingJavaScriptInvocations.get(0) + .getInvocation(); + Assert.assertTrue("Invocation does not look like a promise callback", + invocation.getExpression() + .contains(JsonConstants.RPC_PROMISE_CALLBACK_NAME)); + + List parameters = invocation.getParameters(); + Assert.assertEquals( + "Expected three paramters: promiseId, value, target", 3, + parameters.size()); + Assert.assertEquals( + "Promise id should match the value passed to invokeMethod", + Integer.valueOf(promiseId), parameters.get(0)); + Assert.assertEquals("Promise value should be sqrt(36) = 6", + Integer.valueOf(6), parameters.get(1)); + Assert.assertEquals("Target should be the component's element", + component.getElement(), parameters.get(2)); + } + + @Test + public void promiseFailure() { + int promiseId = 4; + + JsonArray args = Json.createArray(); + args.set(0, -36); + + ComponentWithMethod component = new ComponentWithMethod(); + MockUI ui = new MockUI(); + ui.add(component); + + // Get rid of attach invocations + ui.getInternals().getStateTree().runExecutionsBeforeClientResponse(); + ui.getInternals().dumpPendingJavaScriptInvocations(); + + try { + PublishedServerEventHandlerRpcHandler.invokeMethod(component, + component.getClass(), "compute", args, promiseId); + Assert.fail("Exception should be thrown"); + } catch (RuntimeException e) { + Assert.assertTrue(e.getCause() instanceof ArithmeticException); + } + + List pendingJavaScriptInvocations = ui + .dumpPendingJsInvocations(); + Assert.assertEquals(1, pendingJavaScriptInvocations.size()); + + JavaScriptInvocation invocation = pendingJavaScriptInvocations.get(0) + .getInvocation(); + Assert.assertTrue("Invocation does not look like a promise callback", + invocation.getExpression() + .contains(JsonConstants.RPC_PROMISE_CALLBACK_NAME)); + + List parameters = invocation.getParameters(); + Assert.assertEquals("Expected two paramters: promiseId, target", 2, + parameters.size()); + Assert.assertEquals( + "Promise id should match the value passed to invokeMethod", + Integer.valueOf(promiseId), parameters.get(0)); + Assert.assertEquals("Target should be the component's element", + component.getElement(), parameters.get(1)); } @Test(expected = IllegalStateException.class) public void twoEventHandlerMethodsWithTheSameName() { ComponentWithTwoEventHandlerMethodSameName component = new ComponentWithTwoEventHandlerMethodSameName(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "intMethod", Json.createArray()); + component.getClass(), "intMethod", Json.createArray(), -1); } @Test(expected = IllegalArgumentException.class) public void methodWithParametersInvokedWithoutParameters() { MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "intMethod", Json.createArray()); + component.getClass(), "intMethod", Json.createArray(), -1); } @Test @@ -299,7 +397,7 @@ public void methodWithParameterInvokedWithProperParameter() { array.set(0, 65); MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "intMethod", array); + component.getClass(), "intMethod", array, -1); Assert.assertEquals(65, component.intArg); } @@ -314,7 +412,7 @@ public void methodWithArrayParamIsInvoked() { array.set(1, secondArg); MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method1", array); + component.getClass(), "method1", array, -1); Assert.assertEquals("foo", component.strArg); Assert.assertArrayEquals(new boolean[] { true, false }, @@ -336,7 +434,7 @@ public void methodWithVarArgIsInvoked_varArgsAreNotArray() { MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method2", array); + component.getClass(), "method2", array, -1); Assert.assertArrayEquals( new Double[] { firstArg.getNumber(0), firstArg.getNumber(1) }, @@ -370,7 +468,7 @@ public void methodWithDoubleArrayIsInvoked() { MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method3", array); + component.getClass(), "method3", array, -1); Assert.assertArrayEquals(new int[] { (int) first.getNumber(0), (int) first.getNumber(1) }, component.doubleArray[0]); @@ -389,7 +487,7 @@ public void methodWithJsonValueIsInvoked() { MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method4", array); + component.getClass(), "method4", array, -1); Assert.assertEquals(component.jsonValue, json); } @@ -412,7 +510,7 @@ public void methodWithVarArgIsInvoked_varArgsIsArray() { MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method2", array); + component.getClass(), "method2", array, -1); Assert.assertArrayEquals( new Double[] { firstArg.getNumber(0), firstArg.getNumber(1) }, @@ -429,7 +527,7 @@ public void methodWithVarArg_acceptNoValues() { MethodWithVarArgParameter component = new MethodWithVarArgParameter(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "varArgMethod", array); + component.getClass(), "varArgMethod", array, -1); Assert.assertEquals(0, component.varArg.length); } @@ -446,7 +544,7 @@ public void methodWithSeveralArgsAndVarArg_acceptNoValues() { MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method2", array); + component.getClass(), "method2", array, -1); Assert.assertArrayEquals( new Double[] { firstArg.getNumber(0), firstArg.getNumber(1) }, @@ -464,7 +562,7 @@ public void methodWithVarArg_acceptOneValue() { MethodWithVarArgParameter component = new MethodWithVarArgParameter(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "varArgMethod", array); + component.getClass(), "varArgMethod", array, -1); Assert.assertEquals(1, component.varArg.length); Assert.assertEquals("foo", component.varArg[0]); @@ -480,7 +578,7 @@ public void methodWithVarArg_arrayIsCorrectlyHandled() { MethodWithVarArgParameter component = new MethodWithVarArgParameter(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "varArgMethod", array); + component.getClass(), "varArgMethod", array, -1); Assert.assertArrayEquals(new String[] { value.getString(0) }, component.varArg); @@ -492,21 +590,21 @@ public void nullValueIsNotAcceptedForPrimitive() { array.set(0, Json.createNull()); MethodWithParameters component = new MethodWithParameters(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method", array); + component.getClass(), "method", array, -1); } @Test(expected = IllegalStateException.class) public void noEventHandlerMethodException() { ComponentWithNoEventHandlerMethod component = new ComponentWithNoEventHandlerMethod(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "operation", Json.createArray()); + component.getClass(), "operation", Json.createArray(), -1); } @Test(expected = IllegalStateException.class) public void noMethodException() { ComponentWithNoEventHandlerMethod component = new ComponentWithNoEventHandlerMethod(); PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "operation1", Json.createArray()); + component.getClass(), "operation1", Json.createArray(), -1); } @Test @@ -514,7 +612,7 @@ public void methodThrowsException_exceptionHasCorrectCause() { ComponentWithMethodThrowingException component = new ComponentWithMethodThrowingException(); try { PublishedServerEventHandlerRpcHandler.invokeMethod(component, - component.getClass(), "method", Json.createArray()); + component.getClass(), "method", Json.createArray(), -1); } catch (RuntimeException e) { Assert.assertTrue(e.getCause() instanceof NullPointerException); } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractNodeUpdatePackagesTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractNodeUpdatePackagesTest.java index 48889ba780b..7de2a1c28f6 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractNodeUpdatePackagesTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractNodeUpdatePackagesTest.java @@ -77,7 +77,7 @@ public void setup() throws Exception { NodeUpdateTestUtil.createStubNode(true, true, baseDir.getAbsolutePath()); - packageCreator = new TaskCreatePackageJson(baseDir, generatedDir); + packageCreator = new TaskCreatePackageJson(baseDir, generatedDir, null); ClassFinder classFinder = getClassFinder(); packageUpdater = new TaskUpdatePackages(classFinder, diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractUpdateImportsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractUpdateImportsTest.java index d66fbbe84e5..a6c68721337 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractUpdateImportsTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/AbstractUpdateImportsTest.java @@ -18,8 +18,9 @@ package com.vaadin.flow.server.frontend; import java.io.File; -import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -27,6 +28,8 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.io.FileUtils; import org.hamcrest.CoreMatchers; @@ -43,6 +46,10 @@ import org.slf4j.Logger; import org.slf4j.impl.SimpleLogger; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.dependency.JavaScript; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.router.Route; import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.frontend.scanner.ClassFinder; import com.vaadin.flow.server.frontend.scanner.CssData; @@ -55,6 +62,7 @@ import static com.vaadin.flow.server.frontend.FrontendUtils.FLOW_NPM_PACKAGE_NAME; import static com.vaadin.flow.server.frontend.FrontendUtils.NODE_MODULES; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -66,6 +74,7 @@ public abstract class AbstractUpdateImportsTest extends NodeUpdateTestUtil { @Rule public ExpectedException exception = ExpectedException.none(); + private File tmpRoot; private File generatedPath; private File frontendDirectory; private File nodeModulesPath; @@ -149,7 +158,7 @@ protected Logger getLogger() { @Before public void setup() throws Exception { - File tmpRoot = temporaryFolder.getRoot(); + tmpRoot = temporaryFolder.getRoot(); // Use a file for logs so as tests can assert the warnings shown to the // user. @@ -377,7 +386,7 @@ public void cssFileNotFound_throws() { } @Test - public void generate_containsLumoThemeFiles() throws Exception { + public void generate_containsLumoThemeFiles() { updater.run(); assertContainsImports(true, "@vaadin/vaadin-lumo-styles/color.js", @@ -390,8 +399,7 @@ public void generate_containsLumoThemeFiles() throws Exception { // flow #6408 @Test - public void jsModuleOnRouterLayout_shouldBe_addedAfterLumoStyles() - throws Exception { + public void jsModuleOnRouterLayout_shouldBe_addedAfterLumoStyles() { updater.run(); assertContainsImports(true, "Frontend/common-js-file.js"); @@ -404,15 +412,70 @@ public void jsModuleOnRouterLayout_shouldBe_addedAfterLumoStyles() } @Test - public void jsModulesOrderIsPreservedAnsAfterJsModules() throws Exception { + public void jsModulesOrderIsPreservedAnsAfterJsModules() { updater.run(); assertImportOrder("jsmodule/g.js", "javascript/a.js", "javascript/b.js", "javascript/c.js"); } - private void assertContainsImports(boolean contains, String... imports) - throws IOException { + @Route(value = "") + private static class MainView extends Component { + NodeTestComponents.TranslatedImports translatedImports; + NodeTestComponents.LocalP3Template localP3Template; + NodeTestComponents.JavaScriptOrder javaScriptOrder; + } + + @Test + public void assertFullSortOrder() throws MalformedURLException { + Class[] testClasses = { MainView.class, NodeTestComponents.TranslatedImports.class, + NodeTestComponents.LocalP3Template.class, + NodeTestComponents.JavaScriptOrder.class }; + ClassFinder classFinder = getClassFinder(testClasses); + + updater = new UpdateImports(classFinder, getScanner(classFinder), + tmpRoot); + updater.run(); + + // Imports are collected as + // - theme and css + // - JsModules (external e.g. in node_modules/) + // - JavaScript + // - Generated webcompoents + // - JsModules (internal e.g. in frontend/) + List expectedImports = new ArrayList<>(); + expectedImports.addAll(updater.getThemeLines()); + + getAnntotationsAsStream(JsModule.class, testClasses).map(JsModule::value).map(this::updateToImport).sorted().forEach(expectedImports::add); + getAnntotationsAsStream(JavaScript.class, testClasses).map(JavaScript::value).map(this::updateToImport).sorted().forEach(expectedImports::add); + + List internals = expectedImports.stream().filter(importValue -> importValue.contains(FrontendUtils.WEBPACK_PREFIX_ALIAS)).sorted().collect( + Collectors.toList()); + updater.getGeneratedModules().stream().map(this::updateToImport).forEach(expectedImports::add); + // Remove internals from the full list + expectedImports.removeAll(internals); + // Add internals to end of list + expectedImports.addAll(internals); + + Assert.assertEquals(expectedImports, updater.resultingLines); + } + + private Stream getAnntotationsAsStream(Class annotation, Class... classes) { + Stream stream = Stream.empty(); + for(Class clazz : classes) { + stream = Stream.concat(stream, Stream.of(clazz.getAnnotationsByType(annotation))); + } + return stream; + } + + private String updateToImport(String value) { + if(value.startsWith("./")) { + value = value.replace("./", FrontendUtils.WEBPACK_PREFIX_ALIAS); + } + return String.format("import '%s';", value); + } + + private void assertContainsImports(boolean contains, String... imports) { for (String line : imports) { boolean result = updater.resultingLines .contains("import '" + addWebpackPrefix(line) + "';"); @@ -426,7 +489,7 @@ private void assertContainsImports(boolean contains, String... imports) } } - private void assertImportOrder(String... imports) throws IOException { + private void assertImportOrder(String... imports) { int curIndex = -1; for (String line : imports) { String prefixed = addWebpackPrefix(line); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java index 03213903aa0..6fa1f6931fa 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/FrontendUtilsTest.java @@ -113,12 +113,15 @@ public void should_useSystemNode() { containsString("node")); assertThat(FrontendUtils.getNodeExecutable(baseDir), not(containsString(DEFAULT_NODE))); - assertThat(FrontendUtils.getNpmExecutable(baseDir) - .get(0), containsString("npm")); assertThat(FrontendUtils.getNodeExecutable(baseDir), not(containsString(NPM_CLI_STRING))); - assertEquals(1, FrontendUtils + + assertEquals(2, FrontendUtils .getNpmExecutable(baseDir).size()); + assertThat(FrontendUtils.getNpmExecutable(baseDir) + .get(0), containsString("npm")); + assertThat(FrontendUtils.getNpmExecutable(baseDir) + .get(1), containsString("--no-update-notifier")); } @Test diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdateTestUtil.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdateTestUtil.java index aff02d044c9..e381b0ab691 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdateTestUtil.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdateTestUtil.java @@ -45,6 +45,11 @@ static ClassFinder getClassFinder() throws MalformedURLException { NodeTestComponents.class.getDeclaredClasses()); } + static ClassFinder getClassFinder(Class... classes) throws MalformedURLException { + return new DefaultClassFinder(new URLClassLoader(getClassPath()), + classes); + } + static URL[] getClassPath() throws MalformedURLException { // Add folder with test classes List classPaths = new ArrayList<>(); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdaterTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdaterTest.java index d50bc484360..f2c95d9866f 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdaterTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeUpdaterTest.java @@ -18,7 +18,6 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,6 +32,9 @@ import com.vaadin.flow.server.frontend.scanner.ClassFinder; import com.vaadin.flow.server.frontend.scanner.FrontendDependencies; +import elemental.json.Json; +import elemental.json.JsonObject; + import static com.vaadin.flow.server.Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT; import static com.vaadin.flow.server.Constants.RESOURCES_FRONTEND_DEFAULT; @@ -92,7 +94,8 @@ public void resolveResource_doesNotHaveModernResourcesFolder() { } @Test - public void getGeneratedModules_should_excludeByFileName() throws IOException { + public void getGeneratedModules_should_excludeByFileName() + throws IOException { File generated = temporaryFolder.newFolder(); File fileA = new File(generated, "a.js"); File fileB = new File(generated, "b.js"); @@ -100,15 +103,39 @@ public void getGeneratedModules_should_excludeByFileName() throws IOException { fileA.createNewFile(); fileB.createNewFile(); fileC.createNewFile(); - - Set modules = NodeUpdater.getGeneratedModules(generated, Stream - .of("a.js", "/b.js").collect(Collectors.toSet())); + + Set modules = NodeUpdater.getGeneratedModules(generated, + Stream.of("a.js", "/b.js").collect(Collectors.toSet())); Assert.assertEquals(1, modules.size()); // GENERATED/ is an added prefix for files from this method Assert.assertTrue(modules.contains("GENERATED/c.js")); } + @Test + public void updateMainDefaultDependencies_polymerVersionIsNull_useDefault() { + JsonObject object = Json.createObject(); + nodeUpdater.updateMainDefaultDependencies(object, null); + + String version = getPolymerVersion(object); + Assert.assertEquals("3.2.0", version); + } + + @Test + public void updateMainDefaultDependencies_polymerVersionIsProvided_useProvided() { + JsonObject object = Json.createObject(); + nodeUpdater.updateMainDefaultDependencies(object, "foo"); + + String version = getPolymerVersion(object); + Assert.assertEquals("foo", version); + } + + private String getPolymerVersion(JsonObject object) { + JsonObject deps = object.get("dependencies"); + String version = deps.getString("@polymer/polymer"); + return version; + } + private void resolveResource_happyPath(String resourceFolder) { Mockito.when(finder.getResource(resourceFolder + "/foo")) .thenReturn(url); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ClassFinderTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ClassFinderTest.java index 02cd5b9d4ea..38c53afe3cd 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ClassFinderTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/ClassFinderTest.java @@ -1,8 +1,11 @@ package com.vaadin.flow.server.frontend.scanner; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.junit.Assert; import org.junit.Rule; @@ -17,30 +20,58 @@ public class ClassFinderTest { @Rule public ExpectedException exception = ExpectedException.none(); + private static class TestList extends ArrayList { + + } + @Test public void should_Fail_when_DifferentClasLoader() throws Exception { ClassLoader loader = new ClassLoader() { @Override - public Class loadClass(String name) throws ClassNotFoundException { + public Class loadClass(String name) + throws ClassNotFoundException { throw new ClassNotFoundException(); } }; exception.expect(ClassNotFoundException.class); - DefaultClassFinder finder = new DefaultClassFinder(loader, Component1.class); + DefaultClassFinder finder = new DefaultClassFinder(loader, + Component1.class); finder.loadClass(Component1.class.getName()); } @Test public void should_LoadClasses() throws Exception { - DefaultClassFinder finder = new DefaultClassFinder(new HashSet<>(Arrays.asList(Component1.class))); + DefaultClassFinder finder = new DefaultClassFinder( + new HashSet<>(Arrays.asList(Component1.class))); Assert.assertNotNull(finder.loadClass(Component1.class.getName())); } @Test public void should_LoadClasses_when_NoClassListProvided() throws Exception { - DefaultClassFinder finder = new DefaultClassFinder(Collections.emptySet()); + DefaultClassFinder finder = new DefaultClassFinder( + Collections.emptySet()); Assert.assertNotNull(finder.loadClass(Component1.class.getName())); } + @Test + public void getSubTypesOf_returnsPlainSubtypes() { + DefaultClassFinder finder = new DefaultClassFinder(new HashSet<>( + Arrays.asList(Double.class, Integer.class, String.class))); + Set> subTypes = finder + .getSubTypesOf(Number.class); + Assert.assertEquals(2, subTypes.size()); + Assert.assertTrue(subTypes.contains(Double.class)); + Assert.assertTrue(subTypes.contains(Integer.class)); + } + + @Test + public void getSubTypesOf_returnsGenericSubtypes() { + DefaultClassFinder finder = new DefaultClassFinder(new HashSet<>( + Arrays.asList(ArrayList.class, TestList.class, String.class))); + Set> subTypes = finder.getSubTypesOf(List.class); + Assert.assertEquals(2, subTypes.size()); + Assert.assertTrue(subTypes.contains(ArrayList.class)); + Assert.assertTrue(subTypes.contains(TestList.class)); + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FrontendDependenciesTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FrontendDependenciesTest.java index 12402226f68..3bcfa7fe3ac 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FrontendDependenciesTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/FrontendDependenciesTest.java @@ -32,9 +32,11 @@ import com.vaadin.flow.component.Component; import com.vaadin.flow.component.WebComponentExporter; import com.vaadin.flow.component.webcomponent.WebComponent; +import com.vaadin.flow.router.HasErrorParameter; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.UIInitListener; import com.vaadin.flow.server.VaadinServiceInitListener; +import com.vaadin.flow.server.frontend.scanner.samples.ErrorComponent; import com.vaadin.flow.server.frontend.scanner.samples.JsOrderComponent; import com.vaadin.flow.server.frontend.scanner.samples.MyServiceListener; import com.vaadin.flow.server.frontend.scanner.samples.MyUIInitListener; @@ -61,10 +63,13 @@ public void setUp() throws ClassNotFoundException { .loadClass(VaadinServiceInitListener.class.getName())) .thenReturn((Class) VaadinServiceInitListener.class); - Mockito.when(classFinder - .loadClass(WebComponentExporter.class.getName())) + Mockito.when( + classFinder.loadClass(WebComponentExporter.class.getName())) .thenReturn((Class) WebComponentExporter.class); + Mockito.when(classFinder.loadClass(HasErrorParameter.class.getName())) + .thenReturn((Class) HasErrorParameter.class); + Mockito.when(classFinder.loadClass(FrontendDependencies.LUMO)) .thenReturn((Class) FakeLumo.class); @@ -90,6 +95,22 @@ public void routedComponent_endpointsAreCollected() Assert.assertEquals("bar.js", scripts.iterator().next()); } + @Test + public void hasErrorParameterComponent_endpointIsCollected() + throws ClassNotFoundException { + Mockito.when(classFinder.getSubTypesOf(HasErrorParameter.class)) + .thenReturn(Collections.singleton(ErrorComponent.class)); + FrontendDependencies dependencies = new FrontendDependencies( + classFinder, false); + List modules = dependencies.getModules(); + Assert.assertEquals(1, modules.size()); + Assert.assertEquals("./src/bar.js", modules.get(0)); + + Set scripts = dependencies.getScripts(); + Assert.assertEquals(1, scripts.size()); + Assert.assertEquals("./src/baz.js", scripts.iterator().next()); + } + @Test public void componentInsideUiInitListener_endpointsAreCollected() throws ClassNotFoundException { @@ -139,25 +160,24 @@ public void jsScriptOrderIsPreserved() throws ClassNotFoundException { // flow #6408 @Test public void annotationsInRouterLayoutWontBeFlaggedAsBelongingToTheme() { - Mockito.when(classFinder.getAnnotatedClasses(Route.class)) - .thenReturn(Collections.singleton(RouteComponentWithLayout.class)); + Mockito.when(classFinder.getAnnotatedClasses(Route.class)).thenReturn( + Collections.singleton(RouteComponentWithLayout.class)); FrontendDependencies dependencies = new FrontendDependencies( classFinder, false); List expectedOrder = Arrays.asList("theme-foo.js", "foo.js"); Assert.assertThat("Theme's annotations should come first", - dependencies.getModules(), is(expectedOrder) - ); + dependencies.getModules(), is(expectedOrder)); } // flow #6524 @Test public void extractsAndScansClassesFromMethodReferences() { - Mockito.when(classFinder.getAnnotatedClasses(Route.class)) - .thenReturn(Collections.singleton(RouteComponentWithMethodReference.class)); + Mockito.when(classFinder.getAnnotatedClasses(Route.class)).thenReturn( + Collections.singleton(RouteComponentWithMethodReference.class)); - FrontendDependencies dependencies = - new FrontendDependencies(classFinder, false); + FrontendDependencies dependencies = new FrontendDependencies( + classFinder, false); List modules = dependencies.getModules(); Assert.assertEquals(3, modules.size()); @@ -180,7 +200,8 @@ public void defaultThemeIsLoadedForExporters() throws Exception { Assert.assertNotNull(dependencies.getThemeDefinition()); } - public static class MyComponent extends Component {} + public static class MyComponent extends Component { + } public static class MyExporter extends WebComponentExporter { public MyExporter() { @@ -188,11 +209,14 @@ public MyExporter() { } @Override - protected void configureInstance(WebComponent webComponent, MyComponent component) { } + protected void configureInstance(WebComponent webComponent, + MyComponent component) { + } } public static class FakeLumo implements AbstractTheme { - public FakeLumo() {} + public FakeLumo() { + } @Override public String getBaseUrl() { diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/ErrorComponent.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/ErrorComponent.java new file mode 100644 index 00000000000..5d4a10615e3 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/scanner/samples/ErrorComponent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2018 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.server.frontend.scanner.samples; + +import com.vaadin.flow.component.dependency.JavaScript; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.ErrorParameter; +import com.vaadin.flow.router.HasErrorParameter; + +@JsModule("./src/bar.js") +@JavaScript("./src/baz.js") +public class ErrorComponent implements HasErrorParameter { + + @Override + public int setErrorParameter(BeforeEnterEvent event, + ErrorParameter parameter) { + return 0; + } + +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeClassFinderTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeClassFinderTest.java index 175ec10dbbb..d52f8e54876 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeClassFinderTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeClassFinderTest.java @@ -27,11 +27,12 @@ import org.junit.Assert; import org.junit.Test; -import com.vaadin.flow.component.internal.ExportsWebComponent; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.dependency.JavaScript; import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.dependency.NpmPackage; +import com.vaadin.flow.component.internal.ExportsWebComponent; +import com.vaadin.flow.router.HasErrorParameter; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.SessionInitListener; import com.vaadin.flow.server.UIInitListener; @@ -66,12 +67,14 @@ public void applicableClasses_knownClasses() { CssImport.class, CssImport.Container.class, Theme.class, - NoTheme.class); + NoTheme.class, + HasErrorParameter.class); for (Class clz : classes) { assertTrue("should be a known class " + clz.getName(), knownClasses.contains(clz)); } Assert.assertEquals(knownClasses.size(), classes.size()); + Assert.assertEquals(15, classes.size()); } @Test diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java index 5034db49487..e935fa06a13 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java @@ -5,6 +5,8 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; @@ -12,6 +14,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import net.jcip.annotations.NotThreadSafe; import org.junit.Assert; @@ -23,15 +26,18 @@ import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.DevModeHandler; import com.vaadin.flow.server.connect.generator.VaadinConnectClientGenerator; import com.vaadin.flow.server.frontend.FallbackChunk; +import elemental.json.Json; +import elemental.json.JsonObject; + import static com.vaadin.flow.server.Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT; import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; import static com.vaadin.flow.server.Constants.RESOURCES_FRONTEND_DEFAULT; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_COMPATIBILITY_MODE; -import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_PRODUCTION_MODE; import static com.vaadin.flow.server.Constants.SERVLET_PARAMETER_REUSE_DEV_SERVER; import static com.vaadin.flow.server.DevModeHandler.getDevModeHandler; @@ -181,7 +187,7 @@ public void listener_should_stopDevModeHandler_onDestroy() @Test public void shouldUseByteCodeScannerIfPropertySet() throws Exception { - System.setProperty(SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE, + initParams.put(Constants.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE, "true"); DevModeInitializer devModeInitializer = new DevModeInitializer(); final Set> classes = new HashSet<>(); @@ -195,9 +201,25 @@ public void shouldUseByteCodeScannerIfPropertySet() throws Exception { Mockito.eq(FallbackChunk.class.getName()), arg.capture()); FallbackChunk fallbackChunk = arg.getValue(); Assert.assertFalse(fallbackChunk.getModules().contains("foo")); + Assert.assertTrue(fallbackChunk.getModules().contains("bar")); } + @Test + public void initDevModeHandler_usePolymerVersion() throws Exception { + DevModeInitializer devModeInitializer = new DevModeInitializer(); + initParams.put(Constants.SERVLET_PARAMETER_DEVMODE_POLYMER_VERSION, + "3.3.0"); + devModeInitializer.onStartup(Collections.emptySet(), servletContext); + + String packageJson = Files + .readAllLines(mainPackageFile.toPath(), StandardCharsets.UTF_8) + .stream().collect(Collectors.joining()); + JsonObject json = Json.parse(packageJson); + JsonObject deps = json.get("dependencies"); + Assert.assertEquals("3.3.0", deps.getString("@polymer/polymer")); + } + @Test public void shouldUseFullPathScannerByDefault() throws Exception { DevModeInitializer devModeInitializer = new DevModeInitializer(); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java index c59fe0652c5..d8fc9f47ddb 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java @@ -58,7 +58,7 @@ public class DevModeInitializerTestBase { public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @Before - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) public void setup() throws Exception { assertNull(getDevModeHandler()); @@ -69,7 +69,8 @@ public void setup() throws Exception { createStubWebpackServer("Compiled", 500, baseDir); servletContext = Mockito.mock(ServletContext.class); - ServletRegistration registration = Mockito.mock(ServletRegistration.class); + ServletRegistration registration = Mockito + .mock(ServletRegistration.class); initParams = new HashMap<>(); initParams.put(FrontendUtils.PROJECT_BASEDIR, baseDir); @@ -111,7 +112,6 @@ public void teardown() throws Exception, SecurityException { System.clearProperty("vaadin." + SERVLET_PARAMETER_COMPATIBILITY_MODE); System.clearProperty("vaadin." + SERVLET_PARAMETER_PRODUCTION_MODE); System.clearProperty("vaadin." + SERVLET_PARAMETER_REUSE_DEV_SERVER); - System.clearProperty("vaadin." + SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE); System.clearProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); webpackFile.delete(); @@ -131,7 +131,6 @@ public void runDestroy() throws Exception { devModeInitializer.contextDestroyed(null); } - static List getClasspathURLs() { return Arrays.stream( System.getProperty("java.class.path").split(File.pathSeparator)) diff --git a/flow-server/src/test/java/com/vaadin/tests/util/MockUI.java b/flow-server/src/test/java/com/vaadin/tests/util/MockUI.java index 2ef02e3fbd5..55868a2ed15 100644 --- a/flow-server/src/test/java/com/vaadin/tests/util/MockUI.java +++ b/flow-server/src/test/java/com/vaadin/tests/util/MockUI.java @@ -15,9 +15,12 @@ */ package com.vaadin.tests.util; +import java.util.List; + import org.mockito.Mockito; import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.internal.PendingJavaScriptInvocation; import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinService; @@ -39,6 +42,13 @@ protected void init(VaadinRequest request) { // Do nothing } + public List dumpPendingJsInvocations() { + // Ensure element invocations are also flushed + getInternals().getStateTree().runExecutionsBeforeClientResponse(); + + return getInternals().dumpPendingJavaScriptInvocations(); + } + private static VaadinSession findOrCreateSession() { VaadinSession session = VaadinSession.getCurrent(); if (session == null) { diff --git a/flow-server/src/test/resources/META-INF/VAADIN/config/babel_stats.json b/flow-server/src/test/resources/META-INF/VAADIN/config/babel_stats.json index d4aa333f93c..e7d27c0ce27 100644 --- a/flow-server/src/test/resources/META-INF/VAADIN/config/babel_stats.json +++ b/flow-server/src/test/resources/META-INF/VAADIN/config/babel_stats.json @@ -1,19976 +1,13 @@ { - "errors": [], - "warnings": [], - "version": "4.29.6", "hash": "e251827e9f087691baf2", - "publicPath": "", - "outputPath": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm/src/main/webapp", - "assetsByChunkName": { - "index": "build/index.js", - "index.es5": "build/index.es5.js" - }, - "assets": [ - { - "name": "build/index.es5.js", - "size": 198541, - "chunks": [ - 1 - ], - "chunkNames": [ - "index.es5" - ], - "emitted": true - }, - { - "name": "build/index.js", - "size": 191156, - "chunks": [ - 0 - ], - "chunkNames": [ - "index" - ], - "emitted": true - }, - { - "name": "build/webcomponentsjs/LICENSE.md", - "size": 1559, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/README.md", - "size": 10783, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-ce.js", - "size": 16910, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-ce.js.map", - "size": 104300, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js", - "size": 109183, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-sd-ce-pf.js.map", - "size": 649507, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-sd-ce.js", - "size": 81281, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-sd-ce.js.map", - "size": 521274, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-sd.js", - "size": 65318, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/bundles/webcomponents-sd.js.map", - "size": 416690, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/custom-elements-es5-adapter.js", - "size": 942, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/entrypoints/custom-elements-es5-adapter-index.js", - "size": 656, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/entrypoints/webcomponents-bundle-index.js", - "size": 1704, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/entrypoints/webcomponents-ce-index.js", - "size": 672, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/entrypoints/webcomponents-sd-ce-index.js", - "size": 851, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/entrypoints/webcomponents-sd-ce-pf-index.js", - "size": 1227, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/entrypoints/webcomponents-sd-index.js", - "size": 761, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/package.json", - "size": 3062, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/webcomponents-bundle.js", - "size": 109711, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/webcomponents-bundle.js.map", - "size": 651839, - "chunks": [], - "chunkNames": [], - "emitted": true - }, - { - "name": "build/webcomponentsjs/webcomponents-loader.js", - "size": 6272, - "chunks": [], - "chunkNames": [], - "emitted": true - } - ], - "filteredAssets": 0, - "entrypoints": { - "index": { - "chunks": [ - 0 - ], - "assets": [ - "build/index.js" - ], - "children": {}, - "childAssets": {} - }, - "index.es5": { - "chunks": [ - 1 - ], - "assets": [ - "build/index.es5.js" - ], - "children": {}, - "childAssets": {} - } - }, - "namedChunkGroups": { - "index": { - "chunks": [ - 0 - ], - "assets": [ - "build/index.js" - ], - "children": {}, - "childAssets": {} - }, - "index.es5": { - "chunks": [ - 1 - ], - "assets": [ - "build/index.es5.js" - ], - "children": {}, - "childAssets": {} - } - }, - "chunks": [ - { - "id": 0, - "rendered": true, - "initial": true, - "entry": true, - "size": 566777, - "names": [ - "index" - ], - "files": [ - "build/index.js" - ], - "hash": "93b6f99575234a9782cc", - "siblings": [], - "parents": [], - "children": [], - "childrenByOrder": {}, - "modules": [ - { - "id": 8, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack\\hot sync nonrecursive /^\\.\\/log$/", - "name": "(webpack)/hot sync nonrecursive ^\\.\\/log$", - "index": 21, - "index2": 20, - "size": 170, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 0, - 1 - ], - "issuer": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es5", - "issuerId": 40, - "issuerName": "(webpack)-dev-server/client?http://localhost:61902", - "issuerPath": [ - { - "id": 39, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es5", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es5 ./main.js?babel-target=es5" - }, - { - "id": 40, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es5", - "name": "(webpack)-dev-server/client?http://localhost:61902" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 20, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "module": "(webpack)-dev-server/client?http://localhost:61902", - "moduleName": "(webpack)-dev-server/client?http://localhost:61902", - "type": "require.context", - "userRequest": "webpack/hot", - "loc": "102:17-67" - }, - { - "moduleId": 40, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es5", - "module": "(webpack)-dev-server/client?http://localhost:61902", - "moduleName": "(webpack)-dev-server/client?http://localhost:61902", - "type": "require.context", - "userRequest": "webpack/hot", - "loc": "102:17-67" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 2 - }, - { - "id": 9, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack\\hot\\log.js&babel-target=es6", - "name": "(webpack)/hot/log.js", - "index": 22, - "index2": 19, - "size": 1134, - "cacheable": true, - "built": true, - "optional": true, - "prefetched": false, - "chunks": [ - 0, - 1 - ], - "issuer": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack\\hot sync nonrecursive /^\\.\\/log$/", - "issuerId": 8, - "issuerName": "(webpack)/hot sync nonrecursive ^\\.\\/log$", - "issuerPath": [ - { - "id": 39, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es5", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es5 ./main.js?babel-target=es5" - }, - { - "id": 40, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es5", - "name": "(webpack)-dev-server/client?http://localhost:61902" - }, - { - "id": 8, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack\\hot sync nonrecursive /^\\.\\/log$/", - "name": "(webpack)/hot sync nonrecursive ^\\.\\/log$" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 8, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack\\hot sync nonrecursive /^\\.\\/log$/", - "module": "(webpack)/hot sync nonrecursive ^\\.\\/log$", - "moduleName": "(webpack)/hot sync nonrecursive ^\\.\\/log$", - "type": "context element", - "userRequest": "./log", - "loc": "./log" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 3, - "source": "var logLevel = \"info\";\n\nfunction dummy() {}\n\nfunction shouldLog(level) {\n\tvar shouldLog =\n\t\t(logLevel === \"info\" && level === \"info\") ||\n\t\t([\"info\", \"warning\"].indexOf(logLevel) >= 0 && level === \"warning\") ||\n\t\t([\"info\", \"warning\", \"error\"].indexOf(logLevel) >= 0 && level === \"error\");\n\treturn shouldLog;\n}\n\nfunction logGroup(logFn) {\n\treturn function(level, msg) {\n\t\tif (shouldLog(level)) {\n\t\t\tlogFn(msg);\n\t\t}\n\t};\n}\n\nmodule.exports = function(level, msg) {\n\tif (shouldLog(level)) {\n\t\tif (level === \"info\") {\n\t\t\tconsole.log(msg);\n\t\t} else if (level === \"warning\") {\n\t\t\tconsole.warn(msg);\n\t\t} else if (level === \"error\") {\n\t\t\tconsole.error(msg);\n\t\t}\n\t}\n};\n\n/* eslint-disable node/no-unsupported-features/node-builtins */\nvar group = console.group || dummy;\nvar groupCollapsed = console.groupCollapsed || dummy;\nvar groupEnd = console.groupEnd || dummy;\n/* eslint-enable node/no-unsupported-features/node-builtins */\n\nmodule.exports.group = logGroup(group);\n\nmodule.exports.groupCollapsed = logGroup(groupCollapsed);\n\nmodule.exports.groupEnd = logGroup(groupEnd);\n\nmodule.exports.setLogLevel = function(level) {\n\tlogLevel = level;\n};\n" - }, - { - "id": 11, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\querystring-es3\\index.js?babel-target=es6", - "name": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/querystring-es3?babel-target=es6", - "index": 2, - "index2": 2, - "size": 127, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "issuerId": 20, - "issuerName": "(webpack)-dev-server/client?http://localhost:61902", - "issuerPath": [ - { - "id": 19, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6" - }, - { - "id": 20, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "name": "(webpack)-dev-server/client?http://localhost:61902" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 20, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "module": "(webpack)-dev-server/client?http://localhost:61902", - "moduleName": "(webpack)-dev-server/client?http://localhost:61902", - "type": "cjs require", - "userRequest": "querystring", - "loc": "6:18-40" - }, - { - "moduleId": 23, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\url\\url.js?babel-target=es6", - "module": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/url/url.js?babel-target=es6", - "moduleName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/url/url.js?babel-target=es6", - "type": "cjs require", - "userRequest": "querystring", - "loc": "100:18-40" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 2, - "source": "'use strict';\n\nexports.decode = exports.parse = require('./decode');\nexports.encode = exports.stringify = require('./encode');\n" - }, - { - "id": 12, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack\\buildin\\global.js&babel-target=es5", - "name": "(webpack)/buildin/global.js", - "index": 8, - "index2": 4, - "size": 472, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\node-libs-browser\\node_modules\\punycode\\punycode.js?babel-target=es6", - "issuerId": 24, - "issuerName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/node-libs-browser/node_modules/punycode/punycode.js?babel-target=es6", - "issuerPath": [ - { - "id": 19, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6" - }, - { - "id": 20, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "name": "(webpack)-dev-server/client?http://localhost:61902" - }, - { - "id": 23, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\url\\url.js?babel-target=es6", - "name": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/url/url.js?babel-target=es6" - }, - { - "id": 24, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\node-libs-browser\\node_modules\\punycode\\punycode.js?babel-target=es6", - "name": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/node-libs-browser/node_modules/punycode/punycode.js?babel-target=es6" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 24, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\node-libs-browser\\node_modules\\punycode\\punycode.js?babel-target=es6", - "module": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/node-libs-browser/node_modules/punycode/punycode.js?babel-target=es6", - "moduleName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/node-libs-browser/node_modules/punycode/punycode.js?babel-target=es6", - "type": "cjs require", - "userRequest": "global", - "loc": "1:0-47" - }, - { - "moduleId": 31, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\sockjs-client\\dist\\sockjs.js?babel-target=es6", - "module": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/sockjs-client/dist/sockjs.js?babel-target=es6", - "moduleName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/sockjs-client/dist/sockjs.js?babel-target=es6", - "type": "cjs require", - "userRequest": "global", - "loc": "1:0-44" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 4, - "source": "var g;\n\n// This works in non-strict mode\ng = (function() {\n\treturn this;\n})();\n\ntry {\n\t// This works if eval is allowed (see CSP)\n\tg = g || new Function(\"return this\")();\n} catch (e) {\n\t// This works if the window reference is available\n\tif (typeof window === \"object\") g = window;\n}\n\n// g can still be undefined, but nothing to do about it...\n// We return undefined, instead of nothing here, so it's\n// easier to handle this case. if(!global) { ...}\n\nmodule.exports = g;\n" - }, - { - "id": 13, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\html-entities\\lib\\html5-entities.js?babel-target=es6", - "name": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities/lib/html5-entities.js?babel-target=es6", - "index": 20, - "index2": 16, - "size": 48986, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\html-entities\\index.js?babel-target=es6", - "issuerId": 34, - "issuerName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities?babel-target=es6", - "issuerPath": [ - { - "id": 19, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6" - }, - { - "id": 20, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "name": "(webpack)-dev-server/client?http://localhost:61902" - }, - { - "id": 32, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\overlay.js?babel-target=es6", - "name": "(webpack)-dev-server/client/overlay.js?babel-target=es6" - }, - { - "id": 34, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\html-entities\\index.js?babel-target=es6", - "name": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities?babel-target=es6" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 34, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\html-entities\\index.js?babel-target=es6", - "module": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities?babel-target=es6", - "moduleName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities?babel-target=es6", - "type": "cjs require", - "userRequest": "./lib/html5-entities.js", - "loc": "4:17-51" - }, - { - "moduleId": 34, - "moduleIdentifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\html-entities\\index.js?babel-target=es6", - "module": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities?babel-target=es6", - "moduleName": "C:/Users/mikae/Code/flow/flow-tests/test-npm/node_modules/html-entities?babel-target=es6", - "type": "cjs require", - "userRequest": "./lib/html5-entities.js", - "loc": "5:19-53" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 4, - "source": "var ENTITIES = [['Aacute', [193]], ['aacute', [225]], ['Abreve', [258]], ['abreve', [259]], ['ac', [8766]], ['acd', [8767]], ['acE', [8766, 819]], ['Acirc', [194]], ['acirc', [226]], ['acute', [180]], ['Acy', [1040]], ['acy', [1072]], ['AElig', [198]], ['aelig', [230]], ['af', [8289]], ['Afr', [120068]], ['afr', [120094]], ['Agrave', [192]], ['agrave', [224]], ['alefsym', [8501]], ['aleph', [8501]], ['Alpha', [913]], ['alpha', [945]], ['Amacr', [256]], ['amacr', [257]], ['amalg', [10815]], ['amp', [38]], ['AMP', [38]], ['andand', [10837]], ['And', [10835]], ['and', [8743]], ['andd', [10844]], ['andslope', [10840]], ['andv', [10842]], ['ang', [8736]], ['ange', [10660]], ['angle', [8736]], ['angmsdaa', [10664]], ['angmsdab', [10665]], ['angmsdac', [10666]], ['angmsdad', [10667]], ['angmsdae', [10668]], ['angmsdaf', [10669]], ['angmsdag', [10670]], ['angmsdah', [10671]], ['angmsd', [8737]], ['angrt', [8735]], ['angrtvb', [8894]], ['angrtvbd', [10653]], ['angsph', [8738]], ['angst', [197]], ['angzarr', [9084]], ['Aogon', [260]], ['aogon', [261]], ['Aopf', [120120]], ['aopf', [120146]], ['apacir', [10863]], ['ap', [8776]], ['apE', [10864]], ['ape', [8778]], ['apid', [8779]], ['apos', [39]], ['ApplyFunction', [8289]], ['approx', [8776]], ['approxeq', [8778]], ['Aring', [197]], ['aring', [229]], ['Ascr', [119964]], ['ascr', [119990]], ['Assign', [8788]], ['ast', [42]], ['asymp', [8776]], ['asympeq', [8781]], ['Atilde', [195]], ['atilde', [227]], ['Auml', [196]], ['auml', [228]], ['awconint', [8755]], ['awint', [10769]], ['backcong', [8780]], ['backepsilon', [1014]], ['backprime', [8245]], ['backsim', [8765]], ['backsimeq', [8909]], ['Backslash', [8726]], ['Barv', [10983]], ['barvee', [8893]], ['barwed', [8965]], ['Barwed', [8966]], ['barwedge', [8965]], ['bbrk', [9141]], ['bbrktbrk', [9142]], ['bcong', [8780]], ['Bcy', [1041]], ['bcy', [1073]], ['bdquo', [8222]], ['becaus', [8757]], ['because', [8757]], ['Because', [8757]], ['bemptyv', [10672]], ['bepsi', [1014]], ['bernou', [8492]], ['Bernoullis', [8492]], ['Beta', [914]], ['beta', [946]], ['beth', [8502]], ['between', [8812]], ['Bfr', [120069]], ['bfr', [120095]], ['bigcap', [8898]], ['bigcirc', [9711]], ['bigcup', [8899]], ['bigodot', [10752]], ['bigoplus', [10753]], ['bigotimes', [10754]], ['bigsqcup', [10758]], ['bigstar', [9733]], ['bigtriangledown', [9661]], ['bigtriangleup', [9651]], ['biguplus', [10756]], ['bigvee', [8897]], ['bigwedge', [8896]], ['bkarow', [10509]], ['blacklozenge', [10731]], ['blacksquare', [9642]], ['blacktriangle', [9652]], ['blacktriangledown', [9662]], ['blacktriangleleft', [9666]], ['blacktriangleright', [9656]], ['blank', [9251]], ['blk12', [9618]], ['blk14', [9617]], ['blk34', [9619]], ['block', [9608]], ['bne', [61, 8421]], ['bnequiv', [8801, 8421]], ['bNot', [10989]], ['bnot', [8976]], ['Bopf', [120121]], ['bopf', [120147]], ['bot', [8869]], ['bottom', [8869]], ['bowtie', [8904]], ['boxbox', [10697]], ['boxdl', [9488]], ['boxdL', [9557]], ['boxDl', [9558]], ['boxDL', [9559]], ['boxdr', [9484]], ['boxdR', [9554]], ['boxDr', [9555]], ['boxDR', [9556]], ['boxh', [9472]], ['boxH', [9552]], ['boxhd', [9516]], ['boxHd', [9572]], ['boxhD', [9573]], ['boxHD', [9574]], ['boxhu', [9524]], ['boxHu', [9575]], ['boxhU', [9576]], ['boxHU', [9577]], ['boxminus', [8863]], ['boxplus', [8862]], ['boxtimes', [8864]], ['boxul', [9496]], ['boxuL', [9563]], ['boxUl', [9564]], ['boxUL', [9565]], ['boxur', [9492]], ['boxuR', [9560]], ['boxUr', [9561]], ['boxUR', [9562]], ['boxv', [9474]], ['boxV', [9553]], ['boxvh', [9532]], ['boxvH', [9578]], ['boxVh', [9579]], ['boxVH', [9580]], ['boxvl', [9508]], ['boxvL', [9569]], ['boxVl', [9570]], ['boxVL', [9571]], ['boxvr', [9500]], ['boxvR', [9566]], ['boxVr', [9567]], ['boxVR', [9568]], ['bprime', [8245]], ['breve', [728]], ['Breve', [728]], ['brvbar', [166]], ['bscr', [119991]], ['Bscr', [8492]], ['bsemi', [8271]], ['bsim', [8765]], ['bsime', [8909]], ['bsolb', [10693]], ['bsol', [92]], ['bsolhsub', [10184]], ['bull', [8226]], ['bullet', [8226]], ['bump', [8782]], ['bumpE', [10926]], ['bumpe', [8783]], ['Bumpeq', [8782]], ['bumpeq', [8783]], ['Cacute', [262]], ['cacute', [263]], ['capand', [10820]], ['capbrcup', [10825]], ['capcap', [10827]], ['cap', [8745]], ['Cap', [8914]], ['capcup', [10823]], ['capdot', [10816]], ['CapitalDifferentialD', [8517]], ['caps', [8745, 65024]], ['caret', [8257]], ['caron', [711]], ['Cayleys', [8493]], ['ccaps', [10829]], ['Ccaron', [268]], ['ccaron', [269]], ['Ccedil', [199]], ['ccedil', [231]], ['Ccirc', [264]], ['ccirc', [265]], ['Cconint', [8752]], ['ccups', [10828]], ['ccupssm', [10832]], ['Cdot', [266]], ['cdot', [267]], ['cedil', [184]], ['Cedilla', [184]], ['cemptyv', [10674]], ['cent', [162]], ['centerdot', [183]], ['CenterDot', [183]], ['cfr', [120096]], ['Cfr', [8493]], ['CHcy', [1063]], ['chcy', [1095]], ['check', [10003]], ['checkmark', [10003]], ['Chi', [935]], ['chi', [967]], ['circ', [710]], ['circeq', [8791]], ['circlearrowleft', [8634]], ['circlearrowright', [8635]], ['circledast', [8859]], ['circledcirc', [8858]], ['circleddash', [8861]], ['CircleDot', [8857]], ['circledR', [174]], ['circledS', [9416]], ['CircleMinus', [8854]], ['CirclePlus', [8853]], ['CircleTimes', [8855]], ['cir', [9675]], ['cirE', [10691]], ['cire', [8791]], ['cirfnint', [10768]], ['cirmid', [10991]], ['cirscir', [10690]], ['ClockwiseContourIntegral', [8754]], ['clubs', [9827]], ['clubsuit', [9827]], ['colon', [58]], ['Colon', [8759]], ['Colone', [10868]], ['colone', [8788]], ['coloneq', [8788]], ['comma', [44]], ['commat', [64]], ['comp', [8705]], ['compfn', [8728]], ['complement', [8705]], ['complexes', [8450]], ['cong', [8773]], ['congdot', [10861]], ['Congruent', [8801]], ['conint', [8750]], ['Conint', [8751]], ['ContourIntegral', [8750]], ['copf', [120148]], ['Copf', [8450]], ['coprod', [8720]], ['Coproduct', [8720]], ['copy', [169]], ['COPY', [169]], ['copysr', [8471]], ['CounterClockwiseContourIntegral', [8755]], ['crarr', [8629]], ['cross', [10007]], ['Cross', [10799]], ['Cscr', [119966]], ['cscr', [119992]], ['csub', [10959]], ['csube', [10961]], ['csup', [10960]], ['csupe', [10962]], ['ctdot', [8943]], ['cudarrl', [10552]], ['cudarrr', [10549]], ['cuepr', [8926]], ['cuesc', [8927]], ['cularr', [8630]], ['cularrp', [10557]], ['cupbrcap', [10824]], ['cupcap', [10822]], ['CupCap', [8781]], ['cup', [8746]], ['Cup', [8915]], ['cupcup', [10826]], ['cupdot', [8845]], ['cupor', [10821]], ['cups', [8746, 65024]], ['curarr', [8631]], ['curarrm', [10556]], ['curlyeqprec', [8926]], ['curlyeqsucc', [8927]], ['curlyvee', [8910]], ['curlywedge', [8911]], ['curren', [164]], ['curvearrowleft', [8630]], ['curvearrowright', [8631]], ['cuvee', [8910]], ['cuwed', [8911]], ['cwconint', [8754]], ['cwint', [8753]], ['cylcty', [9005]], ['dagger', [8224]], ['Dagger', [8225]], ['daleth', [8504]], ['darr', [8595]], ['Darr', [8609]], ['dArr', [8659]], ['dash', [8208]], ['Dashv', [10980]], ['dashv', [8867]], ['dbkarow', [10511]], ['dblac', [733]], ['Dcaron', [270]], ['dcaron', [271]], ['Dcy', [1044]], ['dcy', [1076]], ['ddagger', [8225]], ['ddarr', [8650]], ['DD', [8517]], ['dd', [8518]], ['DDotrahd', [10513]], ['ddotseq', [10871]], ['deg', [176]], ['Del', [8711]], ['Delta', [916]], ['delta', [948]], ['demptyv', [10673]], ['dfisht', [10623]], ['Dfr', [120071]], ['dfr', [120097]], ['dHar', [10597]], ['dharl', [8643]], ['dharr', [8642]], ['DiacriticalAcute', [180]], ['DiacriticalDot', [729]], ['DiacriticalDoubleAcute', [733]], ['DiacriticalGrave', [96]], ['DiacriticalTilde', [732]], ['diam', [8900]], ['diamond', [8900]], ['Diamond', [8900]], ['diamondsuit', [9830]], ['diams', [9830]], ['die', [168]], ['DifferentialD', [8518]], ['digamma', [989]], ['disin', [8946]], ['div', [247]], ['divide', [247]], ['divideontimes', [8903]], ['divonx', [8903]], ['DJcy', [1026]], ['djcy', [1106]], ['dlcorn', [8990]], ['dlcrop', [8973]], ['dollar', [36]], ['Dopf', [120123]], ['dopf', [120149]], ['Dot', [168]], ['dot', [729]], ['DotDot', [8412]], ['doteq', [8784]], ['doteqdot', [8785]], ['DotEqual', [8784]], ['dotminus', [8760]], ['dotplus', [8724]], ['dotsquare', [8865]], ['doublebarwedge', [8966]], ['DoubleContourIntegral', [8751]], ['DoubleDot', [168]], ['DoubleDownArrow', [8659]], ['DoubleLeftArrow', [8656]], ['DoubleLeftRightArrow', [8660]], ['DoubleLeftTee', [10980]], ['DoubleLongLeftArrow', [10232]], ['DoubleLongLeftRightArrow', [10234]], ['DoubleLongRightArrow', [10233]], ['DoubleRightArrow', [8658]], ['DoubleRightTee', [8872]], ['DoubleUpArrow', [8657]], ['DoubleUpDownArrow', [8661]], ['DoubleVerticalBar', [8741]], ['DownArrowBar', [10515]], ['downarrow', [8595]], ['DownArrow', [8595]], ['Downarrow', [8659]], ['DownArrowUpArrow', [8693]], ['DownBreve', [785]], ['downdownarrows', [8650]], ['downharpoonleft', [8643]], ['downharpoonright', [8642]], ['DownLeftRightVector', [10576]], ['DownLeftTeeVector', [10590]], ['DownLeftVectorBar', [10582]], ['DownLeftVector', [8637]], ['DownRightTeeVector', [10591]], ['DownRightVectorBar', [10583]], ['DownRightVector', [8641]], ['DownTeeArrow', [8615]], ['DownTee', [8868]], ['drbkarow', [10512]], ['drcorn', [8991]], ['drcrop', [8972]], ['Dscr', [119967]], ['dscr', [119993]], ['DScy', [1029]], ['dscy', [1109]], ['dsol', [10742]], ['Dstrok', [272]], ['dstrok', [273]], ['dtdot', [8945]], ['dtri', [9663]], ['dtrif', [9662]], ['duarr', [8693]], ['duhar', [10607]], ['dwangle', [10662]], ['DZcy', [1039]], ['dzcy', [1119]], ['dzigrarr', [10239]], ['Eacute', [201]], ['eacute', [233]], ['easter', [10862]], ['Ecaron', [282]], ['ecaron', [283]], ['Ecirc', [202]], ['ecirc', [234]], ['ecir', [8790]], ['ecolon', [8789]], ['Ecy', [1069]], ['ecy', [1101]], ['eDDot', [10871]], ['Edot', [278]], ['edot', [279]], ['eDot', [8785]], ['ee', [8519]], ['efDot', [8786]], ['Efr', [120072]], ['efr', [120098]], ['eg', [10906]], ['Egrave', [200]], ['egrave', [232]], ['egs', [10902]], ['egsdot', [10904]], ['el', [10905]], ['Element', [8712]], ['elinters', [9191]], ['ell', [8467]], ['els', [10901]], ['elsdot', [10903]], ['Emacr', [274]], ['emacr', [275]], ['empty', [8709]], ['emptyset', [8709]], ['EmptySmallSquare', [9723]], ['emptyv', [8709]], ['EmptyVerySmallSquare', [9643]], ['emsp13', [8196]], ['emsp14', [8197]], ['emsp', [8195]], ['ENG', [330]], ['eng', [331]], ['ensp', [8194]], ['Eogon', [280]], ['eogon', [281]], ['Eopf', [120124]], ['eopf', [120150]], ['epar', [8917]], ['eparsl', [10723]], ['eplus', [10865]], ['epsi', [949]], ['Epsilon', [917]], ['epsilon', [949]], ['epsiv', [1013]], ['eqcirc', [8790]], ['eqcolon', [8789]], ['eqsim', [8770]], ['eqslantgtr', [10902]], ['eqslantless', [10901]], ['Equal', [10869]], ['equals', [61]], ['EqualTilde', [8770]], ['equest', [8799]], ['Equilibrium', [8652]], ['equiv', [8801]], ['equivDD', [10872]], ['eqvparsl', [10725]], ['erarr', [10609]], ['erDot', [8787]], ['escr', [8495]], ['Escr', [8496]], ['esdot', [8784]], ['Esim', [10867]], ['esim', [8770]], ['Eta', [919]], ['eta', [951]], ['ETH', [208]], ['eth', [240]], ['Euml', [203]], ['euml', [235]], ['euro', [8364]], ['excl', [33]], ['exist', [8707]], ['Exists', [8707]], ['expectation', [8496]], ['exponentiale', [8519]], ['ExponentialE', [8519]], ['fallingdotseq', [8786]], ['Fcy', [1060]], ['fcy', [1092]], ['female', [9792]], ['ffilig', [64259]], ['fflig', [64256]], ['ffllig', [64260]], ['Ffr', [120073]], ['ffr', [120099]], ['filig', [64257]], ['FilledSmallSquare', [9724]], ['FilledVerySmallSquare', [9642]], ['fjlig', [102, 106]], ['flat', [9837]], ['fllig', [64258]], ['fltns', [9649]], ['fnof', [402]], ['Fopf', [120125]], ['fopf', [120151]], ['forall', [8704]], ['ForAll', [8704]], ['fork', [8916]], ['forkv', [10969]], ['Fouriertrf', [8497]], ['fpartint', [10765]], ['frac12', [189]], ['frac13', [8531]], ['frac14', [188]], ['frac15', [8533]], ['frac16', [8537]], ['frac18', [8539]], ['frac23', [8532]], ['frac25', [8534]], ['frac34', [190]], ['frac35', [8535]], ['frac38', [8540]], ['frac45', [8536]], ['frac56', [8538]], ['frac58', [8541]], ['frac78', [8542]], ['frasl', [8260]], ['frown', [8994]], ['fscr', [119995]], ['Fscr', [8497]], ['gacute', [501]], ['Gamma', [915]], ['gamma', [947]], ['Gammad', [988]], ['gammad', [989]], ['gap', [10886]], ['Gbreve', [286]], ['gbreve', [287]], ['Gcedil', [290]], ['Gcirc', [284]], ['gcirc', [285]], ['Gcy', [1043]], ['gcy', [1075]], ['Gdot', [288]], ['gdot', [289]], ['ge', [8805]], ['gE', [8807]], ['gEl', [10892]], ['gel', [8923]], ['geq', [8805]], ['geqq', [8807]], ['geqslant', [10878]], ['gescc', [10921]], ['ges', [10878]], ['gesdot', [10880]], ['gesdoto', [10882]], ['gesdotol', [10884]], ['gesl', [8923, 65024]], ['gesles', [10900]], ['Gfr', [120074]], ['gfr', [120100]], ['gg', [8811]], ['Gg', [8921]], ['ggg', [8921]], ['gimel', [8503]], ['GJcy', [1027]], ['gjcy', [1107]], ['gla', [10917]], ['gl', [8823]], ['glE', [10898]], ['glj', [10916]], ['gnap', [10890]], ['gnapprox', [10890]], ['gne', [10888]], ['gnE', [8809]], ['gneq', [10888]], ['gneqq', [8809]], ['gnsim', [8935]], ['Gopf', [120126]], ['gopf', [120152]], ['grave', [96]], ['GreaterEqual', [8805]], ['GreaterEqualLess', [8923]], ['GreaterFullEqual', [8807]], ['GreaterGreater', [10914]], ['GreaterLess', [8823]], ['GreaterSlantEqual', [10878]], ['GreaterTilde', [8819]], ['Gscr', [119970]], ['gscr', [8458]], ['gsim', [8819]], ['gsime', [10894]], ['gsiml', [10896]], ['gtcc', [10919]], ['gtcir', [10874]], ['gt', [62]], ['GT', [62]], ['Gt', [8811]], ['gtdot', [8919]], ['gtlPar', [10645]], ['gtquest', [10876]], ['gtrapprox', [10886]], ['gtrarr', [10616]], ['gtrdot', [8919]], ['gtreqless', [8923]], ['gtreqqless', [10892]], ['gtrless', [8823]], ['gtrsim', [8819]], ['gvertneqq', [8809, 65024]], ['gvnE', [8809, 65024]], ['Hacek', [711]], ['hairsp', [8202]], ['half', [189]], ['hamilt', [8459]], ['HARDcy', [1066]], ['hardcy', [1098]], ['harrcir', [10568]], ['harr', [8596]], ['hArr', [8660]], ['harrw', [8621]], ['Hat', [94]], ['hbar', [8463]], ['Hcirc', [292]], ['hcirc', [293]], ['hearts', [9829]], ['heartsuit', [9829]], ['hellip', [8230]], ['hercon', [8889]], ['hfr', [120101]], ['Hfr', [8460]], ['HilbertSpace', [8459]], ['hksearow', [10533]], ['hkswarow', [10534]], ['hoarr', [8703]], ['homtht', [8763]], ['hookleftarrow', [8617]], ['hookrightarrow', [8618]], ['hopf', [120153]], ['Hopf', [8461]], ['horbar', [8213]], ['HorizontalLine', [9472]], ['hscr', [119997]], ['Hscr', [8459]], ['hslash', [8463]], ['Hstrok', [294]], ['hstrok', [295]], ['HumpDownHump', [8782]], ['HumpEqual', [8783]], ['hybull', [8259]], ['hyphen', [8208]], ['Iacute', [205]], ['iacute', [237]], ['ic', [8291]], ['Icirc', [206]], ['icirc', [238]], ['Icy', [1048]], ['icy', [1080]], ['Idot', [304]], ['IEcy', [1045]], ['iecy', [1077]], ['iexcl', [161]], ['iff', [8660]], ['ifr', [120102]], ['Ifr', [8465]], ['Igrave', [204]], ['igrave', [236]], ['ii', [8520]], ['iiiint', [10764]], ['iiint', [8749]], ['iinfin', [10716]], ['iiota', [8489]], ['IJlig', [306]], ['ijlig', [307]], ['Imacr', [298]], ['imacr', [299]], ['image', [8465]], ['ImaginaryI', [8520]], ['imagline', [8464]], ['imagpart', [8465]], ['imath', [305]], ['Im', [8465]], ['imof', [8887]], ['imped', [437]], ['Implies', [8658]], ['incare', [8453]], ['in', [8712]], ['infin', [8734]], ['infintie', [10717]], ['inodot', [305]], ['intcal', [8890]], ['int', [8747]], ['Int', [8748]], ['integers', [8484]], ['Integral', [8747]], ['intercal', [8890]], ['Intersection', [8898]], ['intlarhk', [10775]], ['intprod', [10812]], ['InvisibleComma', [8291]], ['InvisibleTimes', [8290]], ['IOcy', [1025]], ['iocy', [1105]], ['Iogon', [302]], ['iogon', [303]], ['Iopf', [120128]], ['iopf', [120154]], ['Iota', [921]], ['iota', [953]], ['iprod', [10812]], ['iquest', [191]], ['iscr', [119998]], ['Iscr', [8464]], ['isin', [8712]], ['isindot', [8949]], ['isinE', [8953]], ['isins', [8948]], ['isinsv', [8947]], ['isinv', [8712]], ['it', [8290]], ['Itilde', [296]], ['itilde', [297]], ['Iukcy', [1030]], ['iukcy', [1110]], ['Iuml', [207]], ['iuml', [239]], ['Jcirc', [308]], ['jcirc', [309]], ['Jcy', [1049]], ['jcy', [1081]], ['Jfr', [120077]], ['jfr', [120103]], ['jmath', [567]], ['Jopf', [120129]], ['jopf', [120155]], ['Jscr', [119973]], ['jscr', [119999]], ['Jsercy', [1032]], ['jsercy', [1112]], ['Jukcy', [1028]], ['jukcy', [1108]], ['Kappa', [922]], ['kappa', [954]], ['kappav', [1008]], ['Kcedil', [310]], ['kcedil', [311]], ['Kcy', [1050]], ['kcy', [1082]], ['Kfr', [120078]], ['kfr', [120104]], ['kgreen', [312]], ['KHcy', [1061]], ['khcy', [1093]], ['KJcy', [1036]], ['kjcy', [1116]], ['Kopf', [120130]], ['kopf', [120156]], ['Kscr', [119974]], ['kscr', [120000]], ['lAarr', [8666]], ['Lacute', [313]], ['lacute', [314]], ['laemptyv', [10676]], ['lagran', [8466]], ['Lambda', [923]], ['lambda', [955]], ['lang', [10216]], ['Lang', [10218]], ['langd', [10641]], ['langle', [10216]], ['lap', [10885]], ['Laplacetrf', [8466]], ['laquo', [171]], ['larrb', [8676]], ['larrbfs', [10527]], ['larr', [8592]], ['Larr', [8606]], ['lArr', [8656]], ['larrfs', [10525]], ['larrhk', [8617]], ['larrlp', [8619]], ['larrpl', [10553]], ['larrsim', [10611]], ['larrtl', [8610]], ['latail', [10521]], ['lAtail', [10523]], ['lat', [10923]], ['late', [10925]], ['lates', [10925, 65024]], ['lbarr', [10508]], ['lBarr', [10510]], ['lbbrk', [10098]], ['lbrace', [123]], ['lbrack', [91]], ['lbrke', [10635]], ['lbrksld', [10639]], ['lbrkslu', [10637]], ['Lcaron', [317]], ['lcaron', [318]], ['Lcedil', [315]], ['lcedil', [316]], ['lceil', [8968]], ['lcub', [123]], ['Lcy', [1051]], ['lcy', [1083]], ['ldca', [10550]], ['ldquo', [8220]], ['ldquor', [8222]], ['ldrdhar', [10599]], ['ldrushar', [10571]], ['ldsh', [8626]], ['le', [8804]], ['lE', [8806]], ['LeftAngleBracket', [10216]], ['LeftArrowBar', [8676]], ['leftarrow', [8592]], ['LeftArrow', [8592]], ['Leftarrow', [8656]], ['LeftArrowRightArrow', [8646]], ['leftarrowtail', [8610]], ['LeftCeiling', [8968]], ['LeftDoubleBracket', [10214]], ['LeftDownTeeVector', [10593]], ['LeftDownVectorBar', [10585]], ['LeftDownVector', [8643]], ['LeftFloor', [8970]], ['leftharpoondown', [8637]], ['leftharpoonup', [8636]], ['leftleftarrows', [8647]], ['leftrightarrow', [8596]], ['LeftRightArrow', [8596]], ['Leftrightarrow', [8660]], ['leftrightarrows', [8646]], ['leftrightharpoons', [8651]], ['leftrightsquigarrow', [8621]], ['LeftRightVector', [10574]], ['LeftTeeArrow', [8612]], ['LeftTee', [8867]], ['LeftTeeVector', [10586]], ['leftthreetimes', [8907]], ['LeftTriangleBar', [10703]], ['LeftTriangle', [8882]], ['LeftTriangleEqual', [8884]], ['LeftUpDownVector', [10577]], ['LeftUpTeeVector', [10592]], ['LeftUpVectorBar', [10584]], ['LeftUpVector', [8639]], ['LeftVectorBar', [10578]], ['LeftVector', [8636]], ['lEg', [10891]], ['leg', [8922]], ['leq', [8804]], ['leqq', [8806]], ['leqslant', [10877]], ['lescc', [10920]], ['les', [10877]], ['lesdot', [10879]], ['lesdoto', [10881]], ['lesdotor', [10883]], ['lesg', [8922, 65024]], ['lesges', [10899]], ['lessapprox', [10885]], ['lessdot', [8918]], ['lesseqgtr', [8922]], ['lesseqqgtr', [10891]], ['LessEqualGreater', [8922]], ['LessFullEqual', [8806]], ['LessGreater', [8822]], ['lessgtr', [8822]], ['LessLess', [10913]], ['lesssim', [8818]], ['LessSlantEqual', [10877]], ['LessTilde', [8818]], ['lfisht', [10620]], ['lfloor', [8970]], ['Lfr', [120079]], ['lfr', [120105]], ['lg', [8822]], ['lgE', [10897]], ['lHar', [10594]], ['lhard', [8637]], ['lharu', [8636]], ['lharul', [10602]], ['lhblk', [9604]], ['LJcy', [1033]], ['ljcy', [1113]], ['llarr', [8647]], ['ll', [8810]], ['Ll', [8920]], ['llcorner', [8990]], ['Lleftarrow', [8666]], ['llhard', [10603]], ['lltri', [9722]], ['Lmidot', [319]], ['lmidot', [320]], ['lmoustache', [9136]], ['lmoust', [9136]], ['lnap', [10889]], ['lnapprox', [10889]], ['lne', [10887]], ['lnE', [8808]], ['lneq', [10887]], ['lneqq', [8808]], ['lnsim', [8934]], ['loang', [10220]], ['loarr', [8701]], ['lobrk', [10214]], ['longleftarrow', [10229]], ['LongLeftArrow', [10229]], ['Longleftarrow', [10232]], ['longleftrightarrow', [10231]], ['LongLeftRightArrow', [10231]], ['Longleftrightarrow', [10234]], ['longmapsto', [10236]], ['longrightarrow', [10230]], ['LongRightArrow', [10230]], ['Longrightarrow', [10233]], ['looparrowleft', [8619]], ['looparrowright', [8620]], ['lopar', [10629]], ['Lopf', [120131]], ['lopf', [120157]], ['loplus', [10797]], ['lotimes', [10804]], ['lowast', [8727]], ['lowbar', [95]], ['LowerLeftArrow', [8601]], ['LowerRightArrow', [8600]], ['loz', [9674]], ['lozenge', [9674]], ['lozf', [10731]], ['lpar', [40]], ['lparlt', [10643]], ['lrarr', [8646]], ['lrcorner', [8991]], ['lrhar', [8651]], ['lrhard', [10605]], ['lrm', [8206]], ['lrtri', [8895]], ['lsaquo', [8249]], ['lscr', [120001]], ['Lscr', [8466]], ['lsh', [8624]], ['Lsh', [8624]], ['lsim', [8818]], ['lsime', [10893]], ['lsimg', [10895]], ['lsqb', [91]], ['lsquo', [8216]], ['lsquor', [8218]], ['Lstrok', [321]], ['lstrok', [322]], ['ltcc', [10918]], ['ltcir', [10873]], ['lt', [60]], ['LT', [60]], ['Lt', [8810]], ['ltdot', [8918]], ['lthree', [8907]], ['ltimes', [8905]], ['ltlarr', [10614]], ['ltquest', [10875]], ['ltri', [9667]], ['ltrie', [8884]], ['ltrif', [9666]], ['ltrPar', [10646]], ['lurdshar', [10570]], ['luruhar', [10598]], ['lvertneqq', [8808, 65024]], ['lvnE', [8808, 65024]], ['macr', [175]], ['male', [9794]], ['malt', [10016]], ['maltese', [10016]], ['Map', [10501]], ['map', [8614]], ['mapsto', [8614]], ['mapstodown', [8615]], ['mapstoleft', [8612]], ['mapstoup', [8613]], ['marker', [9646]], ['mcomma', [10793]], ['Mcy', [1052]], ['mcy', [1084]], ['mdash', [8212]], ['mDDot', [8762]], ['measuredangle', [8737]], ['MediumSpace', [8287]], ['Mellintrf', [8499]], ['Mfr', [120080]], ['mfr', [120106]], ['mho', [8487]], ['micro', [181]], ['midast', [42]], ['midcir', [10992]], ['mid', [8739]], ['middot', [183]], ['minusb', [8863]], ['minus', [8722]], ['minusd', [8760]], ['minusdu', [10794]], ['MinusPlus', [8723]], ['mlcp', [10971]], ['mldr', [8230]], ['mnplus', [8723]], ['models', [8871]], ['Mopf', [120132]], ['mopf', [120158]], ['mp', [8723]], ['mscr', [120002]], ['Mscr', [8499]], ['mstpos', [8766]], ['Mu', [924]], ['mu', [956]], ['multimap', [8888]], ['mumap', [8888]], ['nabla', [8711]], ['Nacute', [323]], ['nacute', [324]], ['nang', [8736, 8402]], ['nap', [8777]], ['napE', [10864, 824]], ['napid', [8779, 824]], ['napos', [329]], ['napprox', [8777]], ['natural', [9838]], ['naturals', [8469]], ['natur', [9838]], ['nbsp', [160]], ['nbump', [8782, 824]], ['nbumpe', [8783, 824]], ['ncap', [10819]], ['Ncaron', [327]], ['ncaron', [328]], ['Ncedil', [325]], ['ncedil', [326]], ['ncong', [8775]], ['ncongdot', [10861, 824]], ['ncup', [10818]], ['Ncy', [1053]], ['ncy', [1085]], ['ndash', [8211]], ['nearhk', [10532]], ['nearr', [8599]], ['neArr', [8663]], ['nearrow', [8599]], ['ne', [8800]], ['nedot', [8784, 824]], ['NegativeMediumSpace', [8203]], ['NegativeThickSpace', [8203]], ['NegativeThinSpace', [8203]], ['NegativeVeryThinSpace', [8203]], ['nequiv', [8802]], ['nesear', [10536]], ['nesim', [8770, 824]], ['NestedGreaterGreater', [8811]], ['NestedLessLess', [8810]], ['nexist', [8708]], ['nexists', [8708]], ['Nfr', [120081]], ['nfr', [120107]], ['ngE', [8807, 824]], ['nge', [8817]], ['ngeq', [8817]], ['ngeqq', [8807, 824]], ['ngeqslant', [10878, 824]], ['nges', [10878, 824]], ['nGg', [8921, 824]], ['ngsim', [8821]], ['nGt', [8811, 8402]], ['ngt', [8815]], ['ngtr', [8815]], ['nGtv', [8811, 824]], ['nharr', [8622]], ['nhArr', [8654]], ['nhpar', [10994]], ['ni', [8715]], ['nis', [8956]], ['nisd', [8954]], ['niv', [8715]], ['NJcy', [1034]], ['njcy', [1114]], ['nlarr', [8602]], ['nlArr', [8653]], ['nldr', [8229]], ['nlE', [8806, 824]], ['nle', [8816]], ['nleftarrow', [8602]], ['nLeftarrow', [8653]], ['nleftrightarrow', [8622]], ['nLeftrightarrow', [8654]], ['nleq', [8816]], ['nleqq', [8806, 824]], ['nleqslant', [10877, 824]], ['nles', [10877, 824]], ['nless', [8814]], ['nLl', [8920, 824]], ['nlsim', [8820]], ['nLt', [8810, 8402]], ['nlt', [8814]], ['nltri', [8938]], ['nltrie', [8940]], ['nLtv', [8810, 824]], ['nmid', [8740]], ['NoBreak', [8288]], ['NonBreakingSpace', [160]], ['nopf', [120159]], ['Nopf', [8469]], ['Not', [10988]], ['not', [172]], ['NotCongruent', [8802]], ['NotCupCap', [8813]], ['NotDoubleVerticalBar', [8742]], ['NotElement', [8713]], ['NotEqual', [8800]], ['NotEqualTilde', [8770, 824]], ['NotExists', [8708]], ['NotGreater', [8815]], ['NotGreaterEqual', [8817]], ['NotGreaterFullEqual', [8807, 824]], ['NotGreaterGreater', [8811, 824]], ['NotGreaterLess', [8825]], ['NotGreaterSlantEqual', [10878, 824]], ['NotGreaterTilde', [8821]], ['NotHumpDownHump', [8782, 824]], ['NotHumpEqual', [8783, 824]], ['notin', [8713]], ['notindot', [8949, 824]], ['notinE', [8953, 824]], ['notinva', [8713]], ['notinvb', [8951]], ['notinvc', [8950]], ['NotLeftTriangleBar', [10703, 824]], ['NotLeftTriangle', [8938]], ['NotLeftTriangleEqual', [8940]], ['NotLess', [8814]], ['NotLessEqual', [8816]], ['NotLessGreater', [8824]], ['NotLessLess', [8810, 824]], ['NotLessSlantEqual', [10877, 824]], ['NotLessTilde', [8820]], ['NotNestedGreaterGreater', [10914, 824]], ['NotNestedLessLess', [10913, 824]], ['notni', [8716]], ['notniva', [8716]], ['notnivb', [8958]], ['notnivc', [8957]], ['NotPrecedes', [8832]], ['NotPrecedesEqual', [10927, 824]], ['NotPrecedesSlantEqual', [8928]], ['NotReverseElement', [8716]], ['NotRightTriangleBar', [10704, 824]], ['NotRightTriangle', [8939]], ['NotRightTriangleEqual', [8941]], ['NotSquareSubset', [8847, 824]], ['NotSquareSubsetEqual', [8930]], ['NotSquareSuperset', [8848, 824]], ['NotSquareSupersetEqual', [8931]], ['NotSubset', [8834, 8402]], ['NotSubsetEqual', [8840]], ['NotSucceeds', [8833]], ['NotSucceedsEqual', [10928, 824]], ['NotSucceedsSlantEqual', [8929]], ['NotSucceedsTilde', [8831, 824]], ['NotSuperset', [8835, 8402]], ['NotSupersetEqual', [8841]], ['NotTilde', [8769]], ['NotTildeEqual', [8772]], ['NotTildeFullEqual', [8775]], ['NotTildeTilde', [8777]], ['NotVerticalBar', [8740]], ['nparallel', [8742]], ['npar', [8742]], ['nparsl', [11005, 8421]], ['npart', [8706, 824]], ['npolint', [10772]], ['npr', [8832]], ['nprcue', [8928]], ['nprec', [8832]], ['npreceq', [10927, 824]], ['npre', [10927, 824]], ['nrarrc', [10547, 824]], ['nrarr', [8603]], ['nrArr', [8655]], ['nrarrw', [8605, 824]], ['nrightarrow', [8603]], ['nRightarrow', [8655]], ['nrtri', [8939]], ['nrtrie', [8941]], ['nsc', [8833]], ['nsccue', [8929]], ['nsce', [10928, 824]], ['Nscr', [119977]], ['nscr', [120003]], ['nshortmid', [8740]], ['nshortparallel', [8742]], ['nsim', [8769]], ['nsime', [8772]], ['nsimeq', [8772]], ['nsmid', [8740]], ['nspar', [8742]], ['nsqsube', [8930]], ['nsqsupe', [8931]], ['nsub', [8836]], ['nsubE', [10949, 824]], ['nsube', [8840]], ['nsubset', [8834, 8402]], ['nsubseteq', [8840]], ['nsubseteqq', [10949, 824]], ['nsucc', [8833]], ['nsucceq', [10928, 824]], ['nsup', [8837]], ['nsupE', [10950, 824]], ['nsupe', [8841]], ['nsupset', [8835, 8402]], ['nsupseteq', [8841]], ['nsupseteqq', [10950, 824]], ['ntgl', [8825]], ['Ntilde', [209]], ['ntilde', [241]], ['ntlg', [8824]], ['ntriangleleft', [8938]], ['ntrianglelefteq', [8940]], ['ntriangleright', [8939]], ['ntrianglerighteq', [8941]], ['Nu', [925]], ['nu', [957]], ['num', [35]], ['numero', [8470]], ['numsp', [8199]], ['nvap', [8781, 8402]], ['nvdash', [8876]], ['nvDash', [8877]], ['nVdash', [8878]], ['nVDash', [8879]], ['nvge', [8805, 8402]], ['nvgt', [62, 8402]], ['nvHarr', [10500]], ['nvinfin', [10718]], ['nvlArr', [10498]], ['nvle', [8804, 8402]], ['nvlt', [60, 8402]], ['nvltrie', [8884, 8402]], ['nvrArr', [10499]], ['nvrtrie', [8885, 8402]], ['nvsim', [8764, 8402]], ['nwarhk', [10531]], ['nwarr', [8598]], ['nwArr', [8662]], ['nwarrow', [8598]], ['nwnear', [10535]], ['Oacute', [211]], ['oacute', [243]], ['oast', [8859]], ['Ocirc', [212]], ['ocirc', [244]], ['ocir', [8858]], ['Ocy', [1054]], ['ocy', [1086]], ['odash', [8861]], ['Odblac', [336]], ['odblac', [337]], ['odiv', [10808]], ['odot', [8857]], ['odsold', [10684]], ['OElig', [338]], ['oelig', [339]], ['ofcir', [10687]], ['Ofr', [120082]], ['ofr', [120108]], ['ogon', [731]], ['Ograve', [210]], ['ograve', [242]], ['ogt', [10689]], ['ohbar', [10677]], ['ohm', [937]], ['oint', [8750]], ['olarr', [8634]], ['olcir', [10686]], ['olcross', [10683]], ['oline', [8254]], ['olt', [10688]], ['Omacr', [332]], ['omacr', [333]], ['Omega', [937]], ['omega', [969]], ['Omicron', [927]], ['omicron', [959]], ['omid', [10678]], ['ominus', [8854]], ['Oopf', [120134]], ['oopf', [120160]], ['opar', [10679]], ['OpenCurlyDoubleQuote', [8220]], ['OpenCurlyQuote', [8216]], ['operp', [10681]], ['oplus', [8853]], ['orarr', [8635]], ['Or', [10836]], ['or', [8744]], ['ord', [10845]], ['order', [8500]], ['orderof', [8500]], ['ordf', [170]], ['ordm', [186]], ['origof', [8886]], ['oror', [10838]], ['orslope', [10839]], ['orv', [10843]], ['oS', [9416]], ['Oscr', [119978]], ['oscr', [8500]], ['Oslash', [216]], ['oslash', [248]], ['osol', [8856]], ['Otilde', [213]], ['otilde', [245]], ['otimesas', [10806]], ['Otimes', [10807]], ['otimes', [8855]], ['Ouml', [214]], ['ouml', [246]], ['ovbar', [9021]], ['OverBar', [8254]], ['OverBrace', [9182]], ['OverBracket', [9140]], ['OverParenthesis', [9180]], ['para', [182]], ['parallel', [8741]], ['par', [8741]], ['parsim', [10995]], ['parsl', [11005]], ['part', [8706]], ['PartialD', [8706]], ['Pcy', [1055]], ['pcy', [1087]], ['percnt', [37]], ['period', [46]], ['permil', [8240]], ['perp', [8869]], ['pertenk', [8241]], ['Pfr', [120083]], ['pfr', [120109]], ['Phi', [934]], ['phi', [966]], ['phiv', [981]], ['phmmat', [8499]], ['phone', [9742]], ['Pi', [928]], ['pi', [960]], ['pitchfork', [8916]], ['piv', [982]], ['planck', [8463]], ['planckh', [8462]], ['plankv', [8463]], ['plusacir', [10787]], ['plusb', [8862]], ['pluscir', [10786]], ['plus', [43]], ['plusdo', [8724]], ['plusdu', [10789]], ['pluse', [10866]], ['PlusMinus', [177]], ['plusmn', [177]], ['plussim', [10790]], ['plustwo', [10791]], ['pm', [177]], ['Poincareplane', [8460]], ['pointint', [10773]], ['popf', [120161]], ['Popf', [8473]], ['pound', [163]], ['prap', [10935]], ['Pr', [10939]], ['pr', [8826]], ['prcue', [8828]], ['precapprox', [10935]], ['prec', [8826]], ['preccurlyeq', [8828]], ['Precedes', [8826]], ['PrecedesEqual', [10927]], ['PrecedesSlantEqual', [8828]], ['PrecedesTilde', [8830]], ['preceq', [10927]], ['precnapprox', [10937]], ['precneqq', [10933]], ['precnsim', [8936]], ['pre', [10927]], ['prE', [10931]], ['precsim', [8830]], ['prime', [8242]], ['Prime', [8243]], ['primes', [8473]], ['prnap', [10937]], ['prnE', [10933]], ['prnsim', [8936]], ['prod', [8719]], ['Product', [8719]], ['profalar', [9006]], ['profline', [8978]], ['profsurf', [8979]], ['prop', [8733]], ['Proportional', [8733]], ['Proportion', [8759]], ['propto', [8733]], ['prsim', [8830]], ['prurel', [8880]], ['Pscr', [119979]], ['pscr', [120005]], ['Psi', [936]], ['psi', [968]], ['puncsp', [8200]], ['Qfr', [120084]], ['qfr', [120110]], ['qint', [10764]], ['qopf', [120162]], ['Qopf', [8474]], ['qprime', [8279]], ['Qscr', [119980]], ['qscr', [120006]], ['quaternions', [8461]], ['quatint', [10774]], ['quest', [63]], ['questeq', [8799]], ['quot', [34]], ['QUOT', [34]], ['rAarr', [8667]], ['race', [8765, 817]], ['Racute', [340]], ['racute', [341]], ['radic', [8730]], ['raemptyv', [10675]], ['rang', [10217]], ['Rang', [10219]], ['rangd', [10642]], ['range', [10661]], ['rangle', [10217]], ['raquo', [187]], ['rarrap', [10613]], ['rarrb', [8677]], ['rarrbfs', [10528]], ['rarrc', [10547]], ['rarr', [8594]], ['Rarr', [8608]], ['rArr', [8658]], ['rarrfs', [10526]], ['rarrhk', [8618]], ['rarrlp', [8620]], ['rarrpl', [10565]], ['rarrsim', [10612]], ['Rarrtl', [10518]], ['rarrtl', [8611]], ['rarrw', [8605]], ['ratail', [10522]], ['rAtail', [10524]], ['ratio', [8758]], ['rationals', [8474]], ['rbarr', [10509]], ['rBarr', [10511]], ['RBarr', [10512]], ['rbbrk', [10099]], ['rbrace', [125]], ['rbrack', [93]], ['rbrke', [10636]], ['rbrksld', [10638]], ['rbrkslu', [10640]], ['Rcaron', [344]], ['rcaron', [345]], ['Rcedil', [342]], ['rcedil', [343]], ['rceil', [8969]], ['rcub', [125]], ['Rcy', [1056]], ['rcy', [1088]], ['rdca', [10551]], ['rdldhar', [10601]], ['rdquo', [8221]], ['rdquor', [8221]], ['CloseCurlyDoubleQuote', [8221]], ['rdsh', [8627]], ['real', [8476]], ['realine', [8475]], ['realpart', [8476]], ['reals', [8477]], ['Re', [8476]], ['rect', [9645]], ['reg', [174]], ['REG', [174]], ['ReverseElement', [8715]], ['ReverseEquilibrium', [8651]], ['ReverseUpEquilibrium', [10607]], ['rfisht', [10621]], ['rfloor', [8971]], ['rfr', [120111]], ['Rfr', [8476]], ['rHar', [10596]], ['rhard', [8641]], ['rharu', [8640]], ['rharul', [10604]], ['Rho', [929]], ['rho', [961]], ['rhov', [1009]], ['RightAngleBracket', [10217]], ['RightArrowBar', [8677]], ['rightarrow', [8594]], ['RightArrow', [8594]], ['Rightarrow', [8658]], ['RightArrowLeftArrow', [8644]], ['rightarrowtail', [8611]], ['RightCeiling', [8969]], ['RightDoubleBracket', [10215]], ['RightDownTeeVector', [10589]], ['RightDownVectorBar', [10581]], ['RightDownVector', [8642]], ['RightFloor', [8971]], ['rightharpoondown', [8641]], ['rightharpoonup', [8640]], ['rightleftarrows', [8644]], ['rightleftharpoons', [8652]], ['rightrightarrows', [8649]], ['rightsquigarrow', [8605]], ['RightTeeArrow', [8614]], ['RightTee', [8866]], ['RightTeeVector', [10587]], ['rightthreetimes', [8908]], ['RightTriangleBar', [10704]], ['RightTriangle', [8883]], ['RightTriangleEqual', [8885]], ['RightUpDownVector', [10575]], ['RightUpTeeVector', [10588]], ['RightUpVectorBar', [10580]], ['RightUpVector', [8638]], ['RightVectorBar', [10579]], ['RightVector', [8640]], ['ring', [730]], ['risingdotseq', [8787]], ['rlarr', [8644]], ['rlhar', [8652]], ['rlm', [8207]], ['rmoustache', [9137]], ['rmoust', [9137]], ['rnmid', [10990]], ['roang', [10221]], ['roarr', [8702]], ['robrk', [10215]], ['ropar', [10630]], ['ropf', [120163]], ['Ropf', [8477]], ['roplus', [10798]], ['rotimes', [10805]], ['RoundImplies', [10608]], ['rpar', [41]], ['rpargt', [10644]], ['rppolint', [10770]], ['rrarr', [8649]], ['Rrightarrow', [8667]], ['rsaquo', [8250]], ['rscr', [120007]], ['Rscr', [8475]], ['rsh', [8625]], ['Rsh', [8625]], ['rsqb', [93]], ['rsquo', [8217]], ['rsquor', [8217]], ['CloseCurlyQuote', [8217]], ['rthree', [8908]], ['rtimes', [8906]], ['rtri', [9657]], ['rtrie', [8885]], ['rtrif', [9656]], ['rtriltri', [10702]], ['RuleDelayed', [10740]], ['ruluhar', [10600]], ['rx', [8478]], ['Sacute', [346]], ['sacute', [347]], ['sbquo', [8218]], ['scap', [10936]], ['Scaron', [352]], ['scaron', [353]], ['Sc', [10940]], ['sc', [8827]], ['sccue', [8829]], ['sce', [10928]], ['scE', [10932]], ['Scedil', [350]], ['scedil', [351]], ['Scirc', [348]], ['scirc', [349]], ['scnap', [10938]], ['scnE', [10934]], ['scnsim', [8937]], ['scpolint', [10771]], ['scsim', [8831]], ['Scy', [1057]], ['scy', [1089]], ['sdotb', [8865]], ['sdot', [8901]], ['sdote', [10854]], ['searhk', [10533]], ['searr', [8600]], ['seArr', [8664]], ['searrow', [8600]], ['sect', [167]], ['semi', [59]], ['seswar', [10537]], ['setminus', [8726]], ['setmn', [8726]], ['sext', [10038]], ['Sfr', [120086]], ['sfr', [120112]], ['sfrown', [8994]], ['sharp', [9839]], ['SHCHcy', [1065]], ['shchcy', [1097]], ['SHcy', [1064]], ['shcy', [1096]], ['ShortDownArrow', [8595]], ['ShortLeftArrow', [8592]], ['shortmid', [8739]], ['shortparallel', [8741]], ['ShortRightArrow', [8594]], ['ShortUpArrow', [8593]], ['shy', [173]], ['Sigma', [931]], ['sigma', [963]], ['sigmaf', [962]], ['sigmav', [962]], ['sim', [8764]], ['simdot', [10858]], ['sime', [8771]], ['simeq', [8771]], ['simg', [10910]], ['simgE', [10912]], ['siml', [10909]], ['simlE', [10911]], ['simne', [8774]], ['simplus', [10788]], ['simrarr', [10610]], ['slarr', [8592]], ['SmallCircle', [8728]], ['smallsetminus', [8726]], ['smashp', [10803]], ['smeparsl', [10724]], ['smid', [8739]], ['smile', [8995]], ['smt', [10922]], ['smte', [10924]], ['smtes', [10924, 65024]], ['SOFTcy', [1068]], ['softcy', [1100]], ['solbar', [9023]], ['solb', [10692]], ['sol', [47]], ['Sopf', [120138]], ['sopf', [120164]], ['spades', [9824]], ['spadesuit', [9824]], ['spar', [8741]], ['sqcap', [8851]], ['sqcaps', [8851, 65024]], ['sqcup', [8852]], ['sqcups', [8852, 65024]], ['Sqrt', [8730]], ['sqsub', [8847]], ['sqsube', [8849]], ['sqsubset', [8847]], ['sqsubseteq', [8849]], ['sqsup', [8848]], ['sqsupe', [8850]], ['sqsupset', [8848]], ['sqsupseteq', [8850]], ['square', [9633]], ['Square', [9633]], ['SquareIntersection', [8851]], ['SquareSubset', [8847]], ['SquareSubsetEqual', [8849]], ['SquareSuperset', [8848]], ['SquareSupersetEqual', [8850]], ['SquareUnion', [8852]], ['squarf', [9642]], ['squ', [9633]], ['squf', [9642]], ['srarr', [8594]], ['Sscr', [119982]], ['sscr', [120008]], ['ssetmn', [8726]], ['ssmile', [8995]], ['sstarf', [8902]], ['Star', [8902]], ['star', [9734]], ['starf', [9733]], ['straightepsilon', [1013]], ['straightphi', [981]], ['strns', [175]], ['sub', [8834]], ['Sub', [8912]], ['subdot', [10941]], ['subE', [10949]], ['sube', [8838]], ['subedot', [10947]], ['submult', [10945]], ['subnE', [10955]], ['subne', [8842]], ['subplus', [10943]], ['subrarr', [10617]], ['subset', [8834]], ['Subset', [8912]], ['subseteq', [8838]], ['subseteqq', [10949]], ['SubsetEqual', [8838]], ['subsetneq', [8842]], ['subsetneqq', [10955]], ['subsim', [10951]], ['subsub', [10965]], ['subsup', [10963]], ['succapprox', [10936]], ['succ', [8827]], ['succcurlyeq', [8829]], ['Succeeds', [8827]], ['SucceedsEqual', [10928]], ['SucceedsSlantEqual', [8829]], ['SucceedsTilde', [8831]], ['succeq', [10928]], ['succnapprox', [10938]], ['succneqq', [10934]], ['succnsim', [8937]], ['succsim', [8831]], ['SuchThat', [8715]], ['sum', [8721]], ['Sum', [8721]], ['sung', [9834]], ['sup1', [185]], ['sup2', [178]], ['sup3', [179]], ['sup', [8835]], ['Sup', [8913]], ['supdot', [10942]], ['supdsub', [10968]], ['supE', [10950]], ['supe', [8839]], ['supedot', [10948]], ['Superset', [8835]], ['SupersetEqual', [8839]], ['suphsol', [10185]], ['suphsub', [10967]], ['suplarr', [10619]], ['supmult', [10946]], ['supnE', [10956]], ['supne', [8843]], ['supplus', [10944]], ['supset', [8835]], ['Supset', [8913]], ['supseteq', [8839]], ['supseteqq', [10950]], ['supsetneq', [8843]], ['supsetneqq', [10956]], ['supsim', [10952]], ['supsub', [10964]], ['supsup', [10966]], ['swarhk', [10534]], ['swarr', [8601]], ['swArr', [8665]], ['swarrow', [8601]], ['swnwar', [10538]], ['szlig', [223]], ['Tab', [9]], ['target', [8982]], ['Tau', [932]], ['tau', [964]], ['tbrk', [9140]], ['Tcaron', [356]], ['tcaron', [357]], ['Tcedil', [354]], ['tcedil', [355]], ['Tcy', [1058]], ['tcy', [1090]], ['tdot', [8411]], ['telrec', [8981]], ['Tfr', [120087]], ['tfr', [120113]], ['there4', [8756]], ['therefore', [8756]], ['Therefore', [8756]], ['Theta', [920]], ['theta', [952]], ['thetasym', [977]], ['thetav', [977]], ['thickapprox', [8776]], ['thicksim', [8764]], ['ThickSpace', [8287, 8202]], ['ThinSpace', [8201]], ['thinsp', [8201]], ['thkap', [8776]], ['thksim', [8764]], ['THORN', [222]], ['thorn', [254]], ['tilde', [732]], ['Tilde', [8764]], ['TildeEqual', [8771]], ['TildeFullEqual', [8773]], ['TildeTilde', [8776]], ['timesbar', [10801]], ['timesb', [8864]], ['times', [215]], ['timesd', [10800]], ['tint', [8749]], ['toea', [10536]], ['topbot', [9014]], ['topcir', [10993]], ['top', [8868]], ['Topf', [120139]], ['topf', [120165]], ['topfork', [10970]], ['tosa', [10537]], ['tprime', [8244]], ['trade', [8482]], ['TRADE', [8482]], ['triangle', [9653]], ['triangledown', [9663]], ['triangleleft', [9667]], ['trianglelefteq', [8884]], ['triangleq', [8796]], ['triangleright', [9657]], ['trianglerighteq', [8885]], ['tridot', [9708]], ['trie', [8796]], ['triminus', [10810]], ['TripleDot', [8411]], ['triplus', [10809]], ['trisb', [10701]], ['tritime', [10811]], ['trpezium', [9186]], ['Tscr', [119983]], ['tscr', [120009]], ['TScy', [1062]], ['tscy', [1094]], ['TSHcy', [1035]], ['tshcy', [1115]], ['Tstrok', [358]], ['tstrok', [359]], ['twixt', [8812]], ['twoheadleftarrow', [8606]], ['twoheadrightarrow', [8608]], ['Uacute', [218]], ['uacute', [250]], ['uarr', [8593]], ['Uarr', [8607]], ['uArr', [8657]], ['Uarrocir', [10569]], ['Ubrcy', [1038]], ['ubrcy', [1118]], ['Ubreve', [364]], ['ubreve', [365]], ['Ucirc', [219]], ['ucirc', [251]], ['Ucy', [1059]], ['ucy', [1091]], ['udarr', [8645]], ['Udblac', [368]], ['udblac', [369]], ['udhar', [10606]], ['ufisht', [10622]], ['Ufr', [120088]], ['ufr', [120114]], ['Ugrave', [217]], ['ugrave', [249]], ['uHar', [10595]], ['uharl', [8639]], ['uharr', [8638]], ['uhblk', [9600]], ['ulcorn', [8988]], ['ulcorner', [8988]], ['ulcrop', [8975]], ['ultri', [9720]], ['Umacr', [362]], ['umacr', [363]], ['uml', [168]], ['UnderBar', [95]], ['UnderBrace', [9183]], ['UnderBracket', [9141]], ['UnderParenthesis', [9181]], ['Union', [8899]], ['UnionPlus', [8846]], ['Uogon', [370]], ['uogon', [371]], ['Uopf', [120140]], ['uopf', [120166]], ['UpArrowBar', [10514]], ['uparrow', [8593]], ['UpArrow', [8593]], ['Uparrow', [8657]], ['UpArrowDownArrow', [8645]], ['updownarrow', [8597]], ['UpDownArrow', [8597]], ['Updownarrow', [8661]], ['UpEquilibrium', [10606]], ['upharpoonleft', [8639]], ['upharpoonright', [8638]], ['uplus', [8846]], ['UpperLeftArrow', [8598]], ['UpperRightArrow', [8599]], ['upsi', [965]], ['Upsi', [978]], ['upsih', [978]], ['Upsilon', [933]], ['upsilon', [965]], ['UpTeeArrow', [8613]], ['UpTee', [8869]], ['upuparrows', [8648]], ['urcorn', [8989]], ['urcorner', [8989]], ['urcrop', [8974]], ['Uring', [366]], ['uring', [367]], ['urtri', [9721]], ['Uscr', [119984]], ['uscr', [120010]], ['utdot', [8944]], ['Utilde', [360]], ['utilde', [361]], ['utri', [9653]], ['utrif', [9652]], ['uuarr', [8648]], ['Uuml', [220]], ['uuml', [252]], ['uwangle', [10663]], ['vangrt', [10652]], ['varepsilon', [1013]], ['varkappa', [1008]], ['varnothing', [8709]], ['varphi', [981]], ['varpi', [982]], ['varpropto', [8733]], ['varr', [8597]], ['vArr', [8661]], ['varrho', [1009]], ['varsigma', [962]], ['varsubsetneq', [8842, 65024]], ['varsubsetneqq', [10955, 65024]], ['varsupsetneq', [8843, 65024]], ['varsupsetneqq', [10956, 65024]], ['vartheta', [977]], ['vartriangleleft', [8882]], ['vartriangleright', [8883]], ['vBar', [10984]], ['Vbar', [10987]], ['vBarv', [10985]], ['Vcy', [1042]], ['vcy', [1074]], ['vdash', [8866]], ['vDash', [8872]], ['Vdash', [8873]], ['VDash', [8875]], ['Vdashl', [10982]], ['veebar', [8891]], ['vee', [8744]], ['Vee', [8897]], ['veeeq', [8794]], ['vellip', [8942]], ['verbar', [124]], ['Verbar', [8214]], ['vert', [124]], ['Vert', [8214]], ['VerticalBar', [8739]], ['VerticalLine', [124]], ['VerticalSeparator', [10072]], ['VerticalTilde', [8768]], ['VeryThinSpace', [8202]], ['Vfr', [120089]], ['vfr', [120115]], ['vltri', [8882]], ['vnsub', [8834, 8402]], ['vnsup', [8835, 8402]], ['Vopf', [120141]], ['vopf', [120167]], ['vprop', [8733]], ['vrtri', [8883]], ['Vscr', [119985]], ['vscr', [120011]], ['vsubnE', [10955, 65024]], ['vsubne', [8842, 65024]], ['vsupnE', [10956, 65024]], ['vsupne', [8843, 65024]], ['Vvdash', [8874]], ['vzigzag', [10650]], ['Wcirc', [372]], ['wcirc', [373]], ['wedbar', [10847]], ['wedge', [8743]], ['Wedge', [8896]], ['wedgeq', [8793]], ['weierp', [8472]], ['Wfr', [120090]], ['wfr', [120116]], ['Wopf', [120142]], ['wopf', [120168]], ['wp', [8472]], ['wr', [8768]], ['wreath', [8768]], ['Wscr', [119986]], ['wscr', [120012]], ['xcap', [8898]], ['xcirc', [9711]], ['xcup', [8899]], ['xdtri', [9661]], ['Xfr', [120091]], ['xfr', [120117]], ['xharr', [10231]], ['xhArr', [10234]], ['Xi', [926]], ['xi', [958]], ['xlarr', [10229]], ['xlArr', [10232]], ['xmap', [10236]], ['xnis', [8955]], ['xodot', [10752]], ['Xopf', [120143]], ['xopf', [120169]], ['xoplus', [10753]], ['xotime', [10754]], ['xrarr', [10230]], ['xrArr', [10233]], ['Xscr', [119987]], ['xscr', [120013]], ['xsqcup', [10758]], ['xuplus', [10756]], ['xutri', [9651]], ['xvee', [8897]], ['xwedge', [8896]], ['Yacute', [221]], ['yacute', [253]], ['YAcy', [1071]], ['yacy', [1103]], ['Ycirc', [374]], ['ycirc', [375]], ['Ycy', [1067]], ['ycy', [1099]], ['yen', [165]], ['Yfr', [120092]], ['yfr', [120118]], ['YIcy', [1031]], ['yicy', [1111]], ['Yopf', [120144]], ['yopf', [120170]], ['Yscr', [119988]], ['yscr', [120014]], ['YUcy', [1070]], ['yucy', [1102]], ['yuml', [255]], ['Yuml', [376]], ['Zacute', [377]], ['zacute', [378]], ['Zcaron', [381]], ['zcaron', [382]], ['Zcy', [1047]], ['zcy', [1079]], ['Zdot', [379]], ['zdot', [380]], ['zeetrf', [8488]], ['ZeroWidthSpace', [8203]], ['Zeta', [918]], ['zeta', [950]], ['zfr', [120119]], ['Zfr', [8488]], ['ZHcy', [1046]], ['zhcy', [1078]], ['zigrarr', [8669]], ['zopf', [120171]], ['Zopf', [8484]], ['Zscr', [119989]], ['zscr', [120015]], ['zwj', [8205]], ['zwnj', [8204]]];\n\nvar alphaIndex = {};\nvar charIndex = {};\n\ncreateIndexes(alphaIndex, charIndex);\n\n/**\n * @constructor\n */\nfunction Html5Entities() {}\n\n/**\n * @param {String} str\n * @returns {String}\n */\nHtml5Entities.prototype.decode = function(str) {\n if (!str || !str.length) {\n return '';\n }\n return str.replace(/&(#?[\\w\\d]+);?/g, function(s, entity) {\n var chr;\n if (entity.charAt(0) === \"#\") {\n var code = entity.charAt(1) === 'x' ?\n parseInt(entity.substr(2).toLowerCase(), 16) :\n parseInt(entity.substr(1));\n\n if (!(isNaN(code) || code < -32768 || code > 65535)) {\n chr = String.fromCharCode(code);\n }\n } else {\n chr = alphaIndex[entity];\n }\n return chr || s;\n });\n};\n\n/**\n * @param {String} str\n * @returns {String}\n */\n Html5Entities.decode = function(str) {\n return new Html5Entities().decode(str);\n };\n\n/**\n * @param {String} str\n * @returns {String}\n */\nHtml5Entities.prototype.encode = function(str) {\n if (!str || !str.length) {\n return '';\n }\n var strLength = str.length;\n var result = '';\n var i = 0;\n while (i < strLength) {\n var charInfo = charIndex[str.charCodeAt(i)];\n if (charInfo) {\n var alpha = charInfo[str.charCodeAt(i + 1)];\n if (alpha) {\n i++;\n } else {\n alpha = charInfo[''];\n }\n if (alpha) {\n result += \"&\" + alpha + \";\";\n i++;\n continue;\n }\n }\n result += str.charAt(i);\n i++;\n }\n return result;\n};\n\n/**\n * @param {String} str\n * @returns {String}\n */\n Html5Entities.encode = function(str) {\n return new Html5Entities().encode(str);\n };\n\n/**\n * @param {String} str\n * @returns {String}\n */\nHtml5Entities.prototype.encodeNonUTF = function(str) {\n if (!str || !str.length) {\n return '';\n }\n var strLength = str.length;\n var result = '';\n var i = 0;\n while (i < strLength) {\n var c = str.charCodeAt(i);\n var charInfo = charIndex[c];\n if (charInfo) {\n var alpha = charInfo[str.charCodeAt(i + 1)];\n if (alpha) {\n i++;\n } else {\n alpha = charInfo[''];\n }\n if (alpha) {\n result += \"&\" + alpha + \";\";\n i++;\n continue;\n }\n }\n if (c < 32 || c > 126) {\n result += '&#' + c + ';';\n } else {\n result += str.charAt(i);\n }\n i++;\n }\n return result;\n};\n\n/**\n * @param {String} str\n * @returns {String}\n */\n Html5Entities.encodeNonUTF = function(str) {\n return new Html5Entities().encodeNonUTF(str);\n };\n\n/**\n * @param {String} str\n * @returns {String}\n */\nHtml5Entities.prototype.encodeNonASCII = function(str) {\n if (!str || !str.length) {\n return '';\n }\n var strLength = str.length;\n var result = '';\n var i = 0;\n while (i < strLength) {\n var c = str.charCodeAt(i);\n if (c <= 255) {\n result += str[i++];\n continue;\n }\n result += '&#' + c + ';';\n i++\n }\n return result;\n};\n\n/**\n * @param {String} str\n * @returns {String}\n */\n Html5Entities.encodeNonASCII = function(str) {\n return new Html5Entities().encodeNonASCII(str);\n };\n\n/**\n * @param {Object} alphaIndex Passed by reference.\n * @param {Object} charIndex Passed by reference.\n */\nfunction createIndexes(alphaIndex, charIndex) {\n var i = ENTITIES.length;\n var _results = [];\n while (i--) {\n var e = ENTITIES[i];\n var alpha = e[0];\n var chars = e[1];\n var chr = chars[0];\n var addChar = (chr < 32 || chr > 126) || chr === 62 || chr === 60 || chr === 38 || chr === 34 || chr === 39;\n var charInfo;\n if (addChar) {\n charInfo = charIndex[chr] = charIndex[chr] || {};\n }\n if (chars[1]) {\n var chr2 = chars[1];\n alphaIndex[alpha] = String.fromCharCode(chr) + String.fromCharCode(chr2);\n _results.push(addChar && (charInfo[chr2] = alpha));\n } else {\n alphaIndex[alpha] = String.fromCharCode(chr);\n _results.push(addChar && (charInfo[''] = alpha));\n }\n }\n}\n\nmodule.exports = Html5Entities;\n" - }, - { - "id": 19, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6", - "index": 0, - "index2": 43, - "size": 40, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": null, - "issuerId": null, - "issuerName": null, - "issuerPath": null, - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": null, - "moduleIdentifier": null, - "module": null, - "moduleName": null, - "type": "babel target multi entry" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 0 - }, - { - "id": 20, - "identifier": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\babel-loader\\lib\\index.js??ref--4-0!C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902&babel-target=es6", - "name": "(webpack)-dev-server/client?http://localhost:61902", - "index": 1, - "index2": 23, - "size": 8291, - "cacheable": true, - "built": true, - "optional": false, - "prefetched": false, - "chunks": [ - 0 - ], - "issuer": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "issuerId": 19, - "issuerName": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6", - "issuerPath": [ - { - "id": 19, - "identifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "name": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6" - } - ], - "failed": false, - "errors": 0, - "warnings": 0, - "assets": [], - "reasons": [ - { - "moduleId": 19, - "moduleIdentifier": "multi C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902 ./main.js?babel-target=es6", - "module": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6", - "moduleName": "multi (webpack)-dev-server/client?http://localhost:61902#babel-target=es6 ./main.js?babel-target=es6", - "type": "babel target single entry", - "userRequest": "C:\\Users\\mikae\\Code\\flow\\flow-tests\\test-npm\\node_modules\\webpack-dev-server\\client\\index.js?http://localhost:61902", - "loc": "index:es6[0]" - } - ], - "usedExports": true, - "providedExports": null, - "optimizationBailout": [ - "ModuleConcatenation bailout: Module is not an ECMAScript module" - ], - "depth": 1, - "source": "'use strict';\n/* global __resourceQuery WorkerGlobalScope self */\n\n/* eslint prefer-destructuring: off */\n\nvar querystring = require('querystring');\n\nvar url = require('url');\n\nvar stripAnsi = require('strip-ansi');\n\nvar log = require('loglevel').getLogger('webpack-dev-server');\n\nvar socket = require('./socket');\n\nvar overlay = require('./overlay');\n\nfunction getCurrentScriptSource() {\n // `document.currentScript` is the most accurate way to find the current script,\n // but is not supported in all browsers.\n if (document.currentScript) {\n return document.currentScript.getAttribute('src');\n } // Fall back to getting all scripts in the document.\n\n\n var scriptElements = document.scripts || [];\n var currentScript = scriptElements[scriptElements.length - 1];\n\n if (currentScript) {\n return currentScript.getAttribute('src');\n } // Fail as there was no script to use.\n\n\n throw new Error('[WDS] Failed to get current script source.');\n}\n\nvar urlParts;\nvar hotReload = true;\n\nif (typeof window !== 'undefined') {\n var qs = window.location.search.toLowerCase();\n hotReload = qs.indexOf('hotreload=false') === -1;\n}\n\nif (typeof __resourceQuery === 'string' && __resourceQuery) {\n // If this bundle is inlined, use the resource query to get the correct url.\n urlParts = url.parse(__resourceQuery.substr(1));\n} else {\n // Else, get the url from the diff --git a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/PageIT.java b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/PageIT.java index 477255b3cbd..5d682634217 100644 --- a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/PageIT.java +++ b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/PageIT.java @@ -6,6 +6,7 @@ import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.Keys; import com.vaadin.flow.component.html.testbench.InputTextElement; @@ -84,10 +85,27 @@ public void testOpenUrlInNewTab() { open(); findElement(By.id("open")).click(); - ArrayList tabs = new ArrayList<>(getDriver().getWindowHandles()); + ArrayList tabs = new ArrayList<>( + getDriver().getWindowHandles()); Assert.assertThat( getDriver().switchTo().window(tabs.get(1)).getCurrentUrl(), - Matchers.endsWith(BaseHrefView.class.getName()) - ); + Matchers.endsWith(BaseHrefView.class.getName())); + } + + @Test + public void testOpenUrlInIFrame() throws InterruptedException { + open(); + + findElement(By.id("openInIFrame")).click(); + + waitUntil(driver -> !getIframeUrl().equals("about:blank")); + + Assert.assertThat(getIframeUrl(), + Matchers.endsWith(BaseHrefView.class.getName())); + } + + private String getIframeUrl() { + return (String) ((JavascriptExecutor) driver).executeScript( + "return document.getElementById('newWindow').contentWindow.location.href;"); } } diff --git a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/RouterSessionExpirationIT.java b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/RouterSessionExpirationIT.java index 3e20c884f4d..3781ec59d60 100644 --- a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/RouterSessionExpirationIT.java +++ b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/RouterSessionExpirationIT.java @@ -17,9 +17,9 @@ import org.junit.Assert; import org.junit.Test; +import org.openqa.selenium.By; import com.vaadin.flow.testutil.ChromeBrowserTest; -import org.openqa.selenium.By; public class RouterSessionExpirationIT extends ChromeBrowserTest { @@ -37,9 +37,13 @@ public void navigationAfterSessionExpired() { navigateToFirstView(); Assert.assertEquals(sessionId, getSessionId()); navigateToSesssionExpireView(); - Assert.assertEquals("No session", getSessionId()); - navigateToFirstView(); + // expired session causes page reload, after the page reload there will + // be a new session Assert.assertNotEquals(sessionId, getSessionId()); + sessionId = getSessionId(); + navigateToFirstView(); + // session is preserved + Assert.assertEquals(sessionId, getSessionId()); } @Test diff --git a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/SessionCloseLogoutIT.java b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/SessionCloseLogoutIT.java new file mode 100644 index 00000000000..24a3923c236 --- /dev/null +++ b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/SessionCloseLogoutIT.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2019 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.uitest.ui; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; + +import com.vaadin.flow.component.html.testbench.NativeButtonElement; +import com.vaadin.flow.testutil.ChromeBrowserTest; + +public class SessionCloseLogoutIT extends ChromeBrowserTest { + + @Test + public void changeOnClient() throws InterruptedException { + open(); + + $(NativeButtonElement.class).first().click(); + + asserNoErrors(); + + waitUntil(driver -> !findElements(By.tagName("a")).isEmpty()); + String sessionExpiredText = $("a").first().getText(); + Assert.assertEquals( + "Unexpected view after navigation with closed session", + "My link", sessionExpiredText); + + asserNoErrors(); + } + + private void asserNoErrors() { + checkLogsForErrors(msg -> msg.contains("VAADIN/static/client")); + } + +} diff --git a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/dependencies/DynamicDependencyIT.java b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/dependencies/DynamicDependencyIT.java index b27ed9b94c1..822c041fbf5 100644 --- a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/dependencies/DynamicDependencyIT.java +++ b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/dependencies/DynamicDependencyIT.java @@ -15,10 +15,15 @@ */ package com.vaadin.flow.uitest.ui.dependencies; +import java.util.List; +import java.util.logging.Level; + +import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; +import org.openqa.selenium.logging.LogEntry; import com.vaadin.flow.testutil.ChromeBrowserTest; @@ -32,4 +37,38 @@ public void dynamicDependencyIsExecutedBeforeOtherMessageProcessing() { // true means that the added component (a new one) is not yet in the DOM Assert.assertEquals(Boolean.TRUE.toString(), depElement.getText()); } + + @Test + public void dependecyIsNoPromise_errorLogged() { + testErrorCase("nopromise", "result is not a Promise"); + } + + @Test + public void dependecyLoaderThrows_errorLogged() + throws InterruptedException { + testErrorCase("throw", "Throw on purpose"); + } + + @Test + public void dependecyLoaderRejects_errorLogged() + throws InterruptedException { + testErrorCase("reject", "Reject on purpose"); + } + + private void testErrorCase(String caseName, String errorMessageSnippet) { + open(); + + findElement(By.id(caseName)).click(); + + String statusText = findElement(By.id("new-component")).getText(); + Assert.assertEquals("Div updated for " + caseName, statusText); + + List entries = getLogEntries(Level.SEVERE); + Assert.assertEquals(2, entries.size()); + + Assert.assertThat(entries.get(0).getMessage(), + Matchers.containsString(errorMessageSnippet)); + Assert.assertThat(entries.get(1).getMessage(), + Matchers.containsString("could not be loaded")); + } } diff --git a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/template/EventHandlerIT.java b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/template/EventHandlerIT.java index 0d76d80c076..285475a6ecd 100644 --- a/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/template/EventHandlerIT.java +++ b/flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/template/EventHandlerIT.java @@ -63,5 +63,20 @@ public void handleEventOnServer() { "Overridden server event was invoked with result: ClientSide handler", findElement(By.id("overridden-event-handler-result")) .getText()); + + // @ClientCallable return value + template.$("button").id("client").click(); + Assert.assertEquals("Server-side message should be present", + "Call from client, message: foo, true", + $("div").id("client-call").getText()); + Assert.assertEquals( + "Message from awaiting return value should be present", "FOO", + template.$("span").id("status").getText()); + + template.$("button").id("clientError").click(); + Assert.assertEquals( + "Message from awaiting exception should be present", + "Error: Something went wrong. Check server-side logs for more information.", + template.$("span").id("status").getText()); } } diff --git a/pom.xml b/pom.xml index 7ab278dd88d..20343f30a43 100644 --- a/pom.xml +++ b/pom.xml @@ -701,7 +701,6 @@ flow-test-util flow-tests flow-server-production-mode - flow-component-demo-helpers build-tools