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

时间:2013-03-21 18:37:30

标签: java webview jvm javafx-2 javafx

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

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

关于三种情况中每种情况发生的事情的更多信息

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。反而改变舞台变化

我还尝试过而不是查看locationProperty的{​​{1}},而是在WebEngine stateProperty的{​​{1}}上聆听,并在{{1} Worker时触发相同的开场代码}}。与先前方法的结果没有区别(除了实际上不能使用#1)。


更新

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

newState == State.SCHEDULED

解决方法

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

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

9 个答案:

答案 0 :(得分:18)

还有另一种处理方法。

您可以向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文档对象。 确保在文档加载完成后完成此操作。

答案 1 :(得分:6)

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

答案 2 :(得分:4)

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

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

答案 3 :(得分:2)

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

<强>更新

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


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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

我找到了另一个解决方案:

  1. 注册一个CreatePopupHandler,返回与主要WebEngine不同的WebEngine
  2. 主WebEngine将加载调用发送到辅助WebEngine
  3. 在辅助WebEngine上注册LocationChangeListener并捕获位置更改(包括地址)并在我们的外部浏览器中打开它
  4. 最后清理辅助WebEngine:停止加载&amp;卸载网址
  5. 我实现了它,因此辅助WebEngine被初始化为lazy。它也可以在构造函数中初始化。两者都有利弊。

    注意:这仅触发以弹出窗口打开的链接。当a元素具有不是&#34; _self&#34;的目标属性时,通常就是这种情况。或者使用JS:window.open(...)

    这是魔术......

    像这样注册:

    engine.setCreatePopupHandler(new BrowserPopupHandler());
    

    核心课程:

    public static class BrowserPopupHandler implements Callback<PopupFeatures, WebEngine>
    {
    
        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<String>()
            {
    
                public void changed(ObservableValue<? extends String> 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;
        }
    
    }
    

答案 7 :(得分:0)

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

答案 8 :(得分:0)

您的“更新”部分实际上与我的工作非常接近,不需要任何解决方法:

wv.getEngine().locationProperty().addListener(new ChangeListener<String>() {    
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
            {
                if((address.getQuery() + "").indexOf("_openmodal=true") > -1) {
                    Platform.runLater(new Runnable() {
                        public void run() {
                            try {
                                Desktop.getDesktop().browse(new URI(newValue));
                            } catch (IOException e) { //Decide how to handle:
                                //Can't find default program handler for link.
                            } catch (URISyntaxException e) {
                                //Bad syntax on link.
                            }
                            wv.getEngine().reload();
                        }
                    });
                }
            }

        });

我喜欢这种方法的地方在于,它不仅包含超链接元素而且还包含指向电子邮件或本地文件之类的链接的URL。