JDK-8141386 : Unable to pass values to java functions which takes wrapper objects as arguments
  • Type: Bug
  • Component: javafx
  • Sub-Component: web
  • Affected Version: 8u51
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux
  • CPU: x86_64
  • Submitted: 2015-10-30
  • Updated: 2015-11-20
  • Resolved: 2015-11-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u92Fixed 9Fixed
Description
FULL PRODUCT VERSION :
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux linux-hj5y 3.11.10-29-desktop #1 SMP PREEMPT Thu Mar 5 16:24:00 UTC 2015 (338c513) x86_64 x86_64 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
I'm writing a connector for a solution written in JavaScript with a library written in Java. Several methods of this library are invoked from JavaScript scripts.
When I invoke methods that have arguments java.lang.Double type, the argument values ������are being converted to null.
If these methods use double native types as arguments , it works perfectly.

I made a simple class to exemplify:


public class SimpleJavaObject {

    private Double doubleAttribute = null;
    
    private final StringBuilder sbDebugText = new StringBuilder();

    public SimpleJavaObject() {

    }
    
    public void setDoubleAttribute(Double doubleAttributeArgument) {
        debug("Double setter: "+doubleAttributeArgument);
        doubleAttribute = doubleAttributeArgument;
    }

//    public void setDoubleAttribute(double doubleAttributeArgument) {
//        debug("double setter: "+doubleAttributeArgument);
//        doubleAttribute = doubleAttributeArgument;
//    }

//    public void setDoubleAttribute(Object doubleAttributeArgument) {
//        debug("Object setter: "+doubleAttributeArgument);
//        doubleAttribute = (Double) doubleAttributeArgument;
//    }

//    public void setDoubleAttribute(String doubleAttributeArgument) {
//        debug("String setter: "+doubleAttributeArgument);
//        doubleAttribute = Double.valueOf(doubleAttributeArgument);
//    }

    public Double getDoubleAttribute() {
        return doubleAttribute;
    }
    
    private void debug(String str) {
        sbDebugText.append(str);
        sbDebugText.append("\n");
    }
    
    public String getDebugText() {
        return sbDebugText.toString();
    }

}

And a simple html page with one simple script:


<!DOCTYPE html>
<html>
    <head>
        <title>WebView tests</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script type="text/javascript">
            
            var simpleJavaObject = null; // setted from outside like jsWindow.setMember("simpleJavaObject", new SimpleJavaObject());
            function test() {
                alert("simpleJavaObject: " + simpleJavaObject); // shows - simpleJavaObject: webviewtests.SimpleJavaObject@8abb96e

                simpleJavaObject.setDoubleAttribute(15.5); // try to change the attribute value

                alert("simpleJavaObject.getDoubleAttribute(): " + simpleJavaObject.getDoubleAttribute()); // shows - simpleJavaObject.getDoubleAttribute(): null

                alert("Test summary:\n"+simpleJavaObject.getDebugText()); // Shows - Test summary:
                                                                          //         Double setter: null
                                                                          // indicating the setter invoked
            }

        </script>
    </head>
    <body>
        
    </body>
</html>


As I uncomment the overloaded setters (keeping commented the method with native double argument) I have the following precedence:

 - setDoubleAttribute(Object doubleAttributeArgument)
 - setDoubleAttribute(String doubleAttributeArgument)
 - setDoubleAttribute(Double doubleAttributeArgument) - with doubleAttributeArgument = null

The question is: Why this method only works with native double, String and Object, and not work with java.lang.Double?

Many thanks in advance for your attention.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Call any method with java.lang.Double or java.lang.Integer argument from html page using javascript inside a webview component.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Show the message with 15.5 value.
ACTUAL -
Show the message with null value.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
// Create all classes and resources in the same package named webviewtests in a simple JavaFX project

// Main class WebViewTests.java


package webviewtests;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 *
 * @author Marcos Martinewski Alves
 */
public class WebViewTests extends Application {
    
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
        
        Scene scene = new Scene(root);
        
        stage.setMaximized(true);
        stage.setScene(scene);
        stage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
    
}

// FXMLDocument.xml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<StackPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="webviewtests.FXMLDocumentController">
    <javafx.scene.web.WebView fx:id="webview"/>
</StackPane>

// FXMLDocumentController.java

package webviewtests;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.web.WebErrorEvent;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javax.swing.JOptionPane;
import netscape.javascript.JSObject;

/**
 *
 * @author Marcos Martinewski Alves
 */
public class FXMLDocumentController implements Initializable {

    @FXML
    private WebView webview;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        initializeWebView();
        loadTestPage();
    }

    private void initializeWebView() {
        webview.getEngine().setOnAlert((WebEvent<String> event) -> {
            JOptionPane.showMessageDialog(null, event.getData());
        });
        webview.getEngine().setOnError((WebErrorEvent event) -> {
            event.getException().printStackTrace(System.err);
        });
        webview.getEngine().getLoadWorker().stateProperty().addListener((ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) -> {
            if (Worker.State.SUCCEEDED.equals(newValue)) {
                initSimpleJavaObject();
            }
        });
    }

    private void loadTestPage() {
        webview.getEngine().load(getClass().getResource("test.html").toExternalForm());
    }

    private void initSimpleJavaObject() {
        SimpleJavaObject simpleJavaObject = new SimpleJavaObject();
        JSObject jsWindow = (JSObject) webview.getEngine().executeScript("window");
        jsWindow.setMember("simpleJavaObject", simpleJavaObject);
        webview.getEngine().executeScript("test();");
    }

}

// SimpleJavaObject.java

package webviewtests;

/**
 *
 * @author Marcos Martinewski Alves
 */
public class SimpleJavaObject {

    private Double doubleAttribute = null;
    
    private final StringBuilder sbDebugText = new StringBuilder();

    public SimpleJavaObject() {

    }
    
    public void setDoubleAttribute(Double doubleAttributeArgument) {
        debug("Double setter: "+doubleAttributeArgument);
        doubleAttribute = doubleAttributeArgument;
    }

//    public void setDoubleAttribute(double doubleAttributeArgument) {
//        debug("double setter: "+doubleAttributeArgument);
//        doubleAttribute = doubleAttributeArgument;
//    }

//    public void setDoubleAttribute(Object doubleAttributeArgument) {
//        debug("Object setter: "+doubleAttributeArgument);
//        doubleAttribute = (Double) doubleAttributeArgument;
//    }

//    public void setDoubleAttribute(String doubleAttributeArgument) {
//        debug("String setter: "+doubleAttributeArgument);
//        doubleAttribute = Double.valueOf(doubleAttributeArgument);
//    }

    public Double getDoubleAttribute() {
        return doubleAttribute;
    }
    
    private void debug(String str) {
        sbDebugText.append(str);
        sbDebugText.append("\n");
    }
    
    public String getDebugText() {
        return sbDebugText.toString();
    }

}

// test.html

<!DOCTYPE html>
<html>
    <head>
        <title>WebView tests</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script type="text/javascript">
            var simpleJavaObject = null;
            function test() {
                alert("simpleJavaObject: " + simpleJavaObject);
                simpleJavaObject.setDoubleAttribute(15.5);
                alert("simpleJavaObject.getDoubleAttribute(): " + simpleJavaObject.getDoubleAttribute());
                alert("Test summary:\n"+simpleJavaObject.getDebugText());
            }
        </script>
    </head>
    <body>
        
    </body>
</html>



---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
There is a one workaround, reimplementing all methods that use java.lang.Double as arguments, to use native double, but this may imply in some alterations of expected behaviour of the app.


Comments
http://hg.openjdk.java.net/openjfx/9-dev/rt/rev/251198e174cd
20-11-2015

+1
20-11-2015

+1 I verified that the new unit tests pass with the fix and fail without the fix which is good.
20-11-2015

+1
19-11-2015

@Kevin: Crash was due to some of the illegal native typecasting which was a side effect of my refactoring. So I removed all refactoring related changes(which I will take care as a separate bug). http://cr.openjdk.java.net/~ghb/arunprasad/8141386/webrev.03/ P.S. The new patch takes care of few special cases (Number, Double, Integer and Boolean). I'm not sure whether we need to handle Short, Byte, Char, Long and Float.
19-11-2015

Thanks @Kevin. I didn't executed "gradle test". I tried running "gradle test" on Linux, the same crash is reproducible.
19-11-2015

I attached the console log file and hs_err file from the test run on OS X. It crashes every time with the patch, so it is not an intermittent failure. The tests were run as follows: gradle --info -PCOMPILE_WEBKIT=true :web:test
18-11-2015

1 0x126c59d9b JSC::JSObject::toString(JSC::ExecState*) const 2 0x126546056 Java_com_sun_webkit_dom_JSObject_toStringImpl 3 0x108c81954 4 0x108c739d0 # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x00000001269097ae, pid=27657, tid=22787 # # JRE version: Java(TM) SE Runtime Environment (8.0_40-b27) (build 1.8.0_40-b27) # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.40-b25 mixed mode bsd-amd64 compressed oops) # Problematic frame: # C [libjfxwebkit.dylib+0xe2e7ae] WTFCrash+0x3e # # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # # An error report file with more information is saved as: # /Users/kcr/javafx/9-kcr/jfx/rt/modules/web/hs_err_pid27657.log # # If you would like to submit a bug report, please visit: # http://bugreport.java.com/bugreport/crash.jsp # The crash happened outside the Java Virtual Machine in native code. # See problematic frame for where to report the bug. # :web:test FAILED :web:test (Thread[main,5,main]) completed. Took 1 mins 4.049 secs. FAILURE: Build failed with an exception.
18-11-2015

I ran "gradle test" with this patch and it crashes for me on Mac OS X.
18-11-2015

This patch includes unrelated white-space change to JNIUtilityPrivate.cpp (to eliminate DOS line endings). It is generally not a best practice to include white-space changes with other bug fixes. Instead we should fix the white-space problems in a separate changeset to isolate the diffs related to the bug fix and to minimize the chance of a merge conflict.
18-11-2015

@Alexander: Fixed the unnecessary call to getJNIEnv(). Also new change uses "break" to exit the control flow instead of "return", this change is done to have a style consistency with the existing code. http://cr.openjdk.java.net/~ghb/arunprasad/8141386/webrev.02/
17-11-2015

It seems to me there might be an impact on the performance after moving getJNIEnv() to beginning of the convertValueToJValue method body. It is now unnecessarily called for primitive types(like JavaTypeByte, etc).
16-11-2015

@Kevin, looks like some issue with webrev tool. It worked after deleting all bookmarks and local branches in mercurial. http://cr.openjdk.java.net/~ghb/arunprasad/8141386/webrev.01/
16-11-2015

The changeset patch in the webrev is incorrect (it is from another bug). http://cr.openjdk.java.net/~ghb/arunprasad/8141386/webrev.00/rt.changeset Please fix it and upload a new version. I didn't notice this until after I did a build so could not verify your fix.
13-11-2015

http://cr.openjdk.java.net/~ghb/arunprasad/8141386/webrev.00/
13-11-2015

@Kevin: It is not a regression, Same problem is reproducible in 8u51 also. This issue is somewhat similar to JDK-8089260.
13-11-2015

Based on the impact I am raising the priority to P3. @Arunprasad: is this a regression from an earlier version of 8uNN? In particular, it would be good to know whether this works in 8u51 or whether this bug has always been present.
05-11-2015

Simplified test case to reproduce the same, ---------- BEGIN SOURCE ---------- import javafx.application.Application; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.concurrent.Worker; import javafx.scene.web.WebEngine; import javafx.stage.Stage; import netscape.javascript.JSObject; public class Main extends Application { public static void main(String[] args) { Application.launch(Main.class); } public void receiveAsDouble(Double d) { assert d != null; } @Override public void start(Stage stage) throws Exception { final WebEngine engine = new WebEngine(); engine.loadContent("<script type='text/javascript'>function init(obj) { obj.receiveAsDouble(10.1); }</script>"); engine.getLoadWorker().stateProperty().addListener((final ObservableValue<? extends Worker.State> observableValue, final Worker.State oldState, final Worker.State newState) -> { if (newState == Worker.State.SUCCEEDED) { JSObject window = (JSObject) engine.executeScript("window"); window.call("init", this); } }); } } ---------- END SOURCE ----------
05-11-2015