Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8236837

WebView: after first call, setMember can't be set before page load

    Details

    • Subcomponent:
      web
    • CPU:
      x86_64
    • OS:
      windows_10

      Description

      ADDITIONAL SYSTEM INFORMATION :
      Windows 10. I tested Java 8, 10, 11, and 13, and the issue is present in all these versions.

      A DESCRIPTION OF THE PROBLEM :
      After the first page load of WebView, it becomes impossible to use setMember to create a JS interface object before a new page is loaded.

      A partial workaround is to set the bridge through a listener,

      ```
      webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
                 if(newState == Worker.State.SUCCEEDED){
                          JSObject window = (JSObject) webEngine.executeScript("window");
                          window.setMember("JavaBridge", bridge);
                 }
      });
      ```
      However this causes the interface to be created after the Javascript of the page is loaded. It thus impossible to use the interface in the initial Javascript.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the code attached.

      Click on the "Load content" button twice. After the first time, an error message shows up, because the bridge is not set before the JS script is ran.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The Java console displays "Invoked from JavaScript: On content load" each time the "Load content" button is pressed.
      ACTUAL -
      The Java console displayed "Invoked from JavaScript: On content load" on the first "Load content" click only. On subsequent clicks, it shows the error "Can't find variable: JavaBridge", because the bridge is not set at that time.

      ---------- BEGIN SOURCE ----------
      package org.openjfx;

      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);
          }

      }
      ---------- END SOURCE ----------

      FREQUENCY : always


        Attachments

          Activity

            People

            • Assignee:
              ghb Guru Hb
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated: