5

I'm making a web based app with Java support for some particular requests (e.g. print without choosing the printer everytime) and there is something that id driving me crazy.

I use JavaFX for instantiate the browser object and everything works fine, but of course I need to make callbacks from JavaScript to Java, here is the code:

...
...
we.getLoadWorker().stateProperty().addListener( new ChangeListener<Worker.State>()
    {
        @Override
        public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue)
        {
            if ( newValue == Worker.State.SUCCEEDED )
            {
                bridge = new Bridge();
                JSObject jsobj = (JSObject) we.executeScript( "window" );
                jsobj.setMember( "app", new Bridge() );
            }

            if ( newValue == Worker.State.CANCELLED )
            {
                System.out.println( newValue );
                System.out.println( "An error accourred" );
            }
        }
    });
    ...
    ...

This works but after a few minutesthe bridge stoo working totally, javascript can't make callbacks anymore and if I try to make an alert() on the "app" object it return me undefined. I'm using JDK9

Matt Chad
  • 75
  • 6
  • 1
    It is not too far-fetched that the `JSObject` only holds weak references to the members passed to it. Could you try holding a reference to the `Bridge` instance? (It appears as if you meant to, but then you create a whole new one for the `JSObject`...) – Itai Oct 14 '17 at 13:31
  • @sillyfly actually, you're right. Inexplicably I passed a new instance of Bridge and I don't why, javafx seems a little intolerant to local assignment like that. Thanks a lot. – Matt Chad Oct 14 '17 at 15:31

2 Answers2

2

To make the solution more obvious: the approach suggested by @sillyfly works.

Instead of creating a new Bridge (or in my case Console) instance for every state change, make a single instance and reassign it every time. Something along the following lines.

public class AppController {
    public WebView webview;

    @FXML
    public void initialize() {
        final Console console = new Console();
        final WebEngine engine = webview.getEngine();
        engine.setJavaScriptEnabled(true);
        engine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
                final JSObject window = (JSObject) engine.executeScript("window");
                window.setMember("console", console);
            }
        });
    }

    public class Console {
        public void log(String text) {
            System.out.println("[WebView console] " + text);
        }
    }
}
David
  • 166
  • 8
0

Note that the Java objects bound using JSObject.setMember, JSObject.setSlot, and JSObject.call are implemented using weak references. This means that the Java object can be garbage collected, causing subsequent accesses to the JavaScript objects to have no effect.

https://openjfx.io/javadoc/17/javafx.web/javafx/scene/web/WebEngine.html

in your case when declaring new Bridge() the collector subsequently destroys it, declares it as a global variable and then passes it by parameter to setMember()

JES510
  • 1
  • 1