javascriptjavajspjavafxjsobject

(JavaFX WebEngine) JavaScript-to-Java Calls Work Inconsistently


Good day,

I am having issues with my app's custom minimize and close buttons working inconsistently. I would like to fix this before advancing this project and making it more complicated.


Background

The app is an undecorated JavaFX stage that contains a WebView that loads a JSP hosted by an embedded Tomcat server. The minimize and close buttons are HTML within the JSP and have onclick listeners that call Java methods to minimize and close the app via a JavaScript-to-Java interface class, AppHandle, and a delegate-class, AppUtility.


Issue

The buttons work, but work inconsistently. Also, whenever the buttons do fail at random, they fail together.

E.g.


Code

JavaFX Components

Stage
|  
\-- StackPane
   |  
   \-- Browser (contains the WebView/WebEngine)

The Browser class extends Region and houses the WebView and its WebEngine. This is where the AppHandle class is bound to the "app" label within the JSP.

public Browser(String page, final AppUtility utility) {
  browser.setFontSmoothingType(FontSmoothingType.GRAY);
  browser.setContextMenuEnabled(false);

  webEngine.getLoadWorker().stateProperty()
    .addListener(
      new ChangeListener<State>() {
        @Override
        public void changed(ObservableValue<? extends State> ov, 
            State oldState, State newState) { 
          if (newState == State.SUCCEEDED) {
            JSObject win = (JSObject) webEngine.executeScript("window");
            win.setMember("app", new AppHandle(utility));
          }
        }
      }
    );
  webEngine.load(page);
  getChildren().add(browser);
}

Here is the relevant portion of the JSP where the buttons call the AppHandler methods onclick.

<div class="container-app">
  <div class="text-title">myApp</div>

  <div class="icon utility minimize" onclick="app.hide()">
    <i class="fa fa-window-minimize" aria-hidden="true"></i>
  </div>

  <div class="icon utility close" onclick="app.exit()">
    <i class="fa fa-times" aria-hidden="true"></i>
  </div>
</div>

This is the AppHandle class, its methods are called from the onclick events in the JSP. If I put print statements in the exit() and hide() classes, they will not execute when the buttons fail. Which leads me to believe something is failing with the JSObject.

public class AppHandle {
  private AppUtility utility;

  public AppHandle(AppUtility utility) {
    this.utility = utility;
  }

  public void exit() {
    utility.exit();
  }

  public void hide() {
    utility.hide();
  }
}

The AppUtility methods act on the JavaFX Stage to hide and close the JavaFX Application. I originally thought something might be wrong with my show/hide/inconified logic, but this code doesn't even get executed when the buttons fail.

public void exit() {
  if (SystemTray.isSupported())
    SystemTray.getSystemTray().remove(app.getTrayIcon());
  Platform.exit();
  System.exit(0);
}

public void hide() {        
  if (SystemTray.isSupported()) {
    try {
      SystemTray.getSystemTray().add(app.getTrayIcon());
      displayIconDialogue();
    } catch (AWTException e) {
      e.printStackTrace();
    }
  }
  app.getWindow().setIconified(true);
  app.getWindow().hide();
}

public void show() {
  if (SystemTray.isSupported())
    SystemTray.getSystemTray().remove(app.getTrayIcon());
  app.getWindow().setIconified(false);
  app.getWindow().show();
}

Questions

  1. How can I assess the "health" of my JSObject during run-time to see if it is the culprit?

  2. Is there anything here that would explicitly break the connection between the JSP and the AppHandle on launch? On minimize? On show?

This would be easier if it just didn't work at all, I'm not sure where the inconsistency is coming from.

Any help would be appreciated,

Thank you.


Solution

  • Bah, I solved it. Let this be a lesson to me to search for questions better.

    This question clued me in to the problem.

    I am creating the AppHandle locally within the JSObject. I guess at some point the local AppHandle gets dereferenced and with it goes my JSP-to-Java connection.

    I now make the AppHandle in the AppClient and pass it into the Browser. That way the app maintains a reference to the handle. The Browser constructor was changed to the following:

    public Browser(String page, AppHandle handle) {
      browser.setFontSmoothingType(FontSmoothingType.GRAY);
      browser.setContextMenuEnabled(false);
    
      webEngine.getLoadWorker().stateProperty()
        .addListener(
          new ChangeListener<State>() {
            @Override
            public void changed(ObservableValue<? extends State> ov, 
                State oldState, State newState) { 
              if (newState == State.SUCCEEDED) {
                JSObject win = (JSObject) webEngine.executeScript("window");
                win.setMember("app", handle);
              }
            }
          }
        );
      webEngine.load(page);
      getChildren().add(browser);
    }