1 | const CustomElementRegistry = require('./CustomElementRegistry');
|
2 | const Event = require('./Event');
|
3 | const CustomEvent = require('./CustomEvent');
|
4 | const Node = require('./Node');
|
5 | const DocumentType = require('./DocumentType');
|
6 | const Attr = require('./Attr');
|
7 | const CSSStyleDeclaration = require('./CSSStyleDeclaration');
|
8 | const Comment = require('./Comment');
|
9 | const DocumentFragment = require('./DocumentFragment');
|
10 | const HTMLElement = require('./HTMLElement');
|
11 | const HTMLHtmlElement = require('./HTMLHtmlElement');
|
12 | const HTMLStyleElement = require('./HTMLStyleElement');
|
13 | const HTMLTemplateElement = require('./HTMLTemplateElement');
|
14 | const HTMLTextAreaElement = require('./HTMLTextAreaElement');
|
15 | const Range = require('./Range');
|
16 | const Text = require('./Text');
|
17 | const TreeWalker = require('./TreeWalker');
|
18 |
|
19 | const headTag = el => el.nodeName === 'head';
|
20 | const bodyTag = el => el.nodeName === 'body';
|
21 |
|
22 | const createElement = (self, name, is) => {
|
23 | const Class = self.customElements.get(is) || HTMLElement;
|
24 | return new Class(self, name);
|
25 | };
|
26 |
|
27 | const getFoundOrNull = result => {
|
28 | if (result) {
|
29 | const el = findById.found;
|
30 | findById.found = null;
|
31 | return el;
|
32 | } else {
|
33 | return null;
|
34 | }
|
35 | };
|
36 |
|
37 | function findById(child) {'use strict';
|
38 | return child.id === this ?
|
39 | !!(findById.found = child) :
|
40 | child.children.some(findById, this);
|
41 | }
|
42 |
|
43 |
|
44 | module.exports = class Document extends Node {
|
45 |
|
46 | constructor(customElements = new CustomElementRegistry()) {
|
47 | super(null);
|
48 | this.nodeType = Node.DOCUMENT_NODE;
|
49 | this.nodeName = '#document';
|
50 | this.appendChild(new DocumentType());
|
51 | this.documentElement = new HTMLHtmlElement(this, 'html');
|
52 | this.appendChild(this.documentElement);
|
53 | this.customElements = customElements;
|
54 | Object.freeze(this.childNodes);
|
55 | }
|
56 |
|
57 | createAttribute(name) {
|
58 | const attr = new Attr(
|
59 | {ownerDocument: this},
|
60 | name,
|
61 | name === 'style' ?
|
62 | new CSSStyleDeclaration() :
|
63 | null
|
64 | );
|
65 | attr.ownerElement = null;
|
66 | return attr;
|
67 | }
|
68 |
|
69 | createAttributeNS(_, name) {
|
70 | return this.createAttribute(name);
|
71 | }
|
72 |
|
73 | createComment(comment) {
|
74 | return new Comment(this, comment);
|
75 | }
|
76 |
|
77 | createDocumentFragment() {
|
78 | return new DocumentFragment(this);
|
79 | }
|
80 |
|
81 | createElement(name, options) {
|
82 | switch (name) {
|
83 | case 'style':
|
84 | return new HTMLStyleElement(this, name);
|
85 | case 'template':
|
86 | return new HTMLTemplateElement(this, name);
|
87 | case 'textarea':
|
88 | return new HTMLTextAreaElement(this, name);
|
89 | case 'canvas':
|
90 | case 'img':
|
91 | try {
|
92 | const file = name === 'img' ? './HTMLImageElement' : './HTMLCanvasElement';
|
93 | const Constructor = require(file);
|
94 | return new Constructor(this);
|
95 | }
|
96 | catch (o_O) {}
|
97 | default:
|
98 | const extending = 1 < arguments.length && 'is' in options;
|
99 | const el = createElement(this, name, extending ? options.is : name);
|
100 | if (extending)
|
101 | el.setAttribute('is', options.is);
|
102 | return el;
|
103 | }
|
104 | }
|
105 |
|
106 | createElementNS(ns, name) {
|
107 | if (ns === 'http://www.w3.org/1999/xhtml') {
|
108 | return this.createElement(name);
|
109 | }
|
110 | return new HTMLElement(this, name + ':' + ns);
|
111 | }
|
112 |
|
113 | createEvent(name) {
|
114 | switch (name) {
|
115 | case 'Event':
|
116 | return new Event();
|
117 | case 'CustomEvent':
|
118 | return new CustomEvent();
|
119 | default:
|
120 | throw new Error(name + ' not implemented');
|
121 | }
|
122 | }
|
123 |
|
124 | createRange() {
|
125 | return new Range;
|
126 | }
|
127 |
|
128 | createTextNode(text) {
|
129 | return new Text(this, text);
|
130 | }
|
131 |
|
132 | createTreeWalker(root, whatToShow) {
|
133 | return new TreeWalker(root, whatToShow);
|
134 | }
|
135 |
|
136 | getElementsByTagName(name) {
|
137 | const html = this.documentElement;
|
138 | return /html/i.test(name) ?
|
139 | [html] :
|
140 | (name === '*' ? [html] : []).concat(html.getElementsByTagName(name));
|
141 | }
|
142 |
|
143 | getElementsByClassName(name) {
|
144 | const html = this.documentElement;
|
145 | return (html.classList.contains(name) ? [html] : [])
|
146 | .concat(html.getElementsByClassName(name));
|
147 | }
|
148 |
|
149 | importNode(node) {
|
150 | return node.cloneNode(!!arguments[1]);
|
151 | }
|
152 |
|
153 | toString() {
|
154 | return this.childNodes[0] + this.documentElement.outerHTML;
|
155 | }
|
156 |
|
157 | get defaultView() {
|
158 | return global;
|
159 | }
|
160 |
|
161 | get head() {
|
162 | const html = this.documentElement;
|
163 | return this.documentElement.childNodes.find(headTag) ||
|
164 | html.insertBefore(this.createElement('head'), this.body);
|
165 | }
|
166 |
|
167 | get body() {
|
168 | const html = this.documentElement;
|
169 | return html.childNodes.find(bodyTag) ||
|
170 | html.appendChild(this.createElement('body'));
|
171 | }
|
172 |
|
173 |
|
174 | getElementById(id) {
|
175 | const html = this.documentElement;
|
176 | return html.id === id ? html : getFoundOrNull(html.children.some(findById, id));
|
177 | }
|
178 |
|
179 |
|
180 | get children() {
|
181 | return [this.documentElement];
|
182 | }
|
183 |
|
184 | get firstElementChild() {
|
185 | return this.documentElement;
|
186 | }
|
187 |
|
188 | get lastElementChild() {
|
189 | return this.documentElement;
|
190 | }
|
191 |
|
192 | get childElementCount() {
|
193 | return 1;
|
194 | }
|
195 |
|
196 | prepend() { throw new Error('Only one element on document allowed.'); }
|
197 | append() { this.prepend(); }
|
198 |
|
199 | querySelector(css) {
|
200 | return this.documentElement.querySelector(css);
|
201 | }
|
202 |
|
203 | querySelectorAll(css) {
|
204 | return this.documentElement.querySelectorAll(css);
|
205 | }
|
206 |
|
207 | };
|