single-file-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global globalThis */
25
+
26
+ const browser = globalThis.browser;
27
+ const document = globalThis.document;
28
+ const Document = globalThis.Document;
29
+
30
+ if (document instanceof Document) {
31
+ const scriptElement = document.createElement("script");
32
+ scriptElement.async = false;
33
+ if (browser && browser.runtime && browser.runtime.getURL) {
34
+ scriptElement.src = browser.runtime.getURL("/lib/web/hooks/hooks-web.js");
35
+ scriptElement.async = false;
36
+ }
37
+ (document.documentElement || document).appendChild(scriptElement);
38
+ scriptElement.remove();
39
+ }
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ import * as frameTree from "./frame-tree/content/content-frame-tree.js";
25
+ import * as hooks from "./hooks/content/content-hooks.js";
26
+ import * as hooksFrames from "./hooks/content/content-hooks-frames.js";
27
+ import * as lazy from "./lazy/content/content-lazy-loader.js";
28
+
29
+ export {
30
+ frameTree,
31
+ hooks,
32
+ hooksFrames,
33
+ lazy
34
+ };
@@ -0,0 +1,229 @@
1
+ /*
2
+ * Copyright 2010-2020 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ /* global globalThis */
25
+
26
+ import * as hooksFrames from "./../../hooks/content/content-hooks-frames";
27
+ import {
28
+ LAZY_SRC_ATTRIBUTE_NAME,
29
+ SINGLE_FILE_UI_ELEMENT_CLASS
30
+ } from "./../../../single-file-helper.js";
31
+ const helper = {
32
+ LAZY_SRC_ATTRIBUTE_NAME,
33
+ SINGLE_FILE_UI_ELEMENT_CLASS
34
+ };
35
+
36
+ const MAX_IDLE_TIMEOUT_CALLS = 10;
37
+ const ATTRIBUTES_MUTATION_TYPE = "attributes";
38
+
39
+ const browser = globalThis.browser;
40
+ const document = globalThis.document;
41
+ const MutationObserver = globalThis.MutationObserver;
42
+ const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
43
+ const removeEventListener = (type, listener, options) => globalThis.removeEventListener(type, listener, options);
44
+ const timeouts = new Map();
45
+
46
+ let idleTimeoutCalls;
47
+
48
+ if (browser && browser.runtime && browser.runtime.onMessage && browser.runtime.onMessage.addListener) {
49
+ browser.runtime.onMessage.addListener(message => {
50
+ if (message.method == "singlefile.lazyTimeout.onTimeout") {
51
+ const timeoutData = timeouts.get(message.type);
52
+ if (timeoutData) {
53
+ timeouts.delete(message.type);
54
+ try {
55
+ timeoutData.callback();
56
+ } catch (error) {
57
+ clearRegularTimeout(message.type);
58
+ }
59
+ }
60
+ }
61
+ });
62
+ }
63
+
64
+ export {
65
+ process,
66
+ resetZoomLevel
67
+ };
68
+
69
+ async function process(options) {
70
+ if (document.documentElement) {
71
+ timeouts.clear();
72
+ const maxScrollY = Math.max(document.documentElement.scrollHeight - (document.documentElement.clientHeight * 1.5), 0);
73
+ const maxScrollX = Math.max(document.documentElement.scrollWidth - (document.documentElement.clientWidth * 1.5), 0);
74
+ if (globalThis.scrollY <= maxScrollY && globalThis.scrollX <= maxScrollX) {
75
+ return triggerLazyLoading(options);
76
+ }
77
+ }
78
+ }
79
+
80
+ function resetZoomLevel(options) {
81
+ hooksFrames.loadDeferredImagesResetZoomLevel(options);
82
+ }
83
+
84
+ function triggerLazyLoading(options) {
85
+ idleTimeoutCalls = 0;
86
+ return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor
87
+ let loadingImages;
88
+ const pendingImages = new Set();
89
+ const observer = new MutationObserver(async mutations => {
90
+ mutations = mutations.filter(mutation => mutation.type == ATTRIBUTES_MUTATION_TYPE);
91
+ if (mutations.length) {
92
+ const updated = mutations.filter(mutation => {
93
+ if (mutation.attributeName == "src") {
94
+ mutation.target.setAttribute(helper.LAZY_SRC_ATTRIBUTE_NAME, mutation.target.src);
95
+ mutation.target.addEventListener("load", onResourceLoad);
96
+ }
97
+ if (mutation.attributeName == "src" || mutation.attributeName == "srcset" || mutation.target.tagName == "SOURCE") {
98
+ return !mutation.target.classList || !mutation.target.classList.contains(helper.SINGLE_FILE_UI_ELEMENT_CLASS);
99
+ }
100
+ });
101
+ if (updated.length) {
102
+ loadingImages = true;
103
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
104
+ if (!pendingImages.size) {
105
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
106
+ }
107
+ }
108
+ }
109
+ });
110
+ await setIdleTimeout(options.loadDeferredImagesMaxIdleTime * 2);
111
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
112
+ observer.observe(document, { subtree: true, childList: true, attributes: true });
113
+ addEventListener(hooksFrames.LOAD_IMAGE_EVENT, onImageLoadEvent);
114
+ addEventListener(hooksFrames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
115
+ hooksFrames.loadDeferredImagesStart(options);
116
+
117
+ async function setIdleTimeout(delay) {
118
+ await setAsyncTimeout("idleTimeout", async () => {
119
+ if (!loadingImages) {
120
+ clearAsyncTimeout("loadTimeout");
121
+ clearAsyncTimeout("maxTimeout");
122
+ lazyLoadEnd(observer, options, cleanupAndResolve);
123
+ } else if (idleTimeoutCalls < MAX_IDLE_TIMEOUT_CALLS) {
124
+ idleTimeoutCalls++;
125
+ clearAsyncTimeout("idleTimeout");
126
+ await setIdleTimeout(Math.max(500, delay / 2));
127
+ }
128
+ }, delay);
129
+ }
130
+
131
+ function onResourceLoad(event) {
132
+ const element = event.target;
133
+ element.removeAttribute(helper.LAZY_SRC_ATTRIBUTE_NAME);
134
+ element.removeEventListener("load", onResourceLoad);
135
+ }
136
+
137
+ async function onImageLoadEvent(event) {
138
+ loadingImages = true;
139
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
140
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
141
+ if (event.detail) {
142
+ pendingImages.add(event.detail);
143
+ }
144
+ }
145
+
146
+ async function onImageLoadedEvent(event) {
147
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
148
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
149
+ pendingImages.delete(event.detail);
150
+ if (!pendingImages.size) {
151
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
152
+ }
153
+ }
154
+
155
+ function cleanupAndResolve(value) {
156
+ observer.disconnect();
157
+ removeEventListener(hooksFrames.LOAD_IMAGE_EVENT, onImageLoadEvent);
158
+ removeEventListener(hooksFrames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
159
+ resolve(value);
160
+ }
161
+ });
162
+ }
163
+
164
+ async function deferLazyLoadEnd(observer, options, resolve) {
165
+ await setAsyncTimeout("loadTimeout", () => lazyLoadEnd(observer, options, resolve), options.loadDeferredImagesMaxIdleTime);
166
+ }
167
+
168
+ async function deferForceLazyLoadEnd(observer, options, resolve) {
169
+ await setAsyncTimeout("maxTimeout", async () => {
170
+ await clearAsyncTimeout("loadTimeout");
171
+ await lazyLoadEnd(observer, options, resolve);
172
+ }, options.loadDeferredImagesMaxIdleTime * 10);
173
+ }
174
+
175
+ async function lazyLoadEnd(observer, options, resolve) {
176
+ await clearAsyncTimeout("idleTimeout");
177
+ hooksFrames.loadDeferredImagesEnd(options);
178
+ await setAsyncTimeout("endTimeout", async () => {
179
+ await clearAsyncTimeout("maxTimeout");
180
+ resolve();
181
+ }, options.loadDeferredImagesMaxIdleTime / 2);
182
+ observer.disconnect();
183
+ }
184
+
185
+ async function setAsyncTimeout(type, callback, delay) {
186
+ if (browser && browser.runtime && browser.runtime.sendMessage) {
187
+ if (!timeouts.get(type) || !timeouts.get(type).pending) {
188
+ const timeoutData = { callback, pending: true };
189
+ timeouts.set(type, timeoutData);
190
+ try {
191
+ await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.setTimeout", type, delay });
192
+ } catch (error) {
193
+ setRegularTimeout(type, callback, delay);
194
+ }
195
+ timeoutData.pending = false;
196
+ }
197
+ } else {
198
+ setRegularTimeout(type, callback, delay);
199
+ }
200
+ }
201
+
202
+ function setRegularTimeout(type, callback, delay) {
203
+ const timeoutId = timeouts.get(type);
204
+ if (timeoutId) {
205
+ globalThis.clearTimeout(timeoutId);
206
+ }
207
+ timeouts.set(type, callback);
208
+ globalThis.setTimeout(callback, delay);
209
+ }
210
+
211
+ async function clearAsyncTimeout(type) {
212
+ if (browser && browser.runtime && browser.runtime.sendMessage) {
213
+ try {
214
+ await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.clearTimeout", type });
215
+ } catch (error) {
216
+ clearRegularTimeout(type);
217
+ }
218
+ } else {
219
+ clearRegularTimeout(type);
220
+ }
221
+ }
222
+
223
+ function clearRegularTimeout(type) {
224
+ const previousTimeoutId = timeouts.get(type);
225
+ timeouts.delete(type);
226
+ if (previousTimeoutId) {
227
+ globalThis.clearTimeout(previousTimeoutId);
228
+ }
229
+ }
@@ -0,0 +1,33 @@
1
+ import * as frameTree from "./processors/frame-tree/content/content-frame-tree.js";
2
+ import * as serializer from "./modules/html-serializer.js";
3
+ import {
4
+ COMMENT_HEADER,
5
+ COMMENT_HEADER_LEGACY,
6
+ ON_BEFORE_CAPTURE_EVENT_NAME,
7
+ ON_AFTER_CAPTURE_EVENT_NAME,
8
+ initUserScriptHandler,
9
+ preProcessDoc,
10
+ postProcessDoc,
11
+ getShadowRoot
12
+ } from "./single-file-helper.js";
13
+
14
+ const processors = { frameTree };
15
+ const helper = {
16
+ COMMENT_HEADER,
17
+ COMMENT_HEADER_LEGACY,
18
+ ON_BEFORE_CAPTURE_EVENT_NAME,
19
+ ON_AFTER_CAPTURE_EVENT_NAME,
20
+ preProcessDoc,
21
+ postProcessDoc,
22
+ serialize(doc, compressHTML) {
23
+ return serializer.process(doc, compressHTML);
24
+ },
25
+ getShadowRoot
26
+ };
27
+
28
+ initUserScriptHandler();
29
+
30
+ export {
31
+ helper,
32
+ processors
33
+ };