检测 JavaFX WebView 中的 URL 更改

问题描述 投票:0回答:1

在 JavaFX 的

WebView
中,我正在努力检测 URL 的变化。

我在课堂上有这个方法:

public Object urlchange() {
    engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
        @Override
        public void changed(ObservableValue ov, State oldState, State newState) {
            if (newState == Worker.State.SUCCEEDED) {
                return engine.getLocation()); 
            }
        }
    });         
}

我正在尝试将它用于一个名为 loginbrowser 的对象,例如:

System.out.print(loginbrowser.urlchange());

你能看出我做错了什么吗?

java webview javafx-2
1个回答
9
投票

(部分)你做错了什么

您在问题中提供的代码甚至无法编译。 ChangeListener 的更改方法是一个 void 函数,它不能返回任何值。

无论如何,在 Web 视图中加载内容是一个异步过程。如果您想要在 Web 视图加载后获取 Web 视图位置的值,则需要等待加载完成(在 JavaFX 应用程序线程上不建议这样做,因为这会挂起您的应用程序,直到加载完成),或者在回调中通知加载已完成(这就是您的侦听器正在执行的操作)。

(可能)你想做什么

将某些属性绑定到 Web 引擎的位置属性。例如:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.*;
import javafx.stage.Stage;

public class LocationViewer extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Label location = new Label();

        WebView webView = new WebView();
        WebEngine engine = webView.getEngine();
        engine.load("http://www.fxexperience.com");

        location.textProperty().bind(engine.locationProperty());

        Scene scene = new Scene(new VBox(10, location, webView));
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

只要 Web 视图的位置发生变化,上面的代码就会更新位置标签(通过运行代码然后单击一些链接来尝试)。如果您希望仅在页面成功加载后更新标签,那么您需要一个基于 WebView 状态的侦听器,例如:

import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.web.*;
import javafx.stage.Stage;

public class LocationAfterLoadViewer extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Label location = new Label();

        WebView webView = new WebView();
        WebEngine engine = webView.getEngine();
        engine.load("http://www.fxexperience.com");

        engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
            if (Worker.State.SUCCEEDED.equals(newValue)) {
                location.setText(engine.getLocation());
            }
        });

        Scene scene = new Scene(new VBox(10, location, webView));
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

如果您运行最后一个程序并单击某些链接,您会注意到它会延迟位置标签的更新,直到您单击的页面完全完成加载之后,而不是第一个程序一旦位置就更新标签变化,无论负载是否需要一段时间或确实有效。

其他问题的解答

如何在条件语句中使用标签中的 url 值?如果操作与原始操作发生了变化,我希望执行该操作。

location.textProperty().addListener((observable, oldValue, newValue) -> {
    // perform required action.
});

2023 年更新

对于大多数用途,我认为上面原始答案中的信息今天仍然有效,并且足以满足大多数用途。这个答案只是添加了一些附加信息来解决巴勃罗的评论:

上面的代码不是完整的解决方案。由于 JavaScript 和 HTML5 历史记录功能,URL 可能会更改,而状态属性不会更改。

我相信这里可能指的是以下情况下位置发生的情况,历史API的推送状态用法:

但它也可能只是对历史对象的标准导航调用,例如 back() 或forward。

WebView 有一个 WebHistory Java 对象,可以深入了解当前会话的导航历史记录。它类似于 JavaScript/HTML 中的历史 API,并且 Java 对象可用于执行其中一些功能,例如在历史记录中向后或向前导航 Web 引擎。 WebHistory API 没有像 JavaScript 那样的

pushState()
API,但您可以从 Java 调用 JavaScript 历史记录 API 来调用它。

所以我组装了一个测试工具来尝试所有这些,看看它的行为和交互方式。

测试工具显示

  • 顶部有一些导航控件
    • 后退/前进按钮
    • 当前网络引擎位置
    • 通过 JavaScript
      pushState
      调用操作历史记录的按钮。
  • 显示当前 HTML 页面的 WebView。
  • 显示 WebEngine WebHistory Java 对象当前状态的表格。

在示例屏幕截图中,您可以看到当页面加载到引擎中时,历史对象中的“上次访问”字段会更新。但如果导航没有导致加载,则不会更新。一个示例是导航到页面上的片段或使用 PushState() API。

在这种情况下,如果您根据成功的页面加载来监控位置,则此时您将不会收到位置更改的通知。

但是,如果您侦听 WebEngine 的位置属性的更改,则对 WebEngine 历史记录的操作即使通过 JavaScript 也将反映在 WebEngine 的位置属性的更改中,并且您将收到这些更改的通知。这可以在示例屏幕截图中看到,其中屏幕顶部的位置属性能够通过历史记录

pushState()
调用反映 JavaScript 中设置的位置。

请注意,即使屏幕顶部的位置字段显示

pushState()
结果,Web 引擎实际上并不会在
pushState()
调用上导航到该 URL,它只是 Web 应用程序操作历史记录和 url 的一种方式浏览器中的位置信息,以便它可以反映重要的 Web 应用程序状态更改(例如,通过单页应用程序中的 Ajax 调用更新页面内容,SPA)。因此,如果希望您的 Java 应用程序收到此类更改的通知,那么您的 Java 应用程序可以通过侦听 WebEngine 的 location 属性来实现。

测试线束代码

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebHistory;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.jetbrains.annotations.NotNull;

import java.util.Date;

public class LocationWithHistoryViewer extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        WebView webView = new WebView();
        WebEngine engine = webView.getEngine();
        engine.load("https://openjfx.io/javadoc/20/");

        HBox controls = createNavigationControls(engine);
        HistoryView historyView = new HistoryView(
                engine.getHistory()
        );

        Scene scene = new Scene(new VBox(10, controls, webView, historyView));
        stage.setScene(scene);
        stage.show();
    }

    @NotNull
    private static HBox createNavigationControls(WebEngine engine) {
        WebHistory history = engine.getHistory();

        Button back = new Button("<");
        back.setOnAction(e -> {
            if (history.getCurrentIndex() > 0) {
// history navigation can be performed via either the Java WebHistory object or JavaScript calls.
// in either case the outcome is the same -> if the navigation results in a page load then
// monitoring the location on a successful load state will pick up the location change,
// if the navigation does not generate a page load, monitoring the location property
// of the webview will pick up the navigation.
//
// Most history navigations will generate a page load,
// with a counterexample being navigating forward to a location created by history.pushState.
//                history.go(-1);
                engine.executeScript("history.back()");
            }
        });
        back.disableProperty().bind(
                history.currentIndexProperty().lessThan(1)
        );

        Button forward = new Button(">");
        forward.setOnAction(e -> {
            if (history.getCurrentIndex() < history.getEntries().size()) {
//                history.go(+1);
                engine.executeScript("history.forward()");
            }
        });
        forward.disableProperty().bind(
                history.currentIndexProperty().isEqualTo(
                        Bindings.size(history.getEntries()).subtract(1)
                )
        );

        // If you try to manipulate the url after navigate off of the openjfx.io site, it won't let you do that
        // you will receive:
        //   Exception in thread "JavaFX Application Thread" netscape.javascript.JSException:
        //     SecurityError: Blocked attempt to use history.pushState()
        //        to change session history URL from https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/RecursiveAction.html
        //        to https://openjfx.io/javadoc/20/javafx.web/javafx/scene/web/WebHistory.html#fake.
        //     Protocols, domains, ports, usernames, and passwords must match.
        // This is an expected cross-domain security enforcement system built into WebView.
        Button manipulateURL = new Button("Manipulate URL");
        final String MANIPULATE_SCRIPT = // language = javascript
                """
                document.title = "Manipulated title";
                history.pushState({}, "", "https://openjfx.io/javadoc/20/javafx.web/javafx/scene/web/WebHistory.html#fake");
                """;
        manipulateURL.setOnAction(e ->
                engine.executeScript(MANIPULATE_SCRIPT)
        );

        Label location = new Label();

        Pane spacer = new Pane();
        HBox.setHgrow(spacer, Priority.ALWAYS);

        HBox controls = new HBox(10,
                back,
                forward,
                location,
                spacer,
                manipulateURL
        );
        controls.setAlignment(Pos.BASELINE_LEFT);
        controls.setPadding(new Insets(3));

// monitoring load state will not pick up history manipulations which do not generate page loads (e.g. history.pushState JavaScript calls).
//        engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
//            if (Worker.State.SUCCEEDED.equals(newValue)) {
//                location.setText(engine.getLocation());
//            }
//        });

// monitoring the location property will pick up history manipulations which do not generate page loads (e.g. history.pushState JavaScript calls).
        location.textProperty().bind(engine.locationProperty());

        return controls;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

class HistoryView extends TableView<WebHistory.Entry> {
    public HistoryView(WebHistory history) {
        setItems(history.getEntries());

        TableColumn<WebHistory.Entry, Date> lastVisitedColumn = new TableColumn<>("Last visited");
        lastVisitedColumn.setCellValueFactory(param ->
                param.getValue().lastVisitedDateProperty()
        );
        lastVisitedColumn.setStyle("-fx-font-family: monospace;");
        lastVisitedColumn.setPrefWidth(240);

        TableColumn<WebHistory.Entry, String> titleColumn = new TableColumn<>("Title");
        titleColumn.setCellValueFactory(param ->
                param.getValue().titleProperty()
        );
        titleColumn.setPrefWidth(300);

        TableColumn<WebHistory.Entry, String> urlColumn = new TableColumn<>("URL");
        urlColumn.setCellValueFactory(param ->
                new ReadOnlyObjectWrapper<>(param.getValue().getUrl())
        );
        urlColumn.setPrefWidth(1_000);

        //noinspection unchecked
        getColumns().addAll(lastVisitedColumn, titleColumn, urlColumn);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.