single-file-core 1.5.87 → 1.5.89
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.
- package/core/helper.js +12 -3
- package/package.json +1 -1
- package/processors/compression/compression-display.js +44 -6
- package/processors/compression/compression-router.js +382 -25
- package/processors/frame-tree/content/content-frame-tree.js +26 -24
- package/vendor/zip/z-worker.js +1 -1
- package/vendor/zip/zip.js +1468 -780
- package/vendor/zip/zip.min.js +1 -1
package/core/helper.js
CHANGED
|
@@ -139,8 +139,18 @@ export {
|
|
|
139
139
|
NESTING_TRACK_ID_ATTRIBUTE_NAME
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
+
let userScriptHandlerObserver;
|
|
143
|
+
|
|
142
144
|
function initUserScriptHandler() {
|
|
143
|
-
addEventListener(ON_INIT_USERSCRIPT_EVENT,
|
|
145
|
+
addEventListener(ON_INIT_USERSCRIPT_EVENT, onInitUserScript);
|
|
146
|
+
if (!userScriptHandlerObserver) {
|
|
147
|
+
userScriptHandlerObserver = new MutationObserver(initUserScriptHandler);
|
|
148
|
+
userScriptHandlerObserver.observe(globalThis.document, { childList: true });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function onInitUserScript({ detail }) {
|
|
153
|
+
globalThis[WAIT_FOR_USERSCRIPT_PROPERTY_NAME] = async (eventPrefixName, options) => {
|
|
144
154
|
const userScriptOptions = Object.assign({}, options);
|
|
145
155
|
delete userScriptOptions.win;
|
|
146
156
|
delete userScriptOptions.doc;
|
|
@@ -181,8 +191,7 @@ function initUserScriptHandler() {
|
|
|
181
191
|
} else {
|
|
182
192
|
resolvePromiseResponse();
|
|
183
193
|
}
|
|
184
|
-
}
|
|
185
|
-
new MutationObserver(initUserScriptHandler).observe(globalThis.document, { childList: true });
|
|
194
|
+
};
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
function initDoc(doc) {
|
package/package.json
CHANGED
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
* Source.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
/* global DOMParser */
|
|
24
|
+
/* global DOMParser, setTimeout */
|
|
25
25
|
|
|
26
26
|
export {
|
|
27
27
|
display
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
async function display(document, docContent, { disableFramePointerEvents } = {}) {
|
|
30
|
+
async function display(document, docContent, { disableFramePointerEvents, inPlace } = {}) {
|
|
31
31
|
docContent = docContent.replace(/<noscript/gi, "<template disabled-noscript");
|
|
32
32
|
docContent = docContent.replace(/<\/noscript/gi, "</template");
|
|
33
33
|
const doc = (new DOMParser()).parseFromString(docContent, "text/html");
|
|
@@ -38,10 +38,48 @@ async function display(document, docContent, { disableFramePointerEvents } = {})
|
|
|
38
38
|
element.style.setProperty(pointerEvents, "none", "important");
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
-
document.open()
|
|
42
|
-
|
|
43
|
-
document
|
|
44
|
-
document.
|
|
41
|
+
// the in-place swap avoids the document churn of document.open() but cannot
|
|
42
|
+
// change the compat mode and does not execute script elements; it morphs
|
|
43
|
+
// documentElement instead of replacing it so that document-level childList
|
|
44
|
+
// observers (e.g. extension content scripts watching for document.open())
|
|
45
|
+
// do not fire on every rendered page
|
|
46
|
+
if (inPlace && doc.compatMode == document.compatMode && !doc.querySelector("script")) {
|
|
47
|
+
// stylesheet links inserted from script are not render-blocking; preload
|
|
48
|
+
// them while the previous page is still displayed and keep the new page
|
|
49
|
+
// hidden until they are applied to avoid a flash of unstyled content
|
|
50
|
+
await Promise.all(Array.from(doc.querySelectorAll("link[rel~=stylesheet][href]")).map(linkElement => new Promise(resolve => {
|
|
51
|
+
const preloadElement = document.createElement("link");
|
|
52
|
+
preloadElement.rel = "preload";
|
|
53
|
+
preloadElement.as = "style";
|
|
54
|
+
preloadElement.href = linkElement.getAttribute("href");
|
|
55
|
+
preloadElement.onload = resolve;
|
|
56
|
+
preloadElement.onerror = resolve;
|
|
57
|
+
document.head.appendChild(preloadElement);
|
|
58
|
+
setTimeout(resolve, 500);
|
|
59
|
+
})));
|
|
60
|
+
const documentElement = document.documentElement;
|
|
61
|
+
const newDocumentElement = document.adoptNode(doc.documentElement);
|
|
62
|
+
while (documentElement.attributes.length) {
|
|
63
|
+
documentElement.removeAttribute(documentElement.attributes[0].name);
|
|
64
|
+
}
|
|
65
|
+
Array.from(newDocumentElement.attributes).forEach(attribute => documentElement.setAttribute(attribute.name, attribute.value));
|
|
66
|
+
documentElement.replaceChildren(...newDocumentElement.childNodes);
|
|
67
|
+
if (document.querySelector("link[rel~=stylesheet][href]")) {
|
|
68
|
+
const hideStyleElement = document.createElement("style");
|
|
69
|
+
hideStyleElement.textContent = "html{visibility:hidden}";
|
|
70
|
+
document.head.appendChild(hideStyleElement);
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
while (Date.now() - start < 500 && Array.from(document.querySelectorAll("link[rel~=stylesheet][href]")).some(linkElement => !linkElement.sheet)) {
|
|
73
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
74
|
+
}
|
|
75
|
+
hideStyleElement.remove();
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
document.open();
|
|
79
|
+
document.write(getDoctypeString(doc));
|
|
80
|
+
document.write(doc.documentElement.outerHTML);
|
|
81
|
+
document.close();
|
|
82
|
+
}
|
|
45
83
|
document.querySelectorAll("template[disabled-noscript]").forEach(element => {
|
|
46
84
|
const noscriptElement = document.createElement("noscript");
|
|
47
85
|
element.removeAttribute("disabled-noscript");
|
|
@@ -21,8 +21,6 @@
|
|
|
21
21
|
* Source.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
/* global globalThis */
|
|
25
|
-
|
|
26
24
|
export {
|
|
27
25
|
router
|
|
28
26
|
};
|
|
@@ -31,10 +29,31 @@ async function router(content, { extract, display }) {
|
|
|
31
29
|
const PAGES_PREFIX = "pages/";
|
|
32
30
|
const PAGES_FILENAME = "sfz-pages.json";
|
|
33
31
|
const ROUTE_PREFIX = "#sfz/";
|
|
34
|
-
const
|
|
32
|
+
const TARGET_ATTRIBUTE = "data-sfz-target";
|
|
33
|
+
const TARGET_PSEUDO_CLASS = /:target(?![\w-])/g;
|
|
34
|
+
const VISITED_ATTRIBUTE = "data-sfz-visited";
|
|
35
|
+
const VISITED_PSEUDO_CLASS = /:visited(?![\w-])/g;
|
|
36
|
+
const VISITED_DEFAULT_COLOR = "#551a8b";
|
|
37
|
+
const UNARCHIVED_ATTRIBUTE = "data-sfz-unarchived";
|
|
38
|
+
// relative units, currentColor and opacity keep the marker legible on any
|
|
39
|
+
// page theme, and \2197 stays ASCII in the windows-1252 prelude
|
|
40
|
+
const UNARCHIVED_STYLE = "a[" + UNARCHIVED_ATTRIBUTE + "]::after{content:\" \\2197\";font-size:.75em;opacity:.7}";
|
|
41
|
+
const UNARCHIVED_TITLE = "Not saved in this archive";
|
|
42
|
+
const UNARCHIVED_PROTOCOLS = ["http:", "https:"];
|
|
43
|
+
const PREFETCH_DELAY = 100;
|
|
44
|
+
const { zip, document, location, history, CSS, setTimeout, clearTimeout } = globalThis;
|
|
35
45
|
const cache = new Map();
|
|
36
46
|
const urlToPath = new Map();
|
|
37
|
-
|
|
47
|
+
const scrollStates = new Map();
|
|
48
|
+
const visitedPaths = new Set();
|
|
49
|
+
const sessionKey = Math.random().toString(36).substring(2);
|
|
50
|
+
let currentPath, currentEntryId, targetStyleElement, prefetchTimeout;
|
|
51
|
+
let nextEntryId = 0;
|
|
52
|
+
try {
|
|
53
|
+
history.scrollRestoration = "manual";
|
|
54
|
+
} catch {
|
|
55
|
+
// ignored
|
|
56
|
+
}
|
|
38
57
|
zip.configure({ useWebWorkers: true });
|
|
39
58
|
const zipReader = new zip.ZipReader(content.readUint8Array ? content : new zip.BlobReader(content));
|
|
40
59
|
const entries = await zipReader.getEntries();
|
|
@@ -42,7 +61,9 @@ async function router(content, { extract, display }) {
|
|
|
42
61
|
if (!pagesEntry) {
|
|
43
62
|
throw new Error("Pages data not found");
|
|
44
63
|
}
|
|
45
|
-
const
|
|
64
|
+
const manifest = JSON.parse(await pagesEntry.getData(new zip.TextWriter()));
|
|
65
|
+
const { pages } = manifest;
|
|
66
|
+
const aliases = new Map(Object.entries(manifest.aliases || {}));
|
|
46
67
|
pages.forEach(page => {
|
|
47
68
|
urlToPath.set(stripFragment(page.url), page.path);
|
|
48
69
|
if (page.originalUrls) {
|
|
@@ -50,60 +71,396 @@ async function router(content, { extract, display }) {
|
|
|
50
71
|
}
|
|
51
72
|
});
|
|
52
73
|
attachListeners();
|
|
53
|
-
|
|
74
|
+
currentEntryId = getEntryId();
|
|
75
|
+
if (currentEntryId === null) {
|
|
76
|
+
currentEntryId = assignEntryId();
|
|
77
|
+
}
|
|
78
|
+
return renderRoute(true);
|
|
54
79
|
|
|
55
80
|
// document.open() removes the listeners of the document and of its window,
|
|
56
81
|
// re-attaching identical function references is idempotent
|
|
57
82
|
function attachListeners() {
|
|
58
83
|
globalThis.addEventListener("click", interceptClick, true);
|
|
84
|
+
globalThis.addEventListener("auxclick", interceptClick, true);
|
|
85
|
+
globalThis.addEventListener("mouseover", prefetchOnHover, true);
|
|
59
86
|
globalThis.addEventListener("hashchange", onHashChange);
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
function onHashChange() {
|
|
63
|
-
|
|
64
|
-
renderRoute().catch(error => globalThis.console.error(error));
|
|
65
|
-
}
|
|
90
|
+
navigate().catch(error => globalThis.console.error(error));
|
|
66
91
|
}
|
|
67
92
|
|
|
68
93
|
function interceptClick(event) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
node = node.parentNode;
|
|
94
|
+
if (event.type == "auxclick" && event.button != 1) {
|
|
95
|
+
return;
|
|
72
96
|
}
|
|
97
|
+
const node = findAnchor(event.target);
|
|
73
98
|
if (node && node.href) {
|
|
74
|
-
const
|
|
99
|
+
const fragment = getFragment(node.href);
|
|
100
|
+
let path = urlToPath.get(stripFragment(node.href));
|
|
101
|
+
if (path === undefined && fragment && stripFragment(node.href) == stripFragment(location.href)) {
|
|
102
|
+
path = currentPath;
|
|
103
|
+
}
|
|
75
104
|
if (path !== undefined) {
|
|
76
105
|
event.preventDefault();
|
|
77
|
-
|
|
106
|
+
// modified and middle clicks open the archive deep link in a new
|
|
107
|
+
// tab instead of letting the browser open the live site URL
|
|
108
|
+
if (event.type == "auxclick" || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
109
|
+
globalThis.open(stripFragment(location.href) + ROUTE_PREFIX + path + (fragment || ""));
|
|
110
|
+
} else {
|
|
111
|
+
// the fragment stays encoded inside the route hash so that
|
|
112
|
+
// reloading or sharing the URL comes back to the same page
|
|
113
|
+
const previousHash = location.hash;
|
|
114
|
+
location.hash = ROUTE_PREFIX + path + (fragment || "");
|
|
115
|
+
if (location.hash == previousHash && fragment) {
|
|
116
|
+
clearTarget();
|
|
117
|
+
scrollToFragment(fragment);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function navigate() {
|
|
125
|
+
clearTarget();
|
|
126
|
+
scrollStates.set(currentEntryId, captureScrollState());
|
|
127
|
+
const continuityScrolls = captureElementScrolls();
|
|
128
|
+
const entryId = getEntryId();
|
|
129
|
+
const { routed, path, fragment } = parseRoute();
|
|
130
|
+
const willRender = routed && path != currentPath && Boolean(pages.find(page => page.path == path));
|
|
131
|
+
// the whole render and scroll sequence runs inside the view transition
|
|
132
|
+
// so that the crossfade ends on the final scroll position
|
|
133
|
+
if (willRender && document.startViewTransition && !prefersReducedMotion()) {
|
|
134
|
+
await document.startViewTransition(update).updateCallbackDone;
|
|
135
|
+
} else {
|
|
136
|
+
await update();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function update() {
|
|
140
|
+
const rendered = await renderRoute();
|
|
141
|
+
if (rendered) {
|
|
142
|
+
focusContent();
|
|
143
|
+
}
|
|
144
|
+
applyScrollState(rendered);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function applyScrollState(rendered) {
|
|
148
|
+
if (entryId !== null) {
|
|
149
|
+
currentEntryId = entryId;
|
|
150
|
+
const scrollState = scrollStates.get(entryId);
|
|
151
|
+
if (scrollState) {
|
|
152
|
+
applyElementScrolls(scrollState.elements);
|
|
153
|
+
globalThis.scrollTo(scrollState.x, scrollState.y);
|
|
154
|
+
}
|
|
155
|
+
if (fragment) {
|
|
156
|
+
const target = findFragmentTarget(fragment);
|
|
157
|
+
if (target) {
|
|
158
|
+
markTarget(target);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
currentEntryId = assignEntryId();
|
|
163
|
+
if (rendered) {
|
|
164
|
+
applyElementScrolls(continuityScrolls);
|
|
165
|
+
}
|
|
166
|
+
if (fragment) {
|
|
167
|
+
scrollToFragment(fragment);
|
|
168
|
+
} else if (rendered) {
|
|
169
|
+
globalThis.scrollTo(0, 0);
|
|
170
|
+
}
|
|
78
171
|
}
|
|
79
172
|
}
|
|
80
173
|
}
|
|
81
174
|
|
|
82
|
-
async function renderRoute() {
|
|
83
|
-
const
|
|
84
|
-
|
|
175
|
+
async function renderRoute(initial) {
|
|
176
|
+
const { routed, path, fragment } = parseRoute();
|
|
177
|
+
if (!routed && !initial) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
85
180
|
if (path == currentPath || !pages.find(page => page.path == path)) {
|
|
86
|
-
return;
|
|
181
|
+
return false;
|
|
87
182
|
}
|
|
88
183
|
const docContent = await getPageContent(path);
|
|
89
184
|
currentPath = path;
|
|
90
|
-
await display(document, docContent);
|
|
185
|
+
await display(document, docContent, { inPlace: true });
|
|
91
186
|
attachListeners();
|
|
187
|
+
visitedPaths.add(path);
|
|
188
|
+
markVisitedLinks();
|
|
189
|
+
if (manifest.markUnarchivedLinks) {
|
|
190
|
+
markUnarchivedLinks();
|
|
191
|
+
}
|
|
192
|
+
if (initial) {
|
|
193
|
+
if (fragment) {
|
|
194
|
+
scrollToFragment(fragment);
|
|
195
|
+
} else if (!routed && location.hash) {
|
|
196
|
+
scrollToFragment(location.hash);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function parseRoute() {
|
|
203
|
+
const hash = location.hash;
|
|
204
|
+
const routed = !hash || hash.startsWith(ROUTE_PREFIX);
|
|
205
|
+
let path = pages[0].path;
|
|
206
|
+
let fragment;
|
|
207
|
+
if (routed && hash) {
|
|
208
|
+
const route = hash.substring(ROUTE_PREFIX.length);
|
|
209
|
+
const indexFragment = route.indexOf("#");
|
|
210
|
+
path = decodeURIComponent(indexFragment == -1 ? route : route.substring(0, indexFragment));
|
|
211
|
+
fragment = indexFragment == -1 ? undefined : route.substring(indexFragment);
|
|
212
|
+
}
|
|
213
|
+
return { routed, path, fragment };
|
|
92
214
|
}
|
|
93
215
|
|
|
94
|
-
|
|
216
|
+
// the cache stores promises so that a click during a hover prefetch awaits
|
|
217
|
+
// the extraction in flight instead of starting a second one
|
|
218
|
+
function getPageContent(path) {
|
|
95
219
|
if (!cache.has(path)) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const { docContent } = await extract(null, { entries: pageEntries, pagePath: path });
|
|
100
|
-
cache.set(path, docContent);
|
|
220
|
+
const contentPromise = extractPageContent(path);
|
|
221
|
+
contentPromise.catch(() => cache.delete(path));
|
|
222
|
+
cache.set(path, contentPromise);
|
|
101
223
|
}
|
|
102
224
|
return cache.get(path);
|
|
103
225
|
}
|
|
104
226
|
|
|
227
|
+
async function extractPageContent(path) {
|
|
228
|
+
const pageEntries = entries
|
|
229
|
+
.filter(entry => belongsToPage(entry.filename, path) && !aliases.has(entry.filename))
|
|
230
|
+
.concat(getAliasEntries(path));
|
|
231
|
+
const { docContent } = await extract(null, { entries: pageEntries, pagePath: path });
|
|
232
|
+
return docContent;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// deduplicated resources exist in the zip as symlink stand-ins for external
|
|
236
|
+
// extractors, they are resolved from the manifest alias map when extracting
|
|
237
|
+
function getAliasEntries(path) {
|
|
238
|
+
return Array.from(aliases)
|
|
239
|
+
.filter(([filename]) => belongsToPage(filename, path))
|
|
240
|
+
.map(([filename, canonicalFilename]) => {
|
|
241
|
+
const entry = entries.find(entry => entry.filename == canonicalFilename);
|
|
242
|
+
return entry && {
|
|
243
|
+
filename,
|
|
244
|
+
comment: entry.comment,
|
|
245
|
+
encrypted: entry.encrypted,
|
|
246
|
+
uncompressedSize: entry.uncompressedSize,
|
|
247
|
+
getData: (writer, options) => entry.getData(writer, options)
|
|
248
|
+
};
|
|
249
|
+
})
|
|
250
|
+
.filter(Boolean);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function belongsToPage(filename, path) {
|
|
254
|
+
return path == "" ?
|
|
255
|
+
!filename.startsWith(PAGES_PREFIX) && filename != PAGES_FILENAME :
|
|
256
|
+
filename.startsWith(path);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function prefetchOnHover(event) {
|
|
260
|
+
const node = findAnchor(event.target);
|
|
261
|
+
if (node && node.href) {
|
|
262
|
+
const path = urlToPath.get(stripFragment(node.href));
|
|
263
|
+
if (path !== undefined && path != currentPath && !cache.has(path)) {
|
|
264
|
+
clearTimeout(prefetchTimeout);
|
|
265
|
+
prefetchTimeout = setTimeout(() => getPageContent(path).catch(() => { }), PREFETCH_DELAY);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function findAnchor(node) {
|
|
271
|
+
while (node && node.tagName != "A") {
|
|
272
|
+
node = node.parentNode;
|
|
273
|
+
}
|
|
274
|
+
return node;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function prefersReducedMotion() {
|
|
278
|
+
return Boolean(globalThis.matchMedia && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getEntryId() {
|
|
282
|
+
const state = history.state;
|
|
283
|
+
if (state && state.sfzSession == sessionKey && typeof state.sfzEntry == "number") {
|
|
284
|
+
return state.sfzEntry;
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function assignEntryId() {
|
|
290
|
+
const entryId = nextEntryId++;
|
|
291
|
+
try {
|
|
292
|
+
history.replaceState({ sfzSession: sessionKey, sfzEntry: entryId }, "");
|
|
293
|
+
} catch {
|
|
294
|
+
// ignored
|
|
295
|
+
}
|
|
296
|
+
return entryId;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function captureScrollState() {
|
|
300
|
+
return { x: globalThis.scrollX, y: globalThis.scrollY, elements: captureElementScrolls() };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function captureElementScrolls() {
|
|
304
|
+
const elementScrolls = [];
|
|
305
|
+
document.querySelectorAll("*").forEach(element => {
|
|
306
|
+
if (element.scrollTop || element.scrollLeft) {
|
|
307
|
+
const path = getElementPath(element);
|
|
308
|
+
if (path) {
|
|
309
|
+
elementScrolls.push({ path, top: element.scrollTop, left: element.scrollLeft });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return elementScrolls;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function applyElementScrolls(elementScrolls) {
|
|
317
|
+
elementScrolls.forEach(({ path, top, left }) => {
|
|
318
|
+
let element;
|
|
319
|
+
try {
|
|
320
|
+
element = document.querySelector(path);
|
|
321
|
+
} catch {
|
|
322
|
+
// ignored
|
|
323
|
+
}
|
|
324
|
+
if (element) {
|
|
325
|
+
element.scrollTop = top;
|
|
326
|
+
element.scrollLeft = left;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function getElementPath(element) {
|
|
332
|
+
const segments = [];
|
|
333
|
+
while (element && element.parentElement) {
|
|
334
|
+
if (element.id && CSS) {
|
|
335
|
+
segments.unshift("#" + CSS.escape(element.id));
|
|
336
|
+
return segments.join(">");
|
|
337
|
+
}
|
|
338
|
+
const parent = element.parentElement;
|
|
339
|
+
segments.unshift(element.tagName + ":nth-child(" + (Array.from(parent.children).indexOf(element) + 1) + ")");
|
|
340
|
+
element = parent;
|
|
341
|
+
}
|
|
342
|
+
return segments.join(">");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// the fragment is scrolled to from script because the real fragment is the
|
|
346
|
+
// route hash, which also means :target never matches in rendered pages; the
|
|
347
|
+
// :target rules of the page are cloned against a marker attribute instead
|
|
348
|
+
function scrollToFragment(fragment) {
|
|
349
|
+
const target = findFragmentTarget(fragment);
|
|
350
|
+
if (target) {
|
|
351
|
+
markTarget(target);
|
|
352
|
+
target.scrollIntoView();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function findFragmentTarget(fragment) {
|
|
357
|
+
const name = decodeURIComponent(fragment.substring(1));
|
|
358
|
+
let target = document.getElementById(name);
|
|
359
|
+
if (!target && CSS) {
|
|
360
|
+
target = document.querySelector("a[name=" + CSS.escape(name) + "]");
|
|
361
|
+
}
|
|
362
|
+
return target;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function markTarget(element) {
|
|
366
|
+
const cssText = getPseudoRules(TARGET_PSEUDO_CLASS, TARGET_ATTRIBUTE);
|
|
367
|
+
if (cssText) {
|
|
368
|
+
element.setAttribute(TARGET_ATTRIBUTE, "");
|
|
369
|
+
targetStyleElement = document.createElement("style");
|
|
370
|
+
targetStyleElement.textContent = cssText;
|
|
371
|
+
document.head.appendChild(targetStyleElement);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// :visited itself is privacy-gated, so visited routes are stamped with an
|
|
376
|
+
// attribute styled by the page's own :visited rules over a default color
|
|
377
|
+
function markVisitedLinks() {
|
|
378
|
+
const styleElement = document.createElement("style");
|
|
379
|
+
styleElement.textContent = "a[" + VISITED_ATTRIBUTE + "]{color:" + VISITED_DEFAULT_COLOR + "}" +
|
|
380
|
+
getPseudoRules(VISITED_PSEUDO_CLASS, VISITED_ATTRIBUTE);
|
|
381
|
+
document.head.appendChild(styleElement);
|
|
382
|
+
document.querySelectorAll("a[href]").forEach(anchorElement => {
|
|
383
|
+
const path = urlToPath.get(stripFragment(anchorElement.href));
|
|
384
|
+
if (path !== undefined && visitedPaths.has(path)) {
|
|
385
|
+
anchorElement.setAttribute(VISITED_ATTRIBUTE, "");
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// links that leave the archive are stamped so that the reader knows before
|
|
391
|
+
// clicking; opt-in at packaging time because the marker alters the rendering
|
|
392
|
+
function markUnarchivedLinks() {
|
|
393
|
+
const styleElement = document.createElement("style");
|
|
394
|
+
styleElement.textContent = UNARCHIVED_STYLE;
|
|
395
|
+
document.head.appendChild(styleElement);
|
|
396
|
+
document.querySelectorAll("a[href]").forEach(anchorElement => {
|
|
397
|
+
if (UNARCHIVED_PROTOCOLS.includes(anchorElement.protocol) &&
|
|
398
|
+
urlToPath.get(stripFragment(anchorElement.href)) === undefined &&
|
|
399
|
+
stripFragment(anchorElement.href) != stripFragment(location.href)) {
|
|
400
|
+
anchorElement.setAttribute(UNARCHIVED_ATTRIBUTE, "");
|
|
401
|
+
if (!anchorElement.hasAttribute("title")) {
|
|
402
|
+
anchorElement.setAttribute("title", UNARCHIVED_TITLE);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function clearTarget() {
|
|
409
|
+
if (targetStyleElement) {
|
|
410
|
+
const markedElement = document.querySelector("[" + TARGET_ATTRIBUTE + "]");
|
|
411
|
+
if (markedElement) {
|
|
412
|
+
markedElement.removeAttribute(TARGET_ATTRIBUTE);
|
|
413
|
+
}
|
|
414
|
+
targetStyleElement.remove();
|
|
415
|
+
targetStyleElement = undefined;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function getPseudoRules(pseudoRegExp, attributeName) {
|
|
420
|
+
let cssText = "";
|
|
421
|
+
Array.from(document.styleSheets).forEach(styleSheet => {
|
|
422
|
+
try {
|
|
423
|
+
cssText += getPseudoRulesText(styleSheet.cssRules, pseudoRegExp, attributeName);
|
|
424
|
+
} catch {
|
|
425
|
+
// ignored
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
return cssText;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function getPseudoRulesText(cssRules, pseudoRegExp, attributeName) {
|
|
432
|
+
let cssText = "";
|
|
433
|
+
Array.from(cssRules).forEach(cssRule => {
|
|
434
|
+
if (cssRule.cssRules && cssRule.cssRules.length) {
|
|
435
|
+
const innerCssText = getPseudoRulesText(cssRule.cssRules, pseudoRegExp, attributeName);
|
|
436
|
+
if (innerCssText) {
|
|
437
|
+
cssText += cssRule.cssText.substring(0, cssRule.cssText.indexOf("{") + 1) + innerCssText + "}";
|
|
438
|
+
}
|
|
439
|
+
} else if (cssRule.selectorText) {
|
|
440
|
+
const selectorText = cssRule.selectorText.replace(pseudoRegExp, "[" + attributeName + "]");
|
|
441
|
+
if (selectorText != cssRule.selectorText) {
|
|
442
|
+
cssText += selectorText + "{" + cssRule.style.cssText + "}";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
return cssText;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function focusContent() {
|
|
450
|
+
const headingElement = document.querySelector("h1") || document.body;
|
|
451
|
+
if (headingElement) {
|
|
452
|
+
headingElement.setAttribute("tabindex", "-1");
|
|
453
|
+
headingElement.focus({ preventScroll: true });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
105
457
|
function stripFragment(url) {
|
|
106
458
|
const indexFragment = url.indexOf("#");
|
|
107
459
|
return indexFragment == -1 ? url : url.substring(0, indexFragment);
|
|
108
460
|
}
|
|
461
|
+
|
|
462
|
+
function getFragment(url) {
|
|
463
|
+
const indexFragment = url.indexOf("#");
|
|
464
|
+
return indexFragment == -1 ? undefined : url.substring(indexFragment);
|
|
465
|
+
}
|
|
109
466
|
}
|
|
@@ -100,33 +100,35 @@ export {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
function init() {
|
|
103
|
-
globalThis.addEventListener("message",
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
103
|
+
globalThis.addEventListener("message", onMessage, true);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function onMessage(event) {
|
|
107
|
+
if (typeof event.data == "string" && event.data.startsWith(MESSAGE_PREFIX)) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
event.stopPropagation();
|
|
110
|
+
const message = JSON.parse(event.data.substring(MESSAGE_PREFIX.length));
|
|
111
|
+
if (message.method == INIT_REQUEST_MESSAGE) {
|
|
112
|
+
if (event.source) {
|
|
113
|
+
sendMessage(event.source, { method: ACK_INIT_REQUEST_MESSAGE, windowId: message.windowId, sessionId: message.sessionId });
|
|
114
|
+
}
|
|
115
|
+
if (!TOP_WINDOW) {
|
|
116
|
+
globalThis.stop();
|
|
117
|
+
if (message.options.loadDeferredImages) {
|
|
118
|
+
lazy.process(message.options);
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
clearFrameTimeout("requestTimeouts", message.sessionId, message.windowId);
|
|
121
|
-
createFrameResponseTimeout(message.sessionId, message.windowId);
|
|
122
|
-
} else if (message.method == CLEANUP_REQUEST_MESSAGE) {
|
|
123
|
-
cleanupRequest(message);
|
|
124
|
-
} else if (message.method == INIT_RESPONSE_MESSAGE && sessions.get(message.sessionId)) {
|
|
125
|
-
const port = event.ports[0];
|
|
126
|
-
port.onmessage = event => initResponse(event.data);
|
|
120
|
+
await initRequestAsync(message);
|
|
127
121
|
}
|
|
122
|
+
} else if (message.method == ACK_INIT_REQUEST_MESSAGE) {
|
|
123
|
+
clearFrameTimeout("requestTimeouts", message.sessionId, message.windowId);
|
|
124
|
+
createFrameResponseTimeout(message.sessionId, message.windowId);
|
|
125
|
+
} else if (message.method == CLEANUP_REQUEST_MESSAGE) {
|
|
126
|
+
cleanupRequest(message);
|
|
127
|
+
} else if (message.method == INIT_RESPONSE_MESSAGE && sessions.get(message.sessionId)) {
|
|
128
|
+
const port = event.ports[0];
|
|
129
|
+
port.onmessage = event => initResponse(event.data);
|
|
128
130
|
}
|
|
129
|
-
}
|
|
131
|
+
}
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
function getAsync(options) {
|