javakotlinjavafxurl-fragment

Javafx getHostServices().showDocument not working when url includes fragments for local files


Im working on a javafx app and a requirement is that on clicking a hyperlink, it opens the given url in the browser. I have this working with the following

url = "files/file.html";
getHostServices().showDocument(url);

However, when the url includes an id to auto focus a specific portion of the page, the browser never launches. There's no errors, and nothing seems to be logged indicating anything went wrong, the browser just never opens. Having trouble finding anyone else who's run into this problem or any potential solutions.

tldr; How do I get the below to actually work and open in a browser?

String url = "files/file.html#section";
getHostServices().showDocument(url);

Also, this is only an issue with local files it seems. Opening https sites this way works fine

UPDATE: So thanks to Sai's great post, it sounds like the official conclusion is that it's a deeper issue than Java, and simply blocked for security reasons relating to data injection. Won't pretend to understand, but I found a solution based on this link (again shared by Sai below) and more specifically this link shared in that answer, and wanted to include it for any future confused people. I also updated the title to be more specific to the problem.

So basically, the only true way around it that gets all the functionality is to create a temporary file that then gets opened, automatically redirects to the proper file with the fragment, and then delete it. Basic code is as follows:

public static void openWebPage(String url) {
    String tmpPath = "path/to/.tmp/temp.html";
    File tempFile = File(tmpPath);
    if (!tempFile.exists()) {
        tempFile.mkdirs();
    } else {
        tempFile.delete();
    }
    tempFile.createNewFile();
    FileWriter tempFileWriter = FileWriter(tmpPath);
    tempFileWriter.write("<html><meta http-equiv=Refresh content=\"0; url=$url\"><body></body></html>");
    tempFileWriter.close();
    Main.getStaticHostServices().showDocument(tmpPath);
}

Solution

  • When you check the source code of the HostServices, it is actually just a plain java code using Runtime.exec, something like this (for windows):

    Runtime.getRuntime().exec(new String[] {
                            "rundll32",
                            "url.dll,FileProtocolHandler",
                            uri
                        });
    

    Pretty much it is quite evident that there is nothing with JavaFX to play a role here.

    So when trying to look for alternate ways using plain Java:

    Desktop desktop = Desktop.getDesktop();
    try {
        desktop.browse(new URI(url));
    } catch (IOException ex) {
        throw new RuntimeException(ex);
    } catch (URISyntaxException ex) {
        throw new RuntimeException(ex);
    }
    

    This works well when opening the file(without id). But when appended with id, it displays the below error::

    Caused by: java.io.IOException: Failed to open <your filepath>. Error message: The system cannot find the file specified.
    
        at java.desktop/sun.awt.windows.WDesktopPeer.ShellExecute(WDesktopPeer.java:115)
        at java.desktop/sun.awt.windows.WDesktopPeer.browse(WDesktopPeer.java:101)
        at java.desktop/java.awt.Desktop.browse(Desktop.java:533)
    

    From the above exception, it is quite evident that the execution is going till the native code and returned with the error message. Which in turn concludes that the api or native code or execution (or whatever it is), is looking for a file path which has # in it. And as it cannot find one, error is thrown.

    So I conclude that, currently there is no support to open fragment of html from a local file using standard java API.

    @Others, feel free to correct me if my analysis is wrong :)

    UPDATE 1:

    After going through this link, looks like we need to prepend with file:/// to the path if we are using url.dll and FileProtocolHandler.

    So changing the code to below actually opened the browser.

    String url="file:///C:/folder/test.html#section";
    getHostServices().showDocument(uri);
    

    But unfortunately the focus to specific portion is still not working. Atleast I will consider this as a win, rather than not showing up anything ;)

    UPDATE 2:

    Ok, looks like I got something for the behavior in Update 1. Refer to the these links link1 and link2.

    After some back-and-forth with Microsoft's MSDN team, they reviewed the source code to the ShellExecute() call and it was determined that yes, when processing File:/// based URLs in ShellExecute(), the ShellExecute() call will strip off the # and any data it finds after the # before launching the default browser and sending in the HTML page to open. MS's stance is that they do this deliberately to prevent injections into the function.