single-file-core 1.6.25 → 1.6.27

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 CHANGED
@@ -376,10 +376,65 @@ function preProcessDoc(doc, win, options) {
376
376
  markedElements: elementsInfo.markedElements,
377
377
  invalidElements,
378
378
  scrollPosition: { x: win.scrollX, y: win.scrollY },
379
- adoptedStyleSheets: getStylesheetsContent(doc.adoptedStyleSheets)
379
+ adoptedStyleSheets: getStylesheetsContent(doc.adoptedStyleSheets),
380
+ manifestData: options.manifest ? getManifestData(doc, win) : undefined
380
381
  };
381
382
  }
382
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
+
383
438
  function insertCustomStylesheet(doc, content) {
384
439
  const parentElement = doc.body || doc.documentElement;
385
440
  if (parentElement) {
@@ -1351,7 +1406,7 @@ async function getDataURI(blob) {
1351
1406
 
1352
1407
  async function digest(algo, text) {
1353
1408
  try {
1354
- 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);
1355
1410
  const hash = globalThis.crypto && crypto.subtle ? await crypto.subtle.digest(algo, data) : sha.digest(algo, data);
1356
1411
  return hex(hash);
1357
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,12 +667,52 @@ 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() {
664
717
  this.doc.querySelectorAll("style[" + util.CUSTOM_STYLESHEET_ATTRIBUTE_NAME + "]").forEach(element => element.removeAttribute(util.CUSTOM_STYLESHEET_ATTRIBUTE_NAME));
665
718
  this.doc.body.querySelectorAll(":not(svg) title, meta, link[href][rel*=\"icon\"]").forEach(element => {
@@ -1463,6 +1516,7 @@ class Processor {
1463
1516
  options.scrollPosition = frameData.scrollPosition;
1464
1517
  options.scrolling = frameData.scrolling;
1465
1518
  options.adoptedStyleSheets = frameData.adoptedStyleSheets;
1519
+ options.manifestData = frameData.manifestData;
1466
1520
  frameData.runner = new Runner(options, processorHelper);
1467
1521
  frameData.frameElement = frameElement;
1468
1522
  await frameData.runner.loadPage();
@@ -1668,6 +1722,9 @@ class Processor {
1668
1722
  const { frameElement, pageData, frameWindowId, frameData } = capturedFrame;
1669
1723
  this.processorHelper.processFrame(frameElement, pageData, this.options, this.resources, frameWindowId, frameData);
1670
1724
  this.stats.addAll(pageData);
1725
+ if (pageData.manifest) {
1726
+ this.frameManifests.push(...pageData.manifest.frames);
1727
+ }
1671
1728
  }
1672
1729
  });
1673
1730
  }
@@ -1702,7 +1759,8 @@ class Processor {
1702
1759
  this.doc.head.appendChild(styleElement);
1703
1760
  }
1704
1761
  let stylesheetContent = "";
1705
- cssVariables.forEach(({ content, url }, indexResource) => {
1762
+ const variables = Array.from(cssVariables.entries()).sort(([firstIndex], [secondIndex]) => firstIndex - secondIndex);
1763
+ variables.forEach(([indexResource, { content, url }]) => {
1706
1764
  cssVariables.delete(indexResource);
1707
1765
  if (stylesheetContent) {
1708
1766
  stylesheetContent += ";";
@@ -1911,6 +1969,18 @@ function nameAssignedElements(hostElement) {
1911
1969
  return splitSlots;
1912
1970
  }
1913
1971
 
1972
+ function getManifestOptions(options) {
1973
+ const manifestOptions = {};
1974
+ Object.keys(options).sort().forEach(name => {
1975
+ const value = options[name];
1976
+ const primitive = typeof value == "boolean" || typeof value == "number";
1977
+ if ((primitive && !MANIFEST_EXCLUDED_OPTION_NAMES.includes(name)) || (typeof value == "string" && MANIFEST_STRING_OPTION_NAMES.includes(name))) {
1978
+ manifestOptions[name] = value;
1979
+ }
1980
+ });
1981
+ return manifestOptions;
1982
+ }
1983
+
1914
1984
  function removeInsertedParagraphs(doc) {
1915
1985
  getInsertedParagraphs(doc).forEach((_, paragraph) => paragraph.remove());
1916
1986
  }
@@ -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);
@@ -477,7 +478,8 @@ function getProcessorHelperClass(utilInstance) {
477
478
  resourceReferrer: options.resourceReferrer,
478
479
  referrerPolicy: getReferrerPolicy(resourceElement),
479
480
  acceptHeaders: options.acceptHeaders,
480
- networkTimeout: options.networkTimeout
481
+ networkTimeout: options.networkTimeout,
482
+ manifestEntries: options.manifestEntries
481
483
  })).data;
482
484
  // eslint-disable-next-line no-unused-vars
483
485
  } catch (error) {
package/core/util.js CHANGED
@@ -72,6 +72,15 @@ const CONTENT_TYPES_HTML = ["text/html", "application/xhtml+xml"];
72
72
  const EXPECTED_TYPES_MEDIA = ["font", "image", "video", "audio", "model"];
73
73
  const EXPECTED_TYPE_IMAGE = "image";
74
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"];
75
84
 
76
85
  const URL = globalThis.URL;
77
86
  const DOMParser = globalThis.DOMParser;
@@ -246,6 +255,16 @@ function getInstance(utilOptions) {
246
255
  };
247
256
 
248
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) {
249
268
  let response, startTime, networkTimeoutId, networkTimeoutPromise, resolveNetworkTimeoutPromise;
250
269
  const fetchResource = utilOptions.fetch;
251
270
  const fetchFrameResource = utilOptions.frameFetch;
@@ -254,6 +273,7 @@ function getInstance(utilOptions) {
254
273
  log(" // STARTED download url =", resourceURL, "asBinary =", options.asBinary);
255
274
  }
256
275
  if (options.blockMixedContent && /^https:/i.test(options.baseURI) && !/^https:/i.test(resourceURL) && !/^blob:https:/i.test(resourceURL)) {
276
+ setManifestOutcome(entry, MANIFEST_OUTCOME_BLOCKED);
257
277
  return getFetchResponse(resourceURL, options);
258
278
  }
259
279
  if (options.networkTimeout) {
@@ -274,21 +294,24 @@ function getInstance(utilOptions) {
274
294
  fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
275
295
  networkTimeoutPromise
276
296
  ]);
297
+ setManifestResponse(entry, response, MANIFEST_FETCHED_BY_FRAME);
277
298
  // eslint-disable-next-line no-unused-vars
278
299
  } catch (error) {
279
300
  response = await Promise.race([
280
301
  fetchResource(resourceURL, { referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
281
302
  networkTimeoutPromise
282
303
  ]);
304
+ setManifestResponse(entry, response, MANIFEST_FETCHED_BY_PAGE);
283
305
  }
284
306
  } else {
285
307
  response = await Promise.race([
286
308
  fetchResource(resourceURL, { referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
287
309
  networkTimeoutPromise
288
310
  ]);
311
+ setManifestResponse(entry, response, MANIFEST_FETCHED_BY_PAGE);
289
312
  }
290
- // eslint-disable-next-line no-unused-vars
291
313
  } catch (error) {
314
+ setManifestError(entry, error);
292
315
  return getFailedFetchResponse(resourceURL, options);
293
316
  } finally {
294
317
  resolveNetworkTimeoutPromise();
@@ -299,10 +322,11 @@ function getInstance(utilOptions) {
299
322
  let buffer;
300
323
  try {
301
324
  buffer = await response.arrayBuffer();
302
- // eslint-disable-next-line no-unused-vars
303
325
  } catch (error) {
326
+ setManifestError(entry, error);
304
327
  return options.inline ? { data: options.asBinary ? helper.EMPTY_RESOURCE : "", resourceURL } : { resourceURL };
305
328
  }
329
+ await setManifestContent(entry, buffer);
306
330
  resourceURL = response.url || resourceURL;
307
331
  let contentType = "", charset;
308
332
  try {
@@ -322,8 +346,15 @@ function getInstance(utilOptions) {
322
346
  if (!charset && options.charset) {
323
347
  charset = options.charset;
324
348
  }
349
+ if (entry) {
350
+ entry.contentType = contentType;
351
+ if (charset) {
352
+ entry.charset = charset;
353
+ }
354
+ }
325
355
  if (options.asBinary) {
326
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);
327
358
  return getFetchResponse(resourceURL, options);
328
359
  }
329
360
  try {
@@ -331,19 +362,23 @@ function getInstance(utilOptions) {
331
362
  log(" // ENDED download url =", resourceURL, "delay =", Date.now() - startTime);
332
363
  }
333
364
  if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
365
+ setManifestOutcome(entry, MANIFEST_OUTCOME_OVERSIZED);
334
366
  return getFetchResponse(resourceURL, options);
335
367
  } else {
368
+ setManifestOutcome(entry, MANIFEST_OUTCOME_EMBEDDED);
336
369
  return getFetchResponse(resourceURL, options, buffer, null, contentType);
337
370
  }
338
- // eslint-disable-next-line no-unused-vars
339
371
  } catch (error) {
372
+ setManifestError(entry, error);
340
373
  return getFetchResponse(resourceURL, options);
341
374
  }
342
375
  } else {
343
376
  if (response.status >= 400 && options.expectedType != EXPECTED_TYPE_DOCUMENT) {
377
+ setManifestOutcome(entry, MANIFEST_OUTCOME_REJECTED);
344
378
  return getFailedFetchResponse(resourceURL, options);
345
379
  }
346
380
  if (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT)) {
381
+ setManifestOutcome(entry, MANIFEST_OUTCOME_REJECTED);
347
382
  return getFetchResponse(resourceURL, options);
348
383
  }
349
384
  if (!charset) {
@@ -353,14 +388,16 @@ function getInstance(utilOptions) {
353
388
  log(" // ENDED download url =", resourceURL, "delay =", Date.now() - startTime);
354
389
  }
355
390
  if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
391
+ setManifestOutcome(entry, MANIFEST_OUTCOME_OVERSIZED);
356
392
  return getFetchResponse(resourceURL, options, null, charset);
357
393
  } else {
358
394
  try {
359
395
  const fetchResponse = await getFetchResponse(resourceURL, options, buffer, charset, contentType);
360
396
  fetchResponse.referrerPolicy = (response.headers && response.headers.get("referrer-policy")) || undefined;
397
+ setManifestOutcome(entry, MANIFEST_OUTCOME_EMBEDDED);
361
398
  return fetchResponse;
362
- // eslint-disable-next-line no-unused-vars
363
399
  } catch (error) {
400
+ setManifestError(entry, error);
364
401
  return getFetchResponse(resourceURL, options, null, charset);
365
402
  }
366
403
  }
@@ -368,6 +405,60 @@ function getInstance(utilOptions) {
368
405
  }
369
406
  }
370
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
+
371
462
  async function getFailedFetchResponse(resourceURL, options) {
372
463
  const response = await getFetchResponse(resourceURL, options);
373
464
  response.failed = true;
@@ -398,7 +489,7 @@ async function getFetchResponse(resourceURL, options, data, charset, contentType
398
489
  charset = "utf-8";
399
490
  data = new TextDecoder(charset).decode(data);
400
491
  }
401
- data = data.replace(/\ufeff/gi, "");
492
+ data = data.replace(/^\ufeff/, "");
402
493
  }
403
494
  } else if (options.inline) {
404
495
  data = options.asBinary ? helper.EMPTY_RESOURCE : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.25",
3
+ "version": "1.6.27",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -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
  }