You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
9.1 KiB
JavaScript

'use strict';
const {performance} = require('../../commonjs/perf_hooks.cjs');
const {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, DOCUMENT_TYPE_NODE, ELEMENT_NODE, SVG_NAMESPACE} = require('../shared/constants.js');
const {
CUSTOM_ELEMENTS, DOM_PARSER, GLOBALS, IMAGE, MUTATION_OBSERVER, DOCTYPE, END, NEXT, MIME, EVENT_TARGET, UPGRADE
} = require('../shared/symbols.js');
const {Facades, illegalConstructor} = require('../shared/facades.js');
const {HTMLClasses} = require('../shared/html-classes.js');
const {Mime} = require('../shared/mime.js');
const {knownSiblings} = require('../shared/utils.js');
const {assign, create, defineProperties, setPrototypeOf} = require('../shared/object.js');
const {NonElementParentNode} = require('../mixin/non-element-parent-node.js');
const {SVGElement} = require('../svg/element.js');
const {Attr} = require('./attr.js');
const {CDATASection} = require('./cdata-section.js')
const {Comment} = require('./comment.js');
const {CustomElementRegistry} = require('./custom-element-registry.js');
const {CustomEvent} = require('./custom-event.js');
const {DocumentFragment} = require('./document-fragment.js');
const {DocumentType} = require('./document-type.js');
const {Element} = require('./element.js');
const {Event} = require('./event.js');
const {EventTarget} = require('./event-target.js');
const {InputEvent} = require('./input-event.js');
const {ImageClass} = require('./image.js');
const {MutationObserverClass} = require('./mutation-observer.js');
const {NamedNodeMap} = require('./named-node-map.js');
const {NodeList} = require('./node-list.js');
const {Range} = require('./range.js');
const {Text} = require('./text.js');
const {TreeWalker} = require('./tree-walker.js');
const query = (method, ownerDocument, selectors) => {
let {[NEXT]: next, [END]: end} = ownerDocument;
return method.call({ownerDocument, [NEXT]: next, [END]: end}, selectors);
};
const globalExports = assign(
{},
Facades,
HTMLClasses,
{
CustomEvent,
Event,
EventTarget,
InputEvent,
NamedNodeMap,
NodeList
}
);
const window = new WeakMap;
/**
* @implements globalThis.Document
*/
class Document extends NonElementParentNode {
constructor(type) {
super(null, '#document', DOCUMENT_NODE);
this[CUSTOM_ELEMENTS] = {active: false, registry: null};
this[MUTATION_OBSERVER] = {active: false, class: null};
this[MIME] = Mime[type];
/** @type {DocumentType} */
this[DOCTYPE] = null;
this[DOM_PARSER] = null;
this[GLOBALS] = null;
this[IMAGE] = null;
this[UPGRADE] = null;
}
/**
* @type {globalThis.Document['defaultView']}
*/
get defaultView() {
if (!window.has(this))
window.set(this, new Proxy(globalThis, {
set: (target, name, value) => {
switch (name) {
case 'addEventListener':
case 'removeEventListener':
case 'dispatchEvent':
this[EVENT_TARGET][name] = value;
break;
default:
target[name] = value;
break;
}
return true;
},
get: (globalThis, name) => {
switch (name) {
case 'addEventListener':
case 'removeEventListener':
case 'dispatchEvent':
if (!this[EVENT_TARGET]) {
const et = this[EVENT_TARGET] = new EventTarget;
et.dispatchEvent = et.dispatchEvent.bind(et);
et.addEventListener = et.addEventListener.bind(et);
et.removeEventListener = et.removeEventListener.bind(et);
}
return this[EVENT_TARGET][name];
case 'document':
return this;
/* c8 ignore start */
case 'navigator':
return {
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
};
/* c8 ignore stop */
case 'window':
return window.get(this);
case 'customElements':
if (!this[CUSTOM_ELEMENTS].registry)
this[CUSTOM_ELEMENTS] = new CustomElementRegistry(this);
return this[CUSTOM_ELEMENTS];
case 'performance':
return performance;
case 'DOMParser':
return this[DOM_PARSER];
case 'Image':
if (!this[IMAGE])
this[IMAGE] = ImageClass(this);
return this[IMAGE];
case 'MutationObserver':
if (!this[MUTATION_OBSERVER].class)
this[MUTATION_OBSERVER] = new MutationObserverClass(this);
return this[MUTATION_OBSERVER].class;
}
return (this[GLOBALS] && this[GLOBALS][name]) ||
globalExports[name] ||
globalThis[name];
}
}));
return window.get(this);
}
get doctype() {
const docType = this[DOCTYPE];
if (docType)
return docType;
const {firstChild} = this;
if (firstChild && firstChild.nodeType === DOCUMENT_TYPE_NODE)
return (this[DOCTYPE] = firstChild);
return null;
}
set doctype(value) {
if (/^([a-z:]+)(\s+system|\s+public(\s+"([^"]+)")?)?(\s+"([^"]+)")?/i.test(value)) {
const {$1: name, $4: publicId, $6: systemId} = RegExp;
this[DOCTYPE] = new DocumentType(this, name, publicId, systemId);
knownSiblings(this, this[DOCTYPE], this[NEXT]);
}
}
get documentElement() {
return this.firstElementChild;
}
get isConnected() { return true; }
/**
* @protected
*/
_getParent() {
return this[EVENT_TARGET];
}
createAttribute(name) { return new Attr(this, name); }
createCDATASection(data) { return new CDATASection(this, data); }
createComment(textContent) { return new Comment(this, textContent); }
createDocumentFragment() { return new DocumentFragment(this); }
createDocumentType(name, publicId, systemId) { return new DocumentType(this, name, publicId, systemId); }
createElement(localName) { return new Element(this, localName); }
createRange() {
const range = new Range;
range.commonAncestorContainer = this;
return range;
}
createTextNode(textContent) { return new Text(this, textContent); }
createTreeWalker(root, whatToShow = -1) { return new TreeWalker(root, whatToShow); }
createNodeIterator(root, whatToShow = -1) { return this.createTreeWalker(root, whatToShow); }
createEvent(name) {
const event = create(name === 'Event' ? new Event('') : new CustomEvent(''));
event.initEvent = event.initCustomEvent = (
type,
canBubble = false,
cancelable = false,
detail
) => {
event.bubbles = !!canBubble;
defineProperties(event, {
type: {value: type},
canBubble: {value: canBubble},
cancelable: {value: cancelable},
detail: {value: detail}
});
};
return event;
}
cloneNode(deep = false) {
const {
constructor,
[CUSTOM_ELEMENTS]: customElements,
[DOCTYPE]: doctype
} = this;
const document = new constructor();
document[CUSTOM_ELEMENTS] = customElements;
if (deep) {
const end = document[END];
const {childNodes} = this;
for (let {length} = childNodes, i = 0; i < length; i++)
document.insertBefore(childNodes[i].cloneNode(true), end);
if (doctype)
document[DOCTYPE] = childNodes[0];
}
return document;
}
importNode(externalNode) {
// important: keep the signature length as *one*
// or it would behave like old IE or Edge with polyfills
const deep = 1 < arguments.length && !!arguments[1];
const node = externalNode.cloneNode(deep);
const {[CUSTOM_ELEMENTS]: customElements} = this;
const {active} = customElements;
const upgrade = element => {
const {ownerDocument, nodeType} = element;
element.ownerDocument = this;
if (active && ownerDocument !== this && nodeType === ELEMENT_NODE)
customElements.upgrade(element);
};
upgrade(node);
if (deep) {
switch (node.nodeType) {
case ELEMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
let {[NEXT]: next, [END]: end} = node;
while (next !== end) {
if (next.nodeType === ELEMENT_NODE)
upgrade(next);
next = next[NEXT];
}
break;
}
}
}
return node;
}
toString() { return this.childNodes.join(''); }
querySelector(selectors) {
return query(super.querySelector, this, selectors);
}
querySelectorAll(selectors) {
return query(super.querySelectorAll, this, selectors);
}
/* c8 ignore start */
getElementsByTagNameNS(_, name) {
return this.getElementsByTagName(name);
}
createAttributeNS(_, name) {
return this.createAttribute(name);
}
createElementNS(nsp, localName, options) {
return nsp === SVG_NAMESPACE ?
new SVGElement(this, localName, null) :
this.createElement(localName, options);
}
/* c8 ignore stop */
}
exports.Document = Document
setPrototypeOf(
globalExports.Document = function Document() {
illegalConstructor();
},
Document
).prototype = Document.prototype;