Skip to content

Commit 66c9357

Browse files
ExE-Bossdomenic
authored andcommitted
Use [[Unforgeables]] object for unforgeable properties
1 parent 3c318aa commit 66c9357

File tree

5 files changed

+480
-279
lines changed

5 files changed

+480
-279
lines changed

lib/constructs/attribute.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,26 @@ class Attribute {
1313
this.static = idl.special === "static";
1414
}
1515

16+
getWhence() {
17+
const { idl } = this;
18+
const isOnInstance = utils.isOnInstance(idl, this.interface.idl);
19+
20+
if (utils.getExtAttr(idl.extAttrs, "LegacyUnforgeable")) {
21+
return "unforgeables";
22+
}
23+
24+
return isOnInstance ? "instance" : "prototype";
25+
}
26+
1627
generate() {
1728
const requires = new utils.RequiresMap(this.ctx);
1829

19-
const configurable = !utils.getExtAttr(this.idl.extAttrs, "LegacyUnforgeable");
30+
const whence = this.getWhence();
31+
const configurable = whence !== "unforgeables";
2032
const shouldReflect =
2133
this.idl.extAttrs.some(attr => attr.name.startsWith("Reflect")) && this.ctx.processReflect !== null;
2234
const sameObject = utils.getExtAttr(this.idl.extAttrs, "SameObject");
2335

24-
const onInstance = utils.isOnInstance(this.idl, this.interface.idl);
25-
2636
const async = this.idl.idlType.generic === "Promise";
2737
const promiseHandlingBefore = async ? `try {` : ``;
2838
const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``;
@@ -40,7 +50,7 @@ class Attribute {
4050

4151
const addMethod = this.static ?
4252
this.interface.addStaticMethod.bind(this.interface) :
43-
this.interface.addMethod.bind(this.interface, onInstance ? "instance" : "prototype");
53+
this.interface.addMethod.bind(this.interface, whence);
4454

4555
if (this.static) {
4656
brandCheck = "";

lib/constructs/interface.js

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class Interface {
5757

5858
this.iterable = null;
5959
this._analyzed = false;
60+
this._needsUnforgeablesObject = false;
6061

6162
this._outputMethods = new Map();
6263
this._outputStaticMethods = new Map();
@@ -109,14 +110,14 @@ class Interface {
109110
}
110111
}
111112

112-
// whence is either "instance" or "prototype"
113+
// whence is either "instance", "prototype" or "unforgeables"
113114
// type is either "regular", "get", or "set"
114115
addMethod(whence, propName, args, body, type = "regular", {
115116
configurable = true,
116117
enumerable = typeof propName === "string",
117118
writable = type === "regular" ? true : undefined
118119
} = {}) {
119-
if (whence !== "instance" && whence !== "prototype") {
120+
if (whence !== "instance" && whence !== "prototype" && whence !== "unforgeables") {
120121
throw new Error(`Internal error: Invalid whence ${whence}`);
121122
}
122123
if (type !== "regular") {
@@ -139,6 +140,10 @@ class Interface {
139140

140141
const descriptor = { configurable, enumerable, writable };
141142
this._outputMethods.set(propName, { whence, type, args, body, descriptor });
143+
144+
if (whence === "unforgeables" && !this.isGlobal) {
145+
this._needsUnforgeablesObject = true;
146+
}
142147
}
143148

144149
// type is either "regular", "get", or "set"
@@ -1205,6 +1210,8 @@ class Interface {
12051210
}
12061211

12071212
generateIface() {
1213+
const { _needsUnforgeablesObject } = this;
1214+
12081215
this.str += `
12091216
function makeWrapper(globalObject, newTarget) {
12101217
let proto;
@@ -1252,7 +1259,13 @@ class Interface {
12521259
const wrapper = exports.create(globalObject, constructorArgs, privateData);
12531260
return utils.implForWrapper(wrapper);
12541261
};
1262+
`;
1263+
1264+
if (_needsUnforgeablesObject) {
1265+
this.generateUnforgeablesObject();
1266+
}
12551267

1268+
this.str += `
12561269
exports._internalSetup = (wrapper, globalObject) => {
12571270
`;
12581271

@@ -1262,6 +1275,16 @@ class Interface {
12621275
`;
12631276
}
12641277

1278+
if (_needsUnforgeablesObject) {
1279+
this.str += `
1280+
const unforgeables = getUnforgeables(globalObject);
1281+
for (const key of Reflect.ownKeys(unforgeables)) {
1282+
const descriptor = Reflect.getOwnPropertyDescriptor(unforgeables, key);
1283+
Object.defineProperty(wrapper, key, descriptor);
1284+
}
1285+
`;
1286+
}
1287+
12651288
this.generateOnInstance();
12661289

12671290
this.str += `
@@ -1496,6 +1519,7 @@ class Interface {
14961519
}
14971520

14981521
generateOnInstance() {
1522+
const { isGlobal } = this;
14991523
const methods = [];
15001524
const props = new Map();
15011525

@@ -1506,7 +1530,7 @@ class Interface {
15061530
}
15071531

15081532
for (const [name, { whence, type, args, body, descriptor }] of this._outputMethods) {
1509-
if (whence !== "instance") {
1533+
if (whence !== "instance" && (whence !== "unforgeables" || !isGlobal)) {
15101534
continue;
15111535
}
15121536

@@ -1564,9 +1588,78 @@ class Interface {
15641588
}
15651589
}
15661590

1591+
generateUnforgeablesObject() {
1592+
const methods = [];
1593+
const props = new Map();
1594+
1595+
function addOne(name, args, body) {
1596+
methods.push(`
1597+
${name}(${utils.formatArgs(args)}) {${body}}
1598+
`);
1599+
}
1600+
1601+
for (const [name, { whence, type, args, body, descriptor }] of this._outputMethods) {
1602+
if (whence !== "unforgeables") {
1603+
continue;
1604+
}
1605+
1606+
const propName = utils.stringifyPropertyKey(name);
1607+
if (type === "regular") {
1608+
addOne(propName, args, body);
1609+
} else {
1610+
if (body[0] !== undefined) {
1611+
addOne(`get ${propName}`, [], body[0]);
1612+
}
1613+
if (body[1] !== undefined) {
1614+
addOne(`set ${propName}`, args, body[1]);
1615+
}
1616+
}
1617+
1618+
const descriptorModifier = utils.getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, type);
1619+
if (descriptorModifier === undefined) {
1620+
continue;
1621+
}
1622+
props.set(utils.stringifyPropertyKey(name), descriptorModifier);
1623+
}
1624+
1625+
this.str += `
1626+
function getUnforgeables(globalObject) {
1627+
let unforgeables = unforgeablesMap.get(globalObject);
1628+
if (unforgeables === undefined) {
1629+
unforgeables = Object.create(
1630+
null,
1631+
`;
1632+
1633+
if (methods.length > 0) {
1634+
this.str += `Object.getOwnPropertyDescriptors({ ${methods.join(", ")} }),`;
1635+
}
1636+
1637+
this.str += ");";
1638+
1639+
if (props.size > 0) {
1640+
const propStrs = [...props].map(([name, body]) => `${name}: ${body}`);
1641+
this.str += `
1642+
Object.defineProperties(unforgeables, {
1643+
${propStrs.join(", ")}
1644+
});`;
1645+
}
1646+
1647+
this.str += `
1648+
unforgeablesMap.set(globalObject, unforgeables);
1649+
}
1650+
return unforgeables;
1651+
}
1652+
`;
1653+
}
1654+
15671655
generateInstall() {
15681656
const { idl, name } = this;
15691657

1658+
if (this._needsUnforgeablesObject) {
1659+
this.str += `
1660+
const unforgeablesMap = new WeakMap();`;
1661+
}
1662+
15701663
this.str += `
15711664
const exposed = new Set(${JSON.stringify([...this.exposed])});
15721665
@@ -1640,6 +1733,7 @@ class Interface {
16401733
this.generateExport();
16411734
this.generateIface();
16421735
this.generateInstall();
1736+
16431737
if (this.isLegacyPlatformObj) {
16441738
this.generateLegacyProxyHandler();
16451739
}

lib/constructs/operation.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,24 @@ class Operation {
1515
this.static = idl.special === "static";
1616
}
1717

18-
isOnInstance() {
19-
const firstOverloadOnInstance = utils.isOnInstance(this.idls[0], this.interface.idl);
20-
for (const overload of this.idls.slice(1)) {
21-
if (utils.isOnInstance(overload, this.interface.idl) !== firstOverloadOnInstance) {
18+
getWhence() {
19+
const { idls } = this;
20+
const firstOverloadOnInstance = utils.isOnInstance(idls[0], this.interface.idl);
21+
const hasLegacyUnforgeable = Boolean(utils.getExtAttr(idls[0].extAttrs, "LegacyUnforgeable"));
22+
23+
for (let i = 1; i < idls.length; i++) {
24+
if (Boolean(utils.getExtAttr(idls[i].extAttrs, "LegacyUnforgeable")) !== hasLegacyUnforgeable) {
2225
throw new Error(
2326
`[LegacyUnforgeable] is not applied uniformly to operation "${this.name}" on ${this.interface.name}`
2427
);
2528
}
2629
}
27-
return firstOverloadOnInstance;
30+
31+
if (hasLegacyUnforgeable) {
32+
return "unforgeables";
33+
}
34+
35+
return firstOverloadOnInstance ? "instance" : "prototype";
2836
}
2937

3038
isAsync() {
@@ -86,7 +94,7 @@ class Operation {
8694
throw new Error(`Internal error: this operation does not have a name (in interface ${this.interface.name})`);
8795
}
8896

89-
const onInstance = this.isOnInstance();
97+
const whence = this.getWhence();
9098
const async = this.isAsync();
9199
const promiseHandlingBefore = async ? `try {` : ``;
92100
const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``;
@@ -159,9 +167,9 @@ class Operation {
159167
if (this.static) {
160168
this.interface.addStaticMethod(this.name, argNames, str);
161169
} else {
162-
const forgeable = !utils.getExtAttr(this.idls[0].extAttrs, "LegacyUnforgeable");
170+
const forgeable = whence !== "unforgeables";
163171
this.interface.addMethod(
164-
onInstance ? "instance" : "prototype",
172+
whence,
165173
this.name,
166174
argNames,
167175
str,

lib/utils.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ function hasCEReactions(idl) {
3939
}
4040

4141
function isOnInstance(memberIDL, interfaceIDL) {
42-
return memberIDL.special !== "static" && (getExtAttr(memberIDL.extAttrs, "LegacyUnforgeable") ||
43-
isGlobal(interfaceIDL));
42+
return memberIDL.special !== "static" && isGlobal(interfaceIDL);
4443
}
4544

4645
function symbolName(symbol) {

0 commit comments

Comments
 (0)