single-file-core 1.6.24 → 1.6.26
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 +73 -2
- package/core/index.js +72 -2
- package/core/lib/processor-helper-common.js +2 -1
- package/core/lib/processor-helper-inline.js +4 -2
- package/core/lib/processor-helper.js +16 -8
- package/core/util.js +100 -6
- package/package.json +1 -1
- package/processors/frame-tree/content/content-frame-tree.js +2 -0
- package/single-file.js +9 -0
package/core/helper.js
CHANGED
|
@@ -69,6 +69,7 @@ const DISABLED_NOSCRIPT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "disable
|
|
|
69
69
|
const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "selected-content";
|
|
70
70
|
const INVALID_ELEMENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "invalid-element";
|
|
71
71
|
const ASYNC_SCRIPT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "async-script";
|
|
72
|
+
const CUSTOM_STYLESHEET_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "custom-stylesheet";
|
|
72
73
|
const FLOW_ELEMENTS_SELECTOR = "*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)";
|
|
73
74
|
const KEPT_TAG_NAMES = ["NOSCRIPT", "DISABLED-NOSCRIPT", "META", "LINK", "STYLE", "TITLE", "TEMPLATE", "SOURCE", "OBJECT", "SCRIPT", "HEAD", "BODY"];
|
|
74
75
|
const INVOKABLE_ELEMENTS_SELECTOR = "[popover][id], dialog[id]";
|
|
@@ -173,6 +174,7 @@ export {
|
|
|
173
174
|
WIN_ID_ATTRIBUTE_NAME,
|
|
174
175
|
PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
|
|
175
176
|
REMOVED_CONTENT_ATTRIBUTE_NAME,
|
|
177
|
+
CUSTOM_STYLESHEET_ATTRIBUTE_NAME,
|
|
176
178
|
HIDDEN_CONTENT_ATTRIBUTE_NAME,
|
|
177
179
|
HIDDEN_FRAME_ATTRIBUTE_NAME,
|
|
178
180
|
IMAGE_ATTRIBUTE_NAME,
|
|
@@ -305,6 +307,9 @@ function preProcessDoc(doc, win, options) {
|
|
|
305
307
|
element.textContent = "";
|
|
306
308
|
});
|
|
307
309
|
initDoc(doc);
|
|
310
|
+
if (options.customStylesheet) {
|
|
311
|
+
insertCustomStylesheet(doc, options.customStylesheet);
|
|
312
|
+
}
|
|
308
313
|
if (doc.head) {
|
|
309
314
|
doc.head.querySelectorAll(FLOW_ELEMENTS_SELECTOR).forEach(element => element.hidden = true);
|
|
310
315
|
}
|
|
@@ -371,10 +376,75 @@ function preProcessDoc(doc, win, options) {
|
|
|
371
376
|
markedElements: elementsInfo.markedElements,
|
|
372
377
|
invalidElements,
|
|
373
378
|
scrollPosition: { x: win.scrollX, y: win.scrollY },
|
|
374
|
-
adoptedStyleSheets: getStylesheetsContent(doc.adoptedStyleSheets)
|
|
379
|
+
adoptedStyleSheets: getStylesheetsContent(doc.adoptedStyleSheets),
|
|
380
|
+
manifestData: options.manifest ? getManifestData(doc, win) : undefined
|
|
375
381
|
};
|
|
376
382
|
}
|
|
377
383
|
|
|
384
|
+
function getManifestData(doc, win) {
|
|
385
|
+
const manifestData = { url: doc.documentURI, timings: {} };
|
|
386
|
+
try {
|
|
387
|
+
const lastModified = new Date(doc.lastModified);
|
|
388
|
+
if (!isNaN(lastModified)) {
|
|
389
|
+
manifestData.lastModified = lastModified.toISOString();
|
|
390
|
+
}
|
|
391
|
+
// eslint-disable-next-line no-unused-vars
|
|
392
|
+
} catch (error) {
|
|
393
|
+
// ignored
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const performance = win.performance;
|
|
397
|
+
manifestData.timeOrigin = new Date(performance.timeOrigin).toISOString();
|
|
398
|
+
const [navigation] = performance.getEntriesByType("navigation");
|
|
399
|
+
if (navigation) {
|
|
400
|
+
manifestData.navigation = getTimingData(navigation);
|
|
401
|
+
manifestData.navigation.type = navigation.type;
|
|
402
|
+
manifestData.navigation.redirectCount = navigation.redirectCount;
|
|
403
|
+
}
|
|
404
|
+
performance.getEntriesByType("resource").forEach(entry => manifestData.timings[entry.name] = getTimingData(entry));
|
|
405
|
+
// eslint-disable-next-line no-unused-vars
|
|
406
|
+
} catch (error) {
|
|
407
|
+
// ignored
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
manifestData.environment = {
|
|
411
|
+
userAgent: win.navigator.userAgent,
|
|
412
|
+
viewport: { width: win.innerWidth, height: win.innerHeight, devicePixelRatio: win.devicePixelRatio },
|
|
413
|
+
media: {
|
|
414
|
+
prefersColorScheme: win.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
|
|
415
|
+
prefersReducedMotion: win.matchMedia("(prefers-reduced-motion: reduce)").matches ? "reduce" : "no-preference"
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
// eslint-disable-next-line no-unused-vars
|
|
419
|
+
} catch (error) {
|
|
420
|
+
// ignored
|
|
421
|
+
}
|
|
422
|
+
return manifestData;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function getTimingData(entry) {
|
|
426
|
+
const timingData = {
|
|
427
|
+
transferSize: entry.transferSize,
|
|
428
|
+
encodedBodySize: entry.encodedBodySize,
|
|
429
|
+
decodedBodySize: entry.decodedBodySize,
|
|
430
|
+
nextHopProtocol: entry.nextHopProtocol
|
|
431
|
+
};
|
|
432
|
+
if (entry.responseStatus) {
|
|
433
|
+
timingData.status = entry.responseStatus;
|
|
434
|
+
}
|
|
435
|
+
return timingData;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function insertCustomStylesheet(doc, content) {
|
|
439
|
+
const parentElement = doc.body || doc.documentElement;
|
|
440
|
+
if (parentElement) {
|
|
441
|
+
const styleElement = doc.createElement("style");
|
|
442
|
+
styleElement.setAttribute(CUSTOM_STYLESHEET_ATTRIBUTE_NAME, "");
|
|
443
|
+
styleElement.textContent = content;
|
|
444
|
+
parentElement.appendChild(styleElement);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
378
448
|
function markInvalidNesting(doc) {
|
|
379
449
|
if (!doc.body) {
|
|
380
450
|
return;
|
|
@@ -1093,6 +1163,7 @@ function testHiddenElement(element, computedStyle) {
|
|
|
1093
1163
|
|
|
1094
1164
|
function postProcessDoc(doc, markedElements, invalidElements) {
|
|
1095
1165
|
removeNestingMarkers(doc);
|
|
1166
|
+
doc.querySelectorAll("style[" + CUSTOM_STYLESHEET_ATTRIBUTE_NAME + "]").forEach(element => element.remove());
|
|
1096
1167
|
doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
|
|
1097
1168
|
element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
|
|
1098
1169
|
element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
|
|
@@ -1335,7 +1406,7 @@ async function getDataURI(blob) {
|
|
|
1335
1406
|
|
|
1336
1407
|
async function digest(algo, text) {
|
|
1337
1408
|
try {
|
|
1338
|
-
const data = new TextEncoder("utf-8").encode(text);
|
|
1409
|
+
const data = typeof text == "string" ? new TextEncoder("utf-8").encode(text) : text instanceof Uint8Array ? text : new Uint8Array(text);
|
|
1339
1410
|
const hash = globalThis.crypto && crypto.subtle ? await crypto.subtle.digest(algo, data) : sha.digest(algo, data);
|
|
1340
1411
|
return hex(hash);
|
|
1341
1412
|
// eslint-disable-next-line no-unused-vars
|
package/core/index.js
CHANGED
|
@@ -207,6 +207,7 @@ class Runner {
|
|
|
207
207
|
this.options.shadowRoots = docData.shadowRoots;
|
|
208
208
|
this.options.referrer = docData.referrer;
|
|
209
209
|
this.options.adoptedStyleSheets = docData.adoptedStyleSheets;
|
|
210
|
+
this.options.manifestData = docData.manifestData;
|
|
210
211
|
this.markedElements = docData.markedElements;
|
|
211
212
|
this.invalidElements = docData.invalidElements;
|
|
212
213
|
}
|
|
@@ -403,8 +404,12 @@ class BatchRequest {
|
|
|
403
404
|
baseURI,
|
|
404
405
|
blockMixedContent,
|
|
405
406
|
acceptHeaders: options.acceptHeaders,
|
|
406
|
-
networkTimeout: options.networkTimeout
|
|
407
|
+
networkTimeout: options.networkTimeout,
|
|
408
|
+
manifestEntries: options.manifestEntries
|
|
407
409
|
});
|
|
410
|
+
if (content.manifestEntry) {
|
|
411
|
+
content.manifestEntry.references = resourceRequests.length;
|
|
412
|
+
}
|
|
408
413
|
if (!this.cancelled) {
|
|
409
414
|
const extension = util.getContentTypeExtension(content.contentType) || util.getFilenameExtension(resourceURL, options.filenameReplacedCharacters, options.filenameReplacementCharacter, options.filenameReplacementCharacters);
|
|
410
415
|
resourceRequests.forEach(callbacks => {
|
|
@@ -451,6 +456,10 @@ const UTF8_CHARSET = "utf-8";
|
|
|
451
456
|
const TAINTED_CANVAS_WARNING_MESSAGE = "SingleFile: canvas elements tainted by a cross-origin resource, dropped from the page:";
|
|
452
457
|
const EMPTY_RESOURCE = "data:,";
|
|
453
458
|
const ORDER_DEPENDENT_RULE = /@(layer|import)\b/i;
|
|
459
|
+
const MANIFEST_TOP_WINDOW_ID = "0";
|
|
460
|
+
const WINDOW_ID_SEPARATOR = ".";
|
|
461
|
+
const MANIFEST_EXCLUDED_OPTION_NAMES = ["rootDocument", "saveFilenameTemplateData", "omitReferrerInTemplateData", "manifest"];
|
|
462
|
+
const MANIFEST_STRING_OPTION_NAMES = ["customStylesheet", "filenameTemplate"];
|
|
454
463
|
|
|
455
464
|
class Processor {
|
|
456
465
|
constructor(options, processorHelper, batchRequest) {
|
|
@@ -471,7 +480,10 @@ class Processor {
|
|
|
471
480
|
frames: new Map()
|
|
472
481
|
};
|
|
473
482
|
this.fontTests = options.fontTests;
|
|
483
|
+
this.frameManifests = [];
|
|
484
|
+
this.startDate = new Date();
|
|
474
485
|
options.generatedDataURIs = new Set();
|
|
486
|
+
options.manifestEntries = options.manifest ? [] : undefined;
|
|
475
487
|
}
|
|
476
488
|
|
|
477
489
|
registerGeneratedDataURI(dataURI) {
|
|
@@ -504,7 +516,8 @@ class Processor {
|
|
|
504
516
|
resourceReferrer: this.options.resourceReferrer,
|
|
505
517
|
expectedType: "document",
|
|
506
518
|
acceptHeaders: this.options.acceptHeaders,
|
|
507
|
-
networkTimeout: this.options.networkTimeout
|
|
519
|
+
networkTimeout: this.options.networkTimeout,
|
|
520
|
+
manifestEntries: this.options.manifestEntries
|
|
508
521
|
});
|
|
509
522
|
pageContent = content.data || "";
|
|
510
523
|
}
|
|
@@ -654,13 +667,54 @@ class Processor {
|
|
|
654
667
|
if (this.options.addProof) {
|
|
655
668
|
pageData.hash = await util.digest("SHA-256", content);
|
|
656
669
|
}
|
|
670
|
+
if (this.options.manifest) {
|
|
671
|
+
pageData.manifest = this.getManifest();
|
|
672
|
+
}
|
|
657
673
|
if (this.options.retrieveLinks) {
|
|
658
674
|
pageData.links = Array.from(new Set(Array.from(this.doc.links).map(linkElement => linkElement.href)));
|
|
659
675
|
}
|
|
660
676
|
return pageData;
|
|
661
677
|
}
|
|
662
678
|
|
|
679
|
+
getManifest() {
|
|
680
|
+
const windowId = this.options.windowId || MANIFEST_TOP_WINDOW_ID;
|
|
681
|
+
const manifestData = this.options.manifestData || {};
|
|
682
|
+
const frame = {
|
|
683
|
+
id: windowId,
|
|
684
|
+
parent: windowId.includes(WINDOW_ID_SEPARATOR) ? windowId.substring(0, windowId.lastIndexOf(WINDOW_ID_SEPARATOR)) : null,
|
|
685
|
+
url: this.baseURI
|
|
686
|
+
};
|
|
687
|
+
if (manifestData.lastModified) {
|
|
688
|
+
frame.lastModified = manifestData.lastModified;
|
|
689
|
+
}
|
|
690
|
+
if (manifestData.navigation) {
|
|
691
|
+
frame.navigation = manifestData.navigation;
|
|
692
|
+
}
|
|
693
|
+
frame.resources = this.options.manifestEntries.map(entry => {
|
|
694
|
+
const timing = manifestData.timings && (manifestData.timings[entry.finalUrl] || manifestData.timings[entry.url]);
|
|
695
|
+
return timing ? Object.assign({}, entry, { timing }) : entry;
|
|
696
|
+
});
|
|
697
|
+
const frames = [frame, ...this.frameManifests];
|
|
698
|
+
if (!this.options.rootDocument) {
|
|
699
|
+
return { frames };
|
|
700
|
+
}
|
|
701
|
+
const manifest = {
|
|
702
|
+
subject: { url: this.options.saveUrl, finalUrl: manifestData.url || this.baseURI, title: this.doc.title },
|
|
703
|
+
time: { captureStarted: this.startDate.toISOString(), captureEnded: new Date().toISOString() }
|
|
704
|
+
};
|
|
705
|
+
if (manifestData.timeOrigin) {
|
|
706
|
+
manifest.time.loaded = manifestData.timeOrigin;
|
|
707
|
+
}
|
|
708
|
+
if (manifestData.environment) {
|
|
709
|
+
manifest.environment = manifestData.environment;
|
|
710
|
+
}
|
|
711
|
+
manifest.options = getManifestOptions(this.options);
|
|
712
|
+
manifest.frames = frames;
|
|
713
|
+
return manifest;
|
|
714
|
+
}
|
|
715
|
+
|
|
663
716
|
preProcessPage() {
|
|
717
|
+
this.doc.querySelectorAll("style[" + util.CUSTOM_STYLESHEET_ATTRIBUTE_NAME + "]").forEach(element => element.removeAttribute(util.CUSTOM_STYLESHEET_ATTRIBUTE_NAME));
|
|
664
718
|
this.doc.body.querySelectorAll(":not(svg) title, meta, link[href][rel*=\"icon\"]").forEach(element => {
|
|
665
719
|
if ((this.options.win && element instanceof this.options.win.HTMLElement) || element instanceof globalThis.HTMLElement) {
|
|
666
720
|
this.doc.head.appendChild(element);
|
|
@@ -1462,6 +1516,7 @@ class Processor {
|
|
|
1462
1516
|
options.scrollPosition = frameData.scrollPosition;
|
|
1463
1517
|
options.scrolling = frameData.scrolling;
|
|
1464
1518
|
options.adoptedStyleSheets = frameData.adoptedStyleSheets;
|
|
1519
|
+
options.manifestData = frameData.manifestData;
|
|
1465
1520
|
frameData.runner = new Runner(options, processorHelper);
|
|
1466
1521
|
frameData.frameElement = frameElement;
|
|
1467
1522
|
await frameData.runner.loadPage();
|
|
@@ -1667,6 +1722,9 @@ class Processor {
|
|
|
1667
1722
|
const { frameElement, pageData, frameWindowId, frameData } = capturedFrame;
|
|
1668
1723
|
this.processorHelper.processFrame(frameElement, pageData, this.options, this.resources, frameWindowId, frameData);
|
|
1669
1724
|
this.stats.addAll(pageData);
|
|
1725
|
+
if (pageData.manifest) {
|
|
1726
|
+
this.frameManifests.push(...pageData.manifest.frames);
|
|
1727
|
+
}
|
|
1670
1728
|
}
|
|
1671
1729
|
});
|
|
1672
1730
|
}
|
|
@@ -1910,6 +1968,18 @@ function nameAssignedElements(hostElement) {
|
|
|
1910
1968
|
return splitSlots;
|
|
1911
1969
|
}
|
|
1912
1970
|
|
|
1971
|
+
function getManifestOptions(options) {
|
|
1972
|
+
const manifestOptions = {};
|
|
1973
|
+
Object.keys(options).sort().forEach(name => {
|
|
1974
|
+
const value = options[name];
|
|
1975
|
+
const primitive = typeof value == "boolean" || typeof value == "number";
|
|
1976
|
+
if ((primitive && !MANIFEST_EXCLUDED_OPTION_NAMES.includes(name)) || (typeof value == "string" && MANIFEST_STRING_OPTION_NAMES.includes(name))) {
|
|
1977
|
+
manifestOptions[name] = value;
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
return manifestOptions;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1913
1983
|
function removeInsertedParagraphs(doc) {
|
|
1914
1984
|
getInsertedParagraphs(doc).forEach((_, paragraph) => paragraph.remove());
|
|
1915
1985
|
}
|
|
@@ -312,7 +312,8 @@ class ProcessorHelperCommon {
|
|
|
312
312
|
blockMixedContent: options.blockMixedContent,
|
|
313
313
|
expectedType: "stylesheet",
|
|
314
314
|
acceptHeaders: options.acceptHeaders,
|
|
315
|
-
networkTimeout: options.networkTimeout
|
|
315
|
+
networkTimeout: options.networkTimeout,
|
|
316
|
+
manifestEntries: options.manifestEntries
|
|
316
317
|
});
|
|
317
318
|
if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
|
|
318
319
|
options = Object.assign({}, options, { charset: getCharset(content.data) });
|
|
@@ -263,7 +263,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
263
263
|
blockMixedContent: options.blockMixedContent,
|
|
264
264
|
expectedType: "stylesheet",
|
|
265
265
|
acceptHeaders: options.acceptHeaders,
|
|
266
|
-
networkTimeout: options.networkTimeout
|
|
266
|
+
networkTimeout: options.networkTimeout,
|
|
267
|
+
manifestEntries: options.manifestEntries
|
|
267
268
|
});
|
|
268
269
|
if (content.failed) {
|
|
269
270
|
const liveContent = this.getLiveStylesheetContent(element, options);
|
|
@@ -405,7 +406,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
405
406
|
resourceReferrer: options.resourceReferrer,
|
|
406
407
|
referrerPolicy: getReferrerPolicy(resourceElement),
|
|
407
408
|
acceptHeaders: options.acceptHeaders,
|
|
408
|
-
networkTimeout: options.networkTimeout
|
|
409
|
+
networkTimeout: options.networkTimeout,
|
|
410
|
+
manifestEntries: options.manifestEntries
|
|
409
411
|
})).data;
|
|
410
412
|
// eslint-disable-next-line no-unused-vars
|
|
411
413
|
} catch (error) {
|
|
@@ -288,7 +288,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
288
288
|
blockMixedContent: options.blockMixedContent,
|
|
289
289
|
expectedType: "stylesheet",
|
|
290
290
|
acceptHeaders: options.acceptHeaders,
|
|
291
|
-
networkTimeout: options.networkTimeout
|
|
291
|
+
networkTimeout: options.networkTimeout,
|
|
292
|
+
manifestEntries: options.manifestEntries
|
|
292
293
|
});
|
|
293
294
|
if (content.failed) {
|
|
294
295
|
const liveContent = this.getLiveStylesheetContent(element, options);
|
|
@@ -331,13 +332,15 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
331
332
|
blockMixedContent: options.blockMixedContent,
|
|
332
333
|
...getStylesheetReferrer(resourceURL, options)
|
|
333
334
|
});
|
|
334
|
-
const name = "fonts/" + indexResource + extension;
|
|
335
|
+
const name = this.testEmptyResource(content) ? util.EMPTY_RESOURCE : "fonts/" + indexResource + extension;
|
|
335
336
|
if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
|
|
336
337
|
urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + name;
|
|
337
338
|
} else {
|
|
338
339
|
urlNode.value = name;
|
|
339
340
|
}
|
|
340
|
-
|
|
341
|
+
if (!this.testEmptyResource(content)) {
|
|
342
|
+
resources.fonts.set(indexResource, { name, content, extension, contentType, url: resourceURL });
|
|
343
|
+
}
|
|
341
344
|
}
|
|
342
345
|
|
|
343
346
|
groupDuplicateFonts(stylesheets, fonts) {
|
|
@@ -412,13 +415,15 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
412
415
|
if ((!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) || testGeneratedDataURI(resourceURL, options)) {
|
|
413
416
|
let { content, indexResource, contentType, extension } = await batchRequest.addURL(resourceURL,
|
|
414
417
|
{ asBinary: true, expectedType: "image", ...getStylesheetReferrer(resourceURL, options) });
|
|
415
|
-
const name = "images/" + indexResource + extension;
|
|
418
|
+
const name = this.testEmptyResource(content) ? util.EMPTY_RESOURCE : "images/" + indexResource + extension;
|
|
416
419
|
if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
|
|
417
420
|
urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + name;
|
|
418
421
|
} else {
|
|
419
422
|
urlNode.value = name;
|
|
420
423
|
}
|
|
421
|
-
|
|
424
|
+
if (!this.testEmptyResource(content)) {
|
|
425
|
+
resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
|
|
426
|
+
}
|
|
422
427
|
}
|
|
423
428
|
} else {
|
|
424
429
|
urlNode.value = util.EMPTY_RESOURCE;
|
|
@@ -473,7 +478,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
473
478
|
resourceReferrer: options.resourceReferrer,
|
|
474
479
|
referrerPolicy: getReferrerPolicy(resourceElement),
|
|
475
480
|
acceptHeaders: options.acceptHeaders,
|
|
476
|
-
networkTimeout: options.networkTimeout
|
|
481
|
+
networkTimeout: options.networkTimeout,
|
|
482
|
+
manifestEntries: options.manifestEntries
|
|
477
483
|
})).data;
|
|
478
484
|
// eslint-disable-next-line no-unused-vars
|
|
479
485
|
} catch (error) {
|
|
@@ -505,8 +511,10 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
505
511
|
|
|
506
512
|
async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest, resourceElement) {
|
|
507
513
|
const { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", referrerPolicy: getReferrerPolicy(resourceElement) });
|
|
508
|
-
const name = "images/" + indexResource + extension;
|
|
509
|
-
|
|
514
|
+
const name = this.testEmptyResource(content) ? util.EMPTY_RESOURCE : "images/" + indexResource + extension;
|
|
515
|
+
if (!this.testEmptyResource(content)) {
|
|
516
|
+
resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
|
|
517
|
+
}
|
|
510
518
|
resourceElement.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
|
|
511
519
|
return serializeSrcset([Object.assign({}, srcsetValue, { url: name })]);
|
|
512
520
|
}
|
package/core/util.js
CHANGED
|
@@ -70,6 +70,17 @@ const ZIP_FILENAME_OFFSET = 30;
|
|
|
70
70
|
const USD_FILENAME_REGEXP = /\.usd[ac]?$/i;
|
|
71
71
|
const CONTENT_TYPES_HTML = ["text/html", "application/xhtml+xml"];
|
|
72
72
|
const EXPECTED_TYPES_MEDIA = ["font", "image", "video", "audio", "model"];
|
|
73
|
+
const EXPECTED_TYPE_IMAGE = "image";
|
|
74
|
+
const EXPECTED_TYPE_DOCUMENT = "document";
|
|
75
|
+
const DATA_URI_PREFIX = "data:";
|
|
76
|
+
const MANIFEST_OUTCOME_EMBEDDED = "embedded";
|
|
77
|
+
const MANIFEST_OUTCOME_FAILED = "failed";
|
|
78
|
+
const MANIFEST_OUTCOME_BLOCKED = "blocked";
|
|
79
|
+
const MANIFEST_OUTCOME_REJECTED = "rejected";
|
|
80
|
+
const MANIFEST_OUTCOME_OVERSIZED = "oversized";
|
|
81
|
+
const MANIFEST_FETCHED_BY_PAGE = "page";
|
|
82
|
+
const MANIFEST_FETCHED_BY_FRAME = "frame";
|
|
83
|
+
const MANIFEST_HEADER_NAMES = ["date", "last-modified", "etag", "content-length", "content-encoding", "cache-control", "age", "expires", "server"];
|
|
73
84
|
|
|
74
85
|
const URL = globalThis.URL;
|
|
75
86
|
const DOMParser = globalThis.DOMParser;
|
|
@@ -226,6 +237,7 @@ function getInstance(utilOptions) {
|
|
|
226
237
|
PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
|
|
227
238
|
STYLESHEET_ATTRIBUTE_NAME: helper.STYLESHEET_ATTRIBUTE_NAME,
|
|
228
239
|
LINK_STYLESHEET_ATTRIBUTE_NAME: helper.LINK_STYLESHEET_ATTRIBUTE_NAME,
|
|
240
|
+
CUSTOM_STYLESHEET_ATTRIBUTE_NAME: helper.CUSTOM_STYLESHEET_ATTRIBUTE_NAME,
|
|
229
241
|
SELECTED_CONTENT_ATTRIBUTE_NAME: helper.SELECTED_CONTENT_ATTRIBUTE_NAME,
|
|
230
242
|
INVALID_ELEMENT_ATTRIBUTE_NAME: helper.INVALID_ELEMENT_ATTRIBUTE_NAME,
|
|
231
243
|
COMMENT_HEADER: helper.COMMENT_HEADER,
|
|
@@ -243,6 +255,16 @@ function getInstance(utilOptions) {
|
|
|
243
255
|
};
|
|
244
256
|
|
|
245
257
|
async function getContent(resourceURL, options) {
|
|
258
|
+
const entry = options.manifestEntries && !resourceURL.startsWith(DATA_URI_PREFIX) ? { url: resourceURL, role: options.expectedType || EXPECTED_TYPE_DOCUMENT, outcome: MANIFEST_OUTCOME_FAILED } : undefined;
|
|
259
|
+
const content = await fetchContent(resourceURL, options, entry);
|
|
260
|
+
if (entry) {
|
|
261
|
+
options.manifestEntries.push(entry);
|
|
262
|
+
content.manifestEntry = entry;
|
|
263
|
+
}
|
|
264
|
+
return content;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function fetchContent(resourceURL, options, entry) {
|
|
246
268
|
let response, startTime, networkTimeoutId, networkTimeoutPromise, resolveNetworkTimeoutPromise;
|
|
247
269
|
const fetchResource = utilOptions.fetch;
|
|
248
270
|
const fetchFrameResource = utilOptions.frameFetch;
|
|
@@ -251,6 +273,7 @@ function getInstance(utilOptions) {
|
|
|
251
273
|
log(" // STARTED download url =", resourceURL, "asBinary =", options.asBinary);
|
|
252
274
|
}
|
|
253
275
|
if (options.blockMixedContent && /^https:/i.test(options.baseURI) && !/^https:/i.test(resourceURL) && !/^blob:https:/i.test(resourceURL)) {
|
|
276
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_BLOCKED);
|
|
254
277
|
return getFetchResponse(resourceURL, options);
|
|
255
278
|
}
|
|
256
279
|
if (options.networkTimeout) {
|
|
@@ -271,21 +294,24 @@ function getInstance(utilOptions) {
|
|
|
271
294
|
fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
|
|
272
295
|
networkTimeoutPromise
|
|
273
296
|
]);
|
|
297
|
+
setManifestResponse(entry, response, MANIFEST_FETCHED_BY_FRAME);
|
|
274
298
|
// eslint-disable-next-line no-unused-vars
|
|
275
299
|
} catch (error) {
|
|
276
300
|
response = await Promise.race([
|
|
277
301
|
fetchResource(resourceURL, { referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
|
|
278
302
|
networkTimeoutPromise
|
|
279
303
|
]);
|
|
304
|
+
setManifestResponse(entry, response, MANIFEST_FETCHED_BY_PAGE);
|
|
280
305
|
}
|
|
281
306
|
} else {
|
|
282
307
|
response = await Promise.race([
|
|
283
308
|
fetchResource(resourceURL, { referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
|
|
284
309
|
networkTimeoutPromise
|
|
285
310
|
]);
|
|
311
|
+
setManifestResponse(entry, response, MANIFEST_FETCHED_BY_PAGE);
|
|
286
312
|
}
|
|
287
|
-
// eslint-disable-next-line no-unused-vars
|
|
288
313
|
} catch (error) {
|
|
314
|
+
setManifestError(entry, error);
|
|
289
315
|
return getFailedFetchResponse(resourceURL, options);
|
|
290
316
|
} finally {
|
|
291
317
|
resolveNetworkTimeoutPromise();
|
|
@@ -296,10 +322,11 @@ function getInstance(utilOptions) {
|
|
|
296
322
|
let buffer;
|
|
297
323
|
try {
|
|
298
324
|
buffer = await response.arrayBuffer();
|
|
299
|
-
// eslint-disable-next-line no-unused-vars
|
|
300
325
|
} catch (error) {
|
|
326
|
+
setManifestError(entry, error);
|
|
301
327
|
return options.inline ? { data: options.asBinary ? helper.EMPTY_RESOURCE : "", resourceURL } : { resourceURL };
|
|
302
328
|
}
|
|
329
|
+
await setManifestContent(entry, buffer);
|
|
303
330
|
resourceURL = response.url || resourceURL;
|
|
304
331
|
let contentType = "", charset;
|
|
305
332
|
try {
|
|
@@ -319,8 +346,15 @@ function getInstance(utilOptions) {
|
|
|
319
346
|
if (!charset && options.charset) {
|
|
320
347
|
charset = options.charset;
|
|
321
348
|
}
|
|
349
|
+
if (entry) {
|
|
350
|
+
entry.contentType = contentType;
|
|
351
|
+
if (charset) {
|
|
352
|
+
entry.charset = charset;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
322
355
|
if (options.asBinary) {
|
|
323
|
-
if (response.status >= 400 || (EXPECTED_TYPES_MEDIA.includes(options.expectedType) && CONTENT_TYPES_HTML.includes(contentType))) {
|
|
356
|
+
if ((response.status >= 400 && options.expectedType != EXPECTED_TYPE_IMAGE) || (EXPECTED_TYPES_MEDIA.includes(options.expectedType) && CONTENT_TYPES_HTML.includes(contentType))) {
|
|
357
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_REJECTED);
|
|
324
358
|
return getFetchResponse(resourceURL, options);
|
|
325
359
|
}
|
|
326
360
|
try {
|
|
@@ -328,19 +362,23 @@ function getInstance(utilOptions) {
|
|
|
328
362
|
log(" // ENDED download url =", resourceURL, "delay =", Date.now() - startTime);
|
|
329
363
|
}
|
|
330
364
|
if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
|
|
365
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_OVERSIZED);
|
|
331
366
|
return getFetchResponse(resourceURL, options);
|
|
332
367
|
} else {
|
|
368
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_EMBEDDED);
|
|
333
369
|
return getFetchResponse(resourceURL, options, buffer, null, contentType);
|
|
334
370
|
}
|
|
335
|
-
// eslint-disable-next-line no-unused-vars
|
|
336
371
|
} catch (error) {
|
|
372
|
+
setManifestError(entry, error);
|
|
337
373
|
return getFetchResponse(resourceURL, options);
|
|
338
374
|
}
|
|
339
375
|
} else {
|
|
340
|
-
if (response.status >= 400) {
|
|
376
|
+
if (response.status >= 400 && options.expectedType != EXPECTED_TYPE_DOCUMENT) {
|
|
377
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_REJECTED);
|
|
341
378
|
return getFailedFetchResponse(resourceURL, options);
|
|
342
379
|
}
|
|
343
380
|
if (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT)) {
|
|
381
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_REJECTED);
|
|
344
382
|
return getFetchResponse(resourceURL, options);
|
|
345
383
|
}
|
|
346
384
|
if (!charset) {
|
|
@@ -350,14 +388,16 @@ function getInstance(utilOptions) {
|
|
|
350
388
|
log(" // ENDED download url =", resourceURL, "delay =", Date.now() - startTime);
|
|
351
389
|
}
|
|
352
390
|
if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
|
|
391
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_OVERSIZED);
|
|
353
392
|
return getFetchResponse(resourceURL, options, null, charset);
|
|
354
393
|
} else {
|
|
355
394
|
try {
|
|
356
395
|
const fetchResponse = await getFetchResponse(resourceURL, options, buffer, charset, contentType);
|
|
357
396
|
fetchResponse.referrerPolicy = (response.headers && response.headers.get("referrer-policy")) || undefined;
|
|
397
|
+
setManifestOutcome(entry, MANIFEST_OUTCOME_EMBEDDED);
|
|
358
398
|
return fetchResponse;
|
|
359
|
-
// eslint-disable-next-line no-unused-vars
|
|
360
399
|
} catch (error) {
|
|
400
|
+
setManifestError(entry, error);
|
|
361
401
|
return getFetchResponse(resourceURL, options, null, charset);
|
|
362
402
|
}
|
|
363
403
|
}
|
|
@@ -365,6 +405,60 @@ function getInstance(utilOptions) {
|
|
|
365
405
|
}
|
|
366
406
|
}
|
|
367
407
|
|
|
408
|
+
function setManifestResponse(entry, response, fetchedBy) {
|
|
409
|
+
if (entry) {
|
|
410
|
+
entry.fetchedBy = fetchedBy;
|
|
411
|
+
if (response.url) {
|
|
412
|
+
entry.finalUrl = response.url;
|
|
413
|
+
}
|
|
414
|
+
if (response.status !== undefined) {
|
|
415
|
+
entry.status = response.status;
|
|
416
|
+
}
|
|
417
|
+
const headers = {};
|
|
418
|
+
MANIFEST_HEADER_NAMES.forEach(name => {
|
|
419
|
+
const value = getHeaderValue(response.headers, name);
|
|
420
|
+
if (value) {
|
|
421
|
+
headers[name] = value;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
if (Object.keys(headers).length) {
|
|
425
|
+
entry.headers = headers;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function getHeaderValue(headers, name) {
|
|
431
|
+
if (headers) {
|
|
432
|
+
if (typeof headers.get == "function") {
|
|
433
|
+
return headers.get(name);
|
|
434
|
+
}
|
|
435
|
+
const key = Object.keys(headers).find(key => key.toLowerCase() == name);
|
|
436
|
+
return key === undefined ? undefined : headers[key];
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function setManifestContent(entry, buffer) {
|
|
441
|
+
if (entry) {
|
|
442
|
+
entry.size = buffer.byteLength;
|
|
443
|
+
entry.sha256 = await helper.digest("SHA-256", buffer);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function setManifestOutcome(entry, outcome) {
|
|
448
|
+
if (entry) {
|
|
449
|
+
entry.outcome = outcome;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function setManifestError(entry, error) {
|
|
454
|
+
if (entry) {
|
|
455
|
+
entry.outcome = MANIFEST_OUTCOME_FAILED;
|
|
456
|
+
if (error && error.message) {
|
|
457
|
+
entry.error = error.message;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
368
462
|
async function getFailedFetchResponse(resourceURL, options) {
|
|
369
463
|
const response = await getFetchResponse(resourceURL, options);
|
|
370
464
|
response.failed = true;
|
package/package.json
CHANGED
|
@@ -237,6 +237,7 @@ function initResponse(message) {
|
|
|
237
237
|
frameData.scrollPosition = messageFrameData.scrollPosition;
|
|
238
238
|
frameData.scrolling = messageFrameData.scrolling;
|
|
239
239
|
frameData.adoptedStyleSheets = messageFrameData.adoptedStyleSheets;
|
|
240
|
+
frameData.manifestData = messageFrameData.manifestData;
|
|
240
241
|
}
|
|
241
242
|
});
|
|
242
243
|
const remainingFrames = windowData.frames.filter(frameData => !frameData.processed).length;
|
|
@@ -459,6 +460,7 @@ function getFrameData(document, win, windowId, options, scrolling) {
|
|
|
459
460
|
scrollPosition: docData.scrollPosition,
|
|
460
461
|
scrolling,
|
|
461
462
|
adoptedStyleSheets: docData.adoptedStyleSheets,
|
|
463
|
+
manifestData: docData.manifestData,
|
|
462
464
|
processed: true
|
|
463
465
|
};
|
|
464
466
|
}
|
package/single-file.js
CHANGED
|
@@ -131,5 +131,14 @@ async function getPageData(options = {}, initOptions, doc, win) {
|
|
|
131
131
|
}
|
|
132
132
|
pageData.content = Array.from(new Uint8Array(arrayBuffer));
|
|
133
133
|
}
|
|
134
|
+
if (pageData.manifest) {
|
|
135
|
+
const content = options.compressContent ? new Uint8Array(pageData.content) : pageData.content;
|
|
136
|
+
pageData.manifest.output = {
|
|
137
|
+
filename: pageData.filename,
|
|
138
|
+
mimeType: pageData.mimeType,
|
|
139
|
+
size: options.compressContent ? content.length : helper.getContentSize(content),
|
|
140
|
+
sha256: await helper.digest("SHA-256", content)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
134
143
|
return pageData;
|
|
135
144
|
}
|