1

I've setup a Javascript-Java bridge for WebView:

webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
                    JSObject window = (JSObject) webEngine.executeScript("window");
                    window.setMember("JavaBridge", bridge);
});

The bridge works fine on the first content load.

On subsequent content loads, the bridge is delayed, and is not recognized until after the rest of the Javascript is loaded.

For example, if I set up a bridge "JavaBridge" and give it a method "log()", the following HTML will work fine on the first content load, but will fail on the second:

<script>JavaBridge.log('On content load')</script>

The bridge is still loaded eventually, just too late for my purpose.

Is there a way to modify my bridge so it is always setup before any Javascript code is ran?

There is also a chance my issue is related to these bug reports.

Full example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class Main extends Application {

    private Bridge bridge = new Bridge();
    private static final String BRIDGE_NAME = "JavaBridge";

    private static final String HTML =
            " <script>try{"
            + BRIDGE_NAME + ".log('On content load');"  // try to log a message on content load
            + "} catch(err) {alert(err.message);}"   // alert message if bridge is not yet defined
            + "</script>"
            + "<button onclick=\"" + BRIDGE_NAME + ".log('Button clicked')\">Log Click</button>";

    @Override
    public void start(Stage stage) {
            // Setup the Webview
            WebView webView = new WebView();
            webView.setPrefSize(100,100);
            WebEngine webEngine = webView.getEngine();
            webEngine.setOnAlert(Main::showAlert);

            // Setup the bridge
            webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
                    JSObject window = (JSObject) webEngine.executeScript("window");
                    window.setMember(BRIDGE_NAME, bridge);
            });

            // Load button, press twice or more to see the bug
            Button button = new Button("Load content");
            button.setOnAction(actionEvent ->  {
                webEngine.loadContent(HTML);
            });

            // Setup stage and scene
            HBox hbox = new HBox(button, webView);
            Scene scene = new Scene(hbox);
            stage.setScene(scene);
            stage.show();
    }

    // The Java bridge. Has a "log" method.
    public static class Bridge {
        public void log(String msg) {
            System.out.println("Invoked from JavaScript: " + msg);
        }
    }

    // Shows the alert, used in JS catch statement
    private static void showAlert(WebEvent<String> event) {
        javafx.scene.control.Dialog<ButtonType> alert = new javafx.scene.control.Dialog<>();
        alert.getDialogPane().setContentText(event.getData());
        alert.getDialogPane().getButtonTypes().add(ButtonType.OK);
        alert.showAndWait();
    }

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

If you click the "Load content" button, it will print the expected message in the console:

Invoked from JavaScript: On content load

If however you click on the "Load content" button a second time, it doesn't print the message to the console, instead giving the alert message:

Can't find variable: JavaBridge

Strangely, the bridge appears to be eventually loaded, as clicking on the "Log click" button gives the correct printed message through the bridge:

Invoked from JavaScript: Button clicked

EDIT: Further tests showed that on the first content load, window.setMember() may be set at anytime before newState reaches State.SUCCEEDED.

On subsequent content loads, window.setMember() is ignored/overridden once State.SUCCEEDEDis reached. A workaround is to create a listener so that window.setMember() is set after State.SUCCEEDED is reached; however, because the bridge is set so late, all the other Javascript stuff will have already ran.

D. Lawrence
  • 943
  • 1
  • 10
  • 23
Guillaume F.
  • 1,010
  • 7
  • 21
  • 1
    Potentially related: [javafx WebView setMember() before script](https://stackoverflow.com/questions/23687127/javafx-webview-setmember-before-script), [consistent setMember on JavaFX window](https://stackoverflow.com/questions/35758313/consistent-setmember-on-javafx-window) – Guillaume F. Dec 29 '19 at 09:36
  • [This solution](https://stackoverflow.com/a/26716272/11329518) doesn't work after the first page load, as mentionned by @mohamnag – Guillaume F. Dec 29 '19 at 19:50
  • This solution works: https://stackoverflow.com/a/28414332/11329518 – Guillaume F. Apr 14 '20 at 21:54

0 Answers0