Why does JavaFX's WebEngine not display images when providing a path without scheme?

119 Views Asked by At

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...

1

There are 1 best solutions below

2
James_D On BEST ANSWER

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();
    }
}