single-file-core 1.6.6 → 1.6.7
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 +27 -12
- package/core/lib/processor-helper-common.js +3 -0
- package/core/lib/processor-helper-inline.js +28 -21
- package/core/lib/processor-helper.js +101 -20
- package/package.json +1 -1
package/core/index.js
CHANGED
|
@@ -146,7 +146,8 @@ const STAGES = [{
|
|
|
146
146
|
}, {
|
|
147
147
|
sequential: [
|
|
148
148
|
{ option: "removeAlternativeImages", action: "removeAlternativeImages" },
|
|
149
|
-
{ action: "groupDuplicateFonts" }
|
|
149
|
+
{ action: "groupDuplicateFonts" },
|
|
150
|
+
{ action: "groupDuplicateImages" }
|
|
150
151
|
],
|
|
151
152
|
parallel: [
|
|
152
153
|
{ option: "removeAlternativeFonts", action: "removeAlternativeFonts" },
|
|
@@ -437,6 +438,7 @@ const SCRIPT_URI_ATTRIBUTE_NAMES = ["href", "src", "action", "formaction", "data
|
|
|
437
438
|
const UTF8_CHARSET = "utf-8";
|
|
438
439
|
const TAINTED_CANVAS_WARNING_MESSAGE = "SingleFile: canvas elements tainted by a cross-origin resource, dropped from the page:";
|
|
439
440
|
const EMPTY_RESOURCE = "data:,";
|
|
441
|
+
const ORDER_DEPENDENT_RULE = /@(layer|import)\b/i;
|
|
440
442
|
|
|
441
443
|
class Processor {
|
|
442
444
|
constructor(options, processorHelper, batchRequest) {
|
|
@@ -1274,23 +1276,32 @@ class Processor {
|
|
|
1274
1276
|
}
|
|
1275
1277
|
|
|
1276
1278
|
async resolveStylesheetsURLs() {
|
|
1277
|
-
const
|
|
1279
|
+
const styleElementGroups = new Map();
|
|
1278
1280
|
this.options.inlineStylesheets = new Map();
|
|
1279
1281
|
this.options.inlineStylesheetsRefs = new Map();
|
|
1280
1282
|
this.doc.querySelectorAll("style").forEach(styleElement => {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (
|
|
1284
|
-
|
|
1285
|
-
styleElement,
|
|
1286
|
-
content: styleElement.textContent
|
|
1287
|
-
});
|
|
1288
|
-
stylesContents.push(styleElement.textContent);
|
|
1289
|
-
} else {
|
|
1290
|
-
this.options.inlineStylesheetsRefs.set(styleElement, indexContent);
|
|
1283
|
+
const content = styleElement.textContent;
|
|
1284
|
+
if (content) {
|
|
1285
|
+
if (!styleElementGroups.has(content)) {
|
|
1286
|
+
styleElementGroups.set(content, []);
|
|
1291
1287
|
}
|
|
1288
|
+
styleElementGroups.get(content).push(styleElement);
|
|
1292
1289
|
}
|
|
1293
1290
|
});
|
|
1291
|
+
let indexContent = 0;
|
|
1292
|
+
styleElementGroups.forEach((styleElements, content) => {
|
|
1293
|
+
const masterIndex = ORDER_DEPENDENT_RULE.test(content) ? 0 : styleElements.length - 1;
|
|
1294
|
+
this.options.inlineStylesheets.set(indexContent, {
|
|
1295
|
+
styleElement: styleElements[masterIndex],
|
|
1296
|
+
content
|
|
1297
|
+
});
|
|
1298
|
+
styleElements.forEach((styleElement, index) => {
|
|
1299
|
+
if (index != masterIndex) {
|
|
1300
|
+
this.options.inlineStylesheetsRefs.set(styleElement, indexContent);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
indexContent++;
|
|
1304
|
+
});
|
|
1294
1305
|
await Promise.all(Array.from(this.doc.querySelectorAll("style, link[rel*=stylesheet]:not([disabled])")).map(async element => {
|
|
1295
1306
|
const options = Object.assign({}, this.options, { charset: this.charset });
|
|
1296
1307
|
let mediaText;
|
|
@@ -1556,6 +1567,10 @@ class Processor {
|
|
|
1556
1567
|
this.processorHelper.groupDuplicateFonts(this.stylesheets, this.resources.fonts, this.options);
|
|
1557
1568
|
}
|
|
1558
1569
|
|
|
1570
|
+
groupDuplicateImages() {
|
|
1571
|
+
this.processorHelper.groupDuplicateImages(this.doc, this.stylesheets, this.styles, this.resources.images);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1559
1574
|
async removeAlternativeFonts() {
|
|
1560
1575
|
await this.processorHelper.removeAlternativeFonts(this.doc, this.stylesheets, this.resources.fonts, this.options.fontTests);
|
|
1561
1576
|
}
|
|
@@ -486,6 +486,9 @@ class ProcessorHelperCommon {
|
|
|
486
486
|
groupDuplicateFonts() {
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
+
groupDuplicateImages() {
|
|
490
|
+
}
|
|
491
|
+
|
|
489
492
|
getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
|
|
490
493
|
let mediaIndex = 0, supportsIndex = 0, layerIndex = 0;
|
|
491
494
|
cssRules.forEach(ruleData => {
|
|
@@ -127,33 +127,40 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
replaceStylesheets(doc, stylesheets, options) {
|
|
130
|
-
doc.querySelectorAll("style")
|
|
130
|
+
const styleElements = Array.from(doc.querySelectorAll("style"));
|
|
131
|
+
styleElements.forEach(element => {
|
|
132
|
+
const stylesheetInfo = stylesheets.get(element);
|
|
133
|
+
if (stylesheetInfo && !options.inlineStylesheetsRefs.has(element)) {
|
|
134
|
+
element.textContent = this.generateStylesheetContent(stylesheetInfo.stylesheet, options);
|
|
135
|
+
options.inlineStylesheets.forEach(({ styleElement }, index) => {
|
|
136
|
+
if (styleElement === element) {
|
|
137
|
+
options.inlineStylesheets.set(index, {
|
|
138
|
+
styleElement,
|
|
139
|
+
content: element.textContent
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
styleElements.forEach(element => {
|
|
131
146
|
const stylesheetInfo = stylesheets.get(element);
|
|
132
147
|
if (stylesheetInfo) {
|
|
133
148
|
stylesheets.delete(element);
|
|
134
149
|
const stylesheetRefIndex = options.inlineStylesheetsRefs.get(element);
|
|
135
|
-
if (stylesheetRefIndex
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
options.inlineStylesheets.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
150
|
+
if (stylesheetRefIndex !== undefined) {
|
|
151
|
+
if (options.groupDuplicateStylesheets) {
|
|
152
|
+
if (!doc.querySelector("style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]")) {
|
|
153
|
+
const styleElement = doc.createElement("style");
|
|
154
|
+
styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
155
|
+
styleElement.setAttribute("media", "not all");
|
|
156
|
+
styleElement.setAttribute(DUPLICATE_STYLESHEET_ATTRIBUTE_NAME, stylesheetRefIndex);
|
|
157
|
+
doc.head.appendChild(styleElement);
|
|
143
158
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
149
|
-
styleElement.setAttribute("media", "not all");
|
|
150
|
-
styleElement.setAttribute(DUPLICATE_STYLESHEET_ATTRIBUTE_NAME, stylesheetRefIndex);
|
|
151
|
-
doc.head.appendChild(styleElement);
|
|
159
|
+
element.textContent = "/* */";
|
|
160
|
+
element.setAttribute("onload", "this.textContent=document.querySelector('style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]')?.textContent;this.removeAttribute('onload')");
|
|
161
|
+
} else {
|
|
162
|
+
element.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
152
163
|
}
|
|
153
|
-
element.textContent = "/* */";
|
|
154
|
-
element.setAttribute("onload", "this.textContent=document.querySelector('style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]')?.textContent;this.removeAttribute('onload')");
|
|
155
|
-
} else {
|
|
156
|
-
element.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
157
164
|
}
|
|
158
165
|
if (stylesheetInfo.mediaText) {
|
|
159
166
|
element.media = stylesheetInfo.mediaText;
|
|
@@ -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 SRCSET_ATTRIBUTE_NAME = "srcset";
|
|
42
|
+
const IMAGE_ATTRIBUTE_NAMES = ["src", "href", "data", "poster", "background", "xlink:href", SRCSET_ATTRIBUTE_NAME];
|
|
43
|
+
const IMAGE_ATTRIBUTE_SELECTOR = "[src], [href], [data], [poster], [background], [srcset], image, feImage";
|
|
41
44
|
|
|
42
45
|
let util;
|
|
43
46
|
|
|
@@ -103,36 +106,49 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
103
106
|
const entries = Array.from(stylesheets).reverse();
|
|
104
107
|
const linkElements = new Map();
|
|
105
108
|
const sharedStyleElements = new Map();
|
|
109
|
+
const emptyStylesheetRefIndexes = new Set();
|
|
106
110
|
Array.from(new Set(options.inlineStylesheetsRefs.values())).forEach(stylesheetRefIndex => {
|
|
107
|
-
const linkElement = doc.createElement("link");
|
|
108
|
-
linkElement.setAttribute("rel", "stylesheet");
|
|
109
|
-
linkElement.setAttribute("type", "text/css");
|
|
110
|
-
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
111
|
-
linkElement.setAttribute("href", name);
|
|
112
111
|
const { styleElement, content } = options.inlineStylesheets.get(stylesheetRefIndex);
|
|
113
112
|
const sharedEntry = entries.find(([key]) => key.element == styleElement);
|
|
114
113
|
const stylesheet = sharedEntry
|
|
115
114
|
? sharedEntry[1].stylesheet
|
|
116
115
|
: cssTree.parse(content, { context: "stylesheet", parseCustomProperty: true });
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
if (isEmptyStylesheet(stylesheet)) {
|
|
117
|
+
emptyStylesheetRefIndexes.add(stylesheetRefIndex);
|
|
118
|
+
} else {
|
|
119
|
+
const linkElement = doc.createElement("link");
|
|
120
|
+
linkElement.setAttribute("rel", "stylesheet");
|
|
121
|
+
linkElement.setAttribute("type", "text/css");
|
|
122
|
+
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
123
|
+
linkElement.setAttribute("href", name);
|
|
124
|
+
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet });
|
|
125
|
+
linkElements.set(stylesheetRefIndex, linkElement);
|
|
126
|
+
}
|
|
119
127
|
sharedStyleElements.set(styleElement, stylesheetRefIndex);
|
|
120
128
|
});
|
|
121
129
|
for (const [key, stylesheetInfo] of entries) {
|
|
122
130
|
if (key.urlNode) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
key.urlNode.type = "Url";
|
|
126
|
-
key.urlNode.value = "-sf-url-original(" + JSON.stringify(stylesheetInfo.url) + ") " + name;
|
|
131
|
+
if (isEmptyStylesheet(stylesheetInfo.stylesheet) && !key.importDeclaresLayer) {
|
|
132
|
+
removeImportRule(key.importParent, key.importNode);
|
|
127
133
|
} else {
|
|
128
|
-
|
|
134
|
+
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
135
|
+
if (!isDataURL(stylesheetInfo.url) && options.saveOriginalURLs) {
|
|
136
|
+
key.urlNode.type = "Url";
|
|
137
|
+
key.urlNode.value = "-sf-url-original(" + JSON.stringify(stylesheetInfo.url) + ") " + name;
|
|
138
|
+
} else {
|
|
139
|
+
key.urlNode.value = name;
|
|
140
|
+
}
|
|
141
|
+
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
|
|
129
142
|
}
|
|
130
|
-
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
|
|
131
143
|
} else if (key.element.tagName.toUpperCase() == "LINK") {
|
|
132
144
|
const linkElement = key.element;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
if (isEmptyStylesheet(stylesheetInfo.stylesheet)) {
|
|
146
|
+
linkElement.remove();
|
|
147
|
+
} else {
|
|
148
|
+
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
149
|
+
linkElement.setAttribute("href", name);
|
|
150
|
+
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
|
|
151
|
+
}
|
|
136
152
|
} else {
|
|
137
153
|
const styleElement = key.element;
|
|
138
154
|
const stylesheetRefIndex = options.inlineStylesheetsRefs.has(styleElement)
|
|
@@ -140,6 +156,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
140
156
|
: sharedStyleElements.get(styleElement);
|
|
141
157
|
if (stylesheetRefIndex === undefined) {
|
|
142
158
|
styleElement.textContent = this.generateStylesheetContent(stylesheetInfo.stylesheet, options);
|
|
159
|
+
} else if (emptyStylesheetRefIndexes.has(stylesheetRefIndex)) {
|
|
160
|
+
styleElement.remove();
|
|
143
161
|
} else {
|
|
144
162
|
const linkElement = linkElements.get(stylesheetRefIndex).cloneNode(true);
|
|
145
163
|
Array.from(styleElement.attributes).forEach(({ name, value }) => {
|
|
@@ -200,7 +218,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
200
218
|
layerName,
|
|
201
219
|
supportsCondition
|
|
202
220
|
};
|
|
203
|
-
|
|
221
|
+
const importDeclaresLayer = Boolean(cssTree.find(node, node => node.type == "Layer" || ((node.type == "Identifier" || node.type == "Function") && node.name.toLowerCase() == "layer")));
|
|
222
|
+
stylesheets.set({ urlNode, importNode: node, importParent: stylesheet, importDeclaresLayer }, stylesheetInfo);
|
|
204
223
|
const requestedURL = resourceURL;
|
|
205
224
|
const content = await this.getStylesheetContent(resourceURL, options);
|
|
206
225
|
stylesheetInfo.url = resourceURL = content.resourceURL;
|
|
@@ -260,7 +279,7 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
260
279
|
} else {
|
|
261
280
|
frameElement.setAttribute("src", name + "index.html");
|
|
262
281
|
}
|
|
263
|
-
resources.frames.set(frameWindowId, { name, content: pageData.content, resources: pageData.resources, url: frameData.url });
|
|
282
|
+
resources.frames.set(frameWindowId, { name, content: pageData.content, resources: pageData.resources, url: frameData.url, title: pageData.title });
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
async processFont(resourceURL, urlNode, originalResourceURL, baseURI, options, resources, batchRequest) {
|
|
@@ -294,12 +313,54 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
294
313
|
if (canonicalNames.size) {
|
|
295
314
|
stylesheets.forEach(stylesheetInfo => {
|
|
296
315
|
if (stylesheetInfo.stylesheet) {
|
|
297
|
-
getUrlFunctions(stylesheetInfo.stylesheet).forEach(urlNode =>
|
|
316
|
+
getUrlFunctions(stylesheetInfo.stylesheet).forEach(urlNode => replaceResourceName(urlNode, canonicalNames));
|
|
298
317
|
}
|
|
299
318
|
});
|
|
300
319
|
}
|
|
301
320
|
}
|
|
302
321
|
|
|
322
|
+
groupDuplicateImages(doc, stylesheets, styles, images) {
|
|
323
|
+
const canonicalNames = new Map();
|
|
324
|
+
const resourcesBySize = new Map();
|
|
325
|
+
[...images]
|
|
326
|
+
.filter(([, resource]) => resource.content && resource.content.length)
|
|
327
|
+
.sort(([indexResource], [otherIndexResource]) => indexResource - otherIndexResource)
|
|
328
|
+
.forEach(([indexResource, resource]) => {
|
|
329
|
+
const sameSizeResources = resourcesBySize.get(resource.content.length) || [];
|
|
330
|
+
const original = sameSizeResources.find(previousResource => testSameContent(previousResource.content, resource.content));
|
|
331
|
+
if (original) {
|
|
332
|
+
canonicalNames.set(resource.name, original.name);
|
|
333
|
+
images.delete(indexResource);
|
|
334
|
+
} else {
|
|
335
|
+
sameSizeResources.push(resource);
|
|
336
|
+
resourcesBySize.set(resource.content.length, sameSizeResources);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
if (canonicalNames.size) {
|
|
340
|
+
stylesheets.forEach(stylesheetInfo => {
|
|
341
|
+
if (stylesheetInfo.stylesheet) {
|
|
342
|
+
getUrlFunctions(stylesheetInfo.stylesheet).forEach(urlNode => replaceResourceName(urlNode, canonicalNames));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
styles.forEach(declarationList => getUrlFunctions(declarationList).forEach(urlNode => replaceResourceName(urlNode, canonicalNames)));
|
|
346
|
+
doc.querySelectorAll(IMAGE_ATTRIBUTE_SELECTOR).forEach(element => {
|
|
347
|
+
IMAGE_ATTRIBUTE_NAMES.forEach(attributeName => {
|
|
348
|
+
const value = element.getAttribute(attributeName);
|
|
349
|
+
if (value !== null) {
|
|
350
|
+
if (canonicalNames.has(value)) {
|
|
351
|
+
element.setAttribute(attributeName, canonicalNames.get(value));
|
|
352
|
+
} else if (attributeName == SRCSET_ATTRIBUTE_NAME) {
|
|
353
|
+
const srcset = util.parseSrcset(value);
|
|
354
|
+
if (srcset.some(srcsetValue => canonicalNames.has(srcsetValue.url))) {
|
|
355
|
+
element.setAttribute(attributeName, serializeSrcset(srcset.map(srcsetValue => Object.assign({}, srcsetValue, { url: canonicalNames.get(srcsetValue.url) || srcsetValue.url }))));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
303
364
|
async processStyle(ruleData, options, resources, batchRequest) {
|
|
304
365
|
const urls = getUrlFunctions(ruleData);
|
|
305
366
|
await Promise.all(urls.map(async urlNode => {
|
|
@@ -594,6 +655,26 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
594
655
|
};
|
|
595
656
|
}
|
|
596
657
|
|
|
658
|
+
function isEmptyStylesheet(stylesheet) {
|
|
659
|
+
return !stylesheet || !stylesheet.children || !stylesheet.children.size;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function removeImportRule(parentStylesheet, importNode) {
|
|
663
|
+
let importItem, importList;
|
|
664
|
+
cssTree.walk(parentStylesheet, {
|
|
665
|
+
visit: "Atrule",
|
|
666
|
+
enter(node, item, list) {
|
|
667
|
+
if (node === importNode) {
|
|
668
|
+
importItem = item;
|
|
669
|
+
importList = list;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
if (importItem) {
|
|
674
|
+
importList.remove(importItem);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
597
678
|
function testSameContent(content, otherContent) {
|
|
598
679
|
if (content.length != otherContent.length) {
|
|
599
680
|
return false;
|
|
@@ -606,7 +687,7 @@ function testSameContent(content, otherContent) {
|
|
|
606
687
|
return true;
|
|
607
688
|
}
|
|
608
689
|
|
|
609
|
-
function
|
|
690
|
+
function replaceResourceName(urlNode, canonicalNames) {
|
|
610
691
|
canonicalNames.forEach((canonicalName, name) => {
|
|
611
692
|
if (urlNode.value == name) {
|
|
612
693
|
urlNode.value = canonicalName;
|