single-file-core 1.6.17 → 1.6.18

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
@@ -64,6 +64,7 @@ const INPUT_VALUE_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "input-value";
64
64
  const INPUT_CHECKED_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "input-checked";
65
65
  const LAZY_SRC_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "lazy-loaded-src";
66
66
  const STYLESHEET_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "stylesheet";
67
+ const LINK_STYLESHEET_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "link-stylesheet";
67
68
  const DISABLED_NOSCRIPT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "disabled-noscript";
68
69
  const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "selected-content";
69
70
  const INVALID_ELEMENT_ATTRIBUTE_NAME = "data-" + SINGLE_FILE_PREFIX + "invalid-element";
@@ -174,6 +175,7 @@ export {
174
175
  STYLE_ATTRIBUTE_NAME,
175
176
  LAZY_SRC_ATTRIBUTE_NAME,
176
177
  STYLESHEET_ATTRIBUTE_NAME,
178
+ LINK_STYLESHEET_ATTRIBUTE_NAME,
177
179
  SELECTED_CONTENT_ATTRIBUTE_NAME,
178
180
  INVALID_ELEMENT_ATTRIBUTE_NAME,
179
181
  ASYNC_SCRIPT_ATTRIBUTE_NAME,
@@ -330,6 +332,7 @@ function preProcessDoc(doc, win, options) {
330
332
  };
331
333
  }
332
334
  setNestingMarkersData(doc);
335
+ const linkStylesheets = markLinkStylesheets(doc, elementsInfo.markedElements);
333
336
  let referrer = "";
334
337
  if (doc.referrer) {
335
338
  try {
@@ -343,7 +346,8 @@ function preProcessDoc(doc, win, options) {
343
346
  canvases: elementsInfo.canvases,
344
347
  fonts: getFontsData(),
345
348
  worklets: getWorkletsData(),
346
- stylesheets: getStylesheetsData(doc),
349
+ stylesheets: getStylesheetsData(doc, elementsInfo.markedElements),
350
+ linkStylesheets,
347
351
  images: elementsInfo.images,
348
352
  posters: elementsInfo.posters,
349
353
  videos: elementsInfo.videos,
@@ -1087,7 +1091,7 @@ function postProcessDoc(doc, markedElements, invalidElements) {
1087
1091
  doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.removeAttribute("hidden"));
1088
1092
  }
1089
1093
  if (!markedElements) {
1090
- const singleFileAttributes = [REMOVED_CONTENT_ATTRIBUTE_NAME, HIDDEN_FRAME_ATTRIBUTE_NAME, HIDDEN_CONTENT_ATTRIBUTE_NAME, PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, IMAGE_ATTRIBUTE_NAME, POSTER_ATTRIBUTE_NAME, VIDEO_ATTRIBUTE_NAME, CANVAS_ATTRIBUTE_NAME, INPUT_VALUE_ATTRIBUTE_NAME, INPUT_CHECKED_ATTRIBUTE_NAME, SHADOW_ROOT_ATTRIBUTE_NAME, SLOT_ATTRIBUTE_NAME, ASSIGNED_SLOT_ATTRIBUTE_NAME, STYLESHEET_ATTRIBUTE_NAME, ASYNC_SCRIPT_ATTRIBUTE_NAME];
1094
+ const singleFileAttributes = [REMOVED_CONTENT_ATTRIBUTE_NAME, HIDDEN_FRAME_ATTRIBUTE_NAME, HIDDEN_CONTENT_ATTRIBUTE_NAME, PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, IMAGE_ATTRIBUTE_NAME, POSTER_ATTRIBUTE_NAME, VIDEO_ATTRIBUTE_NAME, CANVAS_ATTRIBUTE_NAME, INPUT_VALUE_ATTRIBUTE_NAME, INPUT_CHECKED_ATTRIBUTE_NAME, SHADOW_ROOT_ATTRIBUTE_NAME, SLOT_ATTRIBUTE_NAME, ASSIGNED_SLOT_ATTRIBUTE_NAME, STYLESHEET_ATTRIBUTE_NAME, LINK_STYLESHEET_ATTRIBUTE_NAME, ASYNC_SCRIPT_ATTRIBUTE_NAME];
1091
1095
  markedElements = doc.querySelectorAll(singleFileAttributes.map(name => "[" + name + "]").join(","));
1092
1096
  }
1093
1097
  markedElements.forEach(element => {
@@ -1106,6 +1110,7 @@ function postProcessDoc(doc, markedElements, invalidElements) {
1106
1110
  element.removeAttribute(SLOT_ATTRIBUTE_NAME);
1107
1111
  element.removeAttribute(ASSIGNED_SLOT_ATTRIBUTE_NAME);
1108
1112
  element.removeAttribute(STYLESHEET_ATTRIBUTE_NAME);
1113
+ element.removeAttribute(LINK_STYLESHEET_ATTRIBUTE_NAME);
1109
1114
  element.removeAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME);
1110
1115
  element.removeAttribute(STYLE_ATTRIBUTE_NAME);
1111
1116
  });
@@ -1114,7 +1119,16 @@ function postProcessDoc(doc, markedElements, invalidElements) {
1114
1119
  }
1115
1120
  }
1116
1121
 
1117
- function getStylesheetsData(doc) {
1122
+ function markLinkStylesheets(doc, markedElements) {
1123
+ const linkElements = Array.from(doc.querySelectorAll("link[rel*=stylesheet]"));
1124
+ linkElements.forEach((linkElement, linkIndex) => {
1125
+ linkElement.setAttribute(LINK_STYLESHEET_ATTRIBUTE_NAME, linkIndex);
1126
+ markedElements.push(linkElement);
1127
+ });
1128
+ return linkElements;
1129
+ }
1130
+
1131
+ function getStylesheetsData(doc, markedElements) {
1118
1132
  if (doc) {
1119
1133
  const contents = [];
1120
1134
  doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
@@ -1129,6 +1143,7 @@ function getStylesheetsData(doc) {
1129
1143
  const sheetStylesheet = Array.from(styleElement.sheet.cssRules).map(cssRule => cssRule.cssText).join("\n");
1130
1144
  if (!stylesheet || textContentStylesheet != sheetStylesheet) {
1131
1145
  styleElement.setAttribute(STYLESHEET_ATTRIBUTE_NAME, styleIndex);
1146
+ markedElements.push(styleElement);
1132
1147
  contents[styleIndex] = Array.from(styleElement.sheet.cssRules).map(cssRule => cssRule.cssText).join("\n");
1133
1148
  }
1134
1149
  }
package/core/index.js CHANGED
@@ -185,6 +185,7 @@ class Runner {
185
185
  this.options.rootDocument = root;
186
186
  this.options.updatedResources = this.options.updatedResources || {};
187
187
  this.options.fontTests = new Map();
188
+ this.options.stylesheetReferrers = new Map();
188
189
  if (root && !this.options.saveFilenameTemplateData && (this.options.openEditor || /{digest-sha-\d/.test(this.options.filenameTemplate))) {
189
190
  this.options.saveFilenameTemplateData = true;
190
191
  this.options.omitReferrerInTemplateData = !/{url-referrer/.test(this.options.filenameTemplate);
@@ -197,6 +198,7 @@ class Runner {
197
198
  this.options.fonts = docData.fonts;
198
199
  this.options.worklets = docData.worklets;
199
200
  this.options.stylesheets = docData.stylesheets;
201
+ this.options.linkStylesheets = docData.linkStylesheets;
200
202
  this.options.images = docData.images;
201
203
  this.options.posters = docData.posters;
202
204
  this.options.videos = docData.videos;
@@ -345,9 +347,9 @@ class BatchRequest {
345
347
  this.duplicates = new Map();
346
348
  }
347
349
 
348
- addURL(resourceURL, { asBinary, expectedType, groupDuplicates, baseURI, blockMixedContent, contentType } = {}) {
350
+ addURL(resourceURL, { asBinary, expectedType, groupDuplicates, baseURI, blockMixedContent, contentType, referrerPolicy, stylesheetURL, stylesheetReferrerPolicy } = {}) {
349
351
  return new Promise((resolve, reject) => {
350
- const requestKey = JSON.stringify([resourceURL, asBinary, expectedType, baseURI, blockMixedContent, contentType]);
352
+ const requestKey = JSON.stringify([resourceURL, asBinary, expectedType, baseURI, blockMixedContent, contentType, referrerPolicy, stylesheetURL, stylesheetReferrerPolicy]);
351
353
  let resourceRequests = this.requests.get(requestKey);
352
354
  if (this.cancelled) {
353
355
  reject();
@@ -381,7 +383,7 @@ class BatchRequest {
381
383
  const resourceURLs = [...this.requests.keys()];
382
384
  let indexResource = 0;
383
385
  return Promise.all(resourceURLs.map(async requestKey => {
384
- const [resourceURL, asBinary, expectedType, baseURI, blockMixedContent, contentType] = JSON.parse(requestKey);
386
+ const [resourceURL, asBinary, expectedType, baseURI, blockMixedContent, contentType, referrerPolicy, stylesheetURL, stylesheetReferrerPolicy] = JSON.parse(requestKey);
385
387
  const resourceRequests = this.requests.get(requestKey);
386
388
  try {
387
389
  const currentIndexResource = indexResource;
@@ -395,6 +397,9 @@ class BatchRequest {
395
397
  maxResourceSizeEnabled: options.maxResourceSizeEnabled,
396
398
  frameId: options.windowId,
397
399
  resourceReferrer: options.resourceReferrer,
400
+ referrerPolicy,
401
+ stylesheetURL,
402
+ stylesheetReferrerPolicy,
398
403
  baseURI,
399
404
  blockMixedContent,
400
405
  acceptHeaders: options.acceptHeaders,
@@ -1443,6 +1448,7 @@ class Processor {
1443
1448
  options.fonts = frameData.fonts;
1444
1449
  options.worklets = frameData.worklets;
1445
1450
  options.stylesheets = frameData.stylesheets;
1451
+ options.linkStylesheets = null;
1446
1452
  options.images = frameData.images;
1447
1453
  options.posters = frameData.posters;
1448
1454
  options.videos = frameData.videos;
@@ -37,6 +37,7 @@ const FILE_URI_PREFIX = /^file:\/\//;
37
37
  const EMPTY_URL = /^https?:\/\/+\s*$/;
38
38
  const NOT_EMPTY_URL = /^(https?:\/\/|file:\/\/|blob:).+/;
39
39
  const PREFIX_DATA_URI_IMAGE_SVG = "data:image/svg+xml";
40
+ const REFERRER_POLICY_ELEMENTS = ["IMG", "LINK", "SCRIPT"];
40
41
  const UTF8_CHARSET = "utf-8";
41
42
  const REGEXP_URL_FUNCTION = /(url|local|-sf-url-original)\(.*?\)\s*(,|$)/g;
42
43
  const REGEXP_URL_SIMPLE_QUOTES_FN = /url\s*\(\s*'(.*?)'\s*\)/i;
@@ -83,7 +84,9 @@ export {
83
84
  testValidPath,
84
85
  testValidURL,
85
86
  resizeImage,
86
- toDataURI
87
+ toDataURI,
88
+ getReferrerPolicy,
89
+ getStylesheetReferrer
87
90
  };
88
91
 
89
92
  function getProcessorHelperCommonClass(utilInstance, cssTreeInstance) {
@@ -290,7 +293,7 @@ class ProcessorHelperCommon {
290
293
  element.style.setProperty("background-attachment", "scroll", "important");
291
294
  }
292
295
 
293
- async getStylesheetContent(resourceURL, options) {
296
+ async getStylesheetContent(resourceURL, options, stylesheetReferrer = {}) {
294
297
  const content = await util.getContent(resourceURL, {
295
298
  inline: !options.compressContent,
296
299
  maxResourceSize: options.maxResourceSize,
@@ -299,6 +302,8 @@ class ProcessorHelperCommon {
299
302
  frameId: options.frameId,
300
303
  charset: options.charset,
301
304
  resourceReferrer: options.resourceReferrer,
305
+ stylesheetURL: stylesheetReferrer.stylesheetURL,
306
+ stylesheetReferrerPolicy: stylesheetReferrer.stylesheetReferrerPolicy,
302
307
  baseURI: options.baseURI,
303
308
  blockMixedContent: options.blockMixedContent,
304
309
  expectedType: "stylesheet",
@@ -315,6 +320,8 @@ class ProcessorHelperCommon {
315
320
  frameId: options.frameId,
316
321
  charset: options.charset,
317
322
  resourceReferrer: options.resourceReferrer,
323
+ stylesheetURL: stylesheetReferrer.stylesheetURL,
324
+ stylesheetReferrerPolicy: stylesheetReferrer.stylesheetReferrerPolicy,
318
325
  baseURI: options.baseURI,
319
326
  blockMixedContent: options.blockMixedContent,
320
327
  expectedType: "stylesheet",
@@ -326,6 +333,24 @@ class ProcessorHelperCommon {
326
333
  }
327
334
  }
328
335
 
336
+ getLiveStylesheetContent(element, options) {
337
+ const linkIndex = element.getAttribute(util.LINK_STYLESHEET_ATTRIBUTE_NAME);
338
+ if (options.linkStylesheets && linkIndex !== null) {
339
+ const liveElement = options.linkStylesheets[Number(linkIndex)];
340
+ if (liveElement && liveElement.href == element.href) {
341
+ try {
342
+ const cssRules = liveElement.sheet && liveElement.sheet.cssRules;
343
+ if (cssRules && cssRules.length) {
344
+ return Array.from(cssRules).map(cssRule => cssRule.cssText).join("\n");
345
+ }
346
+ // eslint-disable-next-line no-unused-vars
347
+ } catch (error) {
348
+ // ignored
349
+ }
350
+ }
351
+ }
352
+ }
353
+
329
354
  processShortcutIcons(doc) {
330
355
  let shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel=\"shortcut icon\"]")));
331
356
  if (!shortcutIcon) {
@@ -360,7 +385,7 @@ class ProcessorHelperCommon {
360
385
  });
361
386
  }
362
387
 
363
- resolveStylesheetURLs(stylesheet, baseURI, workStylesheet) {
388
+ resolveStylesheetURLs(stylesheet, baseURI, workStylesheet, stylesheetReferrer, stylesheetReferrers) {
364
389
  const urls = getUrlFunctions(stylesheet);
365
390
  urls.map(urlNode => {
366
391
  const originalResourceURL = urlNode.value;
@@ -383,6 +408,9 @@ class ProcessorHelperCommon {
383
408
  }
384
409
  if (testValidURL(resolvedURL)) {
385
410
  urlNode.value = resolvedURL;
411
+ if (stylesheetReferrer && stylesheetReferrers && !stylesheetReferrers.has(normalizeURL(resolvedURL))) {
412
+ stylesheetReferrers.set(normalizeURL(resolvedURL), stylesheetReferrer);
413
+ }
386
414
  }
387
415
  } else {
388
416
  urlNode.value = util.EMPTY_RESOURCE;
@@ -670,6 +698,19 @@ function findShortcutIcon(shortcutIcons) {
670
698
  return shortcutIcons[0];
671
699
  }
672
700
 
701
+ function getStylesheetReferrer(resourceURL, options) {
702
+ return (options.stylesheetReferrers && options.stylesheetReferrers.get(normalizeURL(resourceURL))) || {};
703
+ }
704
+
705
+ function getReferrerPolicy(element) {
706
+ if (element.tagName.toUpperCase() == "SOURCE" && element.parentElement && element.parentElement.tagName.toUpperCase() == "PICTURE") {
707
+ element = element.parentElement.querySelector("img");
708
+ }
709
+ if (element && REFERRER_POLICY_ELEMENTS.includes(element.tagName.toUpperCase()) && element.referrerPolicy) {
710
+ return element.referrerPolicy;
711
+ }
712
+ }
713
+
673
714
  function isDataURL(url) {
674
715
  return url && (url.startsWith(DATA_URI_PREFIX) || url.startsWith(BLOB_URI_PREFIX));
675
716
  }
@@ -71,6 +71,8 @@ import {
71
71
  testIgnoredPath,
72
72
  testValidPath,
73
73
  testValidURL,
74
+ getReferrerPolicy,
75
+ getStylesheetReferrer,
74
76
  resizeImage,
75
77
  toDataURI
76
78
  } from "./processor-helper-common.js";
@@ -99,7 +101,7 @@ function getProcessorHelperClass(utilInstance) {
99
101
  if (!options.inlineStylesheetsRefs.has(element)) {
100
102
  if (!options.blockStylesheets || (options.keepPrintStyleSheets && stylesheetInfo.mediaText == "print")) {
101
103
  if (element.tagName.toUpperCase() == "LINK") {
102
- stylesheet = await this.resolveLinkStylesheetURLs(element.href, baseURI, options, workStyleElement);
104
+ stylesheet = await this.resolveLinkStylesheetURLs(element, element.href, baseURI, options, workStyleElement);
103
105
  } else {
104
106
  stylesheet = cssTree.parse(element.textContent, { context: "stylesheet", parseCustomProperty: true });
105
107
  const importFound = await this.resolveImportURLs(stylesheet, baseURI, options, workStyleElement);
@@ -194,9 +196,9 @@ function getProcessorHelperClass(utilInstance) {
194
196
  });
195
197
  }
196
198
 
197
- async resolveImportURLs(stylesheet, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
199
+ async resolveImportURLs(stylesheet, baseURI, options, workStylesheet, importedStyleSheets = new Set(), stylesheetReferrer) {
198
200
  let importFound;
199
- this.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet);
201
+ this.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet, stylesheetReferrer, options.stylesheetReferrers);
200
202
  const imports = getImportFunctions(stylesheet);
201
203
  await Promise.all(imports.map(async node => {
202
204
  const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
@@ -212,7 +214,7 @@ function getProcessorHelperClass(utilInstance) {
212
214
  }
213
215
  if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
214
216
  options.inline = true;
215
- const content = await this.getStylesheetContent(resourceURL, options);
217
+ const content = await this.getStylesheetContent(resourceURL, options, stylesheetReferrer);
216
218
  resourceURL = content.resourceURL;
217
219
  content.data = getUpdatedResourceContent(resourceURL, options) || content.data;
218
220
  if (content.data && content.data.match(/^<!doctype /i)) {
@@ -233,7 +235,7 @@ function getProcessorHelperClass(utilInstance) {
233
235
  const importedStylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
234
236
  const ancestorStyleSheets = new Set(importedStyleSheets);
235
237
  ancestorStyleSheets.add(resourceURL);
236
- await this.resolveImportURLs(importedStylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets);
238
+ await this.resolveImportURLs(importedStylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets, { stylesheetURL: resourceURL, stylesheetReferrerPolicy: content.referrerPolicy });
237
239
  for (let keyName of Object.keys(importedStylesheet)) {
238
240
  node[keyName] = importedStylesheet[keyName];
239
241
  }
@@ -245,7 +247,7 @@ function getProcessorHelperClass(utilInstance) {
245
247
  return importFound;
246
248
  }
247
249
 
248
- async resolveLinkStylesheetURLs(resourceURL, baseURI, options, workStylesheet) {
250
+ async resolveLinkStylesheetURLs(element, resourceURL, baseURI, options, workStylesheet) {
249
251
  resourceURL = normalizeURL(resourceURL);
250
252
  if (resourceURL && resourceURL != baseURI && resourceURL != ABOUT_BLANK_URI) {
251
253
  const content = await util.getContent(resourceURL, {
@@ -255,6 +257,7 @@ function getProcessorHelperClass(utilInstance) {
255
257
  charset: options.charset,
256
258
  frameId: options.frameId,
257
259
  resourceReferrer: options.resourceReferrer,
260
+ referrerPolicy: getReferrerPolicy(element),
258
261
  validateTextContentType: true,
259
262
  baseURI: baseURI,
260
263
  blockMixedContent: options.blockMixedContent,
@@ -262,9 +265,15 @@ function getProcessorHelperClass(utilInstance) {
262
265
  acceptHeaders: options.acceptHeaders,
263
266
  networkTimeout: options.networkTimeout
264
267
  });
268
+ if (content.failed) {
269
+ const liveContent = this.getLiveStylesheetContent(element, options);
270
+ if (liveContent) {
271
+ content.data = liveContent;
272
+ }
273
+ }
265
274
  if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
266
275
  options = Object.assign({}, options, { charset: getCharset(content.data) });
267
- return this.resolveLinkStylesheetURLs(resourceURL, baseURI, options, workStylesheet);
276
+ return this.resolveLinkStylesheetURLs(element, resourceURL, baseURI, options, workStylesheet);
268
277
  }
269
278
  resourceURL = content.resourceURL;
270
279
  content.data = getUpdatedResourceContent(content.resourceURL, options) || content.data;
@@ -272,7 +281,7 @@ function getProcessorHelperClass(utilInstance) {
272
281
  content.data = "";
273
282
  }
274
283
  let stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
275
- const importFound = await this.resolveImportURLs(stylesheet, resourceURL, options, workStylesheet);
284
+ const importFound = await this.resolveImportURLs(stylesheet, resourceURL, options, workStylesheet, undefined, { stylesheetURL: resourceURL, stylesheetReferrerPolicy: content.referrerPolicy });
276
285
  if (importFound) {
277
286
  stylesheet = cssTree.parse(cssTree.generate(stylesheet), { context: "stylesheet", parseCustomProperty: true });
278
287
  }
@@ -302,7 +311,8 @@ function getProcessorHelperClass(utilInstance) {
302
311
  asBinary: true,
303
312
  expectedType: "font",
304
313
  baseURI,
305
- blockMixedContent: options.blockMixedContent
314
+ blockMixedContent: options.blockMixedContent,
315
+ ...getStylesheetReferrer(resourceURL, options)
306
316
  });
307
317
  let resourceURLs = resources.fonts.get(urlNode);
308
318
  if (!resourceURLs) {
@@ -324,7 +334,7 @@ function getProcessorHelperClass(utilInstance) {
324
334
  if (!options.blockImages) {
325
335
  const resourceURL = normalizeURL(originalResourceURL);
326
336
  if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
327
- let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
337
+ let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages, ...getStylesheetReferrer(resourceURL, options) });
328
338
  if (!originalResourceURL.startsWith("#")) {
329
339
  const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
330
340
  if (duplicate && options.groupDuplicateImages && util.getContentSize(content) < maxSizeDuplicateImages) {
@@ -373,7 +383,7 @@ function getProcessorHelperClass(utilInstance) {
373
383
  const groupDuplicates = options.groupDuplicateImages && resourceElement.tagName.toUpperCase() == "IMG" && attributeName == "src";
374
384
  let { content, indexResource, duplicate } = await batchRequest.addURL(
375
385
  resourceURL,
376
- { asBinary: true, expectedType, contentType: declaredContentType, groupDuplicates });
386
+ { asBinary: true, expectedType, contentType: declaredContentType, groupDuplicates, referrerPolicy: getReferrerPolicy(resourceElement) });
377
387
  if (originURL) {
378
388
  if (this.testEmptyResource(content)) {
379
389
  try {
@@ -393,6 +403,7 @@ function getProcessorHelperClass(utilInstance) {
393
403
  maxResourceSizeEnabled: options.maxResourceSizeEnabled,
394
404
  frameId: options.windowId,
395
405
  resourceReferrer: options.resourceReferrer,
406
+ referrerPolicy: getReferrerPolicy(resourceElement),
396
407
  acceptHeaders: options.acceptHeaders,
397
408
  networkTimeout: options.networkTimeout
398
409
  })).data;
@@ -402,7 +413,7 @@ function getProcessorHelperClass(utilInstance) {
402
413
  }
403
414
  }
404
415
  }
405
- if (options.imageReductionFactor > 1 && expectedType == "image") {
416
+ if (options.imageReductionFactor > 1 && expectedType == "image" && attributeName != "background") {
406
417
  content = await resizeImage(doc, content, options, resourceElement);
407
418
  }
408
419
  if (removeElementIfMissing && this.testEmptyResource(content)) {
@@ -453,8 +464,8 @@ function getProcessorHelperClass(utilInstance) {
453
464
  }));
454
465
  }
455
466
 
456
- async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest) {
457
- const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
467
+ async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest, resourceElement) {
468
+ const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", referrerPolicy: getReferrerPolicy(resourceElement) });
458
469
  const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
459
470
  if (forbiddenPrefixFound) {
460
471
  return "";
@@ -527,6 +538,7 @@ function getProcessorHelperClass(utilInstance) {
527
538
  maxResourceSizeEnabled: options.maxResourceSizeEnabled,
528
539
  frameId: options.windowId,
529
540
  resourceReferrer: options.resourceReferrer,
541
+ referrerPolicy: getReferrerPolicy(element),
530
542
  baseURI: options.baseURI,
531
543
  blockMixedContent: options.blockMixedContent,
532
544
  expectedType: "script",
@@ -60,6 +60,8 @@ import {
60
60
  testIgnoredPath,
61
61
  testValidPath,
62
62
  testValidURL,
63
+ getReferrerPolicy,
64
+ getStylesheetReferrer,
63
65
  resizeImage,
64
66
  toDataURI
65
67
  } from "./processor-helper-common.js";
@@ -212,10 +214,10 @@ function getProcessorHelperClass(utilInstance) {
212
214
  }
213
215
  }
214
216
 
215
- async resolveImportURLs(stylesheetInfo, baseURI, options, workStylesheet, resources, stylesheets, importedStyleSheets = new Set()) {
217
+ async resolveImportURLs(stylesheetInfo, baseURI, options, workStylesheet, resources, stylesheets, importedStyleSheets = new Set(), stylesheetReferrer) {
216
218
  const stylesheet = stylesheetInfo.stylesheet;
217
219
  const scoped = stylesheetInfo.scoped;
218
- this.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet);
220
+ this.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet, stylesheetReferrer, options.stylesheetReferrers);
219
221
  const imports = getImportFunctions(stylesheet);
220
222
  await Promise.all(imports.map(async node => {
221
223
  const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
@@ -252,14 +254,14 @@ function getProcessorHelperClass(utilInstance) {
252
254
  const importDeclaresLayer = Boolean(cssTree.find(node, node => node.type == "Layer" || ((node.type == "Identifier" || node.type == "Function") && node.name.toLowerCase() == "layer")));
253
255
  stylesheets.set({ urlNode, importNode: node, importParent: stylesheet, importDeclaresLayer }, stylesheetInfo);
254
256
  const requestedURL = resourceURL;
255
- const content = await this.getStylesheetContent(resourceURL, options);
257
+ const content = await this.getStylesheetContent(resourceURL, options, stylesheetReferrer);
256
258
  stylesheetInfo.url = resourceURL = content.resourceURL;
257
259
  content.data = getUpdatedResourceContent(resourceURL, options) || content.data;
258
260
  stylesheetInfo.stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
259
261
  const ancestorStyleSheets = new Set(importedStyleSheets);
260
262
  ancestorStyleSheets.add(requestedURL);
261
263
  ancestorStyleSheets.add(resourceURL);
262
- await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets, ancestorStyleSheets);
264
+ await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets, ancestorStyleSheets, { stylesheetURL: resourceURL, stylesheetReferrerPolicy: content.referrerPolicy });
263
265
  urlNode.importedChildren = stylesheetInfo.stylesheet.children;
264
266
  urlNode.importedMediaText = mediaText;
265
267
  urlNode.importedLayerName = layerName;
@@ -279,6 +281,7 @@ function getProcessorHelperClass(utilInstance) {
279
281
  charset: options.charset,
280
282
  frameId: options.frameId,
281
283
  resourceReferrer: options.resourceReferrer,
284
+ referrerPolicy: getReferrerPolicy(element),
282
285
  validateTextContentType: true,
283
286
  baseURI: baseURI,
284
287
  blockMixedContent: options.blockMixedContent,
@@ -286,6 +289,12 @@ function getProcessorHelperClass(utilInstance) {
286
289
  acceptHeaders: options.acceptHeaders,
287
290
  networkTimeout: options.networkTimeout
288
291
  });
292
+ if (content.failed) {
293
+ const liveContent = this.getLiveStylesheetContent(element, options);
294
+ if (liveContent) {
295
+ content.data = liveContent;
296
+ }
297
+ }
289
298
  if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
290
299
  options = Object.assign({}, options, { charset: getCharset(content.data) });
291
300
  await this.resolveLinkStylesheetURLs(stylesheetInfo, element, resourceURL, baseURI, options, workStylesheet, resources, stylesheets);
@@ -293,7 +302,7 @@ function getProcessorHelperClass(utilInstance) {
293
302
  resourceURL = content.resourceURL;
294
303
  content.data = getUpdatedResourceContent(content.resourceURL, options) || content.data;
295
304
  stylesheetInfo.stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
296
- await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets);
305
+ await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets, undefined, { stylesheetURL: resourceURL, stylesheetReferrerPolicy: content.referrerPolicy });
297
306
  }
298
307
  }
299
308
  }
@@ -318,7 +327,8 @@ function getProcessorHelperClass(utilInstance) {
318
327
  asBinary: true,
319
328
  expectedType: "font",
320
329
  baseURI,
321
- blockMixedContent: options.blockMixedContent
330
+ blockMixedContent: options.blockMixedContent,
331
+ ...getStylesheetReferrer(resourceURL, options)
322
332
  });
323
333
  const name = "fonts/" + indexResource + extension;
324
334
  if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
@@ -400,7 +410,7 @@ function getProcessorHelperClass(utilInstance) {
400
410
  const resourceURL = normalizeURL(originalResourceURL);
401
411
  if ((!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) || testGeneratedDataURI(resourceURL, options)) {
402
412
  let { content, indexResource, contentType, extension } = await batchRequest.addURL(resourceURL,
403
- { asBinary: true, expectedType: "image" });
413
+ { asBinary: true, expectedType: "image", ...getStylesheetReferrer(resourceURL, options) });
404
414
  const name = "images/" + indexResource + extension;
405
415
  if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
406
416
  urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + name;
@@ -441,7 +451,7 @@ function getProcessorHelperClass(utilInstance) {
441
451
  if (testValidURL(resourceURL) || generatedDataURI) {
442
452
  const declaredContentType = ["OBJECT", "EMBED"].includes(resourceElement.tagName.toUpperCase()) ? resourceElement.getAttribute("type") : "";
443
453
  let { content, indexResource, extension, contentType, charset } = await batchRequest.addURL(resourceURL,
444
- { asBinary: true, expectedType, contentType: declaredContentType });
454
+ { asBinary: true, expectedType, contentType: declaredContentType, referrerPolicy: getReferrerPolicy(resourceElement) });
445
455
  if (originURL) {
446
456
  if (this.testEmptyResource(content)) {
447
457
  try {
@@ -460,6 +470,7 @@ function getProcessorHelperClass(utilInstance) {
460
470
  maxResourceSizeEnabled: options.maxResourceSizeEnabled,
461
471
  frameId: options.windowId,
462
472
  resourceReferrer: options.resourceReferrer,
473
+ referrerPolicy: getReferrerPolicy(resourceElement),
463
474
  acceptHeaders: options.acceptHeaders,
464
475
  networkTimeout: options.networkTimeout
465
476
  })).data;
@@ -469,7 +480,7 @@ function getProcessorHelperClass(utilInstance) {
469
480
  }
470
481
  }
471
482
  }
472
- if (options.imageReductionFactor > 1 && expectedType == "image") {
483
+ if (options.imageReductionFactor > 1 && expectedType == "image" && attributeName != "background") {
473
484
  const dataURI = await resizeImage(doc, await toDataURI(new Blob([content], { type: contentType }), charset), options, resourceElement);
474
485
  content = (await util.getContent(dataURI, { asBinary: true })).data;
475
486
  }
@@ -492,7 +503,7 @@ function getProcessorHelperClass(utilInstance) {
492
503
  }
493
504
 
494
505
  async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest, resourceElement) {
495
- const { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
506
+ const { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", referrerPolicy: getReferrerPolicy(resourceElement) });
496
507
  const name = "images/" + indexResource + extension;
497
508
  resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
498
509
  resourceElement.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
@@ -543,6 +554,7 @@ function getProcessorHelperClass(utilInstance) {
543
554
  maxResourceSizeEnabled: options.maxResourceSizeEnabled,
544
555
  frameId: options.windowId,
545
556
  resourceReferrer: options.resourceReferrer,
557
+ referrerPolicy: getReferrerPolicy(element),
546
558
  baseURI: options.baseURI,
547
559
  blockMixedContent: options.blockMixedContent,
548
560
  expectedType: "script",
package/core/util.js CHANGED
@@ -225,6 +225,7 @@ function getInstance(utilOptions) {
225
225
  ASSIGNED_SLOT_SEPARATOR: helper.ASSIGNED_SLOT_SEPARATOR,
226
226
  PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
227
227
  STYLESHEET_ATTRIBUTE_NAME: helper.STYLESHEET_ATTRIBUTE_NAME,
228
+ LINK_STYLESHEET_ATTRIBUTE_NAME: helper.LINK_STYLESHEET_ATTRIBUTE_NAME,
228
229
  SELECTED_CONTENT_ATTRIBUTE_NAME: helper.SELECTED_CONTENT_ATTRIBUTE_NAME,
229
230
  INVALID_ELEMENT_ATTRIBUTE_NAME: helper.INVALID_ELEMENT_ATTRIBUTE_NAME,
230
231
  COMMENT_HEADER: helper.COMMENT_HEADER,
@@ -267,25 +268,25 @@ function getInstance(utilOptions) {
267
268
  if (options.frameId) {
268
269
  try {
269
270
  response = await Promise.race([
270
- fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer, headers: { accept } }),
271
+ fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
271
272
  networkTimeoutPromise
272
273
  ]);
273
274
  // eslint-disable-next-line no-unused-vars
274
275
  } catch (error) {
275
276
  response = await Promise.race([
276
- fetchResource(resourceURL, { referrer: options.resourceReferrer, headers: { accept } }),
277
+ fetchResource(resourceURL, { referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
277
278
  networkTimeoutPromise
278
279
  ]);
279
280
  }
280
281
  } else {
281
282
  response = await Promise.race([
282
- fetchResource(resourceURL, { referrer: options.resourceReferrer, headers: { accept } }),
283
+ fetchResource(resourceURL, { referrer: options.resourceReferrer, referrerPolicy: options.referrerPolicy || undefined, stylesheetURL: options.stylesheetURL || undefined, stylesheetReferrerPolicy: options.stylesheetReferrerPolicy || undefined, headers: { accept } }),
283
284
  networkTimeoutPromise
284
285
  ]);
285
286
  }
286
287
  // eslint-disable-next-line no-unused-vars
287
288
  } catch (error) {
288
- return getFetchResponse(resourceURL, options);
289
+ return getFailedFetchResponse(resourceURL, options);
289
290
  } finally {
290
291
  resolveNetworkTimeoutPromise();
291
292
  if (options.networkTimeout) {
@@ -336,7 +337,10 @@ function getInstance(utilOptions) {
336
337
  return getFetchResponse(resourceURL, options);
337
338
  }
338
339
  } else {
339
- if (response.status >= 400 || (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT))) {
340
+ if (response.status >= 400) {
341
+ return getFailedFetchResponse(resourceURL, options);
342
+ }
343
+ if (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT)) {
340
344
  return getFetchResponse(resourceURL, options);
341
345
  }
342
346
  if (!charset) {
@@ -349,7 +353,9 @@ function getInstance(utilOptions) {
349
353
  return getFetchResponse(resourceURL, options, null, charset);
350
354
  } else {
351
355
  try {
352
- return getFetchResponse(resourceURL, options, buffer, charset, contentType);
356
+ const fetchResponse = await getFetchResponse(resourceURL, options, buffer, charset, contentType);
357
+ fetchResponse.referrerPolicy = (response.headers && response.headers.get("referrer-policy")) || undefined;
358
+ return fetchResponse;
353
359
  // eslint-disable-next-line no-unused-vars
354
360
  } catch (error) {
355
361
  return getFetchResponse(resourceURL, options, null, charset);
@@ -359,6 +365,12 @@ function getInstance(utilOptions) {
359
365
  }
360
366
  }
361
367
 
368
+ async function getFailedFetchResponse(resourceURL, options) {
369
+ const response = await getFetchResponse(resourceURL, options);
370
+ response.failed = true;
371
+ return response;
372
+ }
373
+
362
374
  async function getFetchResponse(resourceURL, options, data, charset, contentType) {
363
375
  if (data) {
364
376
  if (options.asBinary) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.17",
3
+ "version": "1.6.18",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",