Search code examples
javafx-webengine

Trying to synchronize states between JavaFX WebView DOM and controlling class


In my controller class, I have a WebView object set up

    @FXML
    private WebView newsletterPreview;
    // some stuff...
    urlString = url.toExternalForm();
    WebEngine engine = newsletterPreview.getEngine();
    bridge = new JSBridge(engine, this);

JSBridge code snippet ...

     private String headlineCandidate;

     public String getHeadlineCandidate() {
         return headlineCandidate;
     }
     //mo stuff... 
     public JSBridge(WebEngine engine, ENewsLetterDialogController controller) {
    engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
        if (newState == State.SUCCEEDED) {
            window = (JSObject) engine.executeScript("window");
            window.setMember("bridge", this);
            engine.executeScript(loadDomListenerScript());
        }
    });
}
private String loadDomListenerScript() {
   return WOWResourceUtils.resourceToString(LISTENER_SCRIPT);

}
 public void captureHeadline(String element) {
    this.headlineCandidate = element;
    System.out.println(headlineCandidate);

}

Listener script snippet..

    //listener script
    "use strict";
    document.addEventListener('click', function(e) {
    e = e || window.event;
    var target = e.target || e.srcElement, text = target.textContent || 
    text.innerText;
    window.bridge.captureHeadline(target.parentElement.outerHTML);
    });

Where the LISTENER_SCRIPT is embedded Javascript as a resource, and headlineCandidate is a public string property

I have the following working:

I navigate load the web page, and I can get the JSBridge class to echo back the expected parent html of whatever element I click (obviously this is for testing). However I cannot find a good way to get that information back to Application thread so I can (for example) enable a button if the user clicks on the correct HTML element. Yes, this design is based on certain assumptions about the page. That's part of the use case.

THe root problem seems to be that the WebView is on a different thread than the Application, but I couldn't find a good way to synchronize the two

Should I try polling from Application Thread?

Sorry if it's been asked before, been searching here for about 3 days


Solution

  • Use Platform.runLater to communicate data from a non-JavaFX thread to the JavaFX thread:

    // Java method invoked as a result of a callback from a 
    // JavaScript event handler in a webpage.
    public void callbackJava(String data) {
        // We are currently not on the JavaFX application thread and
        // should not directly update the scene graph.
        // Instead we call Platform.runLater to ship processing of data
        // to the JavaFX application thread.
        Platform.runLater(() -> handle(data));
    }
    
    public void handle(String data) {
        // now we are on the JavaFX application thread and can 
        // update the scene graph.
        label.setText(data);
    }