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
Add custom element support #2548
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
|
||
const parse5 = require("parse5"); | ||
|
||
const { createElement } = require("../../living/helpers/create-element"); | ||
|
||
const DocumentType = require("../../living/generated/DocumentType"); | ||
const DocumentFragment = require("../../living/generated/DocumentFragment"); | ||
const Text = require("../../living/generated/Text"); | ||
|
@@ -11,6 +13,9 @@ const attributes = require("../../living/attributes"); | |
const nodeTypes = require("../../living/node-type"); | ||
|
||
const serializationAdapter = require("../../living/domparsing/parse5-adapter-serialization"); | ||
const { | ||
customElementReactionsStack, invokeCEReactions, lookupCEDefinition | ||
} = require("../../living/helpers/custom-elements"); | ||
|
||
// Horrible monkey-patch to implement https://github.com/inikulin/parse5/issues/237 and | ||
// https://github.com/inikulin/parse5/issues/285. | ||
|
@@ -40,19 +45,27 @@ OpenElementStack.prototype.pop = function (...args) { | |
}; | ||
|
||
class JSDOMParse5Adapter { | ||
constructor(documentImpl) { | ||
constructor(documentImpl, options = {}) { | ||
this._documentImpl = documentImpl; | ||
this._globalObject = documentImpl._globalObject; | ||
this._fragment = options.fragment || false; | ||
|
||
// Since the createElement hook doesn't provide the parent element, we keep track of this using _currentElement: | ||
// https://github.com/inikulin/parse5/issues/285. See above horrible monkey-patch for how this is maintained. | ||
this._currentElement = undefined; | ||
} | ||
|
||
_ownerDocument() { | ||
// The _currentElement is undefined when parsing elements at the root of the document. In this case we would | ||
// fallback to the global _documentImpl. | ||
return this._currentElement ? this._currentElement._ownerDocument : this._documentImpl; | ||
const { _currentElement } = this; | ||
|
||
// The _currentElement is undefined when parsing elements at the root of the document. | ||
if (_currentElement) { | ||
return _currentElement.localName === "template" ? | ||
_currentElement.content._ownerDocument : | ||
_currentElement._ownerDocument; | ||
} | ||
|
||
return this._documentImpl; | ||
} | ||
|
||
createDocument() { | ||
|
@@ -64,16 +77,38 @@ class JSDOMParse5Adapter { | |
} | ||
|
||
createDocumentFragment() { | ||
return DocumentFragment.createImpl(this._globalObject, [], { ownerDocument: this._currentElement._ownerDocument }); | ||
const ownerDocument = this._ownerDocument(); | ||
return DocumentFragment.createImpl(this._globalObject, [], { ownerDocument }); | ||
} | ||
|
||
// https://html.spec.whatwg.org/#create-an-element-for-the-token | ||
createElement(localName, namespace, attrs) { | ||
const ownerDocument = this._ownerDocument(); | ||
|
||
const element = ownerDocument._createElementWithCorrectElementInterface(localName, namespace); | ||
element._namespaceURI = namespace; | ||
const isAttribute = attrs.find(attr => attr.name === "is"); | ||
const isValue = isAttribute ? isAttribute.value : null; | ||
|
||
const definition = lookupCEDefinition(ownerDocument, namespace, localName); | ||
|
||
let willExecuteScript = false; | ||
if (definition !== null && !this._fragment) { | ||
willExecuteScript = true; | ||
} | ||
|
||
if (willExecuteScript) { | ||
ownerDocument._throwOnDynamicMarkupInsertionCounter++; | ||
customElementReactionsStack.push([]); | ||
} | ||
|
||
const element = createElement(ownerDocument, localName, namespace, null, isValue, willExecuteScript); | ||
this.adoptAttributes(element, attrs); | ||
|
||
if (willExecuteScript) { | ||
const queue = customElementReactionsStack.pop(); | ||
invokeCEReactions(queue); | ||
ownerDocument._throwOnDynamicMarkupInsertionCounter--; | ||
} | ||
|
||
if ("_parserInserted" in element) { | ||
element._parserInserted = true; | ||
} | ||
|
@@ -160,10 +195,14 @@ class JSDOMParse5Adapter { | |
Object.assign(JSDOMParse5Adapter.prototype, serializationAdapter); | ||
|
||
function parseFragment(markup, contextElement) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These changes look independently valuable. Would it make sense to split them out? Hopefully there are some targeted tests they fix, separate from custom elements, although we might have to write them... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can extract the |
||
const ownerDocument = contextElement._ownerDocument; | ||
const ownerDocument = contextElement.localName === "template" ? | ||
contextElement.content._ownerDocument : | ||
contextElement._ownerDocument; | ||
|
||
const config = Object.assign({}, ownerDocument._parseOptions, { | ||
treeAdapter: new JSDOMParse5Adapter(ownerDocument) | ||
treeAdapter: new JSDOMParse5Adapter(ownerDocument, { | ||
fragment: true | ||
}) | ||
}); | ||
|
||
return parse5.parseFragment(contextElement, markup, config); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is necessary to avoid newly introduced circular references.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to add this comment from #2771: