Context
Currently, the constructor and prototype chains are shared between all the realms. This approach has multiple downstream impacts:
- on the maintainer side, it requires hacks to implements interfaces that need a reference to the current global object, eg:
[HTMLConstructor]. WebIDL2JS also had to implement the [WebIDL2JSFactory] IDL extended attribute to generate wrapper class per window object.
- on the consumer side, it is unexpected for developers that all constructors and prototypes are shared, especially when they use a test framework abstracting jsdom. I personally spent a lot of time figuring out the best way to apply polyfill in the context of jsdom.
While recreating a brand new wrapper class for each new realm won't scale, I think there might be other alternatives worth exploring.
Approach 1: New constructor / Same prototype
This approach consists of recreating a brand new constructor per realm and reuse the same prototype. WebIDL2JS would generate for all the interfaces annotated with the [Exposed] IDL extended attribute would an install method that would be in charge of creating the interface in the context of the realm.
class TestGlobal extends BaseGlobal { /* ... */ }
const iface = {
/* ... */
install(globalObject, privateData) {
const Test = function() {
return iface.setup(Object.create(TestGlobal.prototype), [], privateData);
};
// Apply static properties if necessary
Object.setPrototypeOf(Test, TestGlobal.prototype);
Object.defineProperty(globalObject, "Test", {
configurable: true,
overridable: true,
value: Test
});
// Apply [NamedConstructor] if necessary
}
}
module.export = iface;
You can find here an experimental branch with the code change: pmdartus/constructor
Pros:
- Really light-weight approach.
- No need for
[WebIDL2JSFactory] anymore.
- The new install method can be used to implement
[NamedConstructor].
- Minimal change needed on jsdom
Cons:
- It doesn't solve the polyfill issue.
- With this approach the only the
(new Test()).constructor points to TestGlobal instead of Test, since the prototype is shared. When running the experimental WebIDL2JS against jsdom test, this issue made 5 tests failed.
Approach 2: New constructor / New prototype
In this approach, a brand new constructor and prototype chain is created per realm. Instead of creating a brand new wrapper class per realm, like [WebIDL2JSFactory], we would instead only create a new prototype chain but reuse the existing property descriptors. It also reuses the concept of install showed in proposal 1.
The assumption here is that the performance cost of the creation of a prototype chain while reusing the existing descriptors is way lower than re-creating a brand new class per realm.
class TestGlobal extends BaseGlobal { /* ... */ }
const iface = {
/* ... */
install(globalObject, privateData) {
const Test = function() {
return iface.setup(Object.create(TestGlobal.prototype), [], privateData);
};
// Apply static properties if necessary
const proto = Object.create(globalObject.Base.prototype);
Object.defineProperties(proto, Object.getOwnProperties(TestGlobal.prototype));
Object.setPrototypeOf(Test, proto);
Object.defineProperty(globalObject, "Test", {
configurable: true,
overridable: true,
value: Test
});
// Apply [NamedConstructor] if necessary
}
}
module.export = iface;
Pros:
- No need for
[WebIDL2JSFactory] anymore.
- The new install method can be used to implement
[NamedConstructor].
- Solves the polyfill issue.
- Fully spec compliant.
Cons:
- Heavier solution. Would need to do a POC to evaluate the actual performance penalty of this approach.
- Requires changes to jsdom. Now that we have a brand new prototype per realm, we need to pass the global object for every
create() and createImpl() invocations, for it to retrieve the right prototype associated with the realm.
Before diving deeper into this, I would like getting some initial feedback on both approaches and see if it worth pursuing.
/cc @domenic @TimothyGu @Sebmaster @Zirro
Context
Currently, the constructor and prototype chains are shared between all the realms. This approach has multiple downstream impacts:
[HTMLConstructor]. WebIDL2JS also had to implement the[WebIDL2JSFactory]IDL extended attribute to generate wrapper class per window object.While recreating a brand new wrapper class for each new realm won't scale, I think there might be other alternatives worth exploring.
Approach 1: New constructor / Same prototype
This approach consists of recreating a brand new constructor per realm and reuse the same prototype. WebIDL2JS would generate for all the interfaces annotated with the
[Exposed]IDL extended attribute would aninstallmethod that would be in charge of creating the interface in the context of the realm.You can find here an experimental branch with the code change: pmdartus/constructor
Pros:
[WebIDL2JSFactory]anymore.[NamedConstructor].Cons:
(new Test()).constructorpoints toTestGlobalinstead ofTest, since the prototype is shared. When running the experimental WebIDL2JS against jsdom test, this issue made 5 tests failed.Approach 2: New constructor / New prototype
In this approach, a brand new constructor and prototype chain is created per realm. Instead of creating a brand new wrapper class per realm, like
[WebIDL2JSFactory], we would instead only create a new prototype chain but reuse the existing property descriptors. It also reuses the concept ofinstallshowed in proposal 1.The assumption here is that the performance cost of the creation of a prototype chain while reusing the existing descriptors is way lower than re-creating a brand new class per realm.
Pros:
[WebIDL2JSFactory]anymore.[NamedConstructor].Cons:
create()andcreateImpl()invocations, for it to retrieve the right prototype associated with the realm.Before diving deeper into this, I would like getting some initial feedback on both approaches and see if it worth pursuing.
/cc @domenic @TimothyGu @Sebmaster @Zirro