diff --git a/lib/jsdom/browser/Window.js b/lib/jsdom/browser/Window.js index 7eafb4d137..c2c31342e3 100644 --- a/lib/jsdom/browser/Window.js +++ b/lib/jsdom/browser/Window.js @@ -117,6 +117,8 @@ function Window(options) { global: this } }); + this._documentImpl = idlUtils.implForWrapper(this._document); + // https://html.spec.whatwg.org/#session-history this._sessionHistory = new SessionHistory({ document: idlUtils.implForWrapper(this._document), @@ -528,9 +530,8 @@ function Window(options) { return declaration; }; - this.customElements = CustomElementRegistry.create([], { - _ownerDocument: idlUtils.implForWrapper(this._document) - }); + // TODO: Make it a getter + this.customElements = CustomElementRegistry.create(window); // The captureEvents() and releaseEvents() methods must do nothing this.captureEvents = function () {}; diff --git a/lib/jsdom/living/attributes/Attr-impl.js b/lib/jsdom/living/attributes/Attr-impl.js index c9fff6b5e8..b287140283 100644 --- a/lib/jsdom/living/attributes/Attr-impl.js +++ b/lib/jsdom/living/attributes/Attr-impl.js @@ -1,8 +1,6 @@ "use strict"; -const { mixin } = require("../../utils"); const attributes = require("../attributes.js"); -const { CeReactions } = require("../helpers/custom-elements"); class AttrImpl { constructor(globalObject, args, privateData) { @@ -77,9 +75,4 @@ class AttrImpl { } } -// This is theoretically not needed. If the Attr interface would extend the Node interface it would inherit the -// CEReactions mixin. -// TODO: Remove this once the AttrImpl actually inherits from the NodeImpl. -mixin(AttrImpl.prototype, CeReactions.prototype); - exports.implementation = AttrImpl; diff --git a/lib/jsdom/living/attributes/NamedNodeMap-impl.js b/lib/jsdom/living/attributes/NamedNodeMap-impl.js index ec42c84587..24902cce71 100644 --- a/lib/jsdom/living/attributes/NamedNodeMap-impl.js +++ b/lib/jsdom/living/attributes/NamedNodeMap-impl.js @@ -4,11 +4,8 @@ const DOMException = require("domexception"); const idlUtils = require("../generated/utils.js"); -const { mixin } = require("../../utils"); - const attributes = require("../attributes.js"); const { HTML_NS } = require("../helpers/namespaces"); -const { CeReactions } = require("../helpers/custom-elements"); class NamedNodeMapImpl { constructor(globalObject, args, privateData) { @@ -74,6 +71,4 @@ class NamedNodeMapImpl { } } -mixin(NamedNodeMapImpl.prototype, CeReactions.prototype); - exports.implementation = NamedNodeMapImpl; diff --git a/lib/jsdom/living/create-element.js b/lib/jsdom/living/create-element.js index 072e806661..30c94eb200 100644 --- a/lib/jsdom/living/create-element.js +++ b/lib/jsdom/living/create-element.js @@ -444,16 +444,17 @@ function getSVGInterface(name) { } // https://dom.spec.whatwg.org/#concept-create-element -function createElement(documentImpl, localName, namespace, prefix = null, isValue = null, synchronousCE = false) { +function createElement(document, localName, namespace, prefix = null, isValue = null, synchronousCE = false) { let result = null; - const definition = lookupCEDefinition(documentImpl, namespace, localName, isValue); + const { _globalObject } = document; + const definition = lookupCEDefinition(document, namespace, localName, isValue); if (definition !== null && definition.name !== localName) { const elementInterface = getHTMLElementInterface(localName); - result = elementInterface.interface.createImpl([], { - ownerDocument: documentImpl, + result = elementInterface.interface.createImpl(_globalObject, [], { + ownerDocument: document, localName, namespace: HTML_NS, prefix, @@ -488,7 +489,7 @@ function createElement(documentImpl, localName, namespace, prefix = null, isValu if (domSymbolTree.parent(result)) { throw new DOMException("Unexpected element parent.", "NotSupportedError"); } - if (result._ownerDocument !== documentImpl) { + if (result._ownerDocument !== document) { throw new DOMException("Unexpected element owner document.", "NotSupportedError"); } if (result._namespaceURI !== namespace) { @@ -501,10 +502,10 @@ function createElement(documentImpl, localName, namespace, prefix = null, isValu result._prefix = prefix; result._isValue = isValue; } catch (error) { - reportException(documentImpl._defaultView, error); + reportException(document._defaultView, error); - result = HTMLUnknownElement.interface.createImpl([], { - ownerDocument: documentImpl, + result = HTMLUnknownElement.interface.createImpl(_globalObject, [], { + ownerDocument: document, localName, namespace: HTML_NS, prefix, @@ -514,8 +515,8 @@ function createElement(documentImpl, localName, namespace, prefix = null, isValu }); } } else { - result = HTMLElement.interface.createImpl([], { - ownerDocument: documentImpl, + result = HTMLElement.interface.createImpl(_globalObject, [], { + ownerDocument: document, localName, namespace: HTML_NS, prefix, @@ -543,8 +544,8 @@ function createElement(documentImpl, localName, namespace, prefix = null, isValu break; } - result = elementInterface.interface.createImpl([], { - ownerDocument: documentImpl, + result = elementInterface.interface.createImpl(_globalObject, [], { + ownerDocument: document, localName, namespace, prefix, @@ -562,7 +563,7 @@ function createElement(documentImpl, localName, namespace, prefix = null, isValu } // https://dom.spec.whatwg.org/#internal-createelementns-steps -function createElementNS(documentImpl, namespace, qualifiedName, options) { +function createElementNS(document, namespace, qualifiedName, options) { const extracted = validateAndExtract(namespace, qualifiedName); let isValue = null; @@ -570,7 +571,7 @@ function createElementNS(documentImpl, namespace, qualifiedName, options) { isValue = options.is; } - return createElement(documentImpl, extracted.localName, extracted.namespace, extracted.prefix, isValue, true); + return createElement(document, extracted.localName, extracted.namespace, extracted.prefix, isValue, true); } module.exports = { diff --git a/lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js b/lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js index 7ff66587b0..c993c03679 100644 --- a/lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js +++ b/lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js @@ -5,12 +5,10 @@ const webIDLConversions = require("webidl-conversions"); const NODE_TYPE = require("../node-type"); const { getHTMLElementInterface } = require("../create-element"); -const { mixin } = require("../../utils"); const { HTML_NS } = require("../helpers/namespaces"); -const { CeReactions, isValidCustomElementName, tryUpgradeElement, - enqueueCEUpgradeReaction } = require("../helpers/custom-elements"); const { shadowIncludingInclusiveDescendantsIterator } = require("../helpers/shadow-dom"); +const { isValidCustomElementName, tryUpgradeElement, enqueueCEUpgradeReaction } = require("../helpers/custom-elements"); const LIFECYCLE_CALLBACKS = [ "connectedCallback", @@ -48,13 +46,12 @@ function isConstructor(value) { // https://html.spec.whatwg.org/#customelementregistry class CustomElementRegistryImpl { - constructor(args, privateData) { + constructor(globalObject) { this._customElementDefinitions = []; this._elementDefinitionIsRunning = false; this._whenDefinedPromiseMap = Object.create(null); - const { _ownerDocument } = privateData; - this._ownerDocument = _ownerDocument; + this._globalObject = globalObject; } // https://html.spec.whatwg.org/#dom-customelementregistry-define @@ -171,8 +168,10 @@ class CustomElementRegistryImpl { this._customElementDefinitions.push(definition); + const document = this._globalObject._documentImpl; + const upgradeCandidates = []; - for (const candidate of shadowIncludingInclusiveDescendantsIterator(this._ownerDocument)) { + for (const candidate of shadowIncludingInclusiveDescendantsIterator(document)) { if ( (candidate._namespaceURI === HTML_NS && candidate._localName === localName) && (extendsOption === null || candidate._isValue === name) @@ -235,8 +234,6 @@ class CustomElementRegistryImpl { } } -mixin(CustomElementRegistryImpl.prototype, CeReactions.prototype); - module.exports = { implementation: CustomElementRegistryImpl }; diff --git a/lib/jsdom/living/helpers/custom-elements.js b/lib/jsdom/living/helpers/custom-elements.js index a3cf8a5444..39bafb7b29 100644 --- a/lib/jsdom/living/helpers/custom-elements.js +++ b/lib/jsdom/living/helpers/custom-elements.js @@ -10,6 +10,14 @@ const reportException = require("./runtime-script-errors"); const { implForWrapper, wrapperForImpl } = require("../generated/utils"); +// https://dom.spec.whatwg.org/#concept-element-custom-element-state +const CUSTOM_ELEMENT_STATE = { + UNDEFINED: "undefined", + FAILED: "failed", + UNCUSTOMIZED: "uncustomized", + CUSTOM: "custom" +}; + // https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reactions-stack class CEReactionsStack { constructor() { @@ -40,30 +48,17 @@ class CEReactionsStack { } } +// TODO: Attach this the global object. const CUSTOM_ELEMENT_REACTIONS_STACK = new CEReactionsStack(); // https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions -// -// This class should be applied as a mixin to all the interfaces where on the properties is marked with the -// [CEReactions] IDL extended attribute. -class CeReactions { - _ceReactionsPreSteps() { - CUSTOM_ELEMENT_REACTIONS_STACK.push([]); - } - - _ceReactionsPostSteps() { - const queue = CUSTOM_ELEMENT_REACTIONS_STACK.pop(); - invokeCEReactions(queue); - } +function ceReactionsPreSteps() { + CUSTOM_ELEMENT_REACTIONS_STACK.push([]); +} +function ceReactionsPostSteps() { + const queue = CUSTOM_ELEMENT_REACTIONS_STACK.pop(); + invokeCEReactions(queue); } - -// https://dom.spec.whatwg.org/#concept-element-custom-element-state -const CUSTOM_ELEMENT_STATE = { - UNDEFINED: "undefined", - FAILED: "failed", - UNCUSTOMIZED: "uncustomized", - CUSTOM: "custom" -}; const RESTRICTED_CUSTOM_ELEMENT_NAME = new Set([ "annotation-xml", @@ -265,7 +260,8 @@ module.exports = { CUSTOM_ELEMENT_STATE, CUSTOM_ELEMENT_REACTIONS_STACK, - CeReactions, + ceReactionsPreSteps, + ceReactionsPostSteps, isValidCustomElementName, diff --git a/lib/jsdom/living/helpers/html-constructor.js b/lib/jsdom/living/helpers/html-constructor.js index 3b66edb52c..56fbc36ae8 100644 --- a/lib/jsdom/living/helpers/html-constructor.js +++ b/lib/jsdom/living/helpers/html-constructor.js @@ -8,83 +8,12 @@ const { CUSTOM_ELEMENT_STATE } = require("./custom-elements"); const { createElement, INTERFACE_TAG_MAPPING } = require("../create-element"); const { implForWrapper, wrapperForImpl } = require("../generated/utils"); -const GENERATED_INTERFACES = [ - require("../generated/HTMLElement.js"), - require("../generated/HTMLAnchorElement.js"), - require("../generated/HTMLAreaElement.js"), - require("../generated/HTMLAudioElement.js"), - require("../generated/HTMLBaseElement.js"), - require("../generated/HTMLBodyElement.js"), - require("../generated/HTMLBRElement.js"), - require("../generated/HTMLButtonElement.js"), - require("../generated/HTMLCanvasElement.js"), - require("../generated/HTMLDataElement.js"), - require("../generated/HTMLDataListElement.js"), - require("../generated/HTMLDetailsElement.js"), - require("../generated/HTMLDialogElement.js"), - require("../generated/HTMLDirectoryElement.js"), - require("../generated/HTMLDivElement.js"), - require("../generated/HTMLDListElement.js"), - require("../generated/HTMLEmbedElement.js"), - require("../generated/HTMLFieldSetElement.js"), - require("../generated/HTMLFontElement.js"), - require("../generated/HTMLFormElement.js"), - require("../generated/HTMLFrameElement.js"), - require("../generated/HTMLFrameSetElement.js"), - require("../generated/HTMLHeadingElement.js"), - require("../generated/HTMLHeadElement.js"), - require("../generated/HTMLHRElement.js"), - require("../generated/HTMLHtmlElement.js"), - require("../generated/HTMLIFrameElement.js"), - require("../generated/HTMLImageElement.js"), - require("../generated/HTMLInputElement.js"), - require("../generated/HTMLLabelElement.js"), - require("../generated/HTMLLegendElement.js"), - require("../generated/HTMLLIElement.js"), - require("../generated/HTMLLinkElement.js"), - require("../generated/HTMLMapElement.js"), - require("../generated/HTMLMarqueeElement.js"), - require("../generated/HTMLMenuElement.js"), - require("../generated/HTMLMetaElement.js"), - require("../generated/HTMLMeterElement.js"), - require("../generated/HTMLModElement.js"), - require("../generated/HTMLObjectElement.js"), - require("../generated/HTMLOListElement.js"), - require("../generated/HTMLOptGroupElement.js"), - require("../generated/HTMLOptionElement.js"), - require("../generated/HTMLOutputElement.js"), - require("../generated/HTMLParagraphElement.js"), - require("../generated/HTMLParamElement.js"), - require("../generated/HTMLPictureElement.js"), - require("../generated/HTMLPreElement.js"), - require("../generated/HTMLProgressElement.js"), - require("../generated/HTMLQuoteElement.js"), - require("../generated/HTMLScriptElement.js"), - require("../generated/HTMLSelectElement.js"), - require("../generated/HTMLSlotElement.js"), - require("../generated/HTMLSourceElement.js"), - require("../generated/HTMLSpanElement.js"), - require("../generated/HTMLStyleElement.js"), - require("../generated/HTMLTableCaptionElement.js"), - require("../generated/HTMLTableCellElement.js"), - require("../generated/HTMLTableColElement.js"), - require("../generated/HTMLTableElement.js"), - require("../generated/HTMLTimeElement.js"), - require("../generated/HTMLTitleElement.js"), - require("../generated/HTMLTableRowElement.js"), - require("../generated/HTMLTableSectionElement.js"), - require("../generated/HTMLTemplateElement.js"), - require("../generated/HTMLTextAreaElement.js"), - require("../generated/HTMLTrackElement.js"), - require("../generated/HTMLUListElement.js"), - require("../generated/HTMLVideoElement.js") -]; - // https://html.spec.whatwg.org/multipage/custom-elements.html#concept-already-constructed-marker const ALREADY_CONSTRUCTED_MARKER = Symbol("already-constructed-marker"); +// TODO: Evaluate need to pass the constructor name // https://html.spec.whatwg.org/multipage/dom.html#htmlconstructor -function HTMLConstructor({ globalObject, newTarget, constructorName }) { +function HTMLConstructor(globalObject, newTarget) { const registry = implForWrapper(globalObject.customElements); if (newTarget === HTMLConstructor) { @@ -121,8 +50,8 @@ function HTMLConstructor({ globalObject, newTarget, constructorName }) { const documentImpl = implForWrapper(globalObject.document); const elementImpl = createElement(documentImpl, definition.localName, HTML_NS); - const element = wrapperForImpl(elementImpl); + const element = wrapperForImpl(elementImpl); Object.setPrototypeOf(element, prototype); elementImpl._ceState = CUSTOM_ELEMENT_STATE.CUSTOM; @@ -147,13 +76,6 @@ function HTMLConstructor({ globalObject, newTarget, constructorName }) { return element; } -function installHTMLConstructors(globalObject) { - for (const generatedInterface of GENERATED_INTERFACES) { - generatedInterface.installConstructor(globalObject, HTMLConstructor); - } -} - module.exports = { - HTMLConstructor, - installHTMLConstructors + HTMLConstructor }; diff --git a/lib/jsdom/living/interfaces.js b/lib/jsdom/living/interfaces.js index 296d46f9ea..06042004e3 100644 --- a/lib/jsdom/living/interfaces.js +++ b/lib/jsdom/living/interfaces.js @@ -5,7 +5,6 @@ const DOMException = require("domexception"); const { URL, URLSearchParams } = require("whatwg-url"); const XMLSerializer = require("w3c-xmlserializer/lib/XMLSerializer").interface; -const registerElements = require("./register-elements"); const style = require("../level2/style"); const xpath = require("../level3/xpath"); const nodeFilter = require("./node-filter"); @@ -165,6 +164,7 @@ const generatedInterfaces = [ require("./generated/Storage"), + require("./generated/CustomElementRegistry"), require("./generated/ShadowRoot"), require("./generated/MutationObserver"), @@ -200,11 +200,8 @@ exports.installInterfaces = function (window) { // https://html.spec.whatwg.org/#htmldocument install(window, "HTMLDocument", window.Document); - // Install Elements - registerElements(window); - // These need to be cleaned up... - style.addToCore(window); + style(window); xpath(window); // This one is OK but needs migration to webidl2js eventually. diff --git a/lib/jsdom/living/nodes/DOMStringMap-impl.js b/lib/jsdom/living/nodes/DOMStringMap-impl.js index 110f0ccb4c..a83f852305 100644 --- a/lib/jsdom/living/nodes/DOMStringMap-impl.js +++ b/lib/jsdom/living/nodes/DOMStringMap-impl.js @@ -1,11 +1,11 @@ "use strict"; const DOMException = require("domexception"); -const { mixin } = require("../../utils"); + const idlUtils = require("../generated/utils.js"); -const { setAttributeValue, removeAttributeByName } = require("../attributes"); + const validateName = require("../helpers/validate-names").name; -const { CeReactions } = require("../helpers/custom-elements"); +const { setAttributeValue, removeAttributeByName } = require("../attributes"); const dataAttrRe = /^data-([^A-Z]*)$/; @@ -62,6 +62,4 @@ class DOMStringMapImpl { } } -mixin(DOMStringMapImpl.prototype, CeReactions.prototype); - exports.implementation = DOMStringMapImpl; diff --git a/lib/jsdom/living/nodes/DOMTokenList-impl.js b/lib/jsdom/living/nodes/DOMTokenList-impl.js index 0d38a55d87..b38b40d225 100644 --- a/lib/jsdom/living/nodes/DOMTokenList-impl.js +++ b/lib/jsdom/living/nodes/DOMTokenList-impl.js @@ -1,12 +1,11 @@ "use strict"; const DOMException = require("domexception"); -const { mixin } = require("../../utils"); -const { CeReactions } = require("../helpers/custom-elements"); -const OrderedSet = require("../helpers/ordered-set.js"); -const { asciiLowercase } = require("../helpers/strings.js"); + const idlUtils = require("../generated/utils.js"); +const OrderedSet = require("../helpers/ordered-set.js"); +const { asciiLowercase } = require("../helpers/strings.js"); const { getAttributeValue, setAttributeValue, hasAttributeByName } = require("../attributes.js"); function validateTokens(...tokens) { @@ -168,6 +167,4 @@ class DOMTokenListImpl { } } -mixin(DOMTokenListImpl.prototype, CeReactions.prototype); - exports.implementation = DOMTokenListImpl; diff --git a/lib/jsdom/living/nodes/HTMLOptionsCollection-impl.js b/lib/jsdom/living/nodes/HTMLOptionsCollection-impl.js index a8b9a36159..efae694550 100644 --- a/lib/jsdom/living/nodes/HTMLOptionsCollection-impl.js +++ b/lib/jsdom/living/nodes/HTMLOptionsCollection-impl.js @@ -5,10 +5,8 @@ const DOMException = require("domexception"); const Element = require("../generated/Element"); const Node = require("../generated/Node"); const idlUtils = require("../generated/utils.js"); -const HTMLCollectionImpl = require("./HTMLCollection-impl").implementation; -const { mixin } = require("../../utils"); -const { CeReactions } = require("../helpers/custom-elements"); +const HTMLCollectionImpl = require("./HTMLCollection-impl").implementation; const { DOCUMENT_POSITION_CONTAINS, DOCUMENT_POSITION_CONTAINED_BY } = require("../node-document-position"); class HTMLOptionsCollectionImpl extends HTMLCollectionImpl { @@ -110,6 +108,4 @@ class HTMLOptionsCollectionImpl extends HTMLCollectionImpl { } } -mixin(HTMLOptionsCollectionImpl.prototype, CeReactions.prototype); - exports.implementation = HTMLOptionsCollectionImpl; diff --git a/lib/jsdom/living/nodes/Node-impl.js b/lib/jsdom/living/nodes/Node-impl.js index 8857c7cabd..0bb9288cec 100644 --- a/lib/jsdom/living/nodes/Node-impl.js +++ b/lib/jsdom/living/nodes/Node-impl.js @@ -8,13 +8,12 @@ const NODE_DOCUMENT_POSITION = require("../node-document-position"); const NodeList = require("../generated/NodeList"); const { clone, locateNamespacePrefix, locateNamespace } = require("../node"); const attributes = require("../attributes"); -const { simultaneousIterators, mixin } = require("../../utils"); +const { simultaneousIterators } = require("../../utils"); const { domSymbolTree } = require("../helpers/internal-constants"); const { documentBaseURLSerialized } = require("../helpers/document-base-url"); const { queueTreeMutationRecord } = require("../helpers/mutation-observers"); -const { CeReactions, CUSTOM_ELEMENT_STATE, enqueueCECallbackReaction, - tryUpgradeElement } = require("../helpers/custom-elements"); +const { CUSTOM_ELEMENT_STATE, enqueueCECallbackReaction, tryUpgradeElement } = require("../helpers/custom-elements"); const { isShadowRoot, getRoot, shadowIncludingRoot, assignSlot, assignSlotableForTree, assignSlotable, signalSlotChange, isSlot, shadowIncludingInclusiveDescendantsIterator, shadowIncludingDescendantsIterator @@ -968,8 +967,6 @@ class NodeImpl extends EventTargetImpl { } } -mixin(NodeImpl.prototype, CeReactions.prototype); - module.exports = { implementation: NodeImpl }; diff --git a/scripts/webidl/convert.js b/scripts/webidl/convert.js index 4e879e3c6c..0fc30c4f3c 100644 --- a/scripts/webidl/convert.js +++ b/scripts/webidl/convert.js @@ -11,41 +11,24 @@ const Webidl2js = require("webidl2js"); const transformer = new Webidl2js({ implSuffix: "-impl", suppressErrors: true, - processCEReactions(operationIdl, body) { - // In the case of an attribute or a standard method the object holding the reference to the impl is the this value. - // In the case of a setter or a deleter it is the target argument passed to the proxy. - const obj = operationIdl.setter || operationIdl.deleter ? "target" : "this"; + processCEReactions(code) { + const preSteps = this.addImport("../helpers/custom-elements", "ceReactionsPreSteps"); + const postSteps = this.addImport("../helpers/custom-elements", "ceReactionsPostSteps"); return ` - ${obj}[impl]._ceReactionsPreSteps(); - + ${preSteps}(globalObject); try { - ${body} + ${code} } finally { - ${obj}[impl]._ceReactionsPostSteps(); + ${postSteps}(globalObject); } `; }, - processHTMLConstructor(interfaceIdl, content) { - const { name } = interfaceIdl; - - return content + ` - module.exports.installConstructor = function({ globalObject, HTMLConstructor }) { - const constructorName = "${name}"; - - const constructor = function() { - const newTarget = new.target; - return HTMLConstructor({ globalObject, newTarget, constructorName }); - }; - constructor.prototype = ${name}.prototype; + processHTMLConstructor() { + const identifier = this.addImport("../helpers/html-constructor", "HTMLConstructor"); - Object.defineProperty(globalObject, constructorName, { - enumerable: false, - configurable: true, - writable: true, - value: constructor - }); - } + return ` + return ${identifier}(globalObject, new.target); `; } });