Skip to content

Commit

Permalink
Implement the named properties object
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyGu committed Dec 4, 2017
1 parent fd2c9a0 commit 24f9449
Show file tree
Hide file tree
Showing 2 changed files with 509 additions and 10 deletions.
190 changes: 184 additions & 6 deletions lib/constructs/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Interface {
this.ctx = ctx;
this.idl = idl;
this.name = idl.name;
this.factory = Boolean(utils.getExtAttr(this.idl.extAttrs, "WebIDL2JSFactory"));
this.factory = utils.isGlobal(this.idl) || Boolean(utils.getExtAttr(this.idl.extAttrs, "WebIDL2JSFactory"));
for (const member of this.idl.members) {
member.definingInterface = this.name;
}
Expand Down Expand Up @@ -347,15 +347,19 @@ class Interface {
`;
}

if (utils.isGlobal(this.idl) && this.supportsNamedProperties) {
this.generateNamedPropertiesObject();
this.str += `Object.setPrototypeOf(${this.name}.prototype, namedPropertiesObject);`;
} else if (this.idl.inheritance) {
this.str += `Object.setPrototypeOf(${this.name}.prototype, ${this.idl.inheritance}.interface.prototype);`;
} else if (utils.getExtAttr(this.idl.extAttrs, "LegacyArrayClass")) {
this.str += `Object.setPrototypeOf(${this.name}.prototype, Array.prototype);`;
}

if (this.idl.inheritance) {
this.str += `
Object.setPrototypeOf(${this.name}.prototype, ${this.idl.inheritance}.interface.prototype);
Object.setPrototypeOf(${this.name}, ${this.idl.inheritance}.interface);
`;
} else if (utils.getExtAttr(this.idl.extAttrs, "LegacyArrayClass")) {
this.str += `
Object.setPrototypeOf(${this.name}.prototype, Array.prototype);
`;
}

this.str += `
Expand Down Expand Up @@ -544,6 +548,180 @@ class Interface {
return conditions.join(" && ");
}

generateNamedPropertiesObject() {
const proto = (() => {
if (this.idl.inheritance) {
return `${this.idl.inheritance}.interface.prototype`;
} else if (utils.getExtAttr(this.idl.extAttrs, "LegacyArrayClass")) {
return "Array.prototype";
}
return "Object.prototype";
})();

this.str += `
const namedPropertiesObject = new Proxy(Object.create(${proto}, {
[Symbol.toStringTag]: {
value: "${this.name}Properties",
writable: false,
enumerable: false,
configurable: true
}
}), {
`;

// [[SetPrototypeOf]]
this.str += `
setPrototypeOf() {
throw new TypeError("Immutable prototype object '#<${this.name}Properties>' cannot have their prototype set");
},
`;

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

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

const func = this.namedGetter.name !== null ? `.${this.namedGetter.name}` : "[utils.namedGet]";
const enumerable = !utils.getExtAttr(this.idl.extAttrs, "LegacyUnenumerableNamedProperties");
let preamble = "";
const conditions = [];
if (utils.getExtAttr(this.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", false));
}

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;
},
`;

// [[HasProperty]]
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;
},
`;

// [[Get]]
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(target, P, receiver);
}
if (!desc.get && !desc.set) {
return desc.value;
}
const getter = desc.get;
if (getter === undefined) {
return undefined;
}
return Reflect.apply(getter, receiver, []);
},
`;

// [[Set]]
this.str += `
set(target, P, V, receiver) {
if (typeof P === "symbol") {
return Reflect.set(target, P, V, receiver);
}
const ownDesc = this.getOwnPropertyDescriptor(P);
if (ownDesc === undefined) {
const parent = Reflect.getPrototypeOf(target);
// parent is never null.
return Reflect.set(parent, P, V, receiver);
}
// ownDesc.writable is always true.
if (!utils.isObject(receiver)) {
return false;
}
// If receiver is this Proxy object, the following will always return false. Problem is, receiver may not be
// this Proxy object.
const existingDesc = Reflect.getOwnPropertyDescriptor(receiver, P);
let valueDesc;
if (existingDesc !== undefined) {
if (existingDesc.get || existingDesc.set) {
return false;
}
if (!existingDesc.writable) {
return false;
}
valueDesc = { value: V };
} else {
valueDesc = { writable: true, enumerable: true, configurable: true, value: V };
}
return Reflect.defineProperty(receiver, P, valueDesc);
},
`;

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

// [[OwnPropertyKeys]] not overriden

this.str += `
});
`;
}

generateLegacyProxy() {
const hasIndexedSetter = this.indexedSetter !== null;
const hasNamedSetter = this.namedSetter !== null;
Expand Down
Loading

0 comments on commit 24f9449

Please sign in to comment.