-
-
Notifications
You must be signed in to change notification settings - Fork 31
Constructor & prototype reform #140
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
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
installmethod introductionThe
installmethod 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,globalObjectwhich is the global object attached to the realm.Interface wrapper
createandcreateImplsignature 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
globalObjectas the first parameter tocreate(globalObject, constructorArgs, privateData)andcreateImpl(globalObject, constructorArgs, privateData).Interface implementation
constructorsignature changeIn the proof-of-concept, I originally passed the
globalObjectto the implementation via theprivateDatato minimize the amount of code change in jsdom. Now thinking about it, a preferable approach making theconstructorsignature mirror the newcreateandcreateImplsignature:constructor(globalObject, constructorArgs, privateData).Interface wrapper
interfaceandexposeproperty 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 proposedinstallmethod would blindly attach the constructor to the passed global object.