oracle / graaljs

GraalJS – A high-performance, ECMAScript compliant, and embeddable JavaScript runtime for Java
https://www.graalvm.org/javascript/
Universal Permissive License v1.0
1.81k stars 190 forks source link

GraalJS: Cannot bind to proxy object #356

Open REASY opened 4 years ago

REASY commented 4 years ago

The following JS code works in Google Chrome:

console.log("########### Direct call in JS #############")
console.log("window.document.createElement:", window.document.createElement("p"));
console.log("document.createElement.apply:", window.document.createElement.apply(window.document, ["p"]));
console.log("########################")

console.log("\n")

console.log("########## Call via Bind/Call ##############")
var boundCall = Object.bind.apply(Object.call, [Object.bind, Object.call]);
var boundCall2 = boundCall.apply(window, [Object.bind]);
var createElementMethod = boundCall2.apply(window, [document.createElement, window.document]);
console.log(createElementMethod("p"));
console.log(createElementMethod.apply(window.document, ["p"]));
console.log("########################")

And produces image

But when I use GraaJS v20.2.0 with proxy Document objec: https://github.com/REASY/graalvm-js-bind-issue/blob/8402fbb326125db62414a4348ed63cbc70fee6f6/src/main/java/com/examples/graalvm/blink/Document.java#L9t to execute that code https://github.com/REASY/graalvm-js-bind-issue/blob/main/src/main/scala/com/examples/graalvm/BindExample.scala#L20-L23, I get the exception org.graalvm.polyglot.PolyglotException: TypeError: com.examples.graalvm.blink.Document$1@103441bb is not a function:

########### Direct call in JS #############
hasMember: createElement
hasMember: createElement
getMember: createElement
execute(). Received [p]
window.document.createElement: bar
hasMember: createElement
getMember: createElement
execute(). Received [p]
document.createElement.apply: bar
########################
########## Call via Bind/Call ##############
hasMember: createElement
getMember: createElement
[error] (run-main-0) org.graalvm.polyglot.PolyglotException: TypeError: com.examples.graalvm.blink.Document$1@103441bb is not a function
[error] org.graalvm.polyglot.PolyglotException: TypeError: com.examples.graalvm.blink.Document$1@103441bb is not a function
[error]         at <js>.:program(bind_example.js:9)
[error]         at org.graalvm.polyglot.Context.eval(Context.java:345)
[error]         at com.examples.graalvm.BindExample$.execute(BindExample.scala:23)
[error]         at com.examples.graalvm.BindExample$.main(BindExample.scala:29)
[error]         at com.examples.graalvm.BindExample.main(BindExample.scala)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]         at java.lang.reflect.Method.invoke(Method.java:498)
[error] stack trace is suppressed; run last Compile / bgRunMain for the full output
[error] Nonzero exit code: 1
[error] (Compile / runMain) Nonzero exit code: 1
[error] Total time: 1 s, completed Oct 6, 2020 12:13:39 AM

image

GraalVM params:

GraalVM version: 20.2.0 JVM: Oracle JDK 1.8.0_251 Repo: https://github.com/REASY/graalvm-js-bind-issue

wirthi commented 4 years ago

Hi @REASY

thanks for your report. I can confirm this is not working at the moment.

The problem on your side should be easy to fix. You are using a ProxyObject in https://github.com/REASY/graalvm-js-bind-issue/blob/main/src/main/java/com/examples/graalvm/blink/Document.java and from the error message above I take that's what you try to bind. This will fail just like binding a plain JavaScript object would fail: you have to bind something that represents a function. Solution is simple here: just implement ProxyExecutable additionally.

(edit: just saw you actually return a ProxyExecutable from your proxy).

This, however, does not suffice to bind the proxy, as we have not implemented this case currently. The current interop protocol does not allow to specify the this when calling, so we cannot implement bind with correct semantics at the moment. We could just call the function and ignore the bound this, but that could be very surprising to the users.

We are currently investigating our options.

Best, Christian

REASY commented 4 years ago

Hi, @wirthi

Thanks for the reply. Yes, for now I'm wrapping the code by JS function and it works, something like this:

val jsWindow = ctx.eval("js", "window;")
// Document has method `createElement0`
jsWindow.putMember("document", new Document)

// Wrap 
ctx.eval(Source.newBuilder("js", "document.createElement=function(){ return document.createElement0(arguments); };",
     "init.js").build)
wirthi commented 4 years ago

Hi @REASY

the workaround seems fine. But why did you try to bind it then in the first place? The effect of binding is to change the this, and your workaround is fine without that, so why bind in the first place? Was that just convenience to a function outside the scope of document?

Thanks, Christian

REASY commented 4 years ago

@wirthi it is some existing JS code, I just experienced that behavior in case of GraalJS