diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index ff2e6368..ec391b91 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -76,6 +76,105 @@ const IteratorPrototype = Object.create(utils.IteratorPrototype, { } });`; } +} + +Interface.prototype.generateNamedPropertiesObject = function () { + if (!utils.isGlobal(this.idl)) { + return; + } + + let overrideBuiltins = false; + let parent = this.idl; + while (parent) { + if (utils.getExtAttr(parent.extAttrs, 'OverrideBuiltins')) { + overrideBuiltins = true; + } + + const members = parent.members.filter((m) => + m.type === 'attribute' && utils.getExtAttr(m.extAttrs, 'Unforgeable') + ); + const parentInterface = this.opts.interfaces[parent.inheritance]; + parent = parentInterface && parentInterface.idl; + } + + this.str += ` +function namedPropertiesIsVisible(P, O) { + if (!true) { // is supported + return false; + } + + if (Object.getOwnPropertyDescriptor(O, P)) { + return false; + } + `; + + if (overrideBuiltins) { + this.str += ` + return true;`; + } + + this.str += ` + let prototype = Object.getPrototypeOf(O); + while (prototype) { + if (prototype.constructor.name.endsWith("PropertiesConstructor") && Object.getOwnPropertyDescriptor(prototype, P)) { + return false; + } + prototype = Object.getPrototypeOf(prototype); + }`; + this.str += ` + return true; +}`; + + this.str += `var ${this.name}PropertiesConstructor = function () { + throw new TypeError("Illegal constructor"); +}\n`; + + if (this.idl.inheritance) { + this.str += `${this.name}PropertiesConstructor.prototype = Object.create(${this.idl.inheritance}.interface.prototype); +${this.name}PropertiesConstructor.prototype.constructor = ${this.name}PropertiesConstructor;\n`; + } else if (utils.getExtAttr(this.idl.extAttrs, 'LegacyArrayClass')) { + this.str += `${this.name}PropertiesConstructor.prototype = Object.create(Array.prototype); +${this.name}PropertiesConstructor.prototype.constructor = ${this.name}PropertiesConstructor;\n`; + } + + const getter = this.idl.members.filter((m) => m.getter)[0].name; + this.str += ` +const ${this.name}Properties = new Proxy(${this.name}PropertiesConstructor.prototype, { + defineProperty() { + return false; + }, + deleteProperty() { + return false; + }, + getOwnPropertyDescriptor(target, key) { + if (namedPropertiesIsVisible(key, target)) { // is visible + const value = target${getter ? "." + getter : "[utils.anonymousGetter]"}(key); + return { + value, + enumerable: true, + writable: true, + configurable: true + }; + } else { + return Object.getOwnPropertyDescriptor(target, key); + } + }, + get(target, key) { + if (namedPropertiesIsVisible(key, target)) { // is visible + const value = target${getter ? "." + getter : "[utils.anonymousGetter]"}(key); + return value; + } else { + return target[key]; + } + }, + has(target, key) { + if (namedPropertiesIsVisible(key, target)) { // is visible + return true; + } else { + return key in target; + } + } +});\n\n`; }; Interface.prototype.generateConstructor = function () { @@ -116,9 +215,11 @@ Interface.prototype.generateConstructor = function () { }\n`; } - if (this.idl.inheritance) { + if (this.idl.inheritance && !utils.isGlobal(this.idl)) { this.str += `${this.name}.prototype = Object.create(${this.idl.inheritance}.interface.prototype); ${this.name}.prototype.constructor = ${this.name};\n`; + } else if (utils.isGlobal(this.idl)) { + this.str += `Object.setPrototypeOf(${this.name}.prototype, ${this.name}Properties);\n`; } }; @@ -406,6 +507,7 @@ module.exports = { defaultPrivateData = defaultPrivateData === undefined ? {} : defaultPrivateData;\n\n`; } + this.generateNamedPropertiesObject(); this.generateConstructor(); this.generateMixins(); diff --git a/lib/output/utils.js b/lib/output/utils.js index 7bef1c35..d645697c 100644 --- a/lib/output/utils.js +++ b/lib/output/utils.js @@ -43,3 +43,6 @@ module.exports.tryWrapperForImpl = tryWrapperForImpl; module.exports.tryImplForWrapper = tryImplForWrapper; module.exports.iterInternalSymbol = iterInternalSymbol; module.exports.IteratorPrototype = IteratorPrototype; + +module.exports.anonymousGetter = Symbol("anonymousGetter"); +module.exports.anonymousSetter = Symbol("anonymousSetter"); diff --git a/lib/transformer.js b/lib/transformer.js index cfba41d8..aff4ed6c 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -92,7 +92,8 @@ class Transformer { obj = new Interface(instruction, { implDir: path.resolve(outputDir, file.impl), implSuffix: this.options.implSuffix, - customTypes + customTypes, + interfaces }); interfaces[obj.name] = obj; break;