Skip to content

Commit 63516e6

Browse files
committed
feat(Global): Add support for named getters
1 parent 2aecfb6 commit 63516e6

File tree

2 files changed

+554
-0
lines changed

2 files changed

+554
-0
lines changed

lib/constructs/interface.js

+172
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,10 @@ class Interface {
385385
return !this.isGlobal && (this.supportsIndexedProperties || this.supportsNamedProperties);
386386
}
387387

388+
get needsNamedPropertiesObject() {
389+
return this.isGlobal && this.supportsNamedProperties;
390+
}
391+
388392
includes(source) {
389393
const mixin = this.ctx.interfaceMixins.get(source);
390394
if (!mixin) {
@@ -1124,6 +1128,170 @@ class Interface {
11241128
`;
11251129
}
11261130

1131+
// https://heycam.github.io/webidl/#named-properties-object
1132+
generateNamedPropertiesObject() {
1133+
const { idl, name, namedGetter } = this;
1134+
const overrideBuiltins = Boolean(utils.getExtAttr(idl.extAttrs, "OverrideBuiltins"));
1135+
1136+
function supportsPropertyName(O, P, namedValue) {
1137+
let unsupportedValue = utils.getExtAttr(namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported");
1138+
if (unsupportedValue) {
1139+
unsupportedValue = unsupportedValue.rhs.value;
1140+
}
1141+
if (unsupportedValue) {
1142+
const func = namedGetter.name ? `.${namedGetter.name}` : "[utils.namedGet]";
1143+
const value = namedValue || `${O}[impl]${func}(${P})`;
1144+
return `${value} !== ${unsupportedValue}`;
1145+
}
1146+
return `${O}[impl][utils.supportsPropertyName](${P})`;
1147+
}
1148+
1149+
// "named property visibility algorithm"
1150+
// If `supports` is true then skip the supportsPropertyName check.
1151+
function namedPropertyVisible(P, O, supports = false) {
1152+
const conditions = [];
1153+
if (!supports) {
1154+
conditions.push(supportsPropertyName(O, P));
1155+
}
1156+
if (overrideBuiltins) {
1157+
conditions.push(`!utils.hasOwn(${O}, ${P})`);
1158+
} else {
1159+
// TODO: create a named properties object.
1160+
conditions.push(`!(${P} in ${O})`);
1161+
}
1162+
return conditions.join(" && ");
1163+
}
1164+
1165+
this.str += `
1166+
const NamedPropertiesObject = new Proxy(
1167+
Object.create(
1168+
globalObject.${idl.inheritance ? idl.inheritance : "Object"}.prototype,
1169+
{ [Symbol.toStringTag]: { value: "${name}Properties", configurable: true } }
1170+
),
1171+
{
1172+
`;
1173+
1174+
// [[Get]] (necessary because of proxy semantics)
1175+
this.str += `
1176+
get(target, P, receiver) {
1177+
if (typeof P === "symbol") {
1178+
return Reflect.get(target, P, receiver);
1179+
}
1180+
const desc = this.getOwnPropertyDescriptor(target, P);
1181+
if (desc === undefined) {
1182+
const parent = Object.getPrototypeOf(target);
1183+
if (parent === null) {
1184+
return undefined;
1185+
}
1186+
return Reflect.get(parent, P, receiver);
1187+
}
1188+
if (!desc.get && !desc.set) {
1189+
return desc.value;
1190+
}
1191+
const getter = desc.get;
1192+
if (getter === undefined) {
1193+
return undefined;
1194+
}
1195+
return Reflect.apply(getter, receiver, []);
1196+
},
1197+
`;
1198+
1199+
// [[HasProperty]] (necessary because of proxy semantics)
1200+
this.str += `
1201+
has(target, P) {
1202+
if (typeof P === "symbol") {
1203+
return Reflect.has(target, P);
1204+
}
1205+
const desc = this.getOwnPropertyDescriptor(target, P);
1206+
if (desc !== undefined) {
1207+
return true;
1208+
}
1209+
const parent = Object.getPrototypeOf(target);
1210+
if (parent !== null) {
1211+
return Reflect.has(parent, P);
1212+
}
1213+
return false;
1214+
},
1215+
`;
1216+
1217+
// [[GetOwnProperty]]
1218+
this.str += `
1219+
getOwnPropertyDescriptor(target, P) {
1220+
const object = globalObject;
1221+
if (typeof P === "symbol") {
1222+
return Reflect.getOwnPropertyDescriptor(target, P);
1223+
}
1224+
`;
1225+
1226+
const func = namedGetter.name ? `.${namedGetter.name}` : "[utils.namedGet]";
1227+
const enumerable = !utils.getExtAttr(idl.extAttrs, "LegacyUnenumerableNamedProperties");
1228+
let preamble = "";
1229+
const conditions = [];
1230+
if (utils.getExtAttr(namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported")) {
1231+
this.str += `
1232+
const namedValue = object[impl]${func}(P);
1233+
`;
1234+
conditions.push(supportsPropertyName("object", "index", "namedValue"));
1235+
conditions.push(namedPropertyVisible("P", "object", true));
1236+
} else {
1237+
preamble = `
1238+
const namedValue = object[impl]${func}(P);
1239+
`;
1240+
conditions.push(namedPropertyVisible("P", "object"));
1241+
}
1242+
this.str += `
1243+
if (${conditions.join(" && ")}) {
1244+
${preamble}
1245+
return {
1246+
writable: true,
1247+
enumerable: ${enumerable},
1248+
configurable: true,
1249+
value: utils.tryWrapperForImpl(namedValue)
1250+
};
1251+
}
1252+
return Reflect.getOwnPropertyDescriptor(target, P);
1253+
},
1254+
`;
1255+
1256+
// [[DefineOwnProperty]]
1257+
this.str += `
1258+
defineProperty() {
1259+
return false;
1260+
},
1261+
`;
1262+
1263+
// [[Delete]]
1264+
this.str += `
1265+
deleteProperty() {
1266+
return false;
1267+
},
1268+
`;
1269+
1270+
// [[SetPrototypeOf]]
1271+
this.str += `
1272+
setPrototypeOf(O, V) {
1273+
// 1. Return ? SetImmutablePrototype(O, V).
1274+
return Reflect.getPrototypeOf(O) === V;
1275+
},
1276+
`;
1277+
1278+
// [[PreventExtensions]]
1279+
this.str += `
1280+
preventExtensions() {
1281+
return false;
1282+
}
1283+
`;
1284+
1285+
this.str += `
1286+
}
1287+
);
1288+
`;
1289+
1290+
this.str += `
1291+
Object.setPrototypeOf(${name}.prototype, NamedPropertiesObject);
1292+
`;
1293+
}
1294+
11271295
generateIface() {
11281296
this.str += `
11291297
exports.create = function create(globalObject, constructorArgs, privateData) {
@@ -1470,6 +1638,10 @@ class Interface {
14701638

14711639
this.generateOffInstanceAfterClass();
14721640

1641+
if (this.needsNamedPropertiesObject) {
1642+
this.generateNamedPropertiesObject();
1643+
}
1644+
14731645
this.str += `
14741646
if (globalObject[ctorRegistry] === undefined) {
14751647
globalObject[ctorRegistry] = Object.create(null);

0 commit comments

Comments
 (0)