single-file-core 1.6.9 → 1.6.11

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/index.js CHANGED
@@ -460,6 +460,12 @@ class Processor {
460
460
  frames: new Map()
461
461
  };
462
462
  this.fontTests = options.fontTests;
463
+ options.generatedDataURIs = new Set();
464
+ }
465
+
466
+ registerGeneratedDataURI(dataURI) {
467
+ this.options.generatedDataURIs.add(dataURI);
468
+ return dataURI;
463
469
  }
464
470
 
465
471
  initialize() {
@@ -492,6 +498,8 @@ class Processor {
492
498
  pageContent = content.data || "";
493
499
  }
494
500
  this.doc = util.parseDocContent(pageContent, this.baseURI);
501
+ removeInsertedParagraphs(this.doc);
502
+ this.nestingPositions = getNestingPositions(this.doc);
495
503
  util.fixInvalidNesting(this.doc);
496
504
  if (this.options.saveRawPage) {
497
505
  let charset;
@@ -600,6 +608,7 @@ class Processor {
600
608
  if (this.options.displayStats) {
601
609
  size = util.getContentSize(this.doc.documentElement.outerHTML);
602
610
  }
611
+ restoreNestingPositions(this.nestingPositions);
603
612
  if (this.doc.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)) {
604
613
  const scriptElement = this.doc.createElement("script");
605
614
  scriptElement.textContent = `(${util.getFixInvalidNestingSource()})(document, "${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}");`;
@@ -799,7 +808,7 @@ class Processor {
799
808
  if (attributeValue) {
800
809
  const posterURL = this.options.posters[Number(attributeValue)];
801
810
  if (!videoElement.getAttribute("poster") && posterURL && posterURL != EMPTY_RESOURCE) {
802
- videoElement.setAttribute("poster", posterURL);
811
+ videoElement.setAttribute("poster", this.registerGeneratedDataURI(posterURL));
803
812
  }
804
813
  }
805
814
  });
@@ -829,7 +838,7 @@ class Processor {
829
838
  linkElement.style.setProperty("min-height", ICON_SIZE, "important");
830
839
  linkElement.style.setProperty("max-width", ICON_SIZE, "important");
831
840
  linkElement.style.setProperty("max-height", ICON_SIZE, "important");
832
- imgElement.src = LINK_ICON;
841
+ imgElement.src = this.registerGeneratedDataURI(LINK_ICON);
833
842
  imgElement.style.setProperty("width", ICON_SIZE, "important");
834
843
  imgElement.style.setProperty("height", ICON_SIZE, "important");
835
844
  imgElement.style.setProperty("min-width", ICON_SIZE, "important");
@@ -1081,7 +1090,7 @@ class Processor {
1081
1090
  if (canvasData.backgroundColor) {
1082
1091
  backgroundStyle["background-color"] = canvasData.backgroundColor;
1083
1092
  }
1084
- this.processorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")", backgroundStyle);
1093
+ this.processorHelper.setBackgroundImage(canvasElement, "url(" + this.registerGeneratedDataURI(canvasData.dataURI) + ")", backgroundStyle);
1085
1094
  this.stats.add("processed", "canvas", 1);
1086
1095
  } else if (!canvasData.blank) {
1087
1096
  discardedCount++;
@@ -1242,17 +1251,17 @@ class Processor {
1242
1251
  canvasElement.height = temporaryVideoElement.videoHeight || videoData.size.videoHeight;
1243
1252
  context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
1244
1253
  try {
1245
- videoElement.poster = util.getPosterDataURI(canvasElement) || blankPosterURI;
1254
+ videoElement.poster = this.registerGeneratedDataURI(util.getPosterDataURI(canvasElement) || blankPosterURI);
1246
1255
  // eslint-disable-next-line no-unused-vars
1247
1256
  } catch (error) {
1248
- videoElement.poster = blankPosterURI;
1257
+ videoElement.poster = this.registerGeneratedDataURI(blankPosterURI);
1249
1258
  // ignored
1250
1259
  }
1251
1260
  temporaryVideoElement.remove();
1252
1261
  resolve();
1253
1262
  };
1254
1263
  temporaryVideoElement.onerror = () => {
1255
- videoElement.poster = blankPosterURI;
1264
+ videoElement.poster = this.registerGeneratedDataURI(blankPosterURI);
1256
1265
  temporaryVideoElement.remove();
1257
1266
  resolve();
1258
1267
  };
@@ -1342,7 +1351,9 @@ class Processor {
1342
1351
  if (frameElement.tagName.toUpperCase() == "OBJECT") {
1343
1352
  frameElement.setAttribute("data", "data:text/html,");
1344
1353
  } else {
1345
- frameElement.removeAttribute("src");
1354
+ if (frameElement.hasAttribute("src")) {
1355
+ frameElement.setAttribute("src", "");
1356
+ }
1346
1357
  frameElement.removeAttribute("srcdoc");
1347
1358
  }
1348
1359
  Array.from(frameElement.childNodes).forEach(node => node.remove());
@@ -1745,6 +1756,7 @@ const FILE_URI_PREFIX = /^file:\/\//;
1745
1756
  const EMPTY_URL = /^https?:\/\/+\s*$/;
1746
1757
  const NOT_EMPTY_URL = /^(https?:\/\/|file:\/\/|blob:).+/;
1747
1758
  const SINGLE_FILE_VARIABLE_NAME_PREFIX = "--sf-img-";
1759
+ const PARAGRAPH_TAG_NAME = "P";
1748
1760
 
1749
1761
  function normalizeURL(url) {
1750
1762
  if (!url || url.startsWith(DATA_URI_PREFIX)) {
@@ -1783,6 +1795,75 @@ function testIgnoredPath(resourceURL) {
1783
1795
  return resourceURL && (resourceURL.startsWith(DATA_URI_PREFIX) || resourceURL == ABOUT_BLANK_URI);
1784
1796
  }
1785
1797
 
1798
+ function getNestingPositions(doc) {
1799
+ return Array.from(doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)).map(element => ({
1800
+ element,
1801
+ parentNode: element.parentNode,
1802
+ previousSibling: element.previousSibling,
1803
+ nextSibling: element.nextSibling
1804
+ }));
1805
+ }
1806
+
1807
+ function restoreNestingPositions(positions) {
1808
+ if (positions) {
1809
+ positions.forEach(({ element, parentNode, previousSibling, nextSibling }) => {
1810
+ if (element.isConnected && parentNode && parentNode.isConnected) {
1811
+ if (previousSibling && previousSibling.parentNode == parentNode) {
1812
+ parentNode.insertBefore(element, previousSibling.nextSibling);
1813
+ } else if (!previousSibling) {
1814
+ parentNode.insertBefore(element, parentNode.firstChild);
1815
+ } else if (nextSibling && nextSibling.parentNode == parentNode) {
1816
+ parentNode.insertBefore(element, nextSibling);
1817
+ } else {
1818
+ parentNode.appendChild(element);
1819
+ }
1820
+ }
1821
+ });
1822
+ }
1823
+ }
1824
+
1825
+ function removeInsertedParagraphs(doc) {
1826
+ const trackedElements = new Map();
1827
+ doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`).forEach(element =>
1828
+ trackedElements.set(element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME), element));
1829
+ const displacedElements = new Set();
1830
+ trackedElements.forEach((element, trackId) => {
1831
+ const parentTrackId = getParentTrackId(trackId);
1832
+ const expectedParent = trackedElements.get(parentTrackId);
1833
+ if (expectedParent && element.parentElement != expectedParent && !element.contains(expectedParent) &&
1834
+ testParagraphExpectedAncestor(trackedElements, parentTrackId)) {
1835
+ displacedElements.add(element);
1836
+ }
1837
+ });
1838
+ const insertedParagraphs = new Set();
1839
+ displacedElements.forEach(element => {
1840
+ let sibling = element.nextSibling;
1841
+ while (sibling && (sibling.nodeType != 1 || displacedElements.has(sibling))) {
1842
+ sibling = sibling.nextSibling;
1843
+ }
1844
+ if (sibling && sibling.tagName == PARAGRAPH_TAG_NAME && !sibling.attributes.length && !sibling.childNodes.length) {
1845
+ insertedParagraphs.add(sibling);
1846
+ }
1847
+ });
1848
+ insertedParagraphs.forEach(paragraph => paragraph.remove());
1849
+ }
1850
+
1851
+ function testParagraphExpectedAncestor(trackedElements, trackId) {
1852
+ let element = trackedElements.get(trackId);
1853
+ while (element) {
1854
+ if (element.tagName == PARAGRAPH_TAG_NAME) {
1855
+ return true;
1856
+ }
1857
+ trackId = getParentTrackId(trackId);
1858
+ element = trackId ? trackedElements.get(trackId) : null;
1859
+ }
1860
+ return false;
1861
+ }
1862
+
1863
+ function getParentTrackId(trackId) {
1864
+ return trackId.split(".").slice(0, -1).join(".");
1865
+ }
1866
+
1786
1867
  function testValidPath(resourceURL) {
1787
1868
  return resourceURL && !resourceURL.match(EMPTY_URL);
1788
1869
  }
@@ -232,7 +232,7 @@ class ProcessorHelperCommon {
232
232
  // ignored
233
233
  }
234
234
  if (testValidURL(resourceURL)) {
235
- return this.processImageSrcset(resourceURL, srcsetValue, resources, batchRequest);
235
+ return this.processImageSrcset(resourceURL, srcsetValue, resources, batchRequest, resourceElement);
236
236
  } else {
237
237
  return "";
238
238
  }
@@ -51,6 +51,8 @@ const EMPTY_URL_SOURCE = /^url\(["']?data:[^,]*,?["']?\)/;
51
51
  const LOCAL_SOURCE = "local(";
52
52
  const FONT_MAX_LOAD_DELAY = 5000;
53
53
  const DUPLICATE_STYLESHEET_ATTRIBUTE_NAME = "data-sf-duplicate-stylesheet-ref";
54
+ const LAYER_KEYWORD = "layer";
55
+ const SUPPORTS_KEYWORD = "supports";
54
56
  const LINK_FETCH_ATTRIBUTE_NAMES = ["rel", "href", "type", "media", "as", "crossorigin", "integrity",
55
57
  "referrerpolicy", "hreflang", "sizes", "imagesrcset", "imagesizes", "fetchpriority"];
56
58
 
@@ -219,25 +221,17 @@ function getProcessorHelperClass(utilInstance) {
219
221
  if (content.data && content.data.match(/^<!doctype /i)) {
220
222
  content.data = "";
221
223
  }
224
+ const importedLayerName = getImportedLayerName(node);
225
+ if (importedLayerName !== null) {
226
+ content.data = this.wrapLayer(content.data, importedLayerName);
227
+ }
222
228
  const mediaQueryListNode = cssTree.find(node, node => node.type == "MediaQueryList");
223
229
  if (mediaQueryListNode) {
224
230
  content.data = this.wrapMediaQuery(content.data, cssTree.generate(mediaQueryListNode));
225
231
  }
226
- const layerListNode = cssTree.find(node, node => node.type == "LayerList");
227
- if (layerListNode) {
228
- const layerNames = [];
229
- layerListNode.children.forEach(child => {
230
- if (child.type == "Identifier") {
231
- layerNames.push(child.name);
232
- }
233
- });
234
- if (layerNames.length == 1) {
235
- content.data = this.wrapLayer(content.data, layerNames[0]);
236
- }
237
- }
238
- const supportsNode = cssTree.find(node, node => node.type == "Supports");
239
- if (supportsNode) {
240
- content.data = "@supports " + cssTree.generate(supportsNode) + " { " + content.data + " }";
232
+ const importedSupportsCondition = getImportedSupportsCondition(node);
233
+ if (importedSupportsCondition) {
234
+ content.data = "@supports " + importedSupportsCondition + " { " + content.data + " }";
241
235
  }
242
236
  const importedStylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
243
237
  const ancestorStyleSheets = new Set(importedStyleSheets);
@@ -302,7 +296,6 @@ function getProcessorHelperClass(utilInstance) {
302
296
  frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/%/g, "%25").replace(/#/g, "%23"));
303
297
  } else {
304
298
  frameElement.setAttribute("srcdoc", pageData.content);
305
- frameElement.removeAttribute("src");
306
299
  }
307
300
  }
308
301
  }
@@ -520,11 +513,7 @@ function getProcessorHelperClass(utilInstance) {
520
513
  }
521
514
 
522
515
  wrapLayer(stylesheetContent, layerName) {
523
- if (layerName) {
524
- return "@layer " + layerName + " { " + stylesheetContent + " }";
525
- } else {
526
- return stylesheetContent;
527
- }
516
+ return "@layer " + (layerName ? layerName + " " : "") + "{ " + stylesheetContent + " }";
528
517
  }
529
518
 
530
519
  getAdditionalPageData() {
@@ -770,6 +759,32 @@ function getProcessorHelperClass(utilInstance) {
770
759
  };
771
760
  }
772
761
 
762
+ function getImportedLayerName(importNode) {
763
+ const layerNode = cssTree.find(importNode, node => node.type == "Layer");
764
+ if (layerNode) {
765
+ return layerNode.name;
766
+ }
767
+ const prelude = importNode.prelude;
768
+ if (prelude && prelude.children) {
769
+ for (let child = prelude.children.head; child; child = child.next) {
770
+ if (child.data.type == "Identifier" && child.data.name.toLowerCase() == LAYER_KEYWORD) {
771
+ return "";
772
+ }
773
+ }
774
+ }
775
+ return null;
776
+ }
777
+
778
+ function getImportedSupportsCondition(importNode) {
779
+ const supportsNode = cssTree.find(importNode, node => node.type == "Function" && node.name.toLowerCase() == SUPPORTS_KEYWORD);
780
+ if (supportsNode && supportsNode.children && supportsNode.children.head) {
781
+ const conditionNode = supportsNode.children.head.data;
782
+ const condition = cssTree.generate(conditionNode);
783
+ return conditionNode.type == "Declaration" ? "(" + condition + ")" : condition;
784
+ }
785
+ return null;
786
+ }
787
+
773
788
  function getFontFaces(cssRules, scope, fontFaces) {
774
789
  for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
775
790
  const ruleData = cssRule.data;
@@ -38,6 +38,9 @@ const LOCAL_SOURCE = "local(";
38
38
  const FONT_MAX_LOAD_DELAY = 5000;
39
39
  const SCRIPT_EXTENSION = ".js";
40
40
  const LINK_OWN_ATTRIBUTE_NAMES = ["rel", "type", "href", "media"];
41
+ const CROSS_ORIGIN_ATTRIBUTE_NAME = "crossorigin";
42
+ const SOURCE_TAG_NAME = "SOURCE";
43
+ const MEDIA_TAG_NAMES = ["VIDEO", "AUDIO"];
41
44
  const SRCSET_ATTRIBUTE_NAME = "srcset";
42
45
  const IMAGE_ATTRIBUTE_NAMES = ["src", "href", "data", "poster", "background", "xlink:href", SRCSET_ATTRIBUTE_NAME];
43
46
  const IMAGE_ATTRIBUTE_SELECTOR = "[src], [href], [data], [poster], [background], [srcset], image, feImage";
@@ -74,6 +77,7 @@ function getProcessorHelperClass(utilInstance) {
74
77
  async resolveStylesheets(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement, resources) {
75
78
  if (element.tagName.toUpperCase() == "LINK") {
76
79
  element.removeAttribute("integrity");
80
+ element.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
77
81
  if (element.charset) {
78
82
  options.charset = element.charset;
79
83
  }
@@ -107,6 +111,7 @@ function getProcessorHelperClass(utilInstance) {
107
111
  const linkElements = new Map();
108
112
  const sharedStyleElements = new Map();
109
113
  const emptyStylesheetRefIndexes = new Set();
114
+ const sharedStylesheetIndexes = new Set();
110
115
  Array.from(new Set(options.inlineStylesheetsRefs.values())).forEach(stylesheetRefIndex => {
111
116
  const { styleElement, content } = options.inlineStylesheets.get(stylesheetRefIndex);
112
117
  const sharedEntry = entries.find(([key]) => key.element == styleElement);
@@ -121,6 +126,7 @@ function getProcessorHelperClass(utilInstance) {
121
126
  linkElement.setAttribute("type", "text/css");
122
127
  const name = "stylesheet_" + resources.stylesheets.size + ".css";
123
128
  linkElement.setAttribute("href", name);
129
+ sharedStylesheetIndexes.add(resources.stylesheets.size);
124
130
  resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet });
125
131
  linkElements.set(stylesheetRefIndex, linkElement);
126
132
  }
@@ -138,7 +144,7 @@ function getProcessorHelperClass(utilInstance) {
138
144
  } else {
139
145
  key.urlNode.value = name;
140
146
  }
141
- resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
147
+ resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url, urlNode: key.urlNode });
142
148
  }
143
149
  } else if (key.element.tagName.toUpperCase() == "LINK") {
144
150
  const linkElement = key.element;
@@ -173,11 +179,36 @@ function getProcessorHelperClass(utilInstance) {
173
179
  }
174
180
  }
175
181
  }
176
- for (const [, stylesheetResource] of resources.stylesheets) {
177
- if (stylesheetResource.stylesheet) {
178
- stylesheetResource.content = this.generateStylesheetContent(stylesheetResource.stylesheet, options);
179
- stylesheetResource.stylesheet = null;
180
- }
182
+ const stylesheetResources = Array.from(resources.stylesheets);
183
+ const canonicalNames = new Map();
184
+ const canonicalStylesheets = new Map();
185
+ stylesheetResources
186
+ .filter(([indexResource]) => !sharedStylesheetIndexes.has(indexResource))
187
+ .concat(stylesheetResources.filter(([indexResource]) => sharedStylesheetIndexes.has(indexResource)))
188
+ .forEach(([indexResource, stylesheetResource]) => {
189
+ if (stylesheetResource.stylesheet) {
190
+ const content = this.generateStylesheetContent(stylesheetResource.stylesheet, options);
191
+ stylesheetResource.stylesheet = null;
192
+ const canonicalName = canonicalStylesheets.get(content);
193
+ if (canonicalName === undefined) {
194
+ canonicalStylesheets.set(content, stylesheetResource.name);
195
+ stylesheetResource.content = content;
196
+ } else {
197
+ canonicalNames.set(stylesheetResource.name, canonicalName);
198
+ if (stylesheetResource.urlNode) {
199
+ replaceResourceName(stylesheetResource.urlNode, canonicalNames);
200
+ }
201
+ resources.stylesheets.delete(indexResource);
202
+ }
203
+ }
204
+ });
205
+ if (canonicalNames.size) {
206
+ doc.querySelectorAll("link[href]").forEach(linkElement => {
207
+ const href = linkElement.getAttribute("href");
208
+ if (canonicalNames.has(href)) {
209
+ linkElement.setAttribute("href", canonicalNames.get(href));
210
+ }
211
+ });
181
212
  }
182
213
  }
183
214
 
@@ -367,7 +398,7 @@ function getProcessorHelperClass(utilInstance) {
367
398
  const originalResourceURL = urlNode.value;
368
399
  if (!options.blockImages) {
369
400
  const resourceURL = normalizeURL(originalResourceURL);
370
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
401
+ if ((!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) || testGeneratedDataURI(resourceURL, options)) {
371
402
  let { content, indexResource, contentType, extension } = await batchRequest.addURL(resourceURL,
372
403
  { asBinary: true, expectedType: "image" });
373
404
  const name = "images/" + indexResource + extension;
@@ -394,17 +425,20 @@ function getProcessorHelperClass(utilInstance) {
394
425
  resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
395
426
  }
396
427
  delete resourceElement.dataset.singleFileOriginURL;
428
+ const generatedDataURI = testGeneratedDataURI(resourceURL, options);
397
429
  if (!expectedType || !options["block" + expectedType.charAt(0).toUpperCase() + expectedType.substring(1) + "s"]) {
398
- if (!testIgnoredPath(resourceURL)) {
430
+ if (!testIgnoredPath(resourceURL) || generatedDataURI) {
399
431
  this.setAttributeEmpty(resourceElement, attributeName, expectedType);
400
432
  if (testValidPath(resourceURL)) {
401
- try {
402
- resourceURL = util.resolveURL(resourceURL, baseURI);
403
- // eslint-disable-next-line no-unused-vars
404
- } catch (error) {
405
- // ignored
433
+ if (!generatedDataURI) {
434
+ try {
435
+ resourceURL = util.resolveURL(resourceURL, baseURI);
436
+ // eslint-disable-next-line no-unused-vars
437
+ } catch (error) {
438
+ // ignored
439
+ }
406
440
  }
407
- if (testValidURL(resourceURL)) {
441
+ if (testValidURL(resourceURL) || generatedDataURI) {
408
442
  const declaredContentType = ["OBJECT", "EMBED"].includes(resourceElement.tagName.toUpperCase()) ? resourceElement.getAttribute("type") : "";
409
443
  let { content, indexResource, extension, contentType, charset } = await batchRequest.addURL(resourceURL,
410
444
  { asBinary: true, expectedType, contentType: declaredContentType });
@@ -444,6 +478,7 @@ function getProcessorHelperClass(utilInstance) {
444
478
  } else if (!this.testEmptyResource(content)) {
445
479
  const name = "images/" + indexResource + extension;
446
480
  resourceElement.setAttribute(attributeName, name);
481
+ removeCrossOriginAttribute(resourceElement);
447
482
  resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
448
483
  }
449
484
  }
@@ -456,10 +491,11 @@ function getProcessorHelperClass(utilInstance) {
456
491
  }));
457
492
  }
458
493
 
459
- async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest) {
494
+ async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest, resourceElement) {
460
495
  const { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
461
496
  const name = "images/" + indexResource + extension;
462
497
  resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
498
+ resourceElement.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
463
499
  return serializeSrcset([Object.assign({}, srcsetValue, { url: name })]);
464
500
  }
465
501
 
@@ -526,6 +562,7 @@ function getProcessorHelperClass(utilInstance) {
526
562
  // the browser refuses to execute. Stylesheets have always been named this way
527
563
  const name = "scripts/" + indexResource + SCRIPT_EXTENSION;
528
564
  element.setAttribute("src", name);
565
+ element.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
529
566
  resources.scripts.set(indexResource, { name, content, extension, contentType, url: resourceURL });
530
567
  }
531
568
 
@@ -659,6 +696,14 @@ function isEmptyStylesheet(stylesheet) {
659
696
  return !stylesheet || !stylesheet.children || !stylesheet.children.size;
660
697
  }
661
698
 
699
+ function removeCrossOriginAttribute(element) {
700
+ element.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
701
+ const parentElement = element.parentElement;
702
+ if (element.tagName.toUpperCase() == SOURCE_TAG_NAME && parentElement && MEDIA_TAG_NAMES.includes(parentElement.tagName.toUpperCase())) {
703
+ parentElement.removeAttribute(CROSS_ORIGIN_ATTRIBUTE_NAME);
704
+ }
705
+ }
706
+
662
707
  function removeImportRule(parentStylesheet, importNode) {
663
708
  let importItem, importList;
664
709
  cssTree.walk(parentStylesheet, {
@@ -675,6 +720,10 @@ function removeImportRule(parentStylesheet, importNode) {
675
720
  }
676
721
  }
677
722
 
723
+ function testGeneratedDataURI(resourceURL, options) {
724
+ return Boolean(options.generatedDataURIs && options.generatedDataURIs.has(resourceURL));
725
+ }
726
+
678
727
  function testSameContent(content, otherContent) {
679
728
  if (content.length != otherContent.length) {
680
729
  return false;
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright 2010-2022 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ const ESCAPE_CHARACTER = "\\";
25
+ const CSS_ESCAPE_TEST = /\\(?:([0-9a-fA-F]{1,6})(?:\r\n|[ \n\r\t\f])?|([^\n\r\f]))/g;
26
+ const REPLACEMENT_CHARACTER = "�";
27
+ const MAX_CODE_POINT = 0x10ffff;
28
+ const SURROGATE_FIRST_CODE_POINT = 0xd800;
29
+ const SURROGATE_LAST_CODE_POINT = 0xdfff;
30
+ const HEXADECIMAL_RADIX = 16;
31
+
32
+ export {
33
+ ESCAPE_CHARACTER,
34
+ decodeIdentifier,
35
+ decodeName
36
+ };
37
+
38
+ function decodeIdentifier(identifier) {
39
+ if (identifier.indexOf(ESCAPE_CHARACTER) === -1) {
40
+ return identifier;
41
+ }
42
+ return identifier.replace(CSS_ESCAPE_TEST, (match, hexadecimalDigits, character) => {
43
+ if (hexadecimalDigits === undefined) {
44
+ return character;
45
+ }
46
+ const codePoint = parseInt(hexadecimalDigits, HEXADECIMAL_RADIX);
47
+ return codePoint === 0 || codePoint > MAX_CODE_POINT || (codePoint >= SURROGATE_FIRST_CODE_POINT && codePoint <= SURROGATE_LAST_CODE_POINT)
48
+ ? REPLACEMENT_CHARACTER
49
+ : String.fromCodePoint(codePoint);
50
+ });
51
+ }
52
+
53
+ function decodeName(name) {
54
+ return typeof name === "string" ? decodeIdentifier(name).toLowerCase() : name;
55
+ }