Dreen Dreen - 2 months ago 51
Java Question

JavaFX stop opening URL in WebView - open in browser instead

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

Answer

@Avrom's answer of using DOM interceptors offers a better solution than this answer with regards to the question: "JavaFX stop opening URL in WebView - open in browser instead".

This answer just left for posterity.


Use option 1 engine.load(oldValue) and wrap the load call in Platform.runLater as a workaround to prevent the jvm crash.

import javafx.application.*;
import javafx.beans.value.*;
import javafx.scene.Scene;
import javafx.scene.web.*;
import javafx.stage.Stage;

public class GoogleBlock extends Application {
  public static void main(String[] args) throws Exception { launch(args); }

  @Override public void start(final Stage stage) throws Exception {
    final WebView webView = new WebView();
    final WebEngine engine = webView.getEngine();
    engine.load("http://www.google.com");
    engine.locationProperty().addListener(new ChangeListener<String>() {
      @Override public void changed(ObservableValue<? extends String> ov, final String oldLoc, final String loc) {
        if (!loc.contains("google.com")) {
          Platform.runLater(new Runnable() {
            @Override public void run() {
              engine.load(oldLoc);
            }
          });
        }
      }
    });

    stage.setScene(new Scene(webView));
    stage.show();
  }
}

Update

Although the above solution works OK for me in the supplied GoogleBlock sample application under jdk7u15, win7, Dreen reports that just wrapping the load value in Platform.runLater does not fix crash issues in all cases, so the complete replacement of the WebView object with a new WebView (as Dreen outlines in the updated question), might be the preferred solution here (at least until the underlying bug is fixed).


The jvm crash you note in your question is a known issue in JavaFX 2.2:

JDK-8087652 WebView crashes on calling webEngine.load(url) in a webEngine.locationProperty() ChangeListener.