JavaFx WebView callback from Javascript with error after garbage collection

I am currently working on a JavaFX-based application where users can interact with places marked on a world map. To do this, I use an approach similar to that described in http://captaincasa.blogspot.de/2014/01/javafx-and-osm-openstreetmap.html ([1]).

However, I have to deal with the difficult debugging task associated with a Javascript callback variable embedded in an embedded HTML page using the WebEngine setMember () method (see also https://docs.oracle.com/javase/8/javafx/ embedded-browser-tutorial / js-javafx.htm ([2]) for the official tutorial).

When you run the program for some time, the callback variable loses its state unpredictably! To demonstrate this behavior, I developed a minimal working / unsuccessful example. I am using jdk1.8.0_121 64-bit on a machine running Windows 10.

A JavaFx application is as follows:

import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import javafx.application.Application; import javafx.concurrent.Worker.State; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; import netscape.javascript.JSObject; public class WebViewJsCallbackTest extends Application { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); public static void main(String[] args) { launch(args); } public class JavaScriptBridge { public void callback(String data) { System.out.println("callback retrieved: " + data); } } @Override public void start(Stage primaryStage) throws Exception { WebView webView = new WebView(); primaryStage.setScene(new Scene(new AnchorPane(webView))); primaryStage.show(); final WebEngine webEngine = webView.getEngine(); webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm()); webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> { if (newValue == State.SUCCEEDED) { JSObject window = (JSObject) webEngine.executeScript("window"); window.setMember("javaApp", new JavaScriptBridge()); } }); webEngine.setOnAlert(event -> { System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData()); }); } } 

The HTML file "page.html" is as follows:

 <!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <!-- use for in-browser debugging --> <!-- <script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> --> <script type="text/javascript"> var javaApp = null; function javaCallback(data) { try { alert("javaApp=" + javaApp + "(type=" + typeof javaApp + "), data=" + data); javaApp.callback(data); } catch (e) { alert("caugt exception: " + e); } } </script> </head> <body> <button onclick="javaCallback('Test')">Send data to Java</button> <button onclick="setInterval(function(){ javaCallback('Test'); }, 1000)">Send data to Java in endless loop</button> </body> </html> 

The state of the javaApp callback variable can be seen by clicking the "Send data to Java in endless loop" button. He will constantly try to run the callback method using javaApp.callback , which displays some message about registering in the Java application. Alerts are used as an additional communication channel to restore health (they always seem to work and are currently being used as workers, but this is not how things can be ...).

If everything works as expected, write a log each time that looks like the following lines:

 callback retrieved: Test 2017/01/27 21:26:11 alerted: javaApp=webviewtests.WebViewJsCallbackTest$JavaScriptBridge@51fa c693(type=object), data=Test 

However, after a while (something from 2 to 7 minutes), callbacks no longer return, but only records are printed, such as the following line:

2017/01/27 21:32:01 alerted: javaApp=undefined(type=object), data=Test

Printing the variable now gives 'undefined' instead of the path of the Java instance. A strange observation is that the state of javaApp not true "undefined". using typeof returns object , javaApp === undefined evaluates to false . This is consistent with the fact that callback-call does not throw an exception (otherwise, a warning will start, starting with "caugt exception: " ).

Using Java VisualVM showed that the time of failure coincides with the time when the garbage collector is activated. This can be seen by observing the memory consumption of the heap, which drops from approx. 60MB to 16MB due to GC.

What is happening there? Do you know how I can further debug this problem? I could not find the error associated with this ...

Thanks so much for your advice!

PS: the problem was reproduced much faster if you included Javascript code to display the world map through the Flyer (cf [1]). Loading or moving the card in most cases instantly forced the GC to do its job. By debugging this original problem, I traced the problem to the minimum example presented here.

+6
source share
1 answer

I solved the problem by creating a bridge instance variable in Java that contains a JavaScriptBridge instance sent to Javascript via setMember() . This prevents the assembly of the Gargbage Collection instance.

Corresponding code snippet:

 public class JavaScriptBridge { public void callback(String data) { System.out.println("callback retrieved: " + data); } } private JavaScriptBridge bridge; @Override public void start(Stage primaryStage) throws Exception { WebView webView = new WebView(); primaryStage.setScene(new Scene(new AnchorPane(webView))); primaryStage.show(); final WebEngine webEngine = webView.getEngine(); webEngine.load(getClass().getClassLoader().getResource("page.html").toExternalForm()); bridge = new JavaScriptBridge(); webEngine.getLoadWorker().stateProperty().addListener((observableValue, oldValue, newValue) -> { if (newValue == State.SUCCEEDED) { JSObject window = (JSObject) webEngine.executeScript("window"); window.setMember("javaApp", bridge); } }); webEngine.setOnAlert(event -> { System.out.println(DATE_FORMAT.format(new Date()) + " alerted: " + event.getData()); }); } 

Despite the fact that the code now runs smoothly (also in combination with a worksheet), I'm still annoyed by this unexpected behavior ...

Since it looks more like work, I will not accept it as the correct answer until someone can explain why this behavior should be expected / correct.

+7
source

Source: https://habr.com/ru/post/1014502/


All Articles