Skip to content

Commit 0176da0

Browse files
committed
Simplify [LegacyFactoryFunction] implementation
1 parent 45cd1a8 commit 0176da0

File tree

7 files changed

+1156
-1003
lines changed

7 files changed

+1156
-1003
lines changed

README.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -409,21 +409,13 @@ However, it is not required! The wrapper classes will have a correct inheritance
409409

410410
### The `[LegacyFactoryFunction]` extended attribute
411411

412-
For interfaces which have the `[LegacyFactoryFunction]` extended attribute, the implementation class file must contain the `legacyFactoryFunction` export, with the signature `(globalObject, legacyFactoryFunctionArgs, legacyFactoryFunctionName)`, which is used for:
412+
For interfaces which have the `[LegacyFactoryFunction]` extended attribute, the implementation class file must contain the `legacyFactoryFunction` export, with the signature `(globalObject, ...legacyFactoryFunctionArgs)`, which is used for:
413413

414414
- Setting up initial state that will always be used, such as caches or default values
415415
- Keep a reference to the relevant `globalObject` for later consumption.
416416
- Processing constructor arguments `legacyFactoryFunctionArgs` passed to the legacy factory function constructor, if the legacy factory function takes arguments.
417-
- Switching on the `legacyFactoryFunctionName`, if the interface defines multiple legacy factory functions, eg.:
418417

419-
```webidl
420-
[LegacyFactoryFunction=OverloadedLegacyFactoryFunction(DOMString arg1),
421-
LegacyFactoryFunction=OverloadedLegacyFactoryFunction(long arg1, long arg2),
422-
LegacyFactoryFunction=SimpleLegacyFactoryFunction(optional DOMString src)]
423-
interface SomeInterface {};
424-
```
425-
426-
The `legacyFactoryFunction` export is called with a `this` value of a new uninitialized implementation instance, which may be ignored by returning a different object, similarly to how constructors with overridden return types are implemented.
418+
The `legacyFactoryFunction` export is called with a `this` value of a new uninitialized implementation instance, which may be ignored by returning a different object, similarly to how constructors with overridden return values are implemented.
427419

428420
### The init export
429421

lib/constructs/interface.js

Lines changed: 68 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -33,65 +33,6 @@ const defaultClassMethodDescriptor = {
3333
writable: true
3434
};
3535

36-
class LegacyFactoryFunction {
37-
constructor(ctx, I, idl) {
38-
this.ctx = ctx;
39-
this.interface = I;
40-
this.idls = [idl];
41-
this.name = idl.rhs.value;
42-
}
43-
44-
generate() {
45-
const requires = new utils.RequiresMap(this.ctx);
46-
47-
let str = "";
48-
49-
if (!this.name) {
50-
throw new Error(`Internal error: this legacy factory function does not have a name (in interface ${this.interface.name})`);
51-
}
52-
53-
const overloads = Overloads.getEffectiveOverloads("legacy factory function", this.name, 0, this.interface);
54-
let minOp = overloads[0];
55-
for (let i = 1; i < overloads.length; ++i) {
56-
if (overloads[i].nameList.length < minOp.nameList.length) {
57-
minOp = overloads[i];
58-
}
59-
}
60-
61-
const argNames = minOp.nameList;
62-
63-
const conversions = Parameters.generateOverloadConversions(
64-
this.ctx, "legacy factory function", this.name, this.interface, `Failed to construct '${this.name}': `);
65-
requires.merge(conversions.requires);
66-
67-
const setupArgs = [
68-
"globalObject",
69-
conversions.hasArgs ? "args" : "[]",
70-
`"${this.name}"`
71-
];
72-
73-
str += `
74-
if (new.target === undefined) {
75-
throw new TypeError("Class constructor ${this.name} cannot be invoked without 'new'");
76-
}
77-
78-
${conversions.body}
79-
`;
80-
81-
// This implements the WebIDL legacy factory function behavior, as well as support for overridding
82-
// the return type, which is used by HTML's element legacy factory functions:
83-
str += `
84-
const thisArgument = exports.new(globalObject, new.target);
85-
const result = Impl.legacyFactoryFunction.call(thisArgument, ${formatArgs(setupArgs)});
86-
return utils.tryWrapperForImpl(utils.isObject(result) ? result : thisArgument);
87-
`;
88-
89-
this.interface.addLegacyFactoryFunction(this.name, argNames, str);
90-
91-
return { requires };
92-
}
93-
}
94-
9536
class Interface {
9637
constructor(ctx, idl, opts) {
9738
this.ctx = ctx;
@@ -113,7 +54,7 @@ class Interface {
11354
this.attributes = new Map();
11455
this.staticAttributes = new Map();
11556
this.constants = new Map();
116-
this.legacyFactoryFunctions = new Map();
57+
this.legacyFactoryFunctions = [];
11758

11859
this.indexedGetter = null;
11960
this.indexedSetter = null;
@@ -130,7 +71,6 @@ class Interface {
13071
this._outputStaticMethods = new Map();
13172
this._outputProperties = new Map();
13273
this._outputStaticProperties = new Map();
133-
this._outputLegacyFactoryFunctions = new Map();
13474

13575
const global = utils.getExtAttr(this.idl.extAttrs, "Global");
13676
this.isGlobal = Boolean(global);
@@ -242,10 +182,6 @@ class Interface {
242182
this._outputStaticMethods.set(propName, { type, args, body, descriptor });
243183
}
244184

245-
addLegacyFactoryFunction(name, args, body) {
246-
this._outputLegacyFactoryFunctions.set(name, { args, body });
247-
}
248-
249185
// whence is either "instance" or "prototype"
250186
addProperty(whence, propName, str, {
251187
configurable = true,
@@ -460,19 +396,25 @@ class Interface {
460396
}
461397
}
462398

399+
let legacyFactoryFunctionName;
463400
for (const attr of this.idl.extAttrs) {
464401
if (attr.name === "LegacyFactoryFunction") {
465-
if (attr.rhs.type !== "identifier" || !attr.arguments) {
402+
if (!attr.rhs || attr.rhs.type !== "identifier" || !attr.arguments) {
466403
throw new Error(`[LegacyFactoryFunction] must take a named argument list`);
467404
}
468405

469406
const name = attr.rhs.value;
470-
471-
if (!this.legacyFactoryFunctions.has(name)) {
472-
this.legacyFactoryFunctions.set(name, new LegacyFactoryFunction(this.ctx, this, attr));
473-
} else {
474-
this.legacyFactoryFunctions.get(name).idls.push(attr);
407+
if (legacyFactoryFunctionName === undefined) {
408+
legacyFactoryFunctionName = name;
409+
} else if (legacyFactoryFunctionName !== name) {
410+
// This is currently valid, but not used anywhere, and there are plans to disallow it:
411+
// https://github.com/jsdom/webidl2js/pull/213#issuecomment-621277733
412+
throw new Error(
413+
`Multiple [LegacyFactoryFunction] definitions with different names are not supported on ${this.name}`
414+
);
475415
}
416+
417+
this.legacyFactoryFunctions.push(attr);
476418
}
477419
}
478420
}
@@ -1403,11 +1345,6 @@ class Interface {
14031345
const data = member.generate();
14041346
this.requires.merge(data.requires);
14051347
}
1406-
1407-
for (const legacyFactoryFunction of this.legacyFactoryFunctions.values()) {
1408-
const data = legacyFactoryFunction.generate();
1409-
this.requires.merge(data.requires);
1410-
}
14111348
}
14121349

14131350
generateOffInstanceMethods() {
@@ -1572,29 +1509,63 @@ class Interface {
15721509
}
15731510
}
15741511

1575-
generateLegacyFactoryFunctions() {
1576-
for (const [name, { args, body }] of this._outputLegacyFactoryFunctions) {
1577-
this.str += `
1578-
{
1579-
function ${name}(${formatArgs(args)}) {
1580-
${body}
1581-
}
1512+
generateLegacyFactoryFunction() {
1513+
const { legacyFactoryFunctions } = this;
1514+
if (legacyFactoryFunctions.length === 0) {
1515+
return;
1516+
}
15821517

1583-
Object.defineProperty(${name}, "prototype", {
1584-
configurable: false,
1585-
enumerable: false,
1586-
writable: false,
1587-
value: ${this.name}.prototype
1588-
})
1518+
const name = legacyFactoryFunctions[0].rhs.value;
15891519

1590-
Object.defineProperty(globalObject, "${name}", {
1591-
configurable: true,
1592-
writable: true,
1593-
value: ${name}
1594-
});
1595-
}
1596-
`;
1520+
if (!name) {
1521+
throw new Error(`Internal error: The legacy factory function does not have a name (in interface ${this.name})`);
1522+
}
1523+
1524+
const overloads = Overloads.getEffectiveOverloads("legacy factory function", name, 0, this);
1525+
let minOp = overloads[0];
1526+
for (let i = 1; i < overloads.length; ++i) {
1527+
if (overloads[i].nameList.length < minOp.nameList.length) {
1528+
minOp = overloads[i];
1529+
}
15971530
}
1531+
1532+
const args = minOp.nameList;
1533+
const conversions = Parameters.generateOverloadConversions(
1534+
this.ctx, "legacy factory function", name, this, `Failed to construct '${name}': `);
1535+
this.requires.merge(conversions.requires);
1536+
1537+
const argsSpread = conversions.hasArgs ? "...args" : "";
1538+
1539+
this.str += `
1540+
function ${name}(${formatArgs(args)}) {
1541+
if (new.target === undefined) {
1542+
throw new TypeError("Class constructor ${name} cannot be invoked without 'new'");
1543+
}
1544+
1545+
${conversions.body}
1546+
`;
1547+
1548+
// This implements the WebIDL legacy factory function behavior, as well as support for overridding
1549+
// the return type, which is used by HTML's element legacy factory functions:
1550+
this.str += `
1551+
const thisArgument = exports.new(globalObject, new.target);
1552+
const result = Impl.legacyFactoryFunction.call(thisArgument, globalObject, ${argsSpread});
1553+
return utils.tryWrapperForImpl(utils.isObject(result) ? result : thisArgument);
1554+
}
1555+
1556+
Object.defineProperty(${name}, "prototype", {
1557+
configurable: false,
1558+
enumerable: false,
1559+
writable: false,
1560+
value: ${this.name}.prototype
1561+
})
1562+
1563+
Object.defineProperty(globalObject, "${name}", {
1564+
configurable: true,
1565+
writable: true,
1566+
value: ${name}
1567+
});
1568+
`;
15981569
}
15991570

16001571
generateInstall() {
@@ -1662,7 +1633,7 @@ class Interface {
16621633
}
16631634
}
16641635

1665-
this.generateLegacyFactoryFunctions();
1636+
this.generateLegacyFactoryFunction();
16661637

16671638
this.str += `
16681639
};

lib/overloads.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function getOperations(type, A, I) {
1212
return I.constructorOperations;
1313
}
1414
case "legacy factory function":
15-
return I.legacyFactoryFunctions.get(A).idls;
15+
return I.legacyFactoryFunctions;
1616
}
1717
throw new RangeError(`${type}s are not yet supported`);
1818
}

0 commit comments

Comments
 (0)