From 38214d855eb18bff503cfab7a81246dc38f39d56 Mon Sep 17 00:00:00 2001 From: Vaadin Bot Date: Mon, 21 Sep 2026 20:54:23 +0200 Subject: [PATCH 1/7] docs: document calling JavaScript declared in Java Add a section to the Calling JavaScript page explaining @JsDefinition and @JsExpression, and Element.executeJs(Class), which runs JavaScript that the build collects into the bundle instead of compiling an expression in the browser, so the call works under a content security policy without unsafe-eval. Cross-reference it from the existing executeJs(String, Object...) section, which had no mention of that limitation. Documents vaadin/flow#25749 (`39953e133526e0b268f7c24fd27bbc760bec34ea`). --- .../element-api/calling-javascript.adoc | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index c16c9941a9..02869eb7b0 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -94,6 +94,42 @@ Always pass arguments using the `$0`, `$1`, ... notation to avoid script injecti If you need to run JavaScript without having access to an element, use the [methodname]`UI.getCurrentOrThrow().getPage().executeJs()` method. +The expression is sent to the browser and compiled there, which a content security policy that does not allow `unsafe-eval` does not permit. To run JavaScript that has to work under such a policy, declare it in Java instead (see <<#declaring-javascript,Declaring JavaScript in Java>>). + + +[[declaring-javascript]] +== Declaring JavaScript in Java + +An interface annotated with [annotationname]`@JsDefinition` declares the JavaScript that its methods run, and the build collects that JavaScript into the bundle. Call the declared JavaScript through [methodname]`Element.executeJs(Class)`, which hands out an implementation of the interface; calling a method of it schedules the JavaScript that method declares. + +Each method of the interface is annotated with [annotationname]`@JsExpression`, whose value is the JavaScript expression to run. The arguments of the call are available inside the expression as `$0`, `$1`, and so on, and the element is `this` – the same contract as [methodname]`executeJs(String, Object...)`. A method returns either `void` or [classname]`PendingJavaScriptResult`, to retrieve a return value the same way [methodname]`executeJs(String, Object...)` does. + +.Declaring and calling JavaScript through a `@JsDefinition` interface +[example] +==== +[source,java] +---- +@JsDefinition +public interface GreeterJs extends Serializable { + + @JsExpression("window.alert($0)") + void showGreeting(String greeting); +} +---- + +[source,java] +---- +public void greet(String message) { + getElement().executeJs(GreeterJs.class).showGreeting(message); +} +---- +==== + +Unlike [methodname]`executeJs(String, Object...)`, the expression itself is never sent to the browser. The build generates one function per declared expression into the bundle, and the client runs that function after looking it up by an identifier of the JavaScript, so nothing is compiled from a string on the client. This is what makes the call work under a content security policy without `unsafe-eval`. + +[NOTE] +The interface is checked when [methodname]`executeJs(Class)` hands out the implementation. It must be annotated with [annotationname]`@JsDefinition`, and every method must be annotated with [annotationname]`@JsExpression` and return `void` or [classname]`PendingJavaScriptResult`. A `default` or `static` method is refused, since it is implemented in Java rather than declaring JavaScript to run in the browser. + == Return Values From eae5712b3614b8bbb224bba0530b54b88635be3d Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:41:16 +0000 Subject: [PATCH 2/7] docs: prefer declared JavaScript over expressions in the browser Put the @JsDefinition model first in the Calling JavaScript chapter and present it as the way to run server-initiated JavaScript, since it is the only one compatible with a content security policy without unsafe-eval. The string-based methods follow it, each noting that the browser compiles what they send: executeJs(String, Object...), callJsFunction(), JsFunction, and addJsInitializer(). The return value examples now use declarations. Co-Authored-By: Claude Opus 5 (1M context) --- .../element-api/calling-javascript.adoc | 229 +++++++++++------- 1 file changed, 145 insertions(+), 84 deletions(-) diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index 02869eb7b0..baeed4d69b 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -12,137 +12,100 @@ order: 3 The Element API contains methods for executing JavaScript in the browser from the server side. +Declare the JavaScript in a Java interface and call it through [methodname]`Element.executeJs(Class)`. The build collects the declared JavaScript into the frontend bundle, and the client runs it from there. Nothing is compiled from a string in the browser, so the call works under a content security policy that doesn't allow `unsafe-eval`. -== `callJsFunction` Method +The other methods described on this page -- [methodname]`executeJs(String, Object...)`, [methodname]`callJsFunction()`, [classname]`JsFunction`, and [methodname]`addJsInitializer()` -- send the JavaScript to the browser as a string that's compiled there. Avoid them for anything a declaration can express. -The [methodname]`Element.callJsFunction()` method allows you to run a client-side component function from the server side. The method accepts two parameters: the name of the function to call; and the arguments to pass to the function. -The arguments passed to the function must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, `Component`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). +[[declaring-javascript]] +== Declaring JavaScript in Java -.Calling the `clearSelection()` JavaScript function on the root element from the server side -[example] -==== -[source,java] ----- -public void clearSelection() { - getElement().callJsFunction("clearSelection"); -} ----- -==== +An interface annotated with [annotationname]`@JsDefinition` declares the JavaScript that its methods run, and the build collects that JavaScript into the bundle. Call the declared JavaScript through [methodname]`Element.executeJs(Class)`, which hands out an implementation of the interface; calling a method of it schedules the JavaScript that the method declares. -.Calling the `expand(otherComponentElement)` JavaScript function on the root element from the server side +Each method of the interface is annotated with [annotationname]`@JsExpression`, whose value is the JavaScript expression to run. The arguments of the call are available inside the expression as `$0`, `$1`, and so on, and the element the implementation was obtained from is `this`. + +The arguments must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, and `Component`. + +.Declaring and calling JavaScript through a `@JsDefinition` interface [example] ==== [source,java] ---- -public void setExpanded(Component otherComponent) { - getElement().callJsFunction("expand", - otherComponent.getElement()); +@JsDefinition +public interface GreeterJs extends Serializable { + + @JsExpression("window.alert($0)") + void showGreeting(String greeting); } ---- -==== -.Passing a JSON object to a JavaScript function -[example] -==== [source,java] ---- -public void configure(String label, int count) { - ObjectNode config = JacksonUtils.createObjectNode(); - config.put("label", label); - config.put("count", count); - config.put("enabled", true); - getElement().callJsFunction("configure", config); +public void greet(String message) { + getElement().executeJs(GreeterJs.class).showGreeting(message); } ---- ==== +A method returns either `void` or [classname]`PendingJavaScriptResult`, to retrieve a return value the same way an expression does (see <<#return-values,Return Values>>). -== `executeJs` Method - -You can also use the generic [methodname]`Element.executeJs()` method to run JavaScript asynchronously from the server side. This method can be used in addition to the [methodname]`Element.callJsFunction()` method when calling any JavaScript. - -The [methodname]`executeJs()` method accepts two parameters: the JavaScript expression to invoke; and the parameters to pass to the expression. The given parameters are available as variables named `$0`, `$1`, and so on. - -The arguments passed to the expression must be a type supported by the communication mechanism. The supported types are `String`, `Integer`, `Double`, `Boolean`, `JsonNode`, `Element`, `Component`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). - -.Calling `MyModule.complete(true)` on the client side +.Declaring JavaScript that returns a value [example] ==== [source,java] ---- -public void complete() { - getElement().executeJs("MyModule.complete($0)", true); +@JsDefinition +public interface ClipboardJs extends Serializable { + + @JsExpression("return navigator.clipboard.writeText($0)" + + ".then(() => true, () => false);") + PendingJavaScriptResult copyToClipboard(String text); } ---- -==== -.Passing a JSON array to a JavaScript expression -[example] -==== [source,java] ---- -public void setItems(List items) { - getElement().executeJs("this.items = $0", items); +public void copyOrderId(String orderId) { + getElement().executeJs(ClipboardJs.class) + .copyToClipboard(orderId) + .then(Boolean.class, this::setCopyStatus); } ---- ==== -.Avoid Script Injection Vulnerabilities -[WARNING] -Always pass arguments using the `$0`, `$1`, ... notation to avoid script injection vulnerabilities. Never concatenate or interpolate strings to build JavaScript code to be executed. +Nothing about the JavaScript is decided at the call site: the build generates one function per declared expression into the bundle, and the client runs that function after looking it up by an identifier of the JavaScript. The expression itself is never sent to the browser, and a production bundle carries the generated functions only -- the Java names stay on the server. -If you need to run JavaScript without having access to an element, use the [methodname]`UI.getCurrentOrThrow().getPage().executeJs()` method. +[NOTE] +The interface is checked when [methodname]`executeJs(Class)` hands out the implementation. It must be annotated with [annotationname]`@JsDefinition`, and every method must be annotated with [annotationname]`@JsExpression` and return `void` or [classname]`PendingJavaScriptResult`. A `default` or `static` method is refused, since it's implemented in Java rather than declaring JavaScript to run in the browser. -The expression is sent to the browser and compiled there, which a content security policy that does not allow `unsafe-eval` does not permit. To run JavaScript that has to work under such a policy, declare it in Java instead (see <<#declaring-javascript,Declaring JavaScript in Java>>). +[classname]`Page` has no declaration-based method. When the JavaScript isn't about a particular element, run it on the UI's element, which is the `` element on the client: [methodname]`UI.getCurrentOrThrow().getElement().executeJs(MyJs.class)`. +Passing a [classname]`JsFunction` as an argument brings the browser-side compilation back, since the client builds such a function from its body string. Declare a method for the JavaScript instead of passing a function into it. -[[declaring-javascript]] -== Declaring JavaScript in Java -An interface annotated with [annotationname]`@JsDefinition` declares the JavaScript that its methods run, and the build collects that JavaScript into the bundle. Call the declared JavaScript through [methodname]`Element.executeJs(Class)`, which hands out an implementation of the interface; calling a method of it schedules the JavaScript that method declares. +[[return-values]] +== Return Values -Each method of the interface is annotated with [annotationname]`@JsExpression`, whose value is the JavaScript expression to run. The arguments of the call are available inside the expression as `$0`, `$1`, and so on, and the element is `this` – the same contract as [methodname]`executeJs(String, Object...)`. A method returns either `void` or [classname]`PendingJavaScriptResult`, to retrieve a return value the same way [methodname]`executeJs(String, Object...)` does. +Add a listener to the [classname]`PendingJavaScriptResult` instance that a call answers with to access the value from a `return` statement in the JavaScript. This works the same way for a declared expression, an [methodname]`executeJs()` expression, and a function called through [methodname]`callJsFunction()`. A declared method has to declare [classname]`PendingJavaScriptResult` as its return type to give access to the result. -.Declaring and calling JavaScript through a `@JsDefinition` interface +.Checking for support of Constructable Stylesheets in the browser [example] ==== [source,java] ---- @JsDefinition -public interface GreeterJs extends Serializable { +public interface FeatureDetectionJs extends Serializable { - @JsExpression("window.alert($0)") - void showGreeting(String greeting); + @JsExpression("return 'adoptedStyleSheets' in document") + PendingJavaScriptResult supportsConstructableStylesheets(); } ---- -[source,java] ----- -public void greet(String message) { - getElement().executeJs(GreeterJs.class).showGreeting(message); -} ----- -==== - -Unlike [methodname]`executeJs(String, Object...)`, the expression itself is never sent to the browser. The build generates one function per declared expression into the bundle, and the client runs that function after looking it up by an identifier of the JavaScript, so nothing is compiled from a string on the client. This is what makes the call work under a content security policy without `unsafe-eval`. - -[NOTE] -The interface is checked when [methodname]`executeJs(Class)` hands out the implementation. It must be annotated with [annotationname]`@JsDefinition`, and every method must be annotated with [annotationname]`@JsExpression` and return `void` or [classname]`PendingJavaScriptResult`. A `default` or `static` method is refused, since it is implemented in Java rather than declaring JavaScript to run in the browser. - - -== Return Values - -The return value from the JavaScript function called using [methodname]`callJsFunction()`, or the value from a `return` statement in an `executeJs()` expression can be accessed by adding a listener to the [classname]`PendingJavaScriptResult` instance returned from either method. - -.Checking for support of Constructable Stylesheets in the browser -[example] -==== [source,java] ---- public void checkConstructableStylesheets() { - getElement().executeJs( - "return 'adoptedStyleSheets' in document") + getElement().executeJs(FeatureDetectionJs.class) + .supportsConstructableStylesheets() .then(Boolean.class, supported -> { if (supported) { System.out.println( @@ -169,7 +132,17 @@ The [methodname]`then()` method accepts a [classname]`Class` parameter for simpl ==== [source,java] ---- -getElement().executeJs("return this.getItems()") +@JsDefinition +public interface ItemsJs extends Serializable { + + @JsExpression("return this.getItems()") + PendingJavaScriptResult getItems(); +} +---- + +[source,java] +---- +getElement().executeJs(ItemsJs.class).getItems() .then(new TypeReference>() {}, items -> { // items is List @@ -183,7 +156,7 @@ An error handler can be provided as a second callback. The handler receives the [source,java] ---- -getElement().executeJs("return this.getItems()") +getElement().executeJs(ItemsJs.class).getItems() .then(new TypeReference>() {}, items -> processItems(items), errorMessage -> handleError(errorMessage)); @@ -194,18 +167,104 @@ You can also use [methodname]`toCompletableFuture(TypeReference)` to get the res [source,java] ---- CompletableFuture> future = getElement() - .executeJs("return this.getPersonMap()") + .executeJs(PersonMapJs.class).getPersonMap() .toCompletableFuture( new TypeReference>() {}); ---- +== `executeJs` Method + +The [methodname]`Element.executeJs()` method runs a JavaScript expression given as a string. The browser compiles the expression, which a content security policy that doesn't allow `unsafe-eval` blocks. Declare the JavaScript in Java instead (see <<#declaring-javascript,Declaring JavaScript in Java>>). Use this method only for JavaScript that can't be declared -- an expression that isn't known until runtime, for example. + +The [methodname]`executeJs()` method accepts two parameters: the JavaScript expression to invoke; and the parameters to pass to the expression. The given parameters are available as variables named `$0`, `$1`, and so on. + +The arguments passed to the expression must be a type supported by the communication mechanism. The supported types are `String`, `Integer`, `Double`, `Boolean`, `JsonNode`, `Element`, `Component`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). + +.Calling `MyModule.complete(true)` on the client side +[example] +==== +[source,java] +---- +public void complete() { + getElement().executeJs("MyModule.complete($0)", true); +} +---- +==== + +.Passing a JSON array to a JavaScript expression +[example] +==== +[source,java] +---- +public void setItems(List items) { + getElement().executeJs("this.items = $0", items); +} +---- +==== + +.Avoid Script Injection Vulnerabilities +[WARNING] +Always pass arguments using the `$0`, `$1`, ... notation to avoid script injection vulnerabilities. Never concatenate or interpolate strings to build JavaScript code to be executed. + +If you need to run JavaScript without having access to an element, use the [methodname]`UI.getCurrentOrThrow().getPage().executeJs()` method. + + +== `callJsFunction` Method + +The [methodname]`Element.callJsFunction()` method allows you to run a client-side component function from the server side. The method accepts two parameters: the name of the function to call; and the arguments to pass to the function. + +The call is sent to the browser as an expression and compiled there, the same way [methodname]`executeJs()` is, so it also needs a content security policy that allows `unsafe-eval`. A definition method that declares an expression such as `return this.clearSelection()` calls the same client-side function without one (see <<#declaring-javascript,Declaring JavaScript in Java>>). + +The arguments passed to the function must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, `Component`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). + +.Calling the `clearSelection()` JavaScript function on the root element from the server side +[example] +==== +[source,java] +---- +public void clearSelection() { + getElement().callJsFunction("clearSelection"); +} +---- +==== + +.Calling the `expand(otherComponentElement)` JavaScript function on the root element from the server side +[example] +==== +[source,java] +---- +public void setExpanded(Component otherComponent) { + getElement().callJsFunction("expand", + otherComponent.getElement()); +} +---- +==== + +.Passing a JSON object to a JavaScript function +[example] +==== +[source,java] +---- +public void configure(String label, int count) { + ObjectNode config = JacksonUtils.createObjectNode(); + config.put("label", label); + config.put("count", count); + config.put("enabled", true); + getElement().callJsFunction("configure", config); +} +---- +==== + + [[js-function]] [role="since:com.vaadin:vaadin@V25.2"] == Passing JavaScript Functions A [classname]`JsFunction` lets you build a reusable JavaScript function on the server and pass it as a parameter to [methodname]`executeJs()` or [methodname]`callJsFunction()`. The function arrives on the client as a real callable function with its captured values pre-bound, so you don't need to concatenate JavaScript fragments to embed server-side values. +The function body is compiled on the client, so it needs a content security policy that allows `unsafe-eval` -- also when the function is passed to a method of a JavaScript definition. Where the body is known in advance, declare the JavaScript in Java instead (see <<#declaring-javascript,Declaring JavaScript in Java>>). + The first argument to [methodname]`JsFunction.of()` is a JavaScript function body. The remaining arguments are captured values, referenced inside the body as `$0`, `$1`, … using the same naming convention as [methodname]`executeJs()` parameters. .Defining a function and invoking it @@ -286,6 +345,8 @@ The [methodname]`Element.addJsInitializer()` method registers a JavaScript expre Use this when you need to install something on the client-side DOM – an event listener, a third-party widget, an observer – and reliably tear it down. A one-shot [methodname]`executeJs()` call doesn't cover two cases: a real re-attach gives the element a brand-new DOM node that no longer has your listener, and cleanup from a server-side detach listener cannot be delivered because the element is leaving the tree. +The expression is compiled in the browser, so [methodname]`addJsInitializer()` needs a content security policy that allows `unsafe-eval`. No declaration-based counterpart exists; under a stricter policy, install and tear down from a client-side module of your own instead. + The expression syntax is the same as [methodname]`executeJs()`: `this` is the host element on the client, and parameters are referenced as `$0`, `$1`, …. If the expression returns a function, that function is invoked at teardown. .Installing a listener with cleanup @@ -308,7 +369,7 @@ Remove the registration on the server when the listener is no longer needed; the === Re-Attach Semantics -The initializer is re-run after a real re-attach – when the element is removed from the DOM in one round trip and re-added in a later one, and so the browser receives a fresh DOM node. It is *not* re-run when the element is detached and re-attached on the server inside a single round trip, because the client never discarded its DOM in that case. +The initializer is re-run after a real re-attach – when the element is removed from the DOM in one round trip and re-added in a later one, and so the browser receives a fresh DOM node. It is *not* re-run when the element is detached and re-attached on the server inside a single round trip, because the client never discarded its DOM. === Cleanup Constraints From 5a90b8f236c654ee7c94d9b570270c6cb39e33aa Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:44:10 +0000 Subject: [PATCH 3/7] docs: clarify the declared JavaScript section Mark the section with the version that introduces it, list JsFunction among the supported argument types next to the note about what passing one costs, show the method that the CompletableFuture example calls, and link the two expression-based sections from Return Values. Co-Authored-By: Claude Opus 5 (1M context) --- .../element-api/calling-javascript.adoc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index baeed4d69b..ba67654978 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -18,13 +18,16 @@ The other methods described on this page -- [methodname]`executeJs(String, Objec [[declaring-javascript]] +[role="since:com.vaadin:vaadin@V25.4"] == Declaring JavaScript in Java An interface annotated with [annotationname]`@JsDefinition` declares the JavaScript that its methods run, and the build collects that JavaScript into the bundle. Call the declared JavaScript through [methodname]`Element.executeJs(Class)`, which hands out an implementation of the interface; calling a method of it schedules the JavaScript that the method declares. Each method of the interface is annotated with [annotationname]`@JsExpression`, whose value is the JavaScript expression to run. The arguments of the call are available inside the expression as `$0`, `$1`, and so on, and the element the implementation was obtained from is `this`. -The arguments must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, and `Component`. +The arguments must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, `Component`, and `JsFunction`. + +Passing a [classname]`JsFunction` is allowed, but it brings the browser-side compilation back, since the client builds such a function from its body string. Declare a method for the JavaScript instead of passing a function into it. .Declaring and calling JavaScript through a `@JsDefinition` interface [example] @@ -80,13 +83,11 @@ The interface is checked when [methodname]`executeJs(Class)` hands out the imple [classname]`Page` has no declaration-based method. When the JavaScript isn't about a particular element, run it on the UI's element, which is the `` element on the client: [methodname]`UI.getCurrentOrThrow().getElement().executeJs(MyJs.class)`. -Passing a [classname]`JsFunction` as an argument brings the browser-side compilation back, since the client builds such a function from its body string. Declare a method for the JavaScript instead of passing a function into it. - [[return-values]] == Return Values -Add a listener to the [classname]`PendingJavaScriptResult` instance that a call answers with to access the value from a `return` statement in the JavaScript. This works the same way for a declared expression, an [methodname]`executeJs()` expression, and a function called through [methodname]`callJsFunction()`. A declared method has to declare [classname]`PendingJavaScriptResult` as its return type to give access to the result. +Add a listener to the [classname]`PendingJavaScriptResult` instance that a call answers with to access the value from a `return` statement in the JavaScript. This works the same way for a declared expression, an <<#execute-js,[methodname]`executeJs()` expression>>, and a function called through <<#call-js-function,[methodname]`callJsFunction()`>>. A declared method has to declare [classname]`PendingJavaScriptResult` as its return type to give access to the result. .Checking for support of Constructable Stylesheets in the browser [example] @@ -137,6 +138,9 @@ public interface ItemsJs extends Serializable { @JsExpression("return this.getItems()") PendingJavaScriptResult getItems(); + + @JsExpression("return this.getPersonMap()") + PendingJavaScriptResult getPersonMap(); } ---- @@ -167,12 +171,13 @@ You can also use [methodname]`toCompletableFuture(TypeReference)` to get the res [source,java] ---- CompletableFuture> future = getElement() - .executeJs(PersonMapJs.class).getPersonMap() + .executeJs(ItemsJs.class).getPersonMap() .toCompletableFuture( new TypeReference>() {}); ---- +[[execute-js]] == `executeJs` Method The [methodname]`Element.executeJs()` method runs a JavaScript expression given as a string. The browser compiles the expression, which a content security policy that doesn't allow `unsafe-eval` blocks. Declare the JavaScript in Java instead (see <<#declaring-javascript,Declaring JavaScript in Java>>). Use this method only for JavaScript that can't be declared -- an expression that isn't known until runtime, for example. @@ -210,6 +215,7 @@ Always pass arguments using the `$0`, `$1`, ... notation to avoid script injecti If you need to run JavaScript without having access to an element, use the [methodname]`UI.getCurrentOrThrow().getPage().executeJs()` method. +[[call-js-function]] == `callJsFunction` Method The [methodname]`Element.callJsFunction()` method allows you to run a client-side component function from the server side. The method accepts two parameters: the name of the function to call; and the arguments to pass to the function. From 9efa009673a8a67fb07de56fcfad61da4d53b0ae Mon Sep 17 00:00:00 2001 From: Vaadin Bot Date: Tue, 22 Sep 2026 15:01:44 +0200 Subject: [PATCH 4/7] docs: document declaring page-level JavaScript in Java Add a section on Page.executeJs(Class) to Browser Access, showing how to declare JavaScript with @JsDefinition/@JsExpression instead of a string expression, and why that's needed under a content security policy that forbids unsafe-eval. Documents vaadin/flow#25799 (`53424b8774b839b1ccafeaf5465565b7441419fe`). (cherry picked from commit 03891bbb294fdf18a5e2df28fd5b61171e47bb25) --- articles/flow/advanced/browser-access.adoc | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/articles/flow/advanced/browser-access.adoc b/articles/flow/advanced/browser-access.adoc index 2fa09e6437..c52ade66b0 100644 --- a/articles/flow/advanced/browser-access.adoc +++ b/articles/flow/advanced/browser-access.adoc @@ -130,6 +130,32 @@ The parameter value is `null` for a parameter of type [classname]`Element` that The script is executed asynchronously, so you can't directly pass values back to the server. Instead, you can use the returned [classname]`PendingJavaScriptResult` instance to add a callback that's called when the result is available. + +=== Declaring JavaScript in Java + +[methodname]`Page.executeJs(Class)` runs JavaScript declared in Java instead of as a string expression. Annotate an interface with [annotationname]`@JsDefinition`, and annotate each of its methods with [annotationname]`@JsExpression` giving the JavaScript that method runs, with the method's own arguments available as `$0`, `$1`, and so on. Calling [methodname]`Page.executeJs(Class)` returns an implementation of the interface, and calling one of its methods queues that method's JavaScript the same way a call to [methodname]`executeJs(String, Object...)` does. + +The JavaScript runs on nothing in particular, since page-level JavaScript works on globals rather than on an element. A method that declares a return value can use the returned [classname]`PendingJavaScriptResult` the same way [methodname]`executeJs(String, Object...)` does. + +Unlike a string expression, no JavaScript is sent to the browser or compiled there: the build collects every declaration into the client bundle, and the browser looks up the function by an identifier instead. This lets the call run in an application whose content security policy forbids `unsafe-eval`. + +.Copying text to the clipboard under a content security policy that forbids `unsafe-eval` +[source,java] +---- +@JsDefinition +public interface ClipboardJs extends Serializable { + + @JsExpression("return navigator.clipboard.writeText($0).then(() => true)") + PendingJavaScriptResult writeText(String text); +} + +ClipboardJs clipboard = UI.getCurrentOrThrow().getPage() + .executeJs(ClipboardJs.class); + +clipboard.writeText(order.getTrackingCode()) + .then(Boolean.class, copied -> Notification.show("Copied")); +---- + == Scrolling a Component into View You can scroll any component into the visible area of the browser window using [methodname]`scrollIntoView()`. This calls the browser's native `scrollIntoView()` on the component's element. From 4128a657ecdc5ff225cf57fc623a1c6039e8f494 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:36:34 +0000 Subject: [PATCH 5/7] docs: lead with declared JavaScript in Browser Access too Page.executeJs(Class) now comes first in the Executing JavaScript in the Browser section, with the string expression below it as the fallback that needs unsafe-eval, and the two pages point at each other. Co-Authored-By: Claude Opus 5 (1M context) --- articles/flow/advanced/browser-access.adoc | 61 +++++++++++-------- .../element-api/calling-javascript.adoc | 2 +- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/articles/flow/advanced/browser-access.adoc b/articles/flow/advanced/browser-access.adoc index c52ade66b0..15cdf0bf12 100644 --- a/articles/flow/advanced/browser-access.adoc +++ b/articles/flow/advanced/browser-access.adoc @@ -104,38 +104,17 @@ For that reason, the [methodname]`isIOS()` and [methodname]`isIPad()` methods ar == Executing JavaScript in the Browser You can use server-side Java to execute JavaScript snippets in the browser. -You can also pass parameters to the executed script as variables named `$0`, `$1`, and so on. -Vaadin automatically serializes and escapes the parameter values. - -You can execute JavaScript in the browser and pass parameters as follows: - -[source,java] ----- -public static void logElementSize(String name, - Element element) { - Page page = UI.getCurrentOrThrow().getPage(); - - page.executeJs( - "console.log($0 + ' size:', " - + "$1.offsetWidth, $1.offsetHeight)", - name, element); -} ----- +Declare the JavaScript in a Java interface and run it through [methodname]`Page.executeJs(Class)`: the build collects the declaration into the client bundle, so nothing is compiled from a string in the browser and the call works under a content security policy that doesn't allow `unsafe-eval`. -The supported parameter types are: `String`, `Boolean`, `Integer`, `Double`, `JsonValue`, and `Element`. - -The script is executed after the DOM tree has been updated based on server-side changes. -The parameter value is `null` for a parameter of type [classname]`Element` that isn't attached after the update (according to the server-side component structure). - -The script is executed asynchronously, so you can't directly pass values back to the server. -Instead, you can use the returned [classname]`PendingJavaScriptResult` instance to add a callback that's called when the result is available. +For JavaScript that works on a particular element rather than on the page as a whole, use the Element API instead (see <>). +[role="since:com.vaadin:vaadin@V25.4"] === Declaring JavaScript in Java [methodname]`Page.executeJs(Class)` runs JavaScript declared in Java instead of as a string expression. Annotate an interface with [annotationname]`@JsDefinition`, and annotate each of its methods with [annotationname]`@JsExpression` giving the JavaScript that method runs, with the method's own arguments available as `$0`, `$1`, and so on. Calling [methodname]`Page.executeJs(Class)` returns an implementation of the interface, and calling one of its methods queues that method's JavaScript the same way a call to [methodname]`executeJs(String, Object...)` does. -The JavaScript runs on nothing in particular, since page-level JavaScript works on globals rather than on an element. A method that declares a return value can use the returned [classname]`PendingJavaScriptResult` the same way [methodname]`executeJs(String, Object...)` does. +The JavaScript runs on nothing in particular, since page-level JavaScript works with global browser APIs rather than with an element. A method that declares a return value can use the returned [classname]`PendingJavaScriptResult` the same way [methodname]`executeJs(String, Object...)` does. Unlike a string expression, no JavaScript is sent to the browser or compiled there: the build collects every declaration into the client bundle, and the browser looks up the function by an identifier instead. This lets the call run in an application whose content security policy forbids `unsafe-eval`. @@ -156,6 +135,38 @@ clipboard.writeText(order.getTrackingCode()) .then(Boolean.class, copied -> Notification.show("Copied")); ---- + +=== Running a JavaScript Expression + +[methodname]`Page.executeJs(String, Object...)` takes the JavaScript as a string. +You can pass parameters to the executed script as variables named `$0`, `$1`, and so on. +Vaadin automatically serializes and escapes the parameter values. +The browser compiles the expression, which a content security policy that doesn't allow `unsafe-eval` blocks, so prefer a declaration for JavaScript that's known in advance. + +You can execute JavaScript in the browser and pass parameters as follows: + +[source,java] +---- +public static void logElementSize(String name, + Element element) { + Page page = UI.getCurrentOrThrow().getPage(); + + page.executeJs( + "console.log($0 + ' size:', " + + "$1.offsetWidth, $1.offsetHeight)", + name, element); +} +---- + +The supported parameter types are: `String`, `Boolean`, `Integer`, `Double`, `JsonValue`, and `Element`. + +The script is executed after the DOM tree has been updated based on server-side changes. +The parameter value is `null` for a parameter of type [classname]`Element` that isn't attached after the update (according to the server-side component structure). + +The script is executed asynchronously, so you can't directly pass values back to the server. +Instead, you can use the returned [classname]`PendingJavaScriptResult` instance to add a callback that's called when the result is available. + + == Scrolling a Component into View You can scroll any component into the visible area of the browser window using [methodname]`scrollIntoView()`. This calls the browser's native `scrollIntoView()` on the component's element. diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index ba67654978..cee0240c1e 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -81,7 +81,7 @@ Nothing about the JavaScript is decided at the call site: the build generates on [NOTE] The interface is checked when [methodname]`executeJs(Class)` hands out the implementation. It must be annotated with [annotationname]`@JsDefinition`, and every method must be annotated with [annotationname]`@JsExpression` and return `void` or [classname]`PendingJavaScriptResult`. A `default` or `static` method is refused, since it's implemented in Java rather than declaring JavaScript to run in the browser. -[classname]`Page` has no declaration-based method. When the JavaScript isn't about a particular element, run it on the UI's element, which is the `` element on the client: [methodname]`UI.getCurrentOrThrow().getElement().executeJs(MyJs.class)`. +When the JavaScript isn't about a particular element, declare it the same way and run it through [methodname]`Page.executeJs(Class)`, which works with global browser APIs instead of with an element (see <>). [[return-values]] From dc743cfb52eca7fc4b9b10a35a773ff16722b2e4 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:41:08 +0000 Subject: [PATCH 6/7] docs: trim the page-level JavaScript section Keep only what is specific to Page.executeJs(Class) and point at the element page for the rules a definition follows, so the two sections do not repeat each other. Give the element page an element-scoped example that uses this, leaving the clipboard one to the page-level section. Co-Authored-By: Claude Opus 5 (1M context) --- articles/flow/advanced/browser-access.adoc | 26 +++++++++++-------- .../element-api/calling-javascript.adoc | 15 +++++------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/articles/flow/advanced/browser-access.adoc b/articles/flow/advanced/browser-access.adoc index 15cdf0bf12..115dcc59e4 100644 --- a/articles/flow/advanced/browser-access.adoc +++ b/articles/flow/advanced/browser-access.adoc @@ -112,28 +112,32 @@ For JavaScript that works on a particular element rather than on the page as a w [role="since:com.vaadin:vaadin@V25.4"] === Declaring JavaScript in Java -[methodname]`Page.executeJs(Class)` runs JavaScript declared in Java instead of as a string expression. Annotate an interface with [annotationname]`@JsDefinition`, and annotate each of its methods with [annotationname]`@JsExpression` giving the JavaScript that method runs, with the method's own arguments available as `$0`, `$1`, and so on. Calling [methodname]`Page.executeJs(Class)` returns an implementation of the interface, and calling one of its methods queues that method's JavaScript the same way a call to [methodname]`executeJs(String, Object...)` does. +[methodname]`Page.executeJs(Class)` runs the JavaScript that an interface annotated with [annotationname]`@JsDefinition` declares, instead of a string expression. It returns an implementation of the interface, and calling a method of that implementation queues the JavaScript the method declares with [annotationname]`@JsExpression`, with the arguments of the call available as `$0`, `$1`, and so on. What such an interface may contain is described in <>. -The JavaScript runs on nothing in particular, since page-level JavaScript works with global browser APIs rather than with an element. A method that declares a return value can use the returned [classname]`PendingJavaScriptResult` the same way [methodname]`executeJs(String, Object...)` does. +The JavaScript runs on nothing in particular, since page-level JavaScript works with global browser APIs, where the JavaScript of [methodname]`Element.executeJs(Class)` runs on the element it was obtained from. A method that declares a return value can use the returned [classname]`PendingJavaScriptResult` the same way [methodname]`executeJs(String, Object...)` does. -Unlike a string expression, no JavaScript is sent to the browser or compiled there: the build collects every declaration into the client bundle, and the browser looks up the function by an identifier instead. This lets the call run in an application whose content security policy forbids `unsafe-eval`. - -.Copying text to the clipboard under a content security policy that forbids `unsafe-eval` +.Copying text to the clipboard +[example] +==== [source,java] ---- @JsDefinition public interface ClipboardJs extends Serializable { - @JsExpression("return navigator.clipboard.writeText($0).then(() => true)") + @JsExpression("return navigator.clipboard.writeText($0)" + + ".then(() => true, () => false);") PendingJavaScriptResult writeText(String text); } +---- -ClipboardJs clipboard = UI.getCurrentOrThrow().getPage() - .executeJs(ClipboardJs.class); - -clipboard.writeText(order.getTrackingCode()) - .then(Boolean.class, copied -> Notification.show("Copied")); +[source,java] +---- +UI.getCurrentOrThrow().getPage().executeJs(ClipboardJs.class) + .writeText(order.getTrackingCode()) + .then(Boolean.class, copied -> Notification.show( + copied ? "Copied" : "Copying failed")); ---- +==== === Running a JavaScript Expression diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index cee0240c1e..4bc9cd79ff 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -58,20 +58,19 @@ A method returns either `void` or [classname]`PendingJavaScriptResult`, to retri [source,java] ---- @JsDefinition -public interface ClipboardJs extends Serializable { +public interface OverflowJs extends Serializable { - @JsExpression("return navigator.clipboard.writeText($0)" - + ".then(() => true, () => false);") - PendingJavaScriptResult copyToClipboard(String text); + @JsExpression("return this.scrollWidth > this.clientWidth") + PendingJavaScriptResult isContentClipped(); } ---- [source,java] ---- -public void copyOrderId(String orderId) { - getElement().executeJs(ClipboardJs.class) - .copyToClipboard(orderId) - .then(Boolean.class, this::setCopyStatus); +public void updateTooltip() { + getElement().executeJs(OverflowJs.class) + .isContentClipped() + .then(Boolean.class, this::setTooltipEnabled); } ---- ==== From 15fb81a9ae59ba0742ca4763a422f5e0b8124c35 Mon Sep 17 00:00:00 2001 From: "totally-not-ai[bot]" <290682512+totally-not-ai[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 13:47:52 +0000 Subject: [PATCH 7/7] docs: use printing as the page-level JavaScript example The clipboard has a built-in API, so a declaration for it is not what a reader should reach for. Opening the browser print dialog has no framework counterpart and is page-level in the same way. Co-Authored-By: Claude Opus 5 (1M context) --- articles/flow/advanced/browser-access.adoc | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/articles/flow/advanced/browser-access.adoc b/articles/flow/advanced/browser-access.adoc index 115dcc59e4..94024ab6de 100644 --- a/articles/flow/advanced/browser-access.adoc +++ b/articles/flow/advanced/browser-access.adoc @@ -116,26 +116,23 @@ For JavaScript that works on a particular element rather than on the page as a w The JavaScript runs on nothing in particular, since page-level JavaScript works with global browser APIs, where the JavaScript of [methodname]`Element.executeJs(Class)` runs on the element it was obtained from. A method that declares a return value can use the returned [classname]`PendingJavaScriptResult` the same way [methodname]`executeJs(String, Object...)` does. -.Copying text to the clipboard +.Opening the browser's print dialog [example] ==== [source,java] ---- @JsDefinition -public interface ClipboardJs extends Serializable { +public interface PrintJs extends Serializable { - @JsExpression("return navigator.clipboard.writeText($0)" - + ".then(() => true, () => false);") - PendingJavaScriptResult writeText(String text); + @JsExpression("window.print()") + void print(); } ---- [source,java] ---- -UI.getCurrentOrThrow().getPage().executeJs(ClipboardJs.class) - .writeText(order.getTrackingCode()) - .then(Boolean.class, copied -> Notification.show( - copied ? "Copied" : "Copying failed")); +printButton.addClickListener(event -> UI.getCurrentOrThrow() + .getPage().executeJs(PrintJs.class).print()); ---- ====