JavaFX在WebView中停止打开URL – 而不是在浏览器中打开

我使用的嵌入式WebView浏览器需要对特定URL进行特殊处理,以在本机默认浏览器而不是WebView中打开它们。 实际的浏览部分工作正常但我需要阻止WebView显示该页面。 我可以想到几种方法,但没有一种方法可行。 这是我的代码:

this.wv.getEngine().locationProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue 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); } } }); 

关于三种情况中每种情况会发生什么的更多信息

1.加载以前的地址

 wv.getEngine().load(oldValue); 

这会杀死JVM。 有趣的是,该页面在本机浏览器中打开正常。

 # 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.取消工人

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

什么都不做,页面加载WebView和本机浏览器。

3.使用history.back()

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

同上,没有效果。

4.反而改变舞台变化

我也尝试过而不是查看WebEngine的stateProperty ,如果newState == State.SCHEDULED ,请监听worker的stateProperty并触发相同的开启代码。 与先前方法的结果没有区别(除了实际上不能使用#1)。


更新

我现在使用的代码仍然崩溃了JVM:

 this.wv.getEngine().locationProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue 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); } } }); 

解决方法

好吧,我设法通过拆除webview并重建它来使其工作。

 this.wv.getEngine().locationProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue 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); } } }); 

还有另一种处理方法。

您可以向DOM元素添加事件侦听器并以此方式拦截它。

例:

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

其中document是DOM文档对象。 确保在文档加载完成后完成此操作。

我终于找到了一个适合我的工作解决方案:

 public class HyperlinkRedirectListener implements ChangeListener, 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 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));

这对我有用,因为我必须通常使用target =“_ blank”捕获任何锚点。 我不得不通过向DOM询问指针下的所有元素(例如:hover)来解决PopupFeatures回调绝对没有用的问题。

 // intercept target=_blank hyperlinks webView.getEngine().setCreatePopupHandler( new Callback() { @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; } }); 

@ Avrom使用DOM拦截器的答案提供了一个比这个问题更好的解决方案:“JavaFX在WebView中停止打开URL – 而不是在浏览器中打开”。

这个答案刚刚给后人留下了答案。


使用选项1 engine.load(oldValue)并将加载调用包装在Platform.runLater中作为解决方法以防止jvm崩溃。

 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() { @Override public void changed(ObservableValue 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(); } } 

更新

虽然上面的解决方案适用于我在jdk7u15,win7下提供的GoogleBlock示例应用程序,但Dreen报告说只是在Platform.runLater中包装加载值并不能解决所有情况下的崩溃问题,所以用一个完整的WebView对象替换新的WebView(在更新的问题中作为Dreen概述)可能是这里的首选解决方案(至少在修复底层错误之前)。


你在问题中注意到的jvm崩溃是JavaFX 2.2中的一个已知问题:

JDK-8087652 WebView在webEngine.locationProperty()ChangeListener中调用webEngine.load(url)时崩溃 。

很抱歉挖掘这个旧线程,但我找到了另一个解决方案,我想与其他人一起分享同样的问题。 我找到了一个围绕整个问题有一个很好的包装的库,请参阅github上的文档 。

编辑:哦,sry没有告诉项目的作用:链接库包含一个实际实现此线程中讨论的所有代码的类。 用户可以简单地创建WebViewHyperlinkListener的新实例,当链接发生某些事情(鼠标输入,鼠标退出,鼠标单击)时,会自动调用该实例。 处理程序终止后,它返回一个布尔值:如果处理程序返回true ,则WebView将不会导航到链接的网页。 如果处理程序返回false ,它将。

将d.browse调用包装到Runnable对象时,运行时错误永远不会再次发生。 奇怪的是没有包装ChangeListener在几秒钟后第二次调用同一个新位置,第二次调用崩溃了JVM。

我找到了另一个解决方

  1. 注册一个CreatePopupHandler,它返回一个不同于主要WebEngine的WebEngine
  2. 主WebEngine将加载调用发送到辅助WebEngine
  3. 在辅助WebEngine上注册LocationChangeListener并捕获位置更改(包括地址)并在我们的外部浏览器中打开它
  4. 最后清理辅助WebEngine:停止加载和卸载URL

我实现了它,因此辅助WebEngine初始化是懒惰的。 它也可以在构造函数中初始化。 两者都有利弊。

注意:这仅触发作为弹出窗口打开的链接。 当a-element的target-attribute不是“_self”或者是JS: window.open(...)时,通常会出现这种情况。

这是魔术……

像这样注册:

 engine.setCreatePopupHandler(new BrowserPopupHandler()); 

核心课程:

 public static class BrowserPopupHandler implements Callback { private WebEngine popupHandlerEngine; public WebEngine call(PopupFeatures popupFeatures) { // by returning null here the action would be canceled // by returning a different WebEngine (than the main one where we register our listener) the load-call will go to that one // we return a different WebEngine here and register a location change listener on it (see blow) return getPopupHandler(); } private WebEngine getPopupHandler() { if (popupHandlerEngine == null) // lazy init - so we only initialize it when needed ... { synchronized (this) // double checked synchronization { if (popupHandlerEngine == null) { popupHandlerEngine = initEngine(); } } } return popupHandlerEngine; } private WebEngine initEngine() { final WebEngine popupHandlerEngine = new WebEngine(); // this change listener will trigger when our secondary popupHandlerEngine starts to load the url ... popupHandlerEngine.locationProperty().addListener(new ChangeListener() { public void changed(ObservableValue observable, String oldValue, String location) { if (!location.isEmpty()) { Platform.runLater(new Runnable() { public void run() { popupHandlerEngine.loadContent(""); // stop loading and unload the url // -> does this internally: popupHandlerEngine.getLoadWorker().cancelAndReset(); } }); try { // Open URL in Browser: Desktop desktop = Desktop.getDesktop(); if (desktop.isSupported(Desktop.Action.BROWSE)) { URI uri = new URI(location); desktop.browse(uri); } else { System.out.println("Could not load URL: " + location); } } catch (Exception e) { e.printStackTrace(); } } } }); return popupHandlerEngine; } } 

2017版 – 仍然非常hacky但更简洁:

 class AboutDialog extends Dialog { private final Controller controller; private final String url; AboutDialog() { super(); this.controller = Controller.getInstance(); this.setTitle(controller.getProperty("about_title")); this.setHeaderText(null); this.url = getClass().getResource("/about_dialog.html").toExternalForm(); this.setWebView(); this.getDialogPane().getButtonTypes().add(new ButtonType(controller.getProperty("close"), ButtonBar.ButtonData.CANCEL_CLOSE)); this.getDialogPane().setPrefWidth(600); } private void setWebView() { final WebView webView = new WebView(); webView.getEngine().load(url); webView.getEngine().locationProperty().addListener((observable, oldValue, newValue) -> { controller.getMainFxApp().getHostServices().showDocument(newValue); Platform.runLater(this::setWebView); }); this.getDialogPane().setContent(webView); } }