google / jsinterop-base

Utilities for GWT and J2CL to interact with JavaScript beyond JsInterop
Apache License 2.0
66 stars 16 forks source link

Support for multiple window contexts for globals #4

Open my2iu opened 6 years ago

my2iu commented 6 years ago

This is sort of obscure, but it would be nice to have support for the same classes existing in different global window contexts. For example, if you have an iframe, it will have its own set of global system classes in its own window object. Sometimes, you need to construct objects from this separate context or do weird comparisons etc.

// Example JS code of different system classes
// How can this be done in JsInterop?
a = new iframe.contentWindow.Date()
b = new Date()
a.constructor === b.constructor   // returns false

Whatever pattern you come up with for that might also be useful when running JavaScript code inside Java because in that case, you might have multiple JavaScript contexts, and you need to specify which JS context you are constructing objects in.

JSInterpreter jsInterpreter1 = new JSInterpreter();
JSInterpreter jsInterpreter2 = new JSInterpreter();
jsInterpreter2.create(Int8Array.class, 10);
jDramaix commented 6 years ago

I don't test the code but I think the code below could work:

import elemental2.core.JsDate;
import elemental2.base.JsConstructorFn;

@JsType(isNative = true)
interface HasDateCtor {
  @JsProperty(name = "Date")
  public JsConstructorFn<JsDate> getDateCtor();
}

//...
JsDate a = ((HasDateCtor)iframe.contentWindow).getDateCtor().construct();
JsDate b = new JsDate();

you could even add default method newDate() on the interface and invoke it directly:

 @JsOverlay
 default JsDate newDate() {
   return getDateCtor().construct();
 }

JsDate a = ((HasDateCtor)iframe.contentWindow).newDate();

Like I said I didn't test the code but this is what came first to my mind.

my2iu commented 6 years ago

Possibly. It's all dependent on the output of J2CL, and I don't have access to that. In any case, the proposed workaround starts getting messy when working with namespaced system classes like Intl.Collator or something.

I was thinking of having an explicit method in jsinterop.base.Js like

public static <T> JsConstructorFn<T> asConstructorFnInContext(Class<T> clazz, @DoNotAutobox Object base) { ... }

that would magically work when used like this:

Js.asConstructorFnInContext(Intl.Collator.class, iframe.contentWindow);

And maybe there could also be a version that explicitly invokes the constructor as well

public static <T> JsConstructorFn<T> constructInContext(Class<T> clazz, @DoNotAutobox Object base, @DoNotAutobox Object... args) { ... }

Though I'm not sure if J2CL changes would be needed to get something like that to work.

With a Js method like that, you could use an iframe to load up some code and define some classes, like GWT. And you could reach in there and create objects from those classes without needing to create a whole bunch of separate factory classes.

my2iu commented 6 years ago

Oh, it would also be good if this worked with JS Interop interfaces that represent JS classes too. I'm not sure if J2CL already does this or not.

@JsType(isNative=true,namespace="maps") interface MapMarker { ... }

MapMarker obj = Js.constructInContext(mapIframe.contentWindow, 
     MapMarker.class, 37.42, -122.08);