15

私が使用している埋め込み 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);
        }
    }
});

3 つのケースのそれぞれで何が起こるかについてのもう少しの情報

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.代わりにステージの変更に対応する

また、 of を探す代わりに、locationPropertyof の変更WebEngineをリッスンし、同じ開始コード ifを起動しようとしました。前の方法と結果に違いはありませんでした (実際には #1 を使用できないことを除けば)。statePropertyWorkernewState == 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);
        }
    }
});

回避策

OK、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()
                    {
                        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);
        }
    }
});
4

9 に答える 9

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 ドキュメント オブジェクトです。これは、ドキュメントの読み込みが完了した後に行ってください。

于 2013-08-30T15:29:58.750 に答える
8

私はついに私のために働いた実用的な解決策を見つけました:

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));
于 2015-10-30T21:46:18.143 に答える
5

target="_blank" を使用してアンカーを一般的にトラップする必要があったため、これはうまくいきました。PopupFeatures コールバックには有用なコンテキストがまったくないという事実を回避する必要がありました。これには、ポインターの下にあるすべての要素 (:hover など) を DOM に要求する必要がありました。

// 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;
        }
    });
于 2014-01-22T23:39:42.700 に答える
2

DOM インターセプターを使用するという @Avrom の回答は、「JavaFX が WebView で URL を開くのを停止する - 代わりにブラウザーで開く」という質問に関して、この回答よりも優れたソリューションを提供します。

この答えは後世に残されました。


jvm クラッシュを回避するための回避策として、オプション 1を使用し、 Platform.runLaterengine.load(oldValue)でロード呼び出しをラップします。

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 webEngine.locationProperty() ChangeListener で webEngine.load(url) を呼び出すと、WebView がクラッシュします

于 2013-03-21T18:56:40.470 に答える
0

d.browse 呼び出しを実行可能オブジェクトにラップすると、ランタイム エラーが再び発生することはありませんでした。奇妙なことに、ChangeListener のラッピングが同じ新しい場所で数秒後に 2 回目に呼び出され、この 2 回目の呼び出しで JVM がクラッシュしました。

于 2013-03-26T09:44:46.387 に答える
0

別の解決策を見つけました:

  1. メインの WebEngine とは異なる WebEngine を返す CreatePopupHandler を登録する
  2. メインの WebEngine が load-call をセカンダリ WebEngine に送信します
  3. セカンダリ WebEngine に LocationChangeListener を登録し、場所の変更 (アドレスを含む) をキャッチして、外部ブラウザーで開きます。
  4. 最後に、セカンダリ WebEngine をクリーンアップします。読み込みを停止して URL をアンロードします。

セカンダリ WebEngine が遅延して初期化されるように実装しました。コンストラクターで初期化することもできます。どちらにも長所と短所があります。

注:これは、ポップアップとして開くリンクに対してのみトリガーされます。これは通常、a 要素に "_self" ではない、または JS: を使用した target-attribute がある場合に当てはまります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;
    }

}
于 2016-03-09T16:54:38.070 に答える