-
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Constructor & prototype reform #140
Constructor & prototype reform #140
Conversation
This all sounds reasonable to me. Currently Eventually we may want to move in the direction of #101 where you can do something like require('./generated/FooInterface.js').expose("Window", windowObj); or even require('./generated/bundle-entry.js').exposeAllInterfaces("Window", windowObj); But that is a separate project. So in the meantime let's just remove the expose metadata entirely and have |
test/__snapshots__/test.js.snap
Outdated
const proto = {}; | ||
|
||
Object.defineProperties(proto, { | ||
...Object.getOwnPropertyDescriptors(iface.interface.prototype), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I'm unsure whether this will actually be faster than just doing globalObject.DOMImplementation = { ... a bunch of generated code ... }
. And it seems like it'll have issues like window.DOMImplementation.prototype.createDocument instanceof window.Function === false
.
But, it seems reasonable for now though as a smaller delta from what we're doing currently...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know, creating new function every install
invocation would have worse runtime performance characteristics than creating a brand new class constructor per install
(like with the [WebIDL2JSFactory]
).
I didn't want to make too many changes into a single PR, but my next goal is to get rid of the class
and generate a property descriptor for the prototype and a property descriptor for the constructor. With this PR, the class is only used to retrieve its property descriptors. The idea would be to generate something like this in the future:
const constructorDescriptors = {
/* generated descriptors */
};
const prototypeDescriptors = {
/* generated descriptors */
};
function install() {
const ctor = function Node() {
throw new TypeError('Illegal constructor');
};
Object.defineProperties(ctor, constructorDescriptors);
const proto = {};
Object.defineProperties(proto, constructorDescriptors);
Object.defineProperty(ctor, 'prototype', { value: proto });
Object.defineProperty(proto, 'constructor', { value: ctor });
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to not pursue any complicated attempts at sharing descriptors (and, notably, sharing the functions that are the value
property of descriptors) without some benchmarks showing that's better than just creating a new copy each time. Engines are smart; I suspect that all these descriptor tricks could actually be less optimized than just
function install() {
class Node {
// everything goes inside here
}
}
f62a3b5
to
0220d3d
Compare
I am finally done updating jsdom to consume the new APIs 🎉. You can see the new APIs in actions here: jsdom/jsdom#2691 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly seems good. I'm pretty hesitant about all this copying of descriptors, and would prefer the straightforward approach that WebIDL2JSFactory used of declaring the whole class inside the install function. I suspect that will be more performant, actually; engines can deduplicate the functions behind the scenes as long as they have the same source position, and it won't involve all the reifying of property descriptors and manual re-construction of the class each time.
But, I'm more interested in getting this landed, so if this is the architecture you prefer, we can start here. Just a few comments.
I came to the realization that there is more nuance to the The following changes are needed to accommodate the new API and keep backward compatibility:
After thinking more about it, making I wanted to raise this concern before moving forward with this PR. I would be happy to make the required changes if you think that we should bring back the |
@domenic you were right the inline class declaration has the same performance characteristics or outperforms in some cases, the synthetic constructor creation. You can find the performance results below and the code here. @TimothyGu Do you still have the code somewhere with your initial investigation (#133 (comment))? I would like to better understand why your approach didn't work in the first place. Performance results
|
Thanks for outlining this. This is indeed more subtle. I think these changes are reasonable, although I'd like to name them something like |
Based on the performance investigation results, I decided to move forward with the inline class declaration. The PR is ready for another round of review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one question. Let's get this merged :).
When will this be merged? |
When we have free time. |
This PR adds the capability for webidl2js to generate a brand new constructor and prototype chain per realm. Fixes #133
Proposal
Interface wrapper
install
method introductionThe
install
method takes care of creating a new constructor and a new prototype chain for a realm. This method is only generated on WebIDL interfaces. This method takes a single parameter,globalObject
which is the global object attached to the realm.Interface wrapper
create
andcreateImpl
signature changeNow that we have a brand new constructor and prototype per realm, we need to create wrapper instances with the right constructor. In order to do so, I am proposing passing the
globalObject
as the first parameter tocreate(globalObject, constructorArgs, privateData)
andcreateImpl(globalObject, constructorArgs, privateData)
.Interface implementation
constructor
signature changeIn the proof-of-concept, I originally passed the
globalObject
to the implementation via theprivateData
to minimize the amount of code change in jsdom. Now thinking about it, a preferable approach making theconstructor
signature mirror the newcreate
andcreateImpl
signature:constructor(globalObject, constructorArgs, privateData)
.Interface wrapper
interface
andexpose
property removalI don't make sense to expose the shared interface wrapper class anymore.
Custom WebIDL extended attribute
[WebIDL2JSFactory]
removalNot needed anymore with this proposal.
Open questions
[Exposed]
WebIDL ext attribute since JSDOM doesn't need it right? The proposedinstall
method would blindly attach the constructor to the passed global object.