From ff61f8d1393c79f7e9068172e9a680215ec6314b Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Thu, 13 Feb 2020 23:40:00 +0100 Subject: [PATCH 1/4] =?UTF-8?q?feat(Global):=20Add=C2=A0support=20for?= =?UTF-8?q?=C2=A0named=C2=A0getters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/constructs/interface.js | 172 ++++++++++++++ test/__snapshots__/test.js.snap | 382 ++++++++++++++++++++++++++++++++ test/cases/Window.webidl | 9 + 3 files changed, 563 insertions(+) create mode 100644 test/cases/Window.webidl diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index deda7ff4..4b9b79a3 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -385,6 +385,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) { @@ -1124,6 +1128,170 @@ class Interface { `; } + // https://heycam.github.io/webidl/#named-properties-object + generateNamedPropertiesObject() { + const { idl, name, namedGetter } = this; + const overrideBuiltins = Boolean(utils.getExtAttr(idl.extAttrs, "OverrideBuiltins")); + + function supportsPropertyName(O, P, namedValue) { + let unsupportedValue = utils.getExtAttr(namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported"); + if (unsupportedValue) { + unsupportedValue = unsupportedValue.rhs.value; + } + if (unsupportedValue) { + const func = namedGetter.name ? `.${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(" && "); + } + + 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(supportsPropertyName("object", "index", "namedValue")); + conditions.push(namedPropertyVisible("P", "object", true)); + } else { + preamble = ` + const namedValue = object[impl]${func}(P); + `; + conditions.push(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 += ` + Object.setPrototypeOf(${name}.prototype, NamedPropertiesObject); + `; + } + generateIface() { this.str += ` exports.create = function create(globalObject, constructorArgs, privateData) { @@ -1470,6 +1638,10 @@ class Interface { this.generateOffInstanceAfterClass(); + if (this.needsNamedPropertiesObject) { + this.generateNamedPropertiesObject(); + } + this.str += ` if (globalObject[ctorRegistry] === undefined) { globalObject[ctorRegistry] = Object.create(null); diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 3f13d0a7..ef986679 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -7504,6 +7504,197 @@ const Impl = require(\\"../implementations/Variadic.js\\"); " `; +exports[`with processors Window.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +const impl = utils.implSymbol; +const ctorRegistry = utils.ctorRegistrySymbol; + +/** + * When an interface-module that implements this interface as a mixin is loaded, it will append its own \`.is()\` + * method into this array. It allows objects that directly implements *those* interfaces to be recognized as + * implementing this mixin interface. + */ +exports._mixedIntoPredicates = []; +exports.is = function is(obj) { + if (obj) { + if (utils.hasOwn(obj, impl) && obj[impl] instanceof Impl.implementation) { + return true; + } + for (const isMixedInto of exports._mixedIntoPredicates) { + if (isMixedInto(obj)) { + return true; + } + } + } + return false; +}; +exports.isImpl = function isImpl(obj) { + if (obj) { + if (obj instanceof Impl.implementation) { + return true; + } + + const wrapper = utils.wrapperForImpl(obj); + for (const isMixedInto of exports._mixedIntoPredicates) { + if (isMixedInto(wrapper)) { + return true; + } + } + } + return false; +}; +exports.convert = function convert(obj, { context = \\"The provided value\\" } = {}) { + if (exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'Window'.\`); +}; + +exports.create = function create(globalObject, constructorArgs, privateData) { + if (globalObject[ctorRegistry] === undefined) { + throw new Error(\\"Internal error: invalid global object\\"); + } + + const ctor = globalObject[ctorRegistry][\\"Window\\"]; + if (ctor === undefined) { + throw new Error(\\"Internal error: constructor Window is not installed on the passed global object\\"); + } + + let obj = Object.create(ctor.prototype); + obj = exports.setup(obj, globalObject, constructorArgs, privateData); + return obj; +}; +exports.createImpl = function createImpl(globalObject, constructorArgs, privateData) { + const obj = exports.create(globalObject, constructorArgs, privateData); + return utils.implForWrapper(obj); +}; +exports._internalSetup = function _internalSetup(obj) {}; +exports.setup = function setup(obj, globalObject, constructorArgs = [], privateData = {}) { + privateData.wrapper = obj; + + exports._internalSetup(obj); + Object.defineProperty(obj, impl, { + value: new Impl.implementation(globalObject, constructorArgs, privateData), + configurable: true + }); + + obj[impl][utils.wrapperSymbol] = obj; + if (Impl.init) { + Impl.init(obj[impl], privateData); + } + return obj; +}; + +exports.install = function install(globalObject) { + const interfaceName = \\"Window\\"; + class Window { + constructor() { + throw new TypeError(\\"Illegal constructor\\"); + } + } + Object.defineProperties(Window.prototype, { [Symbol.toStringTag]: { value: \\"Window\\", configurable: true } }); + const NamedPropertiesObject = new Proxy( + Object.create(globalObject.Object.prototype, { + [Symbol.toStringTag]: { value: \\"WindowProperties\\", configurable: true } + }), + { + 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, []); + }, + + 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; + }, + + getOwnPropertyDescriptor(target, P) { + const object = globalObject; + if (typeof P === \\"symbol\\") { + return Reflect.getOwnPropertyDescriptor(target, P); + } + + if (object[impl][utils.supportsPropertyName](P) && !(P in object)) { + const namedValue = object[impl][utils.namedGet](P); + + return { + writable: true, + enumerable: false, + configurable: true, + value: utils.tryWrapperForImpl(namedValue) + }; + } + return Reflect.getOwnPropertyDescriptor(target, P); + }, + + defineProperty() { + return false; + }, + + deleteProperty() { + return false; + }, + + setPrototypeOf(O, V) { + // 1. Return ? SetImmutablePrototype(O, V). + return Reflect.getPrototypeOf(O) === V; + }, + + preventExtensions() { + return false; + } + } + ); + + Object.setPrototypeOf(Window.prototype, NamedPropertiesObject); + + if (globalObject[ctorRegistry] === undefined) { + globalObject[ctorRegistry] = Object.create(null); + } + globalObject[ctorRegistry][interfaceName] = Window; + + Object.defineProperty(globalObject, interfaceName, { + configurable: true, + writable: true, + value: Window + }); +}; + +const Impl = require(\\"../implementations/Window.js\\"); +" +`; + exports[`with processors ZeroArgConstructor.webidl 1`] = ` "\\"use strict\\"; @@ -15087,6 +15278,197 @@ const Impl = require(\\"../implementations/Variadic.js\\"); " `; +exports[`without processors Window.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +const impl = utils.implSymbol; +const ctorRegistry = utils.ctorRegistrySymbol; + +/** + * When an interface-module that implements this interface as a mixin is loaded, it will append its own \`.is()\` + * method into this array. It allows objects that directly implements *those* interfaces to be recognized as + * implementing this mixin interface. + */ +exports._mixedIntoPredicates = []; +exports.is = function is(obj) { + if (obj) { + if (utils.hasOwn(obj, impl) && obj[impl] instanceof Impl.implementation) { + return true; + } + for (const isMixedInto of exports._mixedIntoPredicates) { + if (isMixedInto(obj)) { + return true; + } + } + } + return false; +}; +exports.isImpl = function isImpl(obj) { + if (obj) { + if (obj instanceof Impl.implementation) { + return true; + } + + const wrapper = utils.wrapperForImpl(obj); + for (const isMixedInto of exports._mixedIntoPredicates) { + if (isMixedInto(wrapper)) { + return true; + } + } + } + return false; +}; +exports.convert = function convert(obj, { context = \\"The provided value\\" } = {}) { + if (exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'Window'.\`); +}; + +exports.create = function create(globalObject, constructorArgs, privateData) { + if (globalObject[ctorRegistry] === undefined) { + throw new Error(\\"Internal error: invalid global object\\"); + } + + const ctor = globalObject[ctorRegistry][\\"Window\\"]; + if (ctor === undefined) { + throw new Error(\\"Internal error: constructor Window is not installed on the passed global object\\"); + } + + let obj = Object.create(ctor.prototype); + obj = exports.setup(obj, globalObject, constructorArgs, privateData); + return obj; +}; +exports.createImpl = function createImpl(globalObject, constructorArgs, privateData) { + const obj = exports.create(globalObject, constructorArgs, privateData); + return utils.implForWrapper(obj); +}; +exports._internalSetup = function _internalSetup(obj) {}; +exports.setup = function setup(obj, globalObject, constructorArgs = [], privateData = {}) { + privateData.wrapper = obj; + + exports._internalSetup(obj); + Object.defineProperty(obj, impl, { + value: new Impl.implementation(globalObject, constructorArgs, privateData), + configurable: true + }); + + obj[impl][utils.wrapperSymbol] = obj; + if (Impl.init) { + Impl.init(obj[impl], privateData); + } + return obj; +}; + +exports.install = function install(globalObject) { + const interfaceName = \\"Window\\"; + class Window { + constructor() { + throw new TypeError(\\"Illegal constructor\\"); + } + } + Object.defineProperties(Window.prototype, { [Symbol.toStringTag]: { value: \\"Window\\", configurable: true } }); + const NamedPropertiesObject = new Proxy( + Object.create(globalObject.Object.prototype, { + [Symbol.toStringTag]: { value: \\"WindowProperties\\", configurable: true } + }), + { + 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, []); + }, + + 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; + }, + + getOwnPropertyDescriptor(target, P) { + const object = globalObject; + if (typeof P === \\"symbol\\") { + return Reflect.getOwnPropertyDescriptor(target, P); + } + + if (object[impl][utils.supportsPropertyName](P) && !(P in object)) { + const namedValue = object[impl][utils.namedGet](P); + + return { + writable: true, + enumerable: false, + configurable: true, + value: utils.tryWrapperForImpl(namedValue) + }; + } + return Reflect.getOwnPropertyDescriptor(target, P); + }, + + defineProperty() { + return false; + }, + + deleteProperty() { + return false; + }, + + setPrototypeOf(O, V) { + // 1. Return ? SetImmutablePrototype(O, V). + return Reflect.getPrototypeOf(O) === V; + }, + + preventExtensions() { + return false; + } + } + ); + + Object.setPrototypeOf(Window.prototype, NamedPropertiesObject); + + if (globalObject[ctorRegistry] === undefined) { + globalObject[ctorRegistry] = Object.create(null); + } + globalObject[ctorRegistry][interfaceName] = Window; + + Object.defineProperty(globalObject, interfaceName, { + configurable: true, + writable: true, + value: Window + }); +}; + +const Impl = require(\\"../implementations/Window.js\\"); +" +`; + exports[`without processors ZeroArgConstructor.webidl 1`] = ` "\\"use strict\\"; diff --git a/test/cases/Window.webidl b/test/cases/Window.webidl new file mode 100644 index 00000000..25bfa78b --- /dev/null +++ b/test/cases/Window.webidl @@ -0,0 +1,9 @@ +[Global=Window, + Exposed=Window, + LegacyUnenumerableNamedProperties] +interface Window { + getter object (DOMString name); + // Since this is the global object, the IDL named getter adds a NamedPropertiesObject exotic + // object on the prototype chain. Indeed, this does not make the global object an exotic object. + // Indexed access is taken care of by the WindowProxy exotic object. +}; From 1165da83ce62b4709315982c2b29633069bcaf0a Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Thu, 13 Feb 2020 23:50:00 +0100 Subject: [PATCH 2/4] =?UTF-8?q?refactor(interface):=20Share=C2=A0named?= =?UTF-8?q?=C2=A0property=20visibility=C2=A0algorithm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/constructs/interface.js | 149 +++++++++++++++--------------------- 1 file changed, 60 insertions(+), 89 deletions(-) diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index 4b9b79a3..f45464ce 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -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" @@ -587,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(`!(${P} in ${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) => { @@ -652,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 { @@ -691,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 { @@ -777,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}\`); } } @@ -810,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 += ` @@ -839,13 +840,13 @@ class Interface { 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 += ` @@ -920,10 +921,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 += ` @@ -1022,7 +1023,7 @@ class Interface { if (!hasNamedSetter) { needFallback = true; this.str += ` - const creating = !(${supportsPropertyName("target", "P")}); + const creating = !(${this.supportsPropertyName("target", "P")}); if (!creating) { return false; } @@ -1072,13 +1073,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 += ` @@ -1131,36 +1132,6 @@ class Interface { // https://heycam.github.io/webidl/#named-properties-object generateNamedPropertiesObject() { const { idl, name, namedGetter } = this; - const overrideBuiltins = Boolean(utils.getExtAttr(idl.extAttrs, "OverrideBuiltins")); - - function supportsPropertyName(O, P, namedValue) { - let unsupportedValue = utils.getExtAttr(namedGetter.extAttrs, "WebIDL2JSValueAsUnsupported"); - if (unsupportedValue) { - unsupportedValue = unsupportedValue.rhs.value; - } - if (unsupportedValue) { - const func = namedGetter.name ? `.${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(" && "); - } this.str += ` const NamedPropertiesObject = new Proxy( @@ -1231,13 +1202,13 @@ class Interface { this.str += ` const namedValue = object[impl]${func}(P); `; - conditions.push(supportsPropertyName("object", "index", "namedValue")); - conditions.push(namedPropertyVisible("P", "object", true)); + conditions.push(this.supportsPropertyName("object", "index", "namedValue")); + conditions.push(this.namedPropertyVisible("P", "object", true)); } else { preamble = ` const namedValue = object[impl]${func}(P); `; - conditions.push(namedPropertyVisible("P", "object")); + conditions.push(this.namedPropertyVisible("P", "object")); } this.str += ` if (${conditions.join(" && ")}) { From 838642d1d459d8c446574dde0da917594c01fdaa Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Fri, 14 Feb 2020 00:10:00 +0100 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20Add=C2=A0linebreak=C2=A0before?= =?UTF-8?q?=20the=C2=A0named=C2=A0properties=C2=A0object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/constructs/interface.js | 1 + test/__snapshots__/test.js.snap | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index f45464ce..09f0900d 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -1134,6 +1134,7 @@ class Interface { const { idl, name, namedGetter } = this; this.str += ` + const NamedPropertiesObject = new Proxy( Object.create( globalObject.${idl.inheritance ? idl.inheritance : "Object"}.prototype, diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index ef986679..6a25ef4b 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -7597,6 +7597,7 @@ exports.install = function install(globalObject) { } } Object.defineProperties(Window.prototype, { [Symbol.toStringTag]: { value: \\"Window\\", configurable: true } }); + const NamedPropertiesObject = new Proxy( Object.create(globalObject.Object.prototype, { [Symbol.toStringTag]: { value: \\"WindowProperties\\", configurable: true } @@ -15371,6 +15372,7 @@ exports.install = function install(globalObject) { } } Object.defineProperties(Window.prototype, { [Symbol.toStringTag]: { value: \\"Window\\", configurable: true } }); + const NamedPropertiesObject = new Proxy( Object.create(globalObject.Object.prototype, { [Symbol.toStringTag]: { value: \\"WindowProperties\\", configurable: true } From 30091e817e751cf09f44ee8b16859e3fd7243851 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Fri, 14 Feb 2020 01:00:00 +0100 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20Move=C2=A0named=C2=A0property?= =?UTF-8?q?=20visibility=C2=A0algorithm=20to=C2=A0`utils.js`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/constructs/interface.js | 13 +++---- lib/output/utils.js | 17 +++++++++ test/__snapshots__/test.js.snap | 66 +++++++++++++++++---------------- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index 09f0900d..9f35e20b 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -625,7 +625,7 @@ class Interface { if (this.overrideBuiltins) { conditions.push(`!utils.hasOwn(${O}, ${P})`); } else { - conditions.push(`!(${P} in ${O})`); + conditions.push(`utils.isNamedPropertyVisible(${P}, ${O})`); } return conditions.join(" && "); } @@ -835,7 +835,7 @@ 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); @@ -848,7 +848,6 @@ class Interface { `; conditions.push(this.namedPropertyVisible("P", "target", false)); } - conditions.push("!ignoreNamedProps"); this.str += ` if (${conditions.join(" && ")}) { ${preamble} @@ -1136,10 +1135,9 @@ class Interface { this.str += ` const NamedPropertiesObject = new Proxy( - Object.create( - globalObject.${idl.inheritance ? idl.inheritance : "Object"}.prototype, - { [Symbol.toStringTag]: { value: "${name}Properties", configurable: true } } - ), + Object.create(globalObject.${idl.inheritance ? idl.inheritance : "Object"}.prototype, { + [Symbol.toStringTag]: { value: "${name}Properties", configurable: true }, + }), { `; @@ -1260,6 +1258,7 @@ class Interface { `; this.str += ` + utils.registerNamedPropertiesObject(NamedPropertiesObject); Object.setPrototypeOf(${name}.prototype, NamedPropertiesObject); `; } diff --git a/lib/output/utils.js b/lib/output/utils.js index c020d0b0..8fc318ec 100644 --- a/lib/output/utils.js +++ b/lib/output/utils.js @@ -74,6 +74,21 @@ function isArrayBuffer(value) { } } +const namedPropertiesObjects = new WeakSet(); +function registerNamedPropertiesObject(obj) { + namedPropertiesObjects.add(obj); +} + +function isNamedPropertyVisible(P, O) { + while (O !== null) { + if (!namedPropertiesObjects.has(O) && hasOwn(O, P)) { + return false; + } + O = Object.getPrototypeOf(O); + } + return true; +} + const supportsPropertyIndex = Symbol("supports property index"); const supportedPropertyIndices = Symbol("supported property indices"); const supportsPropertyName = Symbol("supports property name"); @@ -101,6 +116,8 @@ module.exports = exports = { IteratorPrototype, isArrayBuffer, isArrayIndexPropName, + registerNamedPropertiesObject, + isNamedPropertyVisible, supportsPropertyIndex, supportedPropertyIndices, supportsPropertyName, diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 6a25ef4b..d04b8a64 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -465,7 +465,7 @@ const proxyHandler = { const keys = new Set(); for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -482,7 +482,7 @@ const proxyHandler = { } let ignoreNamedProps = false; - if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { const namedValue = target[impl][utils.namedGet](P); return { @@ -594,7 +594,7 @@ const proxyHandler = { return Reflect.deleteProperty(target, P); } - if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + if (target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { CEReactions.preSteps(globalObject); try { target[impl][utils.namedDelete](P); @@ -3398,7 +3398,7 @@ const proxyHandler = { const keys = new Set(); for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -3415,7 +3415,7 @@ const proxyHandler = { } let ignoreNamedProps = false; - if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { const namedValue = target[impl].getItem(P); return { @@ -3507,7 +3507,7 @@ const proxyHandler = { return Reflect.deleteProperty(target, P); } - if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + if (target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { target[impl].removeItem(P); return true; } @@ -5907,7 +5907,7 @@ const proxyHandler = { } for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -5940,7 +5940,7 @@ const proxyHandler = { const namedValue = target[impl].namedItem(P); - if (namedValue !== null && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && namedValue !== null && utils.isNamedPropertyVisible(P, target)) { return { writable: false, enumerable: false, @@ -6035,7 +6035,7 @@ const proxyHandler = { return !(target[impl].item(index) !== undefined); } - if (target[impl].namedItem(P) !== null && !(P in target)) { + if (target[impl].namedItem(P) !== null && utils.isNamedPropertyVisible(P, target)) { return false; } @@ -6219,7 +6219,7 @@ const proxyHandler = { } for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -6252,7 +6252,7 @@ const proxyHandler = { const namedValue = target[impl].namedItem(P); - if (namedValue !== null && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && namedValue !== null && utils.isNamedPropertyVisible(P, target)) { return { writable: true, enumerable: true, @@ -6376,7 +6376,7 @@ const proxyHandler = { return !(target[impl].item(index) !== undefined); } - if (target[impl].namedItem(P) !== null && !(P in target)) { + if (target[impl].namedItem(P) !== null && utils.isNamedPropertyVisible(P, target)) { return false; } @@ -6947,7 +6947,7 @@ const proxyHandler = { const keys = new Set(); for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -6964,7 +6964,7 @@ const proxyHandler = { } let ignoreNamedProps = false; - if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { const namedValue = target[impl][utils.namedGet](P); return { @@ -7068,7 +7068,7 @@ const proxyHandler = { return Reflect.deleteProperty(target, P); } - if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + if (target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { return false; } @@ -7646,7 +7646,7 @@ exports.install = function install(globalObject) { return Reflect.getOwnPropertyDescriptor(target, P); } - if (object[impl][utils.supportsPropertyName](P) && !(P in object)) { + if (object[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, object)) { const namedValue = object[impl][utils.namedGet](P); return { @@ -7678,6 +7678,7 @@ exports.install = function install(globalObject) { } ); + utils.registerNamedPropertiesObject(NamedPropertiesObject); Object.setPrototypeOf(Window.prototype, NamedPropertiesObject); if (globalObject[ctorRegistry] === undefined) { @@ -8256,7 +8257,7 @@ const proxyHandler = { const keys = new Set(); for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -8273,7 +8274,7 @@ const proxyHandler = { } let ignoreNamedProps = false; - if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { const namedValue = target[impl][utils.namedGet](P); return { @@ -8375,7 +8376,7 @@ const proxyHandler = { return Reflect.deleteProperty(target, P); } - if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + if (target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { target[impl][utils.namedDelete](P); return true; } @@ -11173,7 +11174,7 @@ const proxyHandler = { const keys = new Set(); for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -11190,7 +11191,7 @@ const proxyHandler = { } let ignoreNamedProps = false; - if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { const namedValue = target[impl].getItem(P); return { @@ -11282,7 +11283,7 @@ const proxyHandler = { return Reflect.deleteProperty(target, P); } - if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + if (target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { target[impl].removeItem(P); return true; } @@ -13682,7 +13683,7 @@ const proxyHandler = { } for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -13715,7 +13716,7 @@ const proxyHandler = { const namedValue = target[impl].namedItem(P); - if (namedValue !== null && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && namedValue !== null && utils.isNamedPropertyVisible(P, target)) { return { writable: false, enumerable: false, @@ -13810,7 +13811,7 @@ const proxyHandler = { return !(target[impl].item(index) !== undefined); } - if (target[impl].namedItem(P) !== null && !(P in target)) { + if (target[impl].namedItem(P) !== null && utils.isNamedPropertyVisible(P, target)) { return false; } @@ -13994,7 +13995,7 @@ const proxyHandler = { } for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -14027,7 +14028,7 @@ const proxyHandler = { const namedValue = target[impl].namedItem(P); - if (namedValue !== null && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && namedValue !== null && utils.isNamedPropertyVisible(P, target)) { return { writable: true, enumerable: true, @@ -14151,7 +14152,7 @@ const proxyHandler = { return !(target[impl].item(index) !== undefined); } - if (target[impl].namedItem(P) !== null && !(P in target)) { + if (target[impl].namedItem(P) !== null && utils.isNamedPropertyVisible(P, target)) { return false; } @@ -14722,7 +14723,7 @@ const proxyHandler = { const keys = new Set(); for (const key of target[impl][utils.supportedPropertyNames]) { - if (!(key in target)) { + if (utils.isNamedPropertyVisible(key, target)) { keys.add(\`\${key}\`); } } @@ -14739,7 +14740,7 @@ const proxyHandler = { } let ignoreNamedProps = false; - if (target[impl][utils.supportsPropertyName](P) && !(P in target) && !ignoreNamedProps) { + if (!ignoreNamedProps && target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { const namedValue = target[impl][utils.namedGet](P); return { @@ -14843,7 +14844,7 @@ const proxyHandler = { return Reflect.deleteProperty(target, P); } - if (target[impl][utils.supportsPropertyName](P) && !(P in target)) { + if (target[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, target)) { return false; } @@ -15421,7 +15422,7 @@ exports.install = function install(globalObject) { return Reflect.getOwnPropertyDescriptor(target, P); } - if (object[impl][utils.supportsPropertyName](P) && !(P in object)) { + if (object[impl][utils.supportsPropertyName](P) && utils.isNamedPropertyVisible(P, object)) { const namedValue = object[impl][utils.namedGet](P); return { @@ -15453,6 +15454,7 @@ exports.install = function install(globalObject) { } ); + utils.registerNamedPropertiesObject(NamedPropertiesObject); Object.setPrototypeOf(Window.prototype, NamedPropertiesObject); if (globalObject[ctorRegistry] === undefined) {