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...
CodePudding user response:
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();
}
}