Skip to content

Commit 9260316

Browse files
ExE-Bossdomenic
authored andcommitted
Create sync iterators in the correct realm
Helps with jsdom/jsdom#2727 (comment).
1 parent cfd8f9a commit 9260316

File tree

5 files changed

+1113
-966
lines changed

5 files changed

+1113
-966
lines changed

lib/constructs/async-iterable.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class AsyncIterable {
4343
4444
${conv.body}
4545
46-
const asyncIterator = exports.createDefaultAsyncIterator(this, "${kind}");
46+
const asyncIterator = exports.createDefaultAsyncIterator(globalObject, this, "${kind}");
4747
if (this[implSymbol][utils.asyncIteratorInit]) {
4848
this[implSymbol][utils.asyncIteratorInit](asyncIterator, args);
4949
}

lib/constructs/interface.js

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -412,25 +412,24 @@ class Interface {
412412
this.included.push(source);
413413
}
414414

415-
generateIterator() {
416-
if (!this.iterable) {
415+
generateInstallIteratorPrototype() {
416+
const { iterable } = this;
417+
if (!iterable) {
417418
return;
418419
}
419420

420-
if (this.iterable.isAsync) {
421+
if (iterable.isAsync) {
421422
this.str += `
422-
const AsyncIteratorPrototype = Object.create(utils.AsyncIteratorPrototype, {
423+
ctorRegistry["${this.name} AsyncIterator"] = Object.assign(Object.create(utils.AsyncIteratorPrototype, {
423424
[Symbol.toStringTag]: {
424425
value: "${this.name} AsyncIterator",
425426
configurable: true
426427
}
427-
});
428-
429-
Object.assign(AsyncIteratorPrototype, {
428+
}, {
430429
next() {
431430
const internal = this && this[utils.iterInternalSymbol];
432431
if (!internal) {
433-
return Promise.reject(new TypeError("next() called on a value that is not an async iterator prototype object"));
432+
return Promise.reject(new TypeError("next() called on a value that is not a ${this.name} async iterator object"));
434433
}
435434
436435
const nextSteps = () => {
@@ -446,7 +445,7 @@ class Interface {
446445
internal.isFinished = true;
447446
return { value: undefined, done: true };
448447
}`;
449-
if (this.iterable.isPair) {
448+
if (iterable.isPair) {
450449
this.str += `
451450
return utils.iteratorResult(next.map(utils.tryWrapperForImpl), kind);
452451
`;
@@ -472,12 +471,12 @@ class Interface {
472471
},
473472
`;
474473

475-
if (this.iterable.hasReturnSteps) {
474+
if (iterable.hasReturnSteps) {
476475
this.str += `
477476
return(value) {
478477
const internal = this && this[utils.iterInternalSymbol];
479478
if (!internal) {
480-
return Promise.reject(new TypeError("return() called on a value that is not an async iterator prototype object"));
479+
return Promise.reject(new TypeError("return() called on a value that is not a ${this.name} async iterator object"));
481480
}
482481
483482
const returnSteps = () => {
@@ -497,16 +496,22 @@ class Interface {
497496
`;
498497
}
499498
this.str += `
500-
});
499+
}));
501500
`;
502-
} else if (this.iterable.isPair) {
501+
} else if (iterable.isPair) {
503502
this.str += `
504-
const IteratorPrototype = Object.create(utils.IteratorPrototype, {
505-
next: {
506-
value: function next() {
503+
ctorRegistry["${this.name} Iterator"] = Object.assign(
504+
Object.create(ctorRegistry["%IteratorPrototype%"], {
505+
[Symbol.toStringTag]: {
506+
configurable: true,
507+
value: "${this.name} Iterator"
508+
}
509+
}),
510+
{
511+
next() {
507512
const internal = this && this[utils.iterInternalSymbol];
508513
if (!internal) {
509-
throw new TypeError("next() called on a value that is not an iterator prototype object");
514+
throw new TypeError("next() called on a value that is not a ${this.name} iterator object");
510515
}
511516
512517
const { target, kind, index } = internal;
@@ -519,16 +524,9 @@ class Interface {
519524
const pair = values[index];
520525
internal.index = index + 1;
521526
return utils.iteratorResult(pair.map(utils.tryWrapperForImpl), kind);
522-
},
523-
writable: true,
524-
enumerable: true,
525-
configurable: true
526-
},
527-
[Symbol.toStringTag]: {
528-
value: "${this.name} Iterator",
529-
configurable: true
527+
}
530528
}
531-
});
529+
);
532530
`;
533531
}
534532
}
@@ -608,8 +606,18 @@ class Interface {
608606
if (this.iterable) {
609607
if (this.iterable.isAsync) {
610608
this.str += `
611-
exports.createDefaultAsyncIterator = (target, kind) => {
612-
const iterator = Object.create(AsyncIteratorPrototype);
609+
exports.createDefaultAsyncIterator = (globalObject, target, kind) => {
610+
const ctorRegistry = globalObject[ctorRegistrySymbol];
611+
if (ctorRegistry === undefined) {
612+
throw new Error('Internal error: invalid global object');
613+
}
614+
615+
if (ctorRegistry["${this.name}"] === undefined) {
616+
throw new Error('Internal error: constructor ${this.name} is not installed on the passed global object');
617+
}
618+
619+
const asyncIteratorPrototype = ctorRegistry["${this.name} AsyncIterator"];
620+
const iterator = Object.create(asyncIteratorPrototype);
613621
Object.defineProperty(iterator, utils.iterInternalSymbol, {
614622
value: { target, kind, ongoingPromise: null, isFinished: false },
615623
configurable: true
@@ -619,8 +627,18 @@ class Interface {
619627
`;
620628
} else if (this.iterable.isPair) {
621629
this.str += `
622-
exports.createDefaultIterator = (target, kind) => {
623-
const iterator = Object.create(IteratorPrototype);
630+
exports.createDefaultIterator = (globalObject, target, kind) => {
631+
const ctorRegistry = globalObject[ctorRegistrySymbol];
632+
if (ctorRegistry === undefined) {
633+
throw new Error('Internal error: invalid global object');
634+
}
635+
636+
if (ctorRegistry["${this.name}"] === undefined) {
637+
throw new Error('Internal error: constructor ${this.name} is not installed on the passed global object');
638+
}
639+
640+
const iteratorPrototype = ctorRegistry["${this.name} Iterator"];
641+
const iterator = Object.create(iteratorPrototype);
624642
Object.defineProperty(iterator, utils.iterInternalSymbol, {
625643
value: { target, kind, index: 0 },
626644
configurable: true
@@ -1572,6 +1590,8 @@ class Interface {
15721590
if (!globalNames.some(globalName => exposed.has(globalName))) {
15731591
return;
15741592
}
1593+
1594+
const ctorRegistry = utils.initCtorRegistry(globalObject);
15751595
`;
15761596

15771597
if (idl.inheritance) {
@@ -1596,12 +1616,11 @@ class Interface {
15961616
this.generateOffInstanceAfterClass();
15971617

15981618
this.str += `
1599-
if (globalObject[ctorRegistrySymbol] === undefined) {
1600-
globalObject[ctorRegistrySymbol] = Object.create(null);
1601-
}
1602-
globalObject[ctorRegistrySymbol][interfaceName] = ${name};
1619+
ctorRegistry[interfaceName] = ${name};
16031620
`;
16041621

1622+
this.generateInstallIteratorPrototype();
1623+
16051624
if (!isLegacyNoInterfaceObject) {
16061625
this.str += `
16071626
Object.defineProperty(globalObject, interfaceName, {
@@ -1641,7 +1660,6 @@ class Interface {
16411660
this.str += `
16421661
const interfaceName = "${this.name}";
16431662
`;
1644-
this.generateIterator();
16451663

16461664
this.generateExport();
16471665
this.generateIface();

lib/constructs/iterable.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Iterable {
2727
if (!exports.is(this)) {
2828
throw new TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}.");
2929
}
30-
return exports.createDefaultIterator(this, "${kind}");
30+
return exports.createDefaultIterator(globalObject, this, "${kind}");
3131
`);
3232
}
3333

@@ -62,10 +62,10 @@ class Iterable {
6262
}
6363
`);
6464
} else {
65-
this.interface.addProperty(whence, "keys", "Array.prototype.keys");
66-
this.interface.addProperty(whence, "values", "Array.prototype[Symbol.iterator]");
67-
this.interface.addProperty(whence, "entries", "Array.prototype.entries");
68-
this.interface.addProperty(whence, "forEach", "Array.prototype.forEach");
65+
this.interface.addProperty(whence, "keys", 'ctorRegistry["%Array%"].prototype.keys');
66+
this.interface.addProperty(whence, "values", 'ctorRegistry["%Array%"].prototype.values');
67+
this.interface.addProperty(whence, "entries", 'ctorRegistry["%Array%"].prototype.entries');
68+
this.interface.addProperty(whence, "forEach", 'ctorRegistry["%Array%"].prototype.forEach');
6969
// @@iterator is added in Interface class.
7070
}
7171

lib/output/utils.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ const implSymbol = Symbol("impl");
1212
const sameObjectCaches = Symbol("SameObject caches");
1313
const ctorRegistrySymbol = Symbol.for("[webidl2js] constructor registry");
1414

15+
// This only contains the intrinsic names that are referenced from the `ctorRegistry`:
16+
const intrinsicConstructors = ["Array"];
17+
18+
function initCtorRegistry(globalObject) {
19+
if (hasOwn(globalObject, ctorRegistrySymbol)) {
20+
return globalObject[ctorRegistrySymbol];
21+
}
22+
23+
const ctorRegistry = Object.create(null);
24+
for (const intrinsic of intrinsicConstructors) {
25+
ctorRegistry[`%${intrinsic}%`] = globalObject[intrinsic];
26+
}
27+
28+
// TODO: Also capture `%AsyncIteratorPrototype%`
29+
ctorRegistry["%IteratorPrototype%"] = Object.getPrototypeOf(
30+
Object.getPrototypeOf(new ctorRegistry["%Array%"]()[Symbol.iterator]())
31+
);
32+
33+
globalObject[ctorRegistrySymbol] = ctorRegistry;
34+
return ctorRegistry;
35+
}
36+
1537
function getSameObject(wrapper, prop, creator) {
1638
if (!wrapper[sameObjectCaches]) {
1739
wrapper[sameObjectCaches] = Object.create(null);
@@ -113,6 +135,7 @@ module.exports = exports = {
113135
implSymbol,
114136
getSameObject,
115137
ctorRegistrySymbol,
138+
initCtorRegistry,
116139
wrapperForImpl,
117140
implForWrapper,
118141
tryWrapperForImpl,

0 commit comments

Comments
 (0)