snflurry
snflurry

Reputation: 169

JavaFX WebView up call from JavaScript doesn't work

I have a JavaFX WebView and want to call the method "hello" of the class "JavaBridge" from "test.html" displayed in the webview. Why doesn't this work? I making sure that the "bridge" object only be added to the window.object when the page has been fully rendered, so that is probably not the problem. I can't see any problem with the HTML either.

Here is the HTML code ("test.html"):

<html>
<head>
</head>
<body>
    <a href="#click" onclick="bridge.hello()">call java</a>
</body>
</html>

And here is the Java Code:

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;

public class HelloWorld extends Application {

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

  @Override
  public void start(Stage primaryStage) {

    java.net.URI uri = java.nio.file.Paths.get("test.html").toAbsolutePath().toUri();
    WebView root = new javafx.scene.web.WebView();
    root.getEngine().load(uri.toString());

    root.getEngine().
    getLoadWorker().
    stateProperty().
    addListener(new ChangeListener < State > () {
      @Override public void changed(ObservableValue ov, State oldState, State newState) {

        if (newState == Worker.State.SUCCEEDED) {
          System.out.println("READY");
          JSObject jsobj = (JSObject) root.getEngine().executeScript("window");
          jsobj.setMember("bridge", new JavaBridge());
        }

      }
    });
    primaryStage.setScene(new javafx.scene.Scene(root, 800, 600));
    primaryStage.show();
  }
}

class JavaBridge {
  public void hello() {
    System.out.println("hello");
  }
}

Upvotes: 5

Views: 3901

Answers (3)

Muhammad Ali
Muhammad Ali

Reputation: 115

I had the same problem and the only way to fix it was storing the Bridge on a static variable. this is an example to use the javafx FileChooser.

public class Controller implements Initializable {
    @FXML private WebView webview;
    @FXML private JFXButton btn_insertimg;
    @FXML private AnchorPane anchorpane;
    private WebEngine webEngine;
    public static Bridge bridge; //it's important to be static

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        webEngine = webview.getEngine();
        webEngine.getLoadWorker().stateProperty().addListener(
                (ov, oldState, newState) -> {
                    if (newState == Worker.State.SUCCEEDED) {
                        //todo when the document is fully loaded
                        FileChooser fileChooser=new FileChooser();
                        bridge=new Bridge(webview,fileChooser);
                        JSObject window = (JSObject) webEngine.executeScript("window");
                        window.setMember("myFileChooser", bridge);
                        System.out.println("member "+window.getMember("myFileChooser").toString());;
                    }//end of SUCCEEDED state
                });

        webEngine.load(getClass().getResource("/patient/texteditor/summernote.html").toExternalForm());
    }
    public class Bridge{
        FileChooser fileChooser;
        WebView webView;
        Bridge(WebView webView,FileChooser fileChooser){
            this.webView=webView;
            this.fileChooser=fileChooser;
        }
        public void display(){
            fileChooser.showOpenDialog(webView.getScene().getWindow());
        }
    }

}

Upvotes: 2

Bruno T. Abrah&#227;o
Bruno T. Abrah&#227;o

Reputation: 51

When using this bridge feature on Java 10.0.2, I noticed that it was not working consistently. Javascript upcalls wasn't working all the times.

After researching, I found out this OpenJDK bug related to Java Garbage Collector, which seems to happen on regular JDK as well: https://bugs.openjdk.java.net/browse/JDK-8170085

Indeed, according to https://docs.oracle.com/javase/9/docs/api/javafx/scene/web/WebEngine.html, it's recommended to store the bridge into a variable to avoid Java GC to collect the object.

After adding a private variable to the class, the JS to Java calls started to work all the time in my Application.

Upvotes: 5

Roland
Roland

Reputation: 18415

Your inner class should be inside the main class. And it should be public. Like this:

import java.net.URL;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class HelloWorld extends Application {

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

    @Override
    public void start(Stage primaryStage) {

        final URL url = getClass().getResource("test.html");

        WebView root = new javafx.scene.web.WebView();
        root.getEngine().load(url.toExternalForm());

        root.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
            @Override
            public void changed(ObservableValue ov, State oldState, State newState) {

                if (newState == Worker.State.SUCCEEDED) {
                    System.out.println("READY");
                    JSObject jsobj = (JSObject) root.getEngine().executeScript("window");
                    jsobj.setMember("bridge", new JavaBridge());
                }

            }
        });
        primaryStage.setScene(new javafx.scene.Scene(root, 800, 600));
        primaryStage.show();
    }

    public class JavaBridge {
        public void hello() {
            System.out.println("hello");
        }
    }
}

Upvotes: 4

Related Questions