single-file-core 1.6.9 → 1.6.10
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 +83 -7
- package/core/lib/processor-helper-common.js +1 -1
- package/core/lib/processor-helper-inline.js +36 -21
- package/core/lib/processor-helper.js +64 -15
- package/modules/css-identifier.js +55 -0
- package/modules/css-rules-minifier.js +663 -262
- package/modules/css-selector-sanitizer.js +28 -24
- package/modules/css-specificity.js +18 -73
- package/modules/template-formatter.js +1 -0
- package/package.json +1 -1
- package/vendor/css-tree.js +16 -12
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.
|
|
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,70 @@ 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
|
+
nextSibling: element.nextSibling
|
|
1803
|
+
}));
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function restoreNestingPositions(positions) {
|
|
1807
|
+
if (positions) {
|
|
1808
|
+
positions.slice().reverse().forEach(({ element, parentNode, nextSibling }) => {
|
|
1809
|
+
if (element.isConnected && parentNode && parentNode.isConnected) {
|
|
1810
|
+
if (nextSibling && nextSibling.parentNode == parentNode) {
|
|
1811
|
+
parentNode.insertBefore(element, nextSibling);
|
|
1812
|
+
} else {
|
|
1813
|
+
parentNode.appendChild(element);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
function removeInsertedParagraphs(doc) {
|
|
1821
|
+
const trackedElements = new Map();
|
|
1822
|
+
doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`).forEach(element =>
|
|
1823
|
+
trackedElements.set(element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME), element));
|
|
1824
|
+
const displacedElements = new Set();
|
|
1825
|
+
trackedElements.forEach((element, trackId) => {
|
|
1826
|
+
const parentTrackId = getParentTrackId(trackId);
|
|
1827
|
+
const expectedParent = trackedElements.get(parentTrackId);
|
|
1828
|
+
if (expectedParent && element.parentElement != expectedParent && !element.contains(expectedParent) &&
|
|
1829
|
+
testParagraphExpectedAncestor(trackedElements, parentTrackId)) {
|
|
1830
|
+
displacedElements.add(element);
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
const insertedParagraphs = new Set();
|
|
1834
|
+
displacedElements.forEach(element => {
|
|
1835
|
+
let sibling = element.nextSibling;
|
|
1836
|
+
while (sibling && (sibling.nodeType != 1 || displacedElements.has(sibling))) {
|
|
1837
|
+
sibling = sibling.nextSibling;
|
|
1838
|
+
}
|
|
1839
|
+
if (sibling && sibling.tagName == PARAGRAPH_TAG_NAME && !sibling.attributes.length && !sibling.childNodes.length) {
|
|
1840
|
+
insertedParagraphs.add(sibling);
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
insertedParagraphs.forEach(paragraph => paragraph.remove());
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
function testParagraphExpectedAncestor(trackedElements, trackId) {
|
|
1847
|
+
let element = trackedElements.get(trackId);
|
|
1848
|
+
while (element) {
|
|
1849
|
+
if (element.tagName == PARAGRAPH_TAG_NAME) {
|
|
1850
|
+
return true;
|
|
1851
|
+
}
|
|
1852
|
+
trackId = getParentTrackId(trackId);
|
|
1853
|
+
element = trackId ? trackedElements.get(trackId) : null;
|
|
1854
|
+
}
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function getParentTrackId(trackId) {
|
|
1859
|
+
return trackId.split(".").slice(0, -1).join(".");
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1786
1862
|
function testValidPath(resourceURL) {
|
|
1787
1863
|
return resourceURL && !resourceURL.match(EMPTY_URL);
|
|
1788
1864
|
}
|
|
@@ -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
|
|
227
|
-
if (
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
}
|