JavaFX's WebEngine
, which is part of the WebView
node, does not seem to be able to display images whose paths are declared as a "normal" file path, like /home/user/images/image.png
, unlike conventional browsers. It will however display the image when adding a scheme to the URI, which would look like that: file:///home/user/images/image.png
.
Why is that?
Here is some context to why I am asking this question:
I have some HTML in memory that contains img
tags pointing to image files stored on disk. The paths used to identify those images are absolute, so in the form of /home/user/images/image.png
. That HTML then gets loaded using the WebEngine
's loadContent
method. However, as mentioned above, the WebEngine
will not display those images. So the only solution I have yet come up with is to go through the HTML and append the scheme to each path. But I find this to be quite an ugly solution...
Relative URLs in an HTML page will be resolved in the context of the location of the page. Thus if you open an HTML file in a browser, the location of the page is file:///path/to/file.html
. An absolute file path /home/user/images/image.png
resolved relative to that URL will resolve, as expected, to file:///home/user/images/image.png
. In other words, the relative resolution of the URL preserves the scheme file://
.
In the case where you load HTML into the JavaFX WebView
using the loadContent
method, there is no location (the HTML is simply in memory) and thus resolution of a relative URL will not work (there is no scheme).
I can think of two solutions. One requires a change to the HTML, which may or may not be convenient. The solution here is simply to add a <base>
element to the <head>
of the HTML, specifying file:///
as the document base.
Here is a complete example demonstrating this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FileChooser chooser = new FileChooser();
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
Button browse = new Button("Browse");
browse.setOnAction(e -> chooseImage(chooser, stage, webEngine));
BorderPane root = new BorderPane();
root.setTop(new HBox(5, browse));
root.setCenter(webView);
Scene scene = new Scene(root, 800, 500);
stage.setScene(scene);
stage.show();
}
private void chooseImage(FileChooser chooser, Stage stage, WebEngine webEngine) {
File file = chooser.showOpenDialog(stage);
if (file == null) return;
String imagePath = file.getAbsolutePath();
String html = """
<html>
<head>
<base href="file:///"></base>
</head>
<body>
<div>Image:</div>
<img src="%s"/>
</body>
</html>
""";
html = String.format(html, imagePath);
webEngine.loadContent(html);
}
public static void main(String[] args) {
launch();
}
}
If it's not possible (or desirable) to modify the HTML for some reason, the other solution is to write the HTML to a temporary file and load it from the file. This is the same example using this approach. You probably want to clean up the file afterwards, which involves a little work (though not too much).
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
public class HelloApplication extends Application {
private Path tempDir ;
@Override
public void init() {
try {
tempDir = Files.createTempDirectory(Paths.get(System.getProperty("user.home")), ".myApp");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void start(Stage stage) throws IOException {
FileChooser chooser = new FileChooser();
WebView webView = new WebView();
WebEngine webEngine = webView.getEngine();
Button browse = new Button("Browse");
browse.setOnAction(e -> chooseImage(chooser, stage, webEngine));
BorderPane root = new BorderPane();
root.setTop(new HBox(5, browse));
root.setCenter(webView);
Scene scene = new Scene(root, 800, 500);
stage.setScene(scene);
stage.show();
}
private void chooseImage(FileChooser chooser, Stage stage, WebEngine webEngine) {
File file = chooser.showOpenDialog(stage);
if (file == null) return;
String imagePath = file.getAbsolutePath();
String html = """
<html>
<head>
</head>
<body>
<div>Image:</div>
<img src="%s"/>
</body>
</html>
""";
html = String.format(html, imagePath);
try {
Path tempFile = Files.createTempFile(tempDir, "page", ".html");
Files.writeString(tempFile, html);
String url = tempFile.toUri().toString();
System.out.println(url);
webEngine.load(url);
} catch (IOException e) {
throw new RuntimeException(e);
}
//webEngine.loadContent(html);
}
@Override
public void stop() {
try {
List<Path> files = Files.list(tempDir).collect(Collectors.toList());
for (Path f : files) Files.delete(f);
Files.delete(tempDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
launch();
}
}