Skip to content

Commit 57711f4

Browse files
pmdartusdomenic
authored andcommitted
Constructor and prototype reform
This fixes #133 by implementing the proposal therein, which changes webidl2js's overall architecture. Now, the code generated by webidl2js installs new constructors and prototypes on a given global object, instead of exporting classes directly. See the updated README for more usage details, but a quick summary of the changes: * The generated files for interfaces now export an install(globalObject) function, instead of an interface property. * The create() and createImpl() exports now take a global object as their new first parameter, making the signature (globalObject, constructorArgs, privateData). * Similarly, impl class constructors now also take a global object as their first argument, given them the same signature. * The expose export was removed, as it was not generally used. We may introduce better support for exposure in the future, probably as an option to the install() export. * [WebIDL2JSFactory] was removed, as it is no longer necessary: all interfaces are now per-global object.
1 parent 43c480e commit 57711f4

File tree

6 files changed

+3666
-3323
lines changed

6 files changed

+3666
-3323
lines changed

README.md

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ will generate a JavaScript wrapper class file roughly like this:
2727
```js
2828
const conversions = require("webidl-conversions");
2929
const impl = require("./utils.js").implSymbol;
30+
const ctorRegistry = require("./utils.js").ctorRegistrySymbol;
3031

3132
const Impl = require("./SomeInterface-impl.js").implementation;
3233

@@ -64,10 +65,9 @@ Object.defineProperties(SomeInterface.prototype, {
6465
[Symbol.toStringTag]: { value: "SomeInterface", configurable: true }
6566
});
6667

67-
exports.interface = SomeInterface;
68-
69-
exports.create = (constructorArgs = [], privateData = {}) => {
70-
const obj = Object.create(SomeInterface.prototype);
68+
exports.create = (globalObject, constructorArgs = [], privateData = {}) => {
69+
const ctor = globalObject[ctorRegistry].SomeInterface;
70+
const obj = Object.create(ctor.prototype);
7171
obj[impl] = new Impl(constructorArgs, privateData);
7272
return obj;
7373
};
@@ -151,46 +151,28 @@ Performs the Web IDL conversion algorithm for this interface, converting _value_
151151

152152
In practice, this means doing a type-check equivalent to `is(value)`, and if it passes, returns the corresponding impl. If the type-check fails, it throws an informative exception. _context_ can be used to describe the provided value in any resulting error message.
153153

154-
#### `create(constructorArgs, privateData)`
154+
#### `install(globalObject)`
155+
156+
This method creates a brand new wrapper constructor and prototype and attach it to the passed `globalObject`. It also registers the created constructor with the `globalObject`'s global constructor registry, which makes `create()`, `createImpl()`, and `setup()` work. (Thus, it is important to invoke `install()` before invoking those methods, as otherwise they will throw.)
155157

156-
Creates a new instance of the wrapper class and corresponding implementation class, passing in the `constructorArgs` array and `privateData` object to the implementation class constructor. Then returns the wrapper class.
158+
#### `create(globalObject, constructorArgs, privateData)`
159+
160+
Creates a new instance of the wrapper class and corresponding implementation class, passing in the `globalObject`, the `constructorArgs` array and `privateData` object to the implementation class constructor. Then returns the wrapper class.
157161

158162
This is useful in other parts of your program that are not implementation class files, but instead want to interface with them from the outside. It's also mostly useful when creating instances of classes that do not have a `[Constructor]`, i.e. are not constructible via their wrapper class constructor.
159163

160-
#### `createImpl(constructorArgs, privateData)`
164+
#### `createImpl(globalObject, constructorArgs, privateData)`
161165

162-
Creates a new instance of the wrapper class and corresponding implementation class, passing in the `constructorArgs` array and `privateData` object to the implementation class constructor. Then returns the implementation class.
166+
Creates a new instance of the wrapper class and corresponding implementation class, passing in the `globalObject`, the `constructorArgs` array and `privateData` object to the implementation class constructor. Then returns the implementation class.
163167

164168
This is useful inside implementation class files, where it is easiest to only deal with impls, not wrappers.
165169

166-
#### `setup(obj, constructorArgs, privateData)`
170+
#### `setup(obj, globalObject, constructorArgs, privateData)`
167171

168-
This function is mostly used internally, and almost never should be called by your code. The one exception is if you need to inherit from a wrapper class corresponding to an interface without a `[Constructor]`, from a non-webidl2js-generated class. Then, you can call `SuperClass.setup(this, [], privateData)` as a substitute for doing `super()` (which would throw).
172+
This function is mostly used internally, and almost never should be called by your code. The one exception is if you need to inherit from a wrapper class corresponding to an interface without a `[Constructor]`, from a non-webidl2js-generated class. Then, you can call `SuperClass.setup(this, globalObject, [], privateData)` as a substitute for doing `super()` (which would throw).
169173

170174
jsdom does this for `Window`, which is written in custom, non-webidl2js-generated code, but inherits from `EventTarget`, which is generated by webidl2js.
171175

172-
#### `interface`
173-
174-
This export is the wrapper class interface, suitable for example for putting on a global scope or exporting to module consumers who don't know anything about webidl2js.
175-
176-
#### `expose`
177-
178-
This export contains information about where an interface is supposed to be exposed as a property. It takes into account the Web IDL extended attribute `[Exposed]` to generate a data structure of the form:
179-
180-
```js
181-
{
182-
nameOfGlobal1: {
183-
nameOfInterface: InterfaceClass
184-
},
185-
nameOfGlobal2: {
186-
nameOfInterface: InterfaceClass
187-
},
188-
// etc.
189-
}
190-
```
191-
192-
This format may seem a bit verbose, but eventually when we support `[NamedConstructor]`, there will be potentially more than one key/value pair per global, and it will show its worth.
193-
194176
### For dictionaries
195177

196178
#### `convert(value, { context })`
@@ -207,9 +189,10 @@ Implementation class files contain a single export, `implementation`, whose valu
207189

208190
### The constructor
209191

210-
A constructor for your implementation class, with signature `(constructorArgs, privateData)` can serve several purposes:
192+
A constructor for your implementation class, with signature `(globalObject, constructorArgs, privateData)` can serve several purposes:
211193

212194
- Setting up initial state that will always be used, such as caches or default values
195+
- Keep a reference to the relevant `globalObject` for later consumption.
213196
- Processing constructor arguments `constructorArgs` passed to the wrapper class constructor, if the interface in question has a `[Constructor]` extended attribute.
214197
- Processing any private data `privateData` which is provided when other parts of your program use the generated `create()` or `createImpl()` exports of the wrapper class file. This is useful for constructing instances with specific state that cannot be constructed via the wrapper class constructor.
215198

@@ -247,7 +230,7 @@ Note that for IDL attributes that are `readonly`, these properties do not need t
247230

248231
#### Static attributes
249232

250-
Just like static operations, static attributes are defined as properties on the constructor of the implementation class. And just like other attributes, the attribute can either be implemented as an accessor attribute or (if it is readonly) a data attribute. Note that, unless the `[WebIDL2JSFactory]` extended attribute is specified on the interface, any mutations to writable static attributes of the class will reflect on other places that use the same interface.
233+
Just like static operations, static attributes are defined as properties on the constructor of the implementation class. And just like other attributes, the attribute can either be implemented as an accessor attribute or (if it is readonly) a data attribute.
251234

252235
### toString method implementing IDL stringifier
253236

@@ -386,12 +369,6 @@ Note that only the basics of the reflect algorithm are implemented so far: `bool
386369

387370
In the future we may move this extended attribute out into some sort of plugin, since it is more related to HTML than to Web IDL.
388371

389-
### `[WebIDL2JSFactory]`
390-
391-
This extended attribute can be applied to interfaces to cause them to generate a factory that generates wrapper classes, instead of generating a single wrapper class.
392-
393-
It is currently used by [jsdom](https://github.com/tmpvar/jsdom) for classes which need to specialize their behavior per `Window` object; by default [jsdom shares classes across all `Window`s](https://github.com/tmpvar/jsdom#shared-constructors-and-prototypes), but with `[WebIDL2JSFactory]`, an exception can be made for certain classes that need it.
394-
395372
### `[WebIDL2JSValueAsUnsupported=value]`
396373

397374
This extended attribute can be applied to named or indexed getters or setters. It says that whether the interface supports a given property name/index can be automatically derived by looking at the return value of its indexed getter/setter: whenever `value` is returned, the name/index is unsupported. Typically, `value` is either `undefined` or `_null`.

0 commit comments

Comments
 (0)