htmljavafxurijavafx-webengine

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


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


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