Skip to content

Commit 770c63e

Browse files
TimothyGudomenic
authored andcommitted
Add [WebIDL2JSFactory] for producing an interface factory function
This will allow per-Window interfaces to be generated, which is important for certain cases in jsdom.
1 parent 3b0c523 commit 770c63e

File tree

1 file changed

+141
-92
lines changed

1 file changed

+141
-92
lines changed

lib/constructs/interface.js

Lines changed: 141 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ const keywords = require("../keywords");
1414
function Interface(idl, opts) {
1515
this.idl = idl;
1616
this.name = idl.name;
17+
this.factory = !!utils.getExtAttr(this.idl.extAttrs, "WebIDL2JSFactory");
1718

1819
this.mixins = [];
1920
this.requires = {};
2021
this.str = null;
2122
this.opts = opts;
22-
this.iterable = false;
23+
this.iterable = this.idl.members.some(member => member.type === "iterable");
2324
}
2425

2526
Interface.prototype.type = "interface";
@@ -28,6 +29,55 @@ Interface.prototype.implements = function (source) {
2829
this.mixins.push(source);
2930
};
3031

32+
Interface.prototype.generateIterator = function () {
33+
if (this.iterable) {
34+
this.str += `
35+
const IteratorPrototype = Object.create(utils.IteratorPrototype, {
36+
next: {
37+
value: function next() {
38+
const internal = this[utils.iterInternalSymbol];
39+
const target = internal.target;
40+
const kind = internal.kind;
41+
const index = internal.index;
42+
const values = Array.from(target[impl]);
43+
const len = values.length;
44+
if (index >= len) {
45+
return { value: undefined, done: true };
46+
}
47+
48+
const pair = values[index];
49+
internal.index = index + 1;
50+
51+
let result;
52+
switch (kind) {
53+
case "key":
54+
result = pair[0];
55+
break;
56+
case "value":
57+
result = pair[1];
58+
break;
59+
case "key+value":
60+
result = [pair[0], pair[1]];
61+
break;
62+
}
63+
return { value: result, done: false };
64+
},
65+
writable: true,
66+
enumerable: true,
67+
configurable: true
68+
},
69+
toString: {
70+
value: function toString() {
71+
return "[object ${this.name}Iterator]";
72+
},
73+
writable: true,
74+
enumerable: false,
75+
configurable: true
76+
}
77+
});`;
78+
}
79+
};
80+
3181
Interface.prototype.generateConstructor = function () {
3282
const overloads = Overloads.getEffectiveOverloads("constructor", 0, this.idl, null);
3383

@@ -58,7 +108,7 @@ Interface.prototype.generateConstructor = function () {
58108
this.str += conversions.body + "\n";
59109

60110
this.str += `
61-
module.exports.setup(this, args);
111+
iface.setup(this, args);
62112
}\n`;
63113
} else {
64114
this.str += `function ${this.name}() {
@@ -105,6 +155,52 @@ ${this.mixins[i]}.mixedInto.push(${this.name});\n`;
105155
};
106156

107157
Interface.prototype.generateExport = function () {
158+
this.str += `
159+
mixedInto: [],
160+
is(obj) {
161+
if (obj) {
162+
if (obj[impl] instanceof Impl.implementation) {
163+
return true;
164+
}
165+
for (let i = 0; i < module.exports.mixedInto.length; ++i) {
166+
if (obj instanceof module.exports.mixedInto[i]) {
167+
return true;
168+
}
169+
}
170+
}
171+
return false;
172+
},
173+
isImpl(obj) {
174+
if (obj) {
175+
if (obj instanceof Impl.implementation) {
176+
return true;
177+
}
178+
179+
const wrapper = utils.wrapperForImpl(obj);
180+
for (let i = 0; i < module.exports.mixedInto.length; ++i) {
181+
if (wrapper instanceof module.exports.mixedInto[i]) {
182+
return true;
183+
}
184+
}
185+
}
186+
return false;
187+
},`;
188+
189+
if (this.iterable) {
190+
this.str += `
191+
createDefaultIterator(target, kind) {
192+
const iterator = Object.create(IteratorPrototype);
193+
iterator[utils.iterInternalSymbol] = {
194+
target,
195+
kind,
196+
index: 0
197+
};
198+
return iterator;
199+
},`;
200+
}
201+
};
202+
203+
Interface.prototype.generateIface = function () {
108204
const shouldExposeRoot = !utils.getExtAttr(this.idl.extAttrs, "NoInterfaceObject");
109205

110206
const exposedMap = {};
@@ -132,84 +228,9 @@ Interface.prototype.generateExport = function () {
132228
exposers.push(keys[i] + ": { " + exposedOnObj.join(", ") + " }");
133229
}
134230

135-
if (this.iterable) {
136-
this.str += `\nconst IteratorPrototype = Object.create(utils.IteratorPrototype, {
137-
next: {
138-
value: function next() {
139-
const internal = this[utils.iterInternalSymbol];
140-
const target = internal.target;
141-
const kind = internal.kind;
142-
const index = internal.index;
143-
const values = Array.from(target[impl]);
144-
const len = values.length;
145-
if (index >= len) {
146-
return { value: undefined, done: true };
147-
}
148-
149-
const pair = values[index];
150-
internal.index = index + 1;
151-
152-
let result;
153-
switch (kind) {
154-
case "key":
155-
result = pair[0];
156-
break;
157-
case "value":
158-
result = pair[1];
159-
break;
160-
case "key+value":
161-
result = [pair[0], pair[1]];
162-
break;
163-
}
164-
return { value: result, done: false };
165-
},
166-
writable: true,
167-
enumerable: true,
168-
configurable: true
169-
},
170-
toString: {
171-
value: function toString() {
172-
return "[object ${this.name}Iterator]";
173-
},
174-
writable: true,
175-
enumerable: false,
176-
configurable: true
177-
}
178-
});`;
179-
}
180-
181231
// since we don't have spread arg calls, we can't do new Interface(...arguments) yet
182232
// add initialized symbol as to not destroy the object shape and cause deopts
183-
this.str += `\nmodule.exports = {
184-
mixedInto: [],
185-
is(obj) {
186-
if (obj) {
187-
if (obj[impl] instanceof Impl.implementation) {
188-
return true;
189-
}
190-
for (let i = 0; i < module.exports.mixedInto.length; ++i) {
191-
if (obj instanceof module.exports.mixedInto[i]) {
192-
return true;
193-
}
194-
}
195-
}
196-
return false;
197-
},
198-
isImpl(obj) {
199-
if (obj) {
200-
if (obj instanceof Impl.implementation) {
201-
return true;
202-
}
203-
204-
const wrapper = utils.wrapperForImpl(obj);
205-
for (let i = 0; i < module.exports.mixedInto.length; ++i) {
206-
if (wrapper instanceof module.exports.mixedInto[i]) {
207-
return true;
208-
}
209-
}
210-
}
211-
return false;
212-
},
233+
this.str += `
213234
create(constructorArgs, privateData) {
214235
let obj = Object.create(${this.name}.prototype);
215236
this.setup(obj, constructorArgs, privateData);
@@ -220,15 +241,6 @@ Interface.prototype.generateExport = function () {
220241
this.setup(obj, constructorArgs, privateData);
221242
return utils.implForWrapper(obj);
222243
},
223-
createDefaultIterator(target, kind) {
224-
const iterator = Object.create(IteratorPrototype);
225-
iterator[utils.iterInternalSymbol] = {
226-
target,
227-
kind,
228-
index: 0
229-
};
230-
return iterator;
231-
},
232244
_internalSetup(obj) {`;
233245

234246
if (this.idl.inheritance) {
@@ -255,7 +267,18 @@ Interface.prototype.generateExport = function () {
255267
this.str += `
256268
},
257269
setup(obj, constructorArgs, privateData) {
258-
if (!privateData) privateData = {};
270+
if (!privateData) privateData = {};`;
271+
272+
if (this.factory) {
273+
this.str += `
274+
for (var prop in defaultPrivateData) {
275+
if (!(prop in privateData)) {
276+
privateData[prop] = defaultPrivateData[prop];
277+
}
278+
}`;
279+
}
280+
281+
this.str += `
259282
privateData.wrapper = obj;
260283
261284
this._internalSetup(obj);\n`;
@@ -273,8 +296,7 @@ Interface.prototype.generateExport = function () {
273296
interface: ${this.name},
274297
expose: {
275298
${exposers.join(",\n ")}
276-
}
277-
};\n\n`;
299+
}`;
278300
};
279301

280302
Interface.prototype.generateOperations = function () {
@@ -292,7 +314,6 @@ Interface.prototype.generateOperations = function () {
292314
done[member.name] = true;
293315
break;
294316
case "iterable":
295-
this.iterable = true;
296317
member = new Iterable(this, this.idl, memberIdl, { customTypes: this.opts.customTypes });
297318
break;
298319
default:
@@ -375,6 +396,16 @@ ${this.name}.prototype[Symbol.unscopables] = ${JSON.stringify(unscopables, null,
375396
};
376397

377398
Interface.prototype.generate = function () {
399+
this.generateIterator();
400+
401+
if (this.factory) {
402+
// TODO: use default function parameters when jsdom requires Node >=6
403+
this.str += `
404+
module.exports = {
405+
createInterface: function (defaultPrivateData) {
406+
defaultPrivateData = defaultPrivateData === undefined ? {} : defaultPrivateData;\n\n`;
407+
}
408+
378409
this.generateConstructor();
379410
this.generateMixins();
380411

@@ -386,7 +417,25 @@ Interface.prototype.generate = function () {
386417

387418
this.generateSymbols();
388419

389-
this.generateExport();
420+
this.str += `
421+
const iface = {`;
422+
423+
if (this.factory) {
424+
this.generateIface();
425+
this.str += `
426+
};
427+
return iface;
428+
},`;
429+
this.generateExport();
430+
this.str += `
431+
};\n`;
432+
} else {
433+
this.generateExport();
434+
this.generateIface();
435+
this.str += `
436+
};
437+
module.exports = iface;\n`;
438+
}
390439
};
391440

392441
Interface.prototype.toString = function () {

0 commit comments

Comments
 (0)