1 | "use strict";
|
2 |
|
3 | const cheerio = require("cheerio");
|
4 | const DocumentFragment = require("./DocumentFragment");
|
5 | const Element = require("./Element");
|
6 | const getLocation = require("./getLocation");
|
7 | const {EventEmitter} = require("events");
|
8 | const {Event} = require("./Events");
|
9 | const {HTMLCollection} = require("./HTMLCollection");
|
10 |
|
11 | module.exports = Document;
|
12 |
|
13 | function Document(source, cookieJar) {
|
14 | const location = source.location || getLocation(source.url);
|
15 | const referrer = source.referrer || "";
|
16 | const $ = cheerio.load(source.text, {decodeEntities: false});
|
17 | const loaded = [];
|
18 |
|
19 | const emitter = new EventEmitter();
|
20 |
|
21 | let fullscreenElement = null;
|
22 | let forms;
|
23 |
|
24 | const document = {
|
25 | _getElement: getElement,
|
26 | _emitter: emitter,
|
27 | addEventListener,
|
28 | createDocumentFragment() {
|
29 | return DocumentFragment(Element);
|
30 | },
|
31 | createElement,
|
32 | createElementNS,
|
33 | createTextNode,
|
34 | dispatchEvent,
|
35 | exitFullscreen,
|
36 | getElementById,
|
37 | getElementsByTagName(name) {
|
38 | return this.documentElement.getElementsByTagName(name);
|
39 | },
|
40 | getElementsByClassName(classNames) {
|
41 | return this.documentElement.getElementsByClassName(classNames);
|
42 | },
|
43 | getElementsByName,
|
44 | importNode,
|
45 | location,
|
46 | removeEventListener,
|
47 | referrer,
|
48 | textContent: null,
|
49 | $,
|
50 | };
|
51 |
|
52 | Object.defineProperty(document, "head", {
|
53 | get: getHead
|
54 | });
|
55 |
|
56 | Object.defineProperty(document, "body", {
|
57 | get: getBody
|
58 | });
|
59 |
|
60 | Object.defineProperty(document, "documentElement", {
|
61 | get: () => getElement($("html"))
|
62 | });
|
63 |
|
64 | Object.defineProperty(document, "firstElementChild", {
|
65 | get: () => getElement($("html"))
|
66 | });
|
67 |
|
68 | Object.defineProperty(document, "firstChild", {
|
69 | get: () => getElement($("> :first-child"))
|
70 | });
|
71 |
|
72 | Object.defineProperty(document, "fullscreenElement", {
|
73 | get: () => fullscreenElement
|
74 | });
|
75 |
|
76 | Object.defineProperty(document, "cookie", {
|
77 | get() {
|
78 | return cookieJar.getCookies({path: location.pathname, script: true, domain: location.hostname, secure: location.protocol === "https:"}).toValueString();
|
79 | },
|
80 | set: (value) => {
|
81 | cookieJar.setCookie(value);
|
82 | }
|
83 | });
|
84 |
|
85 | Object.defineProperty(document, "title", {
|
86 | get: () => getElement($("head > title")).textContent,
|
87 | set: (value) => {
|
88 | getElement($("head > title")).textContent = value;
|
89 | }
|
90 | });
|
91 |
|
92 | Object.defineProperty(document, "nodeType", {
|
93 | get: () => 9
|
94 | });
|
95 |
|
96 | Object.defineProperty(document, "forms", {
|
97 | get() {
|
98 | if (!forms) forms = document.documentElement.getElementsByTagName("form");
|
99 | return forms;
|
100 | }
|
101 | });
|
102 |
|
103 | return document;
|
104 |
|
105 | function exitFullscreen() {
|
106 | const fullscreenchangeEvent = new Event("fullscreenchange");
|
107 | fullscreenchangeEvent.target = fullscreenElement;
|
108 |
|
109 | document.dispatchEvent(fullscreenchangeEvent);
|
110 | }
|
111 |
|
112 | function getHead() {
|
113 | return getElement($("head"));
|
114 | }
|
115 |
|
116 | function getBody() {
|
117 | return getElement($("body"));
|
118 | }
|
119 |
|
120 | function getElement($elm) {
|
121 | if ($elm === $) return document;
|
122 | if (!$elm.length) return;
|
123 |
|
124 | let mockElement = loaded.find((mockedElm) => mockedElm.$elm[0] === $elm[0]);
|
125 | if (mockElement) {
|
126 | return mockElement;
|
127 | }
|
128 |
|
129 | mockElement = Element(document, $elm);
|
130 |
|
131 | loaded.push(mockElement);
|
132 | return mockElement;
|
133 | }
|
134 |
|
135 | function getElementById(id) {
|
136 | const $idElm = $(`#${id}`).eq(0);
|
137 |
|
138 | if ($idElm && $idElm.length) return getElement($idElm);
|
139 | return null;
|
140 | }
|
141 |
|
142 | function getElementsByName(name) {
|
143 | return new HTMLCollection(document.documentElement, `[name="${name}"],#${name}`, {attributes: true});
|
144 | }
|
145 |
|
146 | function createElement(elementTagName) {
|
147 | const element = Element(document, document.$(`<${elementTagName}></${elementTagName}>`));
|
148 | loaded.push(element);
|
149 | return element;
|
150 | }
|
151 |
|
152 | function createElementNS(namespaceURI, elementTagName) {
|
153 | return createElement(elementTagName);
|
154 | }
|
155 |
|
156 | function createTextNode(text) {
|
157 | return {
|
158 | textContent: text,
|
159 | };
|
160 | }
|
161 |
|
162 | function addEventListener(...args) {
|
163 | emitter.on(...args);
|
164 | }
|
165 |
|
166 | function removeEventListener(...args) {
|
167 | emitter.removeListener(...args);
|
168 | }
|
169 |
|
170 | function dispatchEvent(event) {
|
171 | if (event && event.type === "fullscreenchange") {
|
172 | _handleFullscreenChange(event);
|
173 | } else {
|
174 | emitter.emit(event.type, event);
|
175 | }
|
176 | }
|
177 |
|
178 | function _handleFullscreenChange(event) {
|
179 | if (!event.target) return;
|
180 |
|
181 | if (fullscreenElement === null) {
|
182 | fullscreenElement = event.target;
|
183 | } else if (fullscreenElement === event.target) {
|
184 | fullscreenElement = null;
|
185 | }
|
186 |
|
187 | emitter.emit("fullscreenchange", event);
|
188 | }
|
189 |
|
190 | function importNode(element, deep) {
|
191 | if (element instanceof DocumentFragment) {
|
192 | return element._clone(deep);
|
193 | }
|
194 |
|
195 | return element.cloneNode(deep);
|
196 | }
|
197 | }
|