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

Time:09-10

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