Calling Java from JavaScript

The example:
- Creates a JavaFX
Label
and a WebView
.
- Loads an HTML document.
- Associates a
JSObject
with the JavaFX app.
- The JavaFX app exposes a method that provides the label.
- JavaScript is invoked which gets the app provided by the JSObject.
- JavaScript gets the label from the app and the text from the label.
- JavaScript sets the copied label text into an element in the HTML document which is displayed by the WebView.
Some things to note:
Use current documentation on WebEngine
(for JavaFX 19), don't use outdated documentation from JavaFX 2.
- The outdated documentation does not explain how to use the
WebEngine
within a modular environment. The additional information on modularity will be critical for some applications.
To access the WebView
, use:
requires javafx.web;
To access the JSObject
which bridges Java and the WebView, use:
requires jdk.jsobject;
To have your code accessible to JavaScript executing in WebView
, use:
opens <package-with-your-code> to javafx.web
This is required because, internally, the WebEngine
will use reflection on your code to allow JavaScript to call it.
Make sure the document is loaded before performing actions on it.
You don't (as far as I can tell) need to provide transitive access to the objects you return to the webview. For instance, in the JavaScript I call, app.getLabel().getText();
, but just opening the package with my app to javafx.web
is sufficient, I don't need to open the javafx.scene.control
package to javafx.web
. I'm not exactly sure why things work like that, but that appeared to be the case.
This JavaScript will also work:
var label = app.getLabel();
document.getElementById('text-from-java').innerHTML = label.getText();
But this next JavaScript will not work, because the type Label
is not known to JavaScript, even though when you use the var
form you can execute methods on the var which is a javafx.scene.control.Label
:
Label label = app.getLabel();
document.getElementById('text-from-java').innerHTML = label.getText();
com/example/bridge/BridgeApp.java
package com.example.bridge;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class BridgeApp extends Application {
private final Label label = new Label("Text copied from JavaFX to WebView");
private static final String HTML = // language=HTML
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<span id="text-from-java"></span>
</body>
</html>
""";
@Override
public void start(Stage stage) {
WebView webView = new WebView();
WebEngine engine = webView.getEngine();
engine.documentProperty().addListener((observable, oldValue, newDocument) -> {
JSObject window = (JSObject) engine.executeScript(
"window"
);
window.setMember(
"app",
this
);
engine.executeScript(
"document.getElementById('text-from-java').innerHTML = app.getLabel().getText();"
);
});
engine.loadContent(HTML);
VBox layout = new VBox(
10,
label,
webView
);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public Label getLabel() {
return label;
}
public static void main(String[] args) {
launch();
}
}
module-info.java
module com.example.bridge {
requires javafx.controls;
requires javafx.web;
requires jdk.jsobject;
opens com.example.bridge to javafx.graphics, javafx.web;
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>bridge</artifactId>
<version>1.0-SNAPSHOT</version>
<name>bridge</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.8.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>19</source>
<target>19</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Calling JavaScript from Java, passing data in the executeScript
call
The example in the answer is just for demo purposes, to demonstrate how to call Java code from a WebView. The same result could be accomplished by executing a script directly on the webengine as shown below, but then it wouldn't be demonstrating calling Java from JavaScript as requested in the question.
engine.executeScript(
"document.getElementById('text-from-java').innerHTML = '" + label.getText() + "';"
);