JavaFX stop opening URL in WebView - open in browser instead


Question

The embedded WebView browser I am using needs special handling for particular URLs, to open them in the native default browser instead of WebView. The actual browsing part works fine but I need to stop the WebView from displaying that page as well. I can think of several ways to do it but none of them work. Here is my code:

this.wv.getEngine().locationProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
    {
        Desktop d = Desktop.getDesktop();
        try
        {
            URI address = new URI(observable.getValue());
            if ((address.getQuery() + "").indexOf("_openmodal=true") > -1)
            {
                // wv.getEngine().load(oldValue); // 1
                // wv.getEngine().getLoadWorker().cancel(); // 2
                // wv.getEngine().executeScript("history.back()"); // 3
                d.browse(address);
            }
        }
        catch (IOException | URISyntaxException e)
        {
            displayError(e);
        }
    }
});

A bit more info about what happens in each of three cases

1. Loading the previous address

wv.getEngine().load(oldValue);

This kills the JVM. Funnily enough, the page opens fine in the native browser.

# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000005b8fef38, pid=7440, tid=8000
#
# JRE version: 7.0_09-b05
# Java VM: Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C  [jfxwebkit.dll+0x2fef38]  Java_com_sun_webpane_platform_BackForwardList_bflItemGetIcon+0x184f58
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\Greg Balaga\eclipse\Companyapp\hs_err_pid7440.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.sun.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.

2. Cancelling the worker

wv.getEngine().getLoadWorker().cancel();

Does nothing, the page loads in both the WebView and native browser.

3. Using history.back()

wv.getEngine().executeScript("history.back()");

Same as above, no effect.

4. Reacting to Stage changes instead

I have also tried to instead of looking the locationProperty of WebEngine, listen on chenges for stateProperty of the Worker and fire the same opening code if newState == State.SCHEDULED. There was no difference in result from previous method (apart from not actually being able to use #1).


Update

The code I'm using now still crashes the JVM:

this.wv.getEngine().locationProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, final String oldValue, String newValue)
    {
        Desktop d = Desktop.getDesktop();
        try
        {
            URI address = new URI(newValue);
            if ((address.getQuery() + "").indexOf("_openmodal=true") > -1)
            {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run()
                    {
                        wv.getEngine().load(oldValue);
                    }
                });
                d.browse(address);
            }
        }
        catch (IOException | URISyntaxException e)
        {
            displayError(e);
        }
    }
});

Workaround

Ok I managed to make it work by tearing down the webview and rebuilding it.

this.wv.getEngine().locationProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, final String oldValue, String newValue)
    {
        Desktop d = Desktop.getDesktop();
        try
        {
            URI address = new URI(newValue);
            if ((address.getQuery() + "").indexOf("_openmodal=true") > -1)
            {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run()
                    {
                        grid_layout.getChildren().remove(wv);
                        wv = new WebView();
                        grid_layout.add(wv, 0, 1);
                        wv.getEngine().load(oldValue);
                    }
                });
                d.browse(address);
            }
        }
        catch (IOException | URISyntaxException e)
        {
            displayError(e);
        }
    }
});
1
11
3/26/2013 12:31:17 PM

Accepted Answer

There is another method for handling this.

You can add an event listener to the DOM elements and intercept it that way.

Example:

NodeList nodeList = document.getElementsByTagName("a");
            for (int i = 0; i < nodeList.getLength(); i++)
            {
                Node node= nodeList.item(i);
                EventTarget eventTarget = (EventTarget) node;
                eventTarget.addEventListener("click", new EventListener()
                {
                    @Override
                    public void handleEvent(Event evt)
                    {
                        EventTarget target = evt.getCurrentTarget();
                        HTMLAnchorElement anchorElement = (HTMLAnchorElement) target;
                        String href = anchorElement.getHref();
                        //handle opening URL outside JavaFX WebView
                        System.out.println(href);
                        evt.preventDefault();
                    }
                }, false);
            }

Where document is the DOM document object. Make sure this is done after the document has finished loading.

18
8/30/2013 3:29:58 PM

I finally found a working solution that worked for me:

public class HyperlinkRedirectListener implements ChangeListener<Worker.State>, EventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(HyperlinkRedirectListener.class);

private static final String CLICK_EVENT = "click";
private static final String ANCHOR_TAG = "a";

private final WebView webView;

public HyperlinkRedirectListener(WebView webView) {
    this.webView = webView;
}

@Override
public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) {
    if (State.SUCCEEDED.equals(newValue)) {
        Document document = webView.getEngine().getDocument();
        NodeList anchors = document.getElementsByTagName(ANCHOR_TAG);
        for (int i = 0; i < anchors.getLength(); i++) {
            Node node = anchors.item(i);
            EventTarget eventTarget = (EventTarget) node;
            eventTarget.addEventListener(CLICK_EVENT, this, false);
        }
    }
}

@Override
public void handleEvent(Event event) {
    HTMLAnchorElement anchorElement = (HTMLAnchorElement)event.getCurrentTarget();
    String href = anchorElement.getHref();

    if (Desktop.isDesktopSupported()) {
        openLinkInSystemBrowser(href);
    } else {
        LOGGER.warn("OS does not support desktop operations like browsing. Cannot open link '{}'.", href);
    }

    event.preventDefault();
}

private void openLinkInSystemBrowser(String url) {
    LOGGER.debug("Opening link '{}' in default system browser.", url);

    try {
        URI uri = new URI(url);
        Desktop.getDesktop().browse(uri);
    } catch (Throwable e) {
        LOGGER.error("Error on opening link '{}' in system browser.", url);
    }
}

}

webView.getEngine().getLoadWorker().stateProperty().addListener(new HyperlinkRedirectListener(webView));


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon