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
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.
wv.getEngine().getLoadWorker().cancel();
Does nothing, the page loads in both the WebView and native browser.
wv.getEngine().executeScript("history.back()");
Same as above, no effect.
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).
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);
}
}
});
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);
}
}
});
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.
I finally found a working solution that worked for me:
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.web.WebView;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.html.HTMLAnchorElement;
import java.awt.*;
import java.net.URI;
public class HyperLinkRedirectListener implements ChangeListener<Worker.State>, EventListener
{
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 Worker.State> observable, Worker.State oldValue, Worker.State newValue)
{
if (Worker.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);
}
}
}
Usage:
webView.getEngine().getLoadWorker().stateProperty().addListener(new HyperLinkRedirectListener(webView));
This worked for me as I had to generically trap any anchor with target="_blank". I had to work around the fact that the PopupFeatures callback has absolutely no useful context by asking the DOM for all elements under the pointer (e.g. :hover).
// intercept target=_blank hyperlinks
webView.getEngine().setCreatePopupHandler(
new Callback<PopupFeatures, WebEngine>() {
@Override
public WebEngine call(PopupFeatures config) {
// grab the last hyperlink that has :hover pseudoclass
Object o = webView
.getEngine()
.executeScript(
"var list = document.querySelectorAll( ':hover' );"
+ "for (i=list.length-1; i>-1; i--) "
+ "{ if ( list.item(i).getAttribute('href') ) "
+ "{ list.item(i).getAttribute('href'); break; } }");
// open in native browser
try {
if (o != null) {
Desktop.getDesktop().browse(
new URI(o.toString()));
} else {
log.error("No result from uri detector: " + o);
}
} catch (IOException e) {
log.error("Unexpected error obtaining uri: " + o, e);
} catch (URISyntaxException e) {
log.error("Could not interpret uri: " + o, e);
}
// prevent from opening in webView
return null;
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With