Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Global): Add support for named getters #167

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 201 additions & 58 deletions lib/constructs/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class Interface {
if (global && !global.rhs) {
throw new Error(`[Global] must take an identifier or an identifier list in interface ${this.name}`);
}

this.overrideBuiltins = Boolean(utils.getExtAttr(this.idl.extAttrs, "OverrideBuiltins"));
}

// whence is either "instance" or "prototype"
Expand Down Expand Up @@ -385,6 +387,10 @@ class Interface {
return !this.isGlobal && (this.supportsIndexedProperties || this.supportsNamedProperties);
}

get needsNamedPropertiesObject() {
return this.isGlobal && this.supportsNamedProperties;
}

includes(source) {
const mixin = this.ctx.interfaceMixins.get(source);
if (!mixin) {
Expand Down Expand Up @@ -583,53 +589,52 @@ class Interface {
}
}

supportsPropertyIndex(O, index, indexedValue) {
let unsupportedValue = utils.getExtAttr(this.indexedGetter.extAttrs, "WebIDL2JSValueAsUnsupported");
if (unsupportedValue) {
unsupportedValue = unsupportedValue.rhs.value;
}
if (unsupportedValue) {
const func = this.indexedGetter.name ? `.${this.indexedGetter.name}` : "[utils.indexedGet]";
const value = indexedValue || `${O}[impl]${func}(${index})`;
return `${value} !== ${unsupportedValue}`;
}
return `${O}[impl][utils.supportsPropertyIndex](${index})`;
}

supportsPropertyName(O, P, namedValue) {
let unsupportedValue = utils.getExtAttr(this.namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported");
if (unsupportedValue) {
unsupportedValue = unsupportedValue.rhs.value;
}
if (unsupportedValue) {
const func = this.namedGetter.name ? `.${this.namedGetter.name}` : "[utils.namedGet]";
const value = namedValue || `${O}[impl]${func}(${P})`;
return `${value} !== ${unsupportedValue}`;
}
return `${O}[impl][utils.supportsPropertyName](${P})`;
}

// "named property visibility algorithm"
// If `supports` is true then skip the supportsPropertyName check.
namedPropertyVisible(P, O, supports = false) {
const conditions = [];
if (!supports) {
conditions.push(this.supportsPropertyName(O, P));
}
if (this.overrideBuiltins) {
conditions.push(`!utils.hasOwn(${O}, ${P})`);
} else {
conditions.push(`utils.isNamedPropertyVisible(${P}, ${O})`);
}
return conditions.join(" && ");
}

generateLegacyProxyHandler() {
const hasIndexedSetter = this.indexedSetter !== null;
const hasNamedSetter = this.namedSetter !== null;
const hasNamedDeleter = this.namedDeleter !== null;
const overrideBuiltins = Boolean(utils.getExtAttr(this.idl.extAttrs, "OverrideBuiltins"));

const supportsPropertyIndex = (O, index, indexedValue) => {
let unsupportedValue = utils.getExtAttr(this.indexedGetter.extAttrs, "WebIDL2JSValueAsUnsupported");
if (unsupportedValue) {
unsupportedValue = unsupportedValue.rhs.value;
}
if (unsupportedValue) {
const func = this.indexedGetter.name ? `.${this.indexedGetter.name}` : "[utils.indexedGet]";
const value = indexedValue || `${O}[impl]${func}(${index})`;
return `${value} !== ${unsupportedValue}`;
}
return `${O}[impl][utils.supportsPropertyIndex](${index})`;
};

const supportsPropertyName = (O, P, namedValue) => {
let unsupportedValue = utils.getExtAttr(this.namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported");
if (unsupportedValue) {
unsupportedValue = unsupportedValue.rhs.value;
}
if (unsupportedValue) {
const func = this.namedGetter.name ? `.${this.namedGetter.name}` : "[utils.namedGet]";
const value = namedValue || `${O}[impl]${func}(${P})`;
return `${value} !== ${unsupportedValue}`;
}
return `${O}[impl][utils.supportsPropertyName](${P})`;
};

// "named property visibility algorithm"
// If `supports` is true then skip the supportsPropertyName check.
function namedPropertyVisible(P, O, supports = false) {
const conditions = [];
if (!supports) {
conditions.push(supportsPropertyName(O, P));
}
if (overrideBuiltins) {
conditions.push(`!utils.hasOwn(${O}, ${P})`);
} else {
// TODO: create a named properties object.
conditions.push(`!(${P} in ${O})`);
}
return conditions.join(" && ");
}
const { overrideBuiltins } = this;

// "invoke an indexed property setter"
const invokeIndexedSetter = (O, P, V) => {
Expand All @@ -648,7 +653,7 @@ class Interface {
let invocation;
if (!this.indexedSetter.name) {
invocation = `
const creating = !(${supportsPropertyIndex(O, "index")});
const creating = !(${this.supportsPropertyIndex(O, "index")});
if (creating) {
${O}[impl][utils.indexedSetNew](index, indexedValue);
} else {
Expand Down Expand Up @@ -687,7 +692,7 @@ class Interface {
let invocation;
if (!this.namedSetter.name) {
invocation = `
const creating = !(${supportsPropertyName(O, P)});
const creating = !(${this.supportsPropertyName(O, P)});
if (creating) {
${O}[impl][utils.namedSetNew](${P}, namedValue);
} else {
Expand Down Expand Up @@ -773,7 +778,7 @@ class Interface {
if (this.supportsNamedProperties) {
this.str += `
for (const key of target[impl][utils.supportedPropertyNames]) {
if (${namedPropertyVisible("key", "target", true)}) {
if (${this.namedPropertyVisible("key", "target", true)}) {
keys.add(\`\${key}\`);
}
}
Expand Down Expand Up @@ -806,10 +811,10 @@ class Interface {
let condition;
if (utils.getExtAttr(this.indexedGetter.extAttrs, "WebIDL2JSValueAsUnsupported")) {
this.str += `const indexedValue = target[impl]${func}(index);`;
condition = supportsPropertyIndex("target", "index", "indexedValue");
condition = this.supportsPropertyIndex("target", "index", "indexedValue");
} else {
preamble = `const indexedValue = target[impl]${func}(index);`;
condition = supportsPropertyIndex("target", "index");
condition = this.supportsPropertyIndex("target", "index");
}

this.str += `
Expand All @@ -830,20 +835,19 @@ class Interface {
const func = this.namedGetter.name ? `.${this.namedGetter.name}` : "[utils.namedGet]";
const enumerable = !utils.getExtAttr(this.idl.extAttrs, "LegacyUnenumerableNamedProperties");
let preamble = "";
const conditions = [];
const conditions = ["!ignoreNamedProps"];
if (utils.getExtAttr(this.namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported")) {
this.str += `
const namedValue = target[impl]${func}(P);
`;
conditions.push(supportsPropertyName("target", "index", "namedValue"));
conditions.push(namedPropertyVisible("P", "target", true));
conditions.push(this.supportsPropertyName("target", "index", "namedValue"));
conditions.push(this.namedPropertyVisible("P", "target", true));
} else {
preamble = `
const namedValue = target[impl]${func}(P);
`;
conditions.push(namedPropertyVisible("P", "target", false));
conditions.push(this.namedPropertyVisible("P", "target", false));
}
conditions.push("!ignoreNamedProps");
this.str += `
if (${conditions.join(" && ")}) {
${preamble}
Expand Down Expand Up @@ -916,10 +920,10 @@ class Interface {
let condition;
if (utils.getExtAttr(this.indexedGetter.extAttrs, "WebIDL2JSValueAsUnsupported")) {
this.str += `const indexedValue = target[impl]${func}(index);`;
condition = supportsPropertyIndex("target", "index", "indexedValue");
condition = this.supportsPropertyIndex("target", "index", "indexedValue");
} else {
preamble = `const indexedValue = target[impl]${func}(index);`;
condition = supportsPropertyIndex("target", "index");
condition = this.supportsPropertyIndex("target", "index");
}

this.str += `
Expand Down Expand Up @@ -1018,7 +1022,7 @@ class Interface {
if (!hasNamedSetter) {
needFallback = true;
this.str += `
const creating = !(${supportsPropertyName("target", "P")});
const creating = !(${this.supportsPropertyName("target", "P")});
if (!creating) {
return false;
}
Expand Down Expand Up @@ -1068,13 +1072,13 @@ class Interface {
this.str += `
if (utils.isArrayIndexPropName(P)) {
const index = P >>> 0;
return !(${supportsPropertyIndex("target", "index")});
return !(${this.supportsPropertyIndex("target", "index")});
}
`;
}
if (this.supportsNamedProperties && !this.isGlobal) {
this.str += `
if (${namedPropertyVisible("P", "target")}) {
if (${this.namedPropertyVisible("P", "target")}) {
`;
if (!hasNamedDeleter) {
this.str += `
Expand Down Expand Up @@ -1124,6 +1128,141 @@ class Interface {
`;
}

// https://heycam.github.io/webidl/#named-properties-object
generateNamedPropertiesObject() {
const { idl, name, namedGetter } = this;

this.str += `

const NamedPropertiesObject = new Proxy(
Object.create(globalObject.${idl.inheritance ? idl.inheritance : "Object"}.prototype, {
[Symbol.toStringTag]: { value: "${name}Properties", configurable: true },
}),
{
`;

// [[Get]] (necessary because of proxy semantics)
this.str += `
get(target, P, receiver) {
if (typeof P === "symbol") {
return Reflect.get(target, P, receiver);
}
const desc = this.getOwnPropertyDescriptor(target, P);
if (desc === undefined) {
const parent = Object.getPrototypeOf(target);
if (parent === null) {
return undefined;
}
return Reflect.get(parent, P, receiver);
}
if (!desc.get && !desc.set) {
return desc.value;
}
const getter = desc.get;
if (getter === undefined) {
return undefined;
}
return Reflect.apply(getter, receiver, []);
},
`;

// [[HasProperty]] (necessary because of proxy semantics)
this.str += `
has(target, P) {
if (typeof P === "symbol") {
return Reflect.has(target, P);
}
const desc = this.getOwnPropertyDescriptor(target, P);
if (desc !== undefined) {
return true;
}
const parent = Object.getPrototypeOf(target);
if (parent !== null) {
return Reflect.has(parent, P);
}
return false;
},
`;

// [[GetOwnProperty]]
this.str += `
getOwnPropertyDescriptor(target, P) {
const object = globalObject;
if (typeof P === "symbol") {
return Reflect.getOwnPropertyDescriptor(target, P);
}
`;

const func = namedGetter.name ? `.${namedGetter.name}` : "[utils.namedGet]";
const enumerable = !utils.getExtAttr(idl.extAttrs, "LegacyUnenumerableNamedProperties");
let preamble = "";
const conditions = [];
if (utils.getExtAttr(namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported")) {
this.str += `
const namedValue = object[impl]${func}(P);
`;
conditions.push(this.supportsPropertyName("object", "index", "namedValue"));
conditions.push(this.namedPropertyVisible("P", "object", true));
} else {
preamble = `
const namedValue = object[impl]${func}(P);
`;
conditions.push(this.namedPropertyVisible("P", "object"));
}
this.str += `
if (${conditions.join(" && ")}) {
${preamble}
return {
writable: true,
enumerable: ${enumerable},
configurable: true,
value: utils.tryWrapperForImpl(namedValue)
};
}
return Reflect.getOwnPropertyDescriptor(target, P);
},
`;

// [[DefineOwnProperty]]
this.str += `
defineProperty() {
return false;
},
`;

// [[Delete]]
this.str += `
deleteProperty() {
return false;
},
`;

// [[SetPrototypeOf]]
this.str += `
setPrototypeOf(O, V) {
// 1. Return ? SetImmutablePrototype(O, V).
return Reflect.getPrototypeOf(O) === V;
},
`;

// [[PreventExtensions]]
this.str += `
preventExtensions() {
return false;
}
`;

this.str += `
}
);
`;

this.str += `
utils.registerNamedPropertiesObject(NamedPropertiesObject);
Object.setPrototypeOf(${name}.prototype, NamedPropertiesObject);
`;
}

generateIface() {
this.str += `
exports.create = function create(globalObject, constructorArgs, privateData) {
Expand Down Expand Up @@ -1470,6 +1609,10 @@ class Interface {

this.generateOffInstanceAfterClass();

if (this.needsNamedPropertiesObject) {
this.generateNamedPropertiesObject();
}

this.str += `
if (globalObject[ctorRegistry] === undefined) {
globalObject[ctorRegistry] = Object.create(null);
Expand Down
Loading