summaryrefslogtreecommitdiff
path: root/school/node_modules/jsdom/lib/jsdom/browser/parser/html.js
diff options
context:
space:
mode:
Diffstat (limited to 'school/node_modules/jsdom/lib/jsdom/browser/parser/html.js')
-rw-r--r--school/node_modules/jsdom/lib/jsdom/browser/parser/html.js223
1 files changed, 223 insertions, 0 deletions
diff --git a/school/node_modules/jsdom/lib/jsdom/browser/parser/html.js b/school/node_modules/jsdom/lib/jsdom/browser/parser/html.js
new file mode 100644
index 0000000..198cc2c
--- /dev/null
+++ b/school/node_modules/jsdom/lib/jsdom/browser/parser/html.js
@@ -0,0 +1,223 @@
+"use strict";
+
+const parse5 = require("parse5");
+
+const { createElement } = require("../../living/helpers/create-element");
+const { HTML_NS } = require("../../living/helpers/namespaces");
+
+const DocumentType = require("../../living/generated/DocumentType");
+const DocumentFragment = require("../../living/generated/DocumentFragment");
+const Text = require("../../living/generated/Text");
+const Comment = require("../../living/generated/Comment");
+
+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.
+const OpenElementStack = require("parse5/lib/parser/open-element-stack");
+
+const openElementStackOriginalPush = OpenElementStack.prototype.push;
+OpenElementStack.prototype.push = function (...args) {
+ openElementStackOriginalPush.apply(this, args);
+ this.treeAdapter._currentElement = this.current;
+
+ const after = this.items[this.stackTop];
+ if (after._pushedOnStackOfOpenElements) {
+ after._pushedOnStackOfOpenElements();
+ }
+};
+
+const openElementStackOriginalPop = OpenElementStack.prototype.pop;
+OpenElementStack.prototype.pop = function (...args) {
+ const before = this.items[this.stackTop];
+
+ openElementStackOriginalPop.apply(this, args);
+ this.treeAdapter._currentElement = this.current;
+
+ if (before._poppedOffStackOfOpenElements) {
+ before._poppedOffStackOfOpenElements();
+ }
+};
+
+class JSDOMParse5Adapter {
+ 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() {
+ const { _currentElement } = this;
+
+ // The _currentElement is undefined when parsing elements at the root of the document.
+ if (_currentElement) {
+ return _currentElement.localName === "template" && _currentElement.namespaceURI === HTML_NS ?
+ _currentElement.content._ownerDocument :
+ _currentElement._ownerDocument;
+ }
+
+ return this._documentImpl;
+ }
+
+ createDocument() {
+ // parse5's model assumes that parse(html) will call into here to create the new Document, then return it. However,
+ // jsdom's model assumes we can create a Window (and through that create an empty Document), do some other setup
+ // stuff, and then parse, stuffing nodes into that Document as we go. So to adapt between these two models, we just
+ // return the already-created Document when asked by parse5 to "create" a Document.
+ return this._documentImpl;
+ }
+
+ createDocumentFragment() {
+ 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 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;
+ }
+
+ return element;
+ }
+
+ createCommentNode(data) {
+ const ownerDocument = this._ownerDocument();
+ return Comment.createImpl(this._globalObject, [], { data, ownerDocument });
+ }
+
+ appendChild(parentNode, newNode) {
+ parentNode._append(newNode);
+ }
+
+ insertBefore(parentNode, newNode, referenceNode) {
+ parentNode._insert(newNode, referenceNode);
+ }
+
+ setTemplateContent(templateElement, contentFragment) {
+ // This code makes the glue between jsdom and parse5 HTMLTemplateElement parsing:
+ //
+ // * jsdom during the construction of the HTMLTemplateElement (for example when create via
+ // `document.createElement("template")`), creates a DocumentFragment and set it into _templateContents.
+ // * parse5 when parsing a <template> tag creates an HTMLTemplateElement (`createElement` adapter hook) and also
+ // create a DocumentFragment (`createDocumentFragment` adapter hook).
+ //
+ // At this point we now have to replace the one created in jsdom with one created by parse5.
+ const { _ownerDocument, _host } = templateElement._templateContents;
+ contentFragment._ownerDocument = _ownerDocument;
+ contentFragment._host = _host;
+
+ templateElement._templateContents = contentFragment;
+ }
+
+ setDocumentType(document, name, publicId, systemId) {
+ const ownerDocument = this._ownerDocument();
+ const documentType = DocumentType.createImpl(this._globalObject, [], { name, publicId, systemId, ownerDocument });
+
+ document._append(documentType);
+ }
+
+ setDocumentMode(document, mode) {
+ // TODO: the rest of jsdom ignores this
+ document._mode = mode;
+ }
+
+ detachNode(node) {
+ node.remove();
+ }
+
+ insertText(parentNode, text) {
+ const { lastChild } = parentNode;
+ if (lastChild && lastChild.nodeType === nodeTypes.TEXT_NODE) {
+ lastChild.data += text;
+ } else {
+ const ownerDocument = this._ownerDocument();
+ const textNode = Text.createImpl(this._globalObject, [], { data: text, ownerDocument });
+ parentNode._append(textNode);
+ }
+ }
+
+ insertTextBefore(parentNode, text, referenceNode) {
+ const { previousSibling } = referenceNode;
+ if (previousSibling && previousSibling.nodeType === nodeTypes.TEXT_NODE) {
+ previousSibling.data += text;
+ } else {
+ const ownerDocument = this._ownerDocument();
+ const textNode = Text.createImpl(this._globalObject, [], { data: text, ownerDocument });
+ parentNode._append(textNode, referenceNode);
+ }
+ }
+
+ adoptAttributes(element, attrs) {
+ for (const attr of attrs) {
+ const prefix = attr.prefix === "" ? null : attr.prefix;
+ attributes.setAttributeValue(element, attr.name, attr.value, prefix, attr.namespace);
+ }
+ }
+}
+
+// Assign shared adapters with serializer.
+Object.assign(JSDOMParse5Adapter.prototype, serializationAdapter);
+
+function parseFragment(markup, contextElement) {
+ const ownerDocument = contextElement.localName === "template" && contextElement.namespaceURI === HTML_NS ?
+ contextElement.content._ownerDocument :
+ contextElement._ownerDocument;
+
+ const config = {
+ ...ownerDocument._parseOptions,
+ treeAdapter: new JSDOMParse5Adapter(ownerDocument, { fragment: true })
+ };
+
+ return parse5.parseFragment(contextElement, markup, config);
+}
+
+function parseIntoDocument(markup, ownerDocument) {
+ const config = {
+ ...ownerDocument._parseOptions,
+ treeAdapter: new JSDOMParse5Adapter(ownerDocument)
+ };
+
+ return parse5.parse(markup, config);
+}
+
+module.exports = {
+ parseFragment,
+ parseIntoDocument
+};