Skip to content

Commit 1f99bb2

Browse files
committed
Create exceptions and promises in the correct realm
Closes #242 by superseding it.
1 parent b8f389f commit 1f99bb2

14 files changed

+2105
-1537
lines changed

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ Returns a boolean indicating whether _value_ is an instance of the wrapper class
258258

259259
This is useful in other parts of your program that are not implementation class files, but instead receive wrapper classes from client code.
260260

261-
#### `convert(value, { context })`
261+
#### `convert(globalObject, value, { context })`
262262

263263
Performs the Web IDL conversion algorithm for this interface, converting _value_ into the correct representation of the interface type suitable for consumption by implementation classes: the corresponding impl.
264264

@@ -296,13 +296,13 @@ jsdom does this for `Window`, which is written in custom, non-webidl2js-generate
296296

297297
### For callback interfaces
298298

299-
#### `convert(value, { context })`
299+
#### `convert(globalObject, value, { context })`
300300

301-
Performs the Web IDL conversion algorithm for this callback interface, converting _value_ into a function that performs [call a user object's operation](https://heycam.github.io/webidl/#call-a-user-objects-operation) when called, with _thisArg_ being the `this` value of the converted function.
301+
Performs the Web IDL conversion algorithm for this callback interface, converting `value` into a function that performs [call a user object's operation](https://heycam.github.io/webidl/#call-a-user-objects-operation) when called, with _thisArg_ being the `this` value of the converted function. `globalObject` is used to ensure error cases result in `Error` or `Promise` objects from the correct realm.
302302

303-
The resulting function has an _objectReference_ property, which is the same object as _value_ and can be used to perform identity checks, as `convert` returns a new function object every time.
303+
The resulting function has an `objectReference` property, which is the same object as `value` and can be used to perform identity checks, as `convert` returns a new function object every time.
304304

305-
If any part of the conversion fails, _context_ can be used to describe the provided value in any resulting error message.
305+
If any part of the conversion fails, `context` can be used to describe the provided value in any resulting error message.
306306

307307
#### `install(globalObject, globalNames)`
308308

@@ -312,11 +312,11 @@ The second argument `globalNames` is the same as for [the `install()` export for
312312

313313
### For dictionaries
314314

315-
#### `convert(value, { context })`
315+
#### `convert(globalObject, value, { context })`
316316

317-
Performs the Web IDL conversion algorithm for this dictionary, converting _value_ into the correct representation of the dictionary type suitable for consumption by implementation classes: a `null`-[[Prototype]] object with its properties properly converted.
317+
Performs the Web IDL conversion algorithm for this dictionary, converting `value` into the correct representation of the dictionary type suitable for consumption by implementation classes: a `null`-[[Prototype]] object with its properties properly converted. `globalObject` is used to ensure error cases result in `Error` or `Promise` objects from the correct realm.
318318

319-
If any part of the conversion fails, _context_ can be used to describe the provided value in any resulting error message.
319+
If any part of the conversion fails, `context` can be used to describe the provided value in any resulting error message.
320320

321321
### Other requirements
322322

lib/constructs/async-iterable.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class AsyncIterable {
3838

3939
this.interface.addMethod(this.interface.defaultWhence, key, [], `
4040
if (!exports.is(this)) {
41-
throw new TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}.");
41+
throw new globalObject.TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}.");
4242
}
4343
4444
${conv.body}

lib/constructs/attribute.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ class Attribute {
3535

3636
const async = this.idl.idlType.generic === "Promise";
3737
const promiseHandlingBefore = async ? `try {` : ``;
38-
const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``;
38+
const promiseHandlingAfter = async ? `} catch (e) { return globalObject.Promise.reject(e); }` : ``;
3939

4040
let brandCheck = `
4141
if (!exports.is(esValue)) {
42-
throw new TypeError("'$KEYWORD$ ${this.idl.name}' called on an object that is not a valid instance of ${this.interface.name}.");
42+
throw new globalObject.TypeError("'$KEYWORD$ ${this.idl.name}' called on an object that is not a valid instance of ${this.interface.name}.");
4343
}
4444
`;
4545
let getterBody = `return utils.tryWrapperForImpl(esValue[implSymbol]["${this.idl.name}"]);`;
@@ -150,7 +150,7 @@ class Attribute {
150150
setterBody = `
151151
const Q = esValue["${this.idl.name}"];
152152
if (!utils.isObject(Q)) {
153-
throw new TypeError("Property '${this.idl.name}' is not an object");
153+
throw new globalObject.TypeError("Property '${this.idl.name}' is not an object");
154154
}
155155
`;
156156

@@ -180,7 +180,7 @@ class Attribute {
180180
addMethod("toString", [], `
181181
const esValue = this;
182182
if (!exports.is(esValue)) {
183-
throw new TypeError("'toString' called on an object that is not a valid instance of ${this.interface.name}.");
183+
throw new globalObject.TypeError("'toString' called on an object that is not a valid instance of ${this.interface.name}.");
184184
}
185185
186186
${getterBody}

lib/constructs/callback-function.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class CallbackFunction {
2525
"" :
2626
`
2727
if (typeof value !== "function") {
28-
throw new TypeError(context + " is not a function");
28+
throw new globalObject.TypeError(context + " is not a function");
2929
}
3030
`;
3131

@@ -109,7 +109,7 @@ class CallbackFunction {
109109
}
110110

111111
this.str += `
112-
exports.convert = (value, { context = "The provided value" } = {}) => {
112+
exports.convert = (globalObject, value, { context = "The provided value" } = {}) => {
113113
${assertCallable}
114114
function invokeTheCallbackFunction(${inputArgs}) {
115115
const thisArg = utils.tryWrapperForImpl(this);
@@ -144,7 +144,7 @@ class CallbackFunction {
144144
if (isAsync) {
145145
this.str += `
146146
} catch (err) {
147-
return Promise.reject(err);
147+
return globalObject.Promise.reject(err);
148148
}
149149
`;
150150
}

lib/constructs/callback-interface.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ class CallbackInterface {
9494
}
9595

9696
this.str += `
97-
exports.convert = function convert(value, { context = "The provided value" } = {}) {
97+
exports.convert = (globalObject, value, { context = "The provided value" } = {}) => {
9898
if (!utils.isObject(value)) {
99-
throw new TypeError(\`\${context} is not an object.\`);
99+
throw new globalObject.TypeError(\`\${context} is not an object.\`);
100100
}
101101
102102
function callTheUserObjectsOperation(${argNames.join(", ")}) {
@@ -115,7 +115,7 @@ class CallbackInterface {
115115
if (typeof O !== "function") {
116116
X = O[${utils.stringifyPropertyName(opName)}];
117117
if (typeof X !== "function") {
118-
throw new TypeError(\`\${context} does not correctly implement ${name}.\`)
118+
throw new globalObject.TypeError(\`\${context} does not correctly implement ${name}.\`)
119119
}
120120
thisArg = O;
121121
}
@@ -151,7 +151,7 @@ class CallbackInterface {
151151
if (isAsync) {
152152
this.str += `
153153
} catch (err) {
154-
return Promise.reject(err);
154+
return globalObject.Promise.reject(err);
155155
}
156156
`;
157157
}
@@ -215,8 +215,9 @@ class CallbackInterface {
215215
return;
216216
}
217217
218+
const ctorRegistry = utils.initCtorRegistry(globalObject);
218219
const ${name} = () => {
219-
throw new TypeError("Illegal invocation");
220+
throw new globalObject.TypeError("Illegal invocation");
220221
};
221222
`;
222223

lib/constructs/dictionary.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Dictionary {
5555
if (field.required) {
5656
str += `
5757
else {
58-
throw new TypeError("${field.name} is required in '${this.name}'");
58+
throw new globalObject.TypeError("${field.name} is required in '${this.name}'");
5959
}
6060
`;
6161
} else if (field.default) {
@@ -76,26 +76,26 @@ class Dictionary {
7676

7777
generate() {
7878
this.str += `
79-
exports._convertInherit = (obj, ret, { context = "The provided value" } = {}) => {
79+
exports._convertInherit = (globalObject, obj, ret, { context = "The provided value" } = {}) => {
8080
`;
8181

8282
if (this.idl.inheritance) {
8383
this.str += `
84-
${this.idl.inheritance}._convertInherit(obj, ret, { context });
84+
${this.idl.inheritance}._convertInherit(globalObject, obj, ret, { context });
8585
`;
8686
}
8787

8888
this.str += `
8989
${this._generateConversions()}
9090
};
9191
92-
exports.convert = function convert(obj, { context = "The provided value" } = {}) {
92+
exports.convert = (globalObject, obj, { context = "The provided value" } = {}) => {
9393
if (obj !== undefined && typeof obj !== "object" && typeof obj !== "function") {
94-
throw new TypeError(\`\${context} is not an object.\`);
94+
throw new globalObject.TypeError(\`\${context} is not an object.\`);
9595
}
9696
9797
const ret = Object.create(null);
98-
exports._convertInherit(obj, ret, { context });
98+
exports._convertInherit(globalObject, obj, ret, { context });
9999
return ret;
100100
};
101101
`;

lib/constructs/enumeration.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ class Enumeration {
1818
const enumerationValues = new Set(${JSON.stringify([...values])});
1919
exports.enumerationValues = enumerationValues;
2020
21-
exports.convert = function convert(value, { context = "The provided value" } = {}) {
21+
exports.convert = (globalObject, value, { context = "The provided value" } = {}) => {
2222
const string = \`\${value}\`;
2323
if (!enumerationValues.has(string)) {
24-
throw new TypeError(\`\${context} '\${string}' is not a valid enumeration value for ${this.name}\`);
24+
throw new globalObject.TypeError(\`\${context} '\${string}' is not a valid enumeration value for ${this.name}\`);
2525
}
2626
return string;
2727
};

lib/constructs/interface.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -437,12 +437,12 @@ class Interface {
437437
next() {
438438
const internal = this && this[utils.iterInternalSymbol];
439439
if (!internal) {
440-
return Promise.reject(new TypeError("next() called on a value that is not a ${this.name} async iterator object"));
440+
return globalObject.Promise.reject(new globalObject.TypeError("next() called on a value that is not a ${this.name} async iterator object"));
441441
}
442442
443443
const nextSteps = () => {
444444
if (internal.isFinished) {
445-
return Promise.resolve(newObjectInRealm(globalObject, { value: undefined, done: true }));
445+
return globalObject.Promise.resolve(newObjectInRealm(globalObject, { value: undefined, done: true }));
446446
}
447447
448448
const nextPromise = internal.target[implSymbol][utils.asyncIteratorNext](this);
@@ -484,12 +484,12 @@ class Interface {
484484
return(value) {
485485
const internal = this && this[utils.iterInternalSymbol];
486486
if (!internal) {
487-
return Promise.reject(new TypeError("return() called on a value that is not a ${this.name} async iterator object"));
487+
return globalObject.Promise.reject(new globalObject.TypeError("return() called on a value that is not a ${this.name} async iterator object"));
488488
}
489489
490490
const returnSteps = () => {
491491
if (internal.isFinished) {
492-
return Promise.resolve(newObjectInRealm(globalObject, { value, done: true }));
492+
return globalObject.Promise.resolve(newObjectInRealm(globalObject, { value, done: true }));
493493
}
494494
internal.isFinished = true;
495495
@@ -522,7 +522,7 @@ class Interface {
522522
next() {
523523
const internal = this && this[utils.iterInternalSymbol];
524524
if (!internal) {
525-
throw new TypeError("next() called on a value that is not a ${this.name} iterator object");
525+
throw new globalObject.TypeError("next() called on a value that is not a ${this.name} iterator object");
526526
}
527527
528528
const { target, kind, index } = internal;
@@ -606,11 +606,11 @@ class Interface {
606606
exports.isImpl = value => {
607607
return utils.isObject(value) && value instanceof Impl.implementation;
608608
};
609-
exports.convert = (value, { context = "The provided value" } = {}) => {
609+
exports.convert = (globalObject, value, { context = "The provided value" } = {}) => {
610610
if (exports.is(value)) {
611611
return utils.implForWrapper(value);
612612
}
613-
throw new TypeError(\`\${context} is not of type '${this.name}'.\`);
613+
throw new globalObject.TypeError(\`\${context} is not of type '${this.name}'.\`);
614614
};
615615
`;
616616

@@ -1364,7 +1364,7 @@ class Interface {
13641364
`;
13651365
} else {
13661366
body = `
1367-
throw new TypeError("Illegal constructor");
1367+
throw new globalObject.TypeError("Illegal constructor");
13681368
`;
13691369
}
13701370

@@ -1386,7 +1386,7 @@ class Interface {
13861386
// Don't bother checking "length" attribute as interfaces that support indexed properties must implement one.
13871387
// "Has value iterator" implies "supports indexed properties".
13881388
if (this.supportsIndexedProperties) {
1389-
this.addProperty(this.defaultWhence, Symbol.iterator, 'ctorRegistry["%Array%"].prototype[Symbol.iterator]');
1389+
this.addProperty(this.defaultWhence, Symbol.iterator, "globalObject.Array.prototype[Symbol.iterator]");
13901390
}
13911391
}
13921392

lib/constructs/iterable.js

+8-9
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Iterable {
2525
generateFunction(key, kind) {
2626
this.interface.addMethod(this.interface.defaultWhence, key, [], `
2727
if (!exports.is(this)) {
28-
throw new TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}.");
28+
throw new globalObject.TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}.");
2929
}
3030
return exports.createDefaultIterator(globalObject, this, "${kind}");
3131
`);
@@ -42,13 +42,12 @@ class Iterable {
4242
this.interface.addProperty(whence, Symbol.iterator, `${this.interface.name}.prototype.entries`);
4343
this.interface.addMethod(whence, "forEach", ["callback"], `
4444
if (!exports.is(this)) {
45-
throw new TypeError("'forEach' called on an object that is not a valid instance of ${this.interface.name}.");
45+
throw new globalObject.TypeError("'forEach' called on an object that is not a valid instance of ${this.interface.name}.");
4646
}
4747
if (arguments.length < 1) {
48-
throw new TypeError("Failed to execute 'forEach' on '${this.name}': 1 argument required, " +
49-
"but only 0 present.");
48+
throw new globalObject.TypeError("Failed to execute 'forEach' on '${this.name}': 1 argument required, but only 0 present.");
5049
}
51-
callback = ${requires.addRelative("Function")}.convert(callback, {
50+
callback = ${requires.addRelative("Function")}.convert(globalObject, callback, {
5251
context: "Failed to execute 'forEach' on '${this.name}': The callback provided as parameter 1"
5352
});
5453
const thisArg = arguments[1];
@@ -62,10 +61,10 @@ class Iterable {
6261
}
6362
`);
6463
} else {
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');
64+
this.interface.addProperty(whence, "keys", "globalObject.Array.prototype.keys");
65+
this.interface.addProperty(whence, "values", "globalObject.Array.prototype.values");
66+
this.interface.addProperty(whence, "entries", "globalObject.Array.prototype.entries");
67+
this.interface.addProperty(whence, "forEach", "globalObject.Array.prototype.forEach");
6968
// @@iterator is added in Interface class.
7069
}
7170

lib/constructs/operation.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Operation {
9797
const whence = this.getWhence();
9898
const async = this.isAsync();
9999
const promiseHandlingBefore = async ? `try {` : ``;
100-
const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``;
100+
const promiseHandlingAfter = async ? `} catch (e) { return globalObject.Promise.reject(e); }` : ``;
101101
const hasCallWithGlobal = this.hasCallWithGlobal();
102102

103103
const type = this.static ? "static operation" : "regular operation";
@@ -115,7 +115,7 @@ class Operation {
115115
str += `
116116
const esValue = this !== null && this !== undefined ? this : globalObject;
117117
if (!exports.is(esValue)) {
118-
throw new TypeError("'${this.name}' called on an object that is not a valid instance of ${this.interface.name}.");
118+
throw new globalObject.TypeError("'${this.name}' called on an object that is not a valid instance of ${this.interface.name}.");
119119
}
120120
`;
121121
}

lib/output/utils.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ const implSymbol = Symbol("impl");
3131
const sameObjectCaches = Symbol("SameObject caches");
3232
const ctorRegistrySymbol = Symbol.for("[webidl2js] constructor registry");
3333

34-
// This only contains the intrinsic names that are referenced from the `ctorRegistry`:
35-
const intrinsicConstructors = ["Array"];
36-
3734
const AsyncIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(async function* () {}).prototype);
3835

3936
function initCtorRegistry(globalObject) {
@@ -42,13 +39,13 @@ function initCtorRegistry(globalObject) {
4239
}
4340

4441
const ctorRegistry = Object.create(null);
45-
for (const intrinsic of intrinsicConstructors) {
46-
ctorRegistry[`%${intrinsic}%`] = globalObject[intrinsic];
47-
}
4842

43+
// In addition to registering all the WebIDL2JS-generated types in the constructor registry,
44+
// we also register a few intrinsics that we make use of in generated code, since they are not
45+
// easy to grab from the globalObject variable.
4946
ctorRegistry["%Object.prototype%"] = globalObject.Object.prototype;
5047
ctorRegistry["%IteratorPrototype%"] = Object.getPrototypeOf(
51-
Object.getPrototypeOf(new ctorRegistry["%Array%"]()[Symbol.iterator]())
48+
Object.getPrototypeOf(new globalObject.Array()[Symbol.iterator]())
5249
);
5350

5451
try {

lib/parameters.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, pare
113113
const plural = minArgs > 1 ? "s" : "";
114114
str += `
115115
if (arguments.length < ${minArgs}) {
116-
throw new TypeError("${errPrefix}${minArgs} argument${plural} required, but only " + arguments.length +
117-
" present.");
116+
throw new globalObject.TypeError(\`${errPrefix}${minArgs} argument${plural} required, but only \${arguments.length} present.\`);
118117
}
119118
`;
120119
}
@@ -127,7 +126,7 @@ module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, pare
127126
.filter(o => o.typeList.length === numArgs);
128127
if (S.length === 0) {
129128
switchCases.push(`
130-
throw new TypeError("${errPrefix}only " + arguments.length + " arguments present.");
129+
throw new globalObject.TypeError(\`${errPrefix}only \${arguments.length} arguments present.\`);
131130
`);
132131
continue;
133132
}
@@ -303,7 +302,7 @@ module.exports.generateOverloadConversions = function (ctx, typeOfOp, name, pare
303302
} else if (any.length) {
304303
possibilities.push(`{ ${continued(any[0], i)} }`);
305304
} else {
306-
possibilities.push(`throw new TypeError("${errPrefix}No such overload");`);
305+
possibilities.push(`throw new globalObject.TypeError("${errPrefix}No such overload");`);
307306
}
308307

309308
caseSrc += possibilities.join(" else ");

0 commit comments

Comments
 (0)