Skip to content

Commit

Permalink
Constructor and prototype reform
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pmdartus authored and domenic committed Nov 22, 2019
1 parent 43c480e commit 57711f4
Show file tree
Hide file tree
Showing 6 changed files with 3,666 additions and 3,323 deletions.
57 changes: 17 additions & 40 deletions README.md
Expand Up @@ -27,6 +27,7 @@ will generate a JavaScript wrapper class file roughly like this:
```js
const conversions = require("webidl-conversions");
const impl = require("./utils.js").implSymbol;
const ctorRegistry = require("./utils.js").ctorRegistrySymbol;

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

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

exports.interface = SomeInterface;

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

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.

#### `create(constructorArgs, privateData)`
#### `install(globalObject)`

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.)

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.
#### `create(globalObject, constructorArgs, privateData)`

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.

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.

#### `createImpl(constructorArgs, privateData)`
#### `createImpl(globalObject, constructorArgs, privateData)`

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.
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.

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

#### `setup(obj, constructorArgs, privateData)`
#### `setup(obj, globalObject, constructorArgs, privateData)`

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).
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).

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

#### `interface`

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.

#### `expose`

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:

```js
{
nameOfGlobal1: {
nameOfInterface: InterfaceClass
},
nameOfGlobal2: {
nameOfInterface: InterfaceClass
},
// etc.
}
```

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.

### For dictionaries

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

### The constructor

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

- Setting up initial state that will always be used, such as caches or default values
- Keep a reference to the relevant `globalObject` for later consumption.
- Processing constructor arguments `constructorArgs` passed to the wrapper class constructor, if the interface in question has a `[Constructor]` extended attribute.
- 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.

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

#### Static attributes

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.
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.

### toString method implementing IDL stringifier

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

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.

### `[WebIDL2JSFactory]`

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.

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.

### `[WebIDL2JSValueAsUnsupported=value]`

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`.
Expand Down

0 comments on commit 57711f4

Please sign in to comment.