JavaFX 停止在 WebView 中打开 URL - 改为在浏览器中打开

2022-09-03 04:25:01

我使用的嵌入式 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. 改为对阶段更改做出反应

我也试图而不是寻找的,听在chenges上为的和发射相同的开场代码如果。与以前的方法相比,结果没有差异(除了实际上不能使用#1)。locationPropertyWebEnginestatePropertyWorkernewState == State.SCHEDULED


更新

我现在使用的代码仍然使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);
        }
    }
});

解决方法

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

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

还有另一种方法可以处理此问题。

您可以将事件侦听器添加到 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);
            }

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


答案 2

我终于找到了一个对我有用的工作解决方案:

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

用法:

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

推荐