Browse Source

move inline functions out of returned objects

Gildas 5 năm trước cách đây
mục cha
commit
2543e54075

+ 39 - 37
lib/single-file/modules/css-fonts-alt-minifier.js

@@ -57,45 +57,47 @@ this.singlefile.lib.modules.fontsAltMinifier = this.singlefile.lib.modules.fonts
 	const FONT_MAX_LOAD_DELAY = 5000;
 
 	return {
-		process: async (doc, stylesheets, fontURLs, fontTests) => {
-			const fontsDetails = {
-				fonts: new Map(),
-				medias: new Map(),
-				supports: new Map()
-			};
-			const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
-			let sheetIndex = 0;
-			stylesheets.forEach(stylesheetInfo => {
-				const cssRules = stylesheetInfo.stylesheet.children;
-				if (cssRules) {
-					stats.rules.processed += cssRules.getSize();
-					stats.rules.discarded += cssRules.getSize();
-					if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
-						const mediaFontsDetails = createFontsDetailsInfo();
-						fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
-						getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
-					} else {
-						getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
-					}
+		process
+	};
+
+	async function process(doc, stylesheets, fontURLs, fontTests) {
+		const fontsDetails = {
+			fonts: new Map(),
+			medias: new Map(),
+			supports: new Map()
+		};
+		const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
+		let sheetIndex = 0;
+		stylesheets.forEach(stylesheetInfo => {
+			const cssRules = stylesheetInfo.stylesheet.children;
+			if (cssRules) {
+				stats.rules.processed += cssRules.getSize();
+				stats.rules.discarded += cssRules.getSize();
+				if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
+					const mediaFontsDetails = createFontsDetailsInfo();
+					fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
+					getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
+				} else {
+					getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
 				}
-				sheetIndex++;
-			});
-			processFontDetails(fontsDetails);
-			await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
-				const cssRules = stylesheetInfo.stylesheet.children;
-				const media = stylesheetInfo.mediaText;
-				if (cssRules) {
-					if (media && media != MEDIA_ALL) {
-						await processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), fontURLs, fontTests, stats);
-					} else {
-						await processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontURLs, fontTests, stats);
-					}
-					stats.rules.discarded -= cssRules.getSize();
+			}
+			sheetIndex++;
+		});
+		processFontDetails(fontsDetails);
+		await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
+			const cssRules = stylesheetInfo.stylesheet.children;
+			const media = stylesheetInfo.mediaText;
+			if (cssRules) {
+				if (media && media != MEDIA_ALL) {
+					await processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), fontURLs, fontTests, stats);
+				} else {
+					await processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontURLs, fontTests, stats);
 				}
-			}));
-			return stats;
-		}
-	};
+				stats.rules.discarded -= cssRules.getSize();
+			}
+		}));
+		return stats;
+	}
 
 	function getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
 		const cssTree = singlefile.lib.vendor.cssTree;

+ 59 - 57
lib/single-file/modules/css-fonts-minifier.js

@@ -34,67 +34,69 @@ this.singlefile.lib.modules.fontsMinifier = this.singlefile.lib.modules.fontsMin
 	const VALID_FONT_STYLES = [/^normal$/, /^italic$/, /^oblique$/, /^oblique\s+/];
 
 	return {
-		process: (doc, stylesheets, styles, options) => {
-			const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
-			const fontsInfo = { declared: [], used: [] };
-			const workStyleElement = doc.createElement("style");
-			let docContent = "";
-			doc.body.appendChild(workStyleElement);
-			stylesheets.forEach(stylesheetInfo => {
-				const cssRules = stylesheetInfo.stylesheet.children;
-				if (cssRules) {
-					stats.processed += cssRules.getSize();
-					stats.discarded += cssRules.getSize();
-					getFontsInfo(cssRules, fontsInfo);
-					docContent = getRulesTextContent(doc, cssRules, workStyleElement, docContent);
-				}
-			});
-			styles.forEach(declarations => {
-				const fontFamilyNames = getFontFamilyNames(declarations);
-				if (fontFamilyNames.length) {
-					fontsInfo.used.push(fontFamilyNames);
-				}
-				docContent = getDeclarationsTextContent(declarations.children, workStyleElement, docContent);
-			});
-			workStyleElement.remove();
-			docContent += doc.body.innerText;
-			if (window.getComputedStyle && options.doc) {
-				fontsInfo.used = fontsInfo.used.map(fontNames => fontNames.map(familyName => {
-					const matchedVar = familyName.match(/^var\((--.*)\)$/);
-					if (matchedVar && matchedVar[1]) {
-						const computedFamilyName = window.getComputedStyle(options.doc.body).getPropertyValue(matchedVar[1]);
-						return (computedFamilyName && computedFamilyName.split(",").map(name => singlefile.lib.helper.normalizeFontFamily(name))) || familyName;
-					}
-					return familyName;
-				}));
-				fontsInfo.used = fontsInfo.used.map(fontNames => singlefile.lib.helper.flatten(fontNames));
+		process
+	};
+
+	function process(doc, stylesheets, styles, options) {
+		const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
+		const fontsInfo = { declared: [], used: [] };
+		const workStyleElement = doc.createElement("style");
+		let docContent = "";
+		doc.body.appendChild(workStyleElement);
+		stylesheets.forEach(stylesheetInfo => {
+			const cssRules = stylesheetInfo.stylesheet.children;
+			if (cssRules) {
+				stats.processed += cssRules.getSize();
+				stats.discarded += cssRules.getSize();
+				getFontsInfo(cssRules, fontsInfo);
+				docContent = getRulesTextContent(doc, cssRules, workStyleElement, docContent);
 			}
-			const variableFound = fontsInfo.used.find(fontNames => fontNames.find(fontName => fontName.startsWith("var(--")));
-			let unusedFonts, filteredUsedFonts;
-			if (variableFound) {
-				unusedFonts = [];
-			} else {
-				filteredUsedFonts = new Map();
-				fontsInfo.used.forEach(fontNames => fontNames.forEach(familyName => {
-					if (fontsInfo.declared.find(fontInfo => fontInfo.fontFamily == familyName)) {
-						const optionalData = options.usedFonts && options.usedFonts.filter(fontInfo => fontInfo[0] == familyName);
-						if (optionalData && optionalData.length) {
-							filteredUsedFonts.set(familyName, optionalData);
-						}
-					}
-				}));
-				unusedFonts = fontsInfo.declared.filter(fontInfo => !filteredUsedFonts.has(fontInfo.fontFamily));
+		});
+		styles.forEach(declarations => {
+			const fontFamilyNames = getFontFamilyNames(declarations);
+			if (fontFamilyNames.length) {
+				fontsInfo.used.push(fontFamilyNames);
 			}
-			stylesheets.forEach(stylesheetInfo => {
-				const cssRules = stylesheetInfo.stylesheet.children;
-				if (cssRules) {
-					filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docContent);
-					stats.rules.discarded -= cssRules.getSize();
+			docContent = getDeclarationsTextContent(declarations.children, workStyleElement, docContent);
+		});
+		workStyleElement.remove();
+		docContent += doc.body.innerText;
+		if (window.getComputedStyle && options.doc) {
+			fontsInfo.used = fontsInfo.used.map(fontNames => fontNames.map(familyName => {
+				const matchedVar = familyName.match(/^var\((--.*)\)$/);
+				if (matchedVar && matchedVar[1]) {
+					const computedFamilyName = window.getComputedStyle(options.doc.body).getPropertyValue(matchedVar[1]);
+					return (computedFamilyName && computedFamilyName.split(",").map(name => singlefile.lib.helper.normalizeFontFamily(name))) || familyName;
 				}
-			});
-			return stats;
+				return familyName;
+			}));
+			fontsInfo.used = fontsInfo.used.map(fontNames => singlefile.lib.helper.flatten(fontNames));
 		}
-	};
+		const variableFound = fontsInfo.used.find(fontNames => fontNames.find(fontName => fontName.startsWith("var(--")));
+		let unusedFonts, filteredUsedFonts;
+		if (variableFound) {
+			unusedFonts = [];
+		} else {
+			filteredUsedFonts = new Map();
+			fontsInfo.used.forEach(fontNames => fontNames.forEach(familyName => {
+				if (fontsInfo.declared.find(fontInfo => fontInfo.fontFamily == familyName)) {
+					const optionalData = options.usedFonts && options.usedFonts.filter(fontInfo => fontInfo[0] == familyName);
+					if (optionalData && optionalData.length) {
+						filteredUsedFonts.set(familyName, optionalData);
+					}
+				}
+			}));
+			unusedFonts = fontsInfo.declared.filter(fontInfo => !filteredUsedFonts.has(fontInfo.fontFamily));
+		}
+		stylesheets.forEach(stylesheetInfo => {
+			const cssRules = stylesheetInfo.stylesheet.children;
+			if (cssRules) {
+				filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docContent);
+				stats.rules.discarded -= cssRules.getSize();
+			}
+		});
+		return stats;
+	}
 
 	function getFontsInfo(cssRules, fontsInfo) {
 		cssRules.forEach(ruleData => {

+ 5 - 3
lib/single-file/modules/css-matched-rules.js

@@ -79,11 +79,13 @@ this.singlefile.lib.modules.matchedRules = this.singlefile.lib.modules.matchedRu
 	}
 
 	return {
-		getMediaAllInfo(doc, stylesheets, styles) {
-			return new MatchedRules(doc, stylesheets, styles).getMediaAllInfo();
-		}
+		getMediaAllInfo
 	};
 
+	function getMediaAllInfo(doc, stylesheets, styles) {
+		return new MatchedRules(doc, stylesheets, styles).getMediaAllInfo();
+	}
+
 	function createMediaInfo(media) {
 		const mediaInfo = { media: media, elements: new Map(), medias: new Map(), rules: new Map(), pseudoRules: new Map() };
 		if (media == MEDIA_ALL) {

+ 14 - 12
lib/single-file/modules/css-medias-alt-minifier.js

@@ -29,20 +29,22 @@ this.singlefile.lib.modules.mediasAltMinifier = this.singlefile.lib.modules.medi
 	const MEDIA_SCREEN = "screen";
 
 	return {
-		process: stylesheets => {
-			const stats = { processed: 0, discarded: 0 };
-			stylesheets.forEach((stylesheetInfo, element) => {
-				if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL, MEDIA_SCREEN) && stylesheetInfo.stylesheet.children) {
-					const removedRules = processRules(stylesheetInfo.stylesheet.children, stats);
-					removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
-				} else {
-					stylesheets.delete(element);
-				}
-			});
-			return stats;
-		}
+		process
 	};
 
+	function process(stylesheets) {
+		const stats = { processed: 0, discarded: 0 };
+		stylesheets.forEach((stylesheetInfo, element) => {
+			if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL, MEDIA_SCREEN) && stylesheetInfo.stylesheet.children) {
+				const removedRules = processRules(stylesheetInfo.stylesheet.children, stats);
+				removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
+			} else {
+				stylesheets.delete(element);
+			}
+		});
+		return stats;
+	}
+
 	function processRules(cssRules, stats, removedRules = []) {
 		for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
 			const ruleData = cssRule.data;

+ 31 - 29
lib/single-file/modules/css-rules-minifier.js

@@ -28,39 +28,41 @@ this.singlefile.lib.modules.cssRulesMinifier = this.singlefile.lib.modules.cssRu
 	const DEBUG = false;
 
 	return {
-		process: (stylesheets, styles, mediaAllInfo) => {
-			const stats = { processed: 0, discarded: 0 };
-			let sheetIndex = 0;
-			stylesheets.forEach(stylesheetInfo => {
-				if (!stylesheetInfo.scoped) {
-					const cssRules = stylesheetInfo.stylesheet.children;
-					if (cssRules) {
-						stats.processed += cssRules.getSize();
-						stats.discarded += cssRules.getSize();
-						let mediaInfo;
-						if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != "all") {
-							mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.mediaText);
-						} else {
-							mediaInfo = mediaAllInfo;
-						}
-						processRules(cssRules, sheetIndex, mediaInfo);
-						stats.discarded -= cssRules.getSize();
+		process
+	};
+
+	function process(stylesheets, styles, mediaAllInfo) {
+		const stats = { processed: 0, discarded: 0 };
+		let sheetIndex = 0;
+		stylesheets.forEach(stylesheetInfo => {
+			if (!stylesheetInfo.scoped) {
+				const cssRules = stylesheetInfo.stylesheet.children;
+				if (cssRules) {
+					stats.processed += cssRules.getSize();
+					stats.discarded += cssRules.getSize();
+					let mediaInfo;
+					if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != "all") {
+						mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.mediaText);
+					} else {
+						mediaInfo = mediaAllInfo;
 					}
+					processRules(cssRules, sheetIndex, mediaInfo);
+					stats.discarded -= cssRules.getSize();
 				}
-				sheetIndex++;
-			});
-			let startTime;
-			if (DEBUG) {
-				startTime = Date.now();
-				log("  -- STARTED processStyleAttribute");
-			}
-			styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
-			if (DEBUG) {
-				log("  -- ENDED   processStyleAttribute delay =", Date.now() - startTime);
 			}
-			return stats;
+			sheetIndex++;
+		});
+		let startTime;
+		if (DEBUG) {
+			startTime = Date.now();
+			log("  -- STARTED processStyleAttribute");
 		}
-	};
+		styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
+		if (DEBUG) {
+			log("  -- ENDED   processStyleAttribute delay =", Date.now() - startTime);
+		}
+		return stats;
+	}
 
 	function processRules(cssRules, sheetIndex, mediaInfo) {
 		const cssTree = singlefile.lib.vendor.cssTree;

+ 18 - 16
lib/single-file/modules/html-images-alt-minifier.js

@@ -28,24 +28,26 @@ this.singlefile.lib.modules.imagesAltMinifier = this.singlefile.lib.modules.imag
 	const EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
 
 	return {
-		process(doc) {
-			doc.querySelectorAll("picture").forEach(pictureElement => {
-				const imgElement = pictureElement.querySelector("img");
-				if (imgElement) {
-					let { src, srcset } = getImgSrcData(imgElement);
-					if (!src) {
-						const data = getSourceSrcData(Array.from(pictureElement.querySelectorAll("source")).reverse());
-						src = data.src;
-						if (!srcset) {
-							srcset = data.srcset;
-						}
+		process
+	};
+
+	function process(doc) {
+		doc.querySelectorAll("picture").forEach(pictureElement => {
+			const imgElement = pictureElement.querySelector("img");
+			if (imgElement) {
+				let { src, srcset } = getImgSrcData(imgElement);
+				if (!src) {
+					const data = getSourceSrcData(Array.from(pictureElement.querySelectorAll("source")).reverse());
+					src = data.src;
+					if (!srcset) {
+						srcset = data.srcset;
 					}
-					setSrc({ src, srcset }, imgElement, pictureElement);
 				}
-			});
-			doc.querySelectorAll(":not(picture) > img[srcset]").forEach(imgElement => setSrc(getImgSrcData(imgElement), imgElement));
-		}
-	};
+				setSrc({ src, srcset }, imgElement, pictureElement);
+			}
+		});
+		doc.querySelectorAll(":not(picture) > img[srcset]").forEach(imgElement => setSrc(getImgSrcData(imgElement), imgElement));
+	}
 
 	function getImgSrcData(imgElement) {
 		let src = imgElement.getAttribute("src");

+ 14 - 12
lib/single-file/modules/html-minifier.js

@@ -134,20 +134,22 @@ this.singlefile.lib.modules.htmlMinifier = this.singlefile.lib.modules.htmlMinif
 	];
 
 	return {
-		process: (doc, options) => {
-			removeEmptyInlineElements(doc);
-			const nodesWalker = doc.createTreeWalker(doc.documentElement, NodeFilter_SHOW_ALL, null, false);
-			let node = nodesWalker.nextNode();
-			while (node) {
-				const deletedNode = modules.find(module => module(node, options));
-				const previousNode = node;
-				node = nodesWalker.nextNode();
-				if (deletedNode) {
-					previousNode.remove();
-				}
+		process
+	};
+
+	function process(doc, options) {
+		removeEmptyInlineElements(doc);
+		const nodesWalker = doc.createTreeWalker(doc.documentElement, NodeFilter_SHOW_ALL, null, false);
+		let node = nodesWalker.nextNode();
+		while (node) {
+			const deletedNode = modules.find(module => module(node, options));
+			const previousNode = node;
+			node = nodesWalker.nextNode();
+			if (deletedNode) {
+				previousNode.remove();
 			}
 		}
-	};
+	}
 
 	function collapseBooleanAttributes(node) {
 		if (node.nodeType == Node_ELEMENT_NODE) {

+ 19 - 17
lib/single-file/modules/html-serializer.js

@@ -58,25 +58,27 @@ this.singlefile.lib.modules.serializer = this.singlefile.lib.modules.serializer
 	const TEXT_NODE_TAGS = ["style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext", "noscript"];
 
 	return {
-		process(doc, compressHTML) {
-			const docType = doc.doctype;
-			let docTypeString = "";
-			if (docType) {
-				docTypeString = "<!DOCTYPE " + docType.nodeName;
-				if (docType.publicId) {
-					docTypeString += " PUBLIC \"" + docType.publicId + "\"";
-					if (docType.systemId)
-						docTypeString += " \"" + docType.systemId + "\"";
-				} else if (docType.systemId)
-					docTypeString += " SYSTEM \"" + docType.systemId + "\"";
-				if (docType.internalSubset)
-					docTypeString += " [" + docType.internalSubset + "]";
-				docTypeString += "> ";
-			}
-			return docTypeString + serialize(doc.documentElement, compressHTML);
-		}
+		process
 	};
 
+	function process(doc, compressHTML) {
+		const docType = doc.doctype;
+		let docTypeString = "";
+		if (docType) {
+			docTypeString = "<!DOCTYPE " + docType.nodeName;
+			if (docType.publicId) {
+				docTypeString += " PUBLIC \"" + docType.publicId + "\"";
+				if (docType.systemId)
+					docTypeString += " \"" + docType.systemId + "\"";
+			} else if (docType.systemId)
+				docTypeString += " SYSTEM \"" + docType.systemId + "\"";
+			if (docType.internalSubset)
+				docTypeString += " [" + docType.internalSubset + "]";
+			docTypeString += "> ";
+		}
+		return docTypeString + serialize(doc.documentElement, compressHTML);
+	}
+
 	function serialize(node, compressHTML, isSVG) {
 		if (node.nodeType == Node_TEXT_NODE) {
 			return serializeTextNode(node);

+ 39 - 32
lib/single-file/processors/frame-tree/content/content-frame-tree.js

@@ -92,43 +92,50 @@ this.singlefile.lib.processors.frameTree.content.frames = this.singlefile.lib.pr
 			}
 		}
 	}, true);
+
 	return {
-		getAsync: options => {
-			const sessionId = getNewSessionId();
-			options = JSON.parse(JSON.stringify(options));
-			return new Promise(resolve => {
-				sessions.set(sessionId, {
-					frames: [],
-					requestTimeouts: {},
-					responseTimeouts: {},
-					resolve: frames => {
-						frames.sessionId = sessionId;
-						resolve(frames);
-					}
-				});
-				initRequestAsync({ windowId, sessionId, options });
-			});
-		},
-		getSync: options => {
-			const sessionId = getNewSessionId();
-			options = JSON.parse(JSON.stringify(options));
+		getAsync,
+		getSync,
+		cleanup,
+		initResponse,
+		TIMEOUT_INIT_REQUEST_MESSAGE
+	};
+
+	function getAsync(options) {
+		const sessionId = getNewSessionId();
+		options = JSON.parse(JSON.stringify(options));
+		return new Promise(resolve => {
 			sessions.set(sessionId, {
 				frames: [],
 				requestTimeouts: {},
-				responseTimeouts: {}
+				responseTimeouts: {},
+				resolve: frames => {
+					frames.sessionId = sessionId;
+					resolve(frames);
+				}
 			});
-			initRequestSync({ windowId, sessionId, options });
-			const frames = sessions.get(sessionId).frames;
-			frames.sessionId = sessionId;
-			return frames;
-		},
-		cleanup: sessionId => {
-			sessions.delete(sessionId);
-			cleanupRequest({ windowId, sessionId, options: { sessionId } });
-		},
-		initResponse,
-		TIMEOUT_INIT_REQUEST_MESSAGE
-	};
+			initRequestAsync({ windowId, sessionId, options });
+		});
+	}
+
+	function getSync(options) {
+		const sessionId = getNewSessionId();
+		options = JSON.parse(JSON.stringify(options));
+		sessions.set(sessionId, {
+			frames: [],
+			requestTimeouts: {},
+			responseTimeouts: {}
+		});
+		initRequestSync({ windowId, sessionId, options });
+		const frames = sessions.get(sessionId).frames;
+		frames.sessionId = sessionId;
+		return frames;
+	}
+
+	function cleanup(sessionId) {
+		sessions.delete(sessionId);
+		cleanupRequest({ windowId, sessionId, options: { sessionId } });
+	}
 
 	function getNewSessionId() {
 		return window.crypto.getRandomValues(new Uint32Array(32)).join("");

+ 1 - 1
lib/single-file/processors/hooks/content/content-hooks-frames-web.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global window, Event */
+/* global window */
 
 (() => {
 

+ 44 - 34
lib/single-file/processors/hooks/content/content-hooks-frames.js

@@ -71,44 +71,54 @@ this.singlefile.lib.processors.hooks.content.frames = this.singlefile.lib.proces
 	}
 
 	return {
-		getFontsData: () => fontFaces,
-		loadDeferredImagesStart: options => {
-			if (options.loadDeferredImagesBlockCookies) {
-				dispatchEvent(new CustomEvent(BLOCK_COOKIES_START_EVENT));
-			}
-			if (options.loadDeferredImagesBlockStorage) {
-				dispatchEvent(new CustomEvent(BLOCK_STORAGE_START_EVENT));
-			}
-			if (options.loadDeferredImagesKeepZoomLevel) {
-				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
-			} else {
-				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
-			}
-		},
-		loadDeferredImagesEnd: options => {
-			if (options.loadDeferredImagesBlockCookies) {
-				dispatchEvent(new CustomEvent(BLOCK_COOKIES_END_EVENT));
-			}
-			if (options.loadDeferredImagesBlockStorage) {
-				dispatchEvent(new CustomEvent(BLOCK_STORAGE_END_EVENT));
-			}
-			if (options.loadDeferredImagesKeepZoomLevel) {
-				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
-			} else {
-				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
-			}
-		},
-		loadDeferredImagesResetZoomLevel: options => {
-			if (options.loadDeferredImagesKeepZoomLevel) {
-				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT));
-			} else {
-				dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_EVENT));
-			}
-		},
+		getFontsData,
+		loadDeferredImagesStart,
+		loadDeferredImagesEnd,
+		loadDeferredImagesResetZoomLevel,
 		LOAD_IMAGE_EVENT,
 		IMAGE_LOADED_EVENT
 	};
 
+	function getFontsData() {
+		return fontFaces;
+	}
+
+	function loadDeferredImagesStart(options) {
+		if (options.loadDeferredImagesBlockCookies) {
+			dispatchEvent(new CustomEvent(BLOCK_COOKIES_START_EVENT));
+		}
+		if (options.loadDeferredImagesBlockStorage) {
+			dispatchEvent(new CustomEvent(BLOCK_STORAGE_START_EVENT));
+		}
+		if (options.loadDeferredImagesKeepZoomLevel) {
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
+		} else {
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
+		}
+	}
+
+	function loadDeferredImagesEnd(options) {
+		if (options.loadDeferredImagesBlockCookies) {
+			dispatchEvent(new CustomEvent(BLOCK_COOKIES_END_EVENT));
+		}
+		if (options.loadDeferredImagesBlockStorage) {
+			dispatchEvent(new CustomEvent(BLOCK_STORAGE_END_EVENT));
+		}
+		if (options.loadDeferredImagesKeepZoomLevel) {
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
+		} else {
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
+		}
+	}
+
+	function loadDeferredImagesResetZoomLevel(options) {
+		if (options.loadDeferredImagesKeepZoomLevel) {
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT));
+		} else {
+			dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_EVENT));
+		}
+	}
+
 	function injectedScript() {
 		const console = window.console;
 		const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });

+ 20 - 16
lib/single-file/processors/lazy/content/content-lazy-loader.js

@@ -48,25 +48,29 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
 	}
 
 	return {
-		process: async options => {
-			if (document.documentElement) {
-				timeouts.clear();
-				const maxScrollY = Math.max(document.documentElement.scrollHeight - (document.documentElement.clientHeight * 1.5), 0);
-				const maxScrollX = Math.max(document.documentElement.scrollWidth - (document.documentElement.clientWidth * 1.5), 0);
-				if (window.scrollY <= maxScrollY && window.scrollX <= maxScrollX) {
-					return process(options);
-				}
-			}
-		},
-		resetZoomLevel: options => {
-			const frames = singlefile.lib.processors.hooks.content.frames;
-			if (frames) {
-				frames.loadDeferredImagesResetZoomLevel(options);
+		process,
+		resetZoomLevel
+	};
+
+	async function process(options) {
+		if (document.documentElement) {
+			timeouts.clear();
+			const maxScrollY = Math.max(document.documentElement.scrollHeight - (document.documentElement.clientHeight * 1.5), 0);
+			const maxScrollX = Math.max(document.documentElement.scrollWidth - (document.documentElement.clientWidth * 1.5), 0);
+			if (window.scrollY <= maxScrollY && window.scrollX <= maxScrollX) {
+				return triggerLazyLoading(options);
 			}
 		}
-	};
+	}
+
+	function resetZoomLevel(options) {
+		const frames = singlefile.lib.processors.hooks.content.frames;
+		if (frames) {
+			frames.loadDeferredImagesResetZoomLevel(options);
+		}
+	}
 
-	function process(options) {
+	function triggerLazyLoading(options) {
 		const frames = singlefile.lib.processors.hooks.content.frames;
 		return new Promise(async resolve => { // eslint-disable-line  no-async-promise-executor
 			let loadingImages;

+ 228 - 226
lib/single-file/single-file-util.js

@@ -42,261 +42,263 @@ this.singlefile.lib.util = this.singlefile.lib.util || (() => {
 	const singlefile = this.singlefile;
 
 	return {
-		getInstance(utilOptions) {
-			const modules = singlefile.lib.modules;
-			const vendor = singlefile.lib.vendor;
-			const helper = singlefile.lib.helper;
+		getInstance
+	};
 
-			if (modules.serializer === undefined) {
-				modules.serializer = {
-					process(doc) {
-						const docType = doc.doctype;
-						let docTypeString = "";
-						if (docType) {
-							docTypeString = "<!DOCTYPE " + docType.nodeName;
-							if (docType.publicId) {
-								docTypeString += " PUBLIC \"" + docType.publicId + "\"";
-								if (docType.systemId)
-									docTypeString += " \"" + docType.systemId + "\"";
-							} else if (docType.systemId)
-								docTypeString += " SYSTEM \"" + docType.systemId + "\"";
-							if (docType.internalSubset)
-								docTypeString += " [" + docType.internalSubset + "]";
-							docTypeString += "> ";
-						}
-						return docTypeString + doc.documentElement.outerHTML;
-					}
-				};
-			}
+	function getInstance(utilOptions) {
+		const modules = singlefile.lib.modules;
+		const vendor = singlefile.lib.vendor;
+		const helper = singlefile.lib.helper;
 
-			utilOptions = utilOptions || {};
-			utilOptions.fetch = utilOptions.fetch || fetch;
-			utilOptions.frameFetch = utilOptions.frameFetch || utilOptions.fetch || fetch;
-			return {
-				getContent,
-				parseURL(resourceURL, baseURI) {
-					if (baseURI === undefined) {
-						return new URL(resourceURL);
-					} else {
-						return new URL(resourceURL, baseURI);
-					}
-				},
-				resolveURL(resourceURL, baseURI) {
-					return this.parseURL(resourceURL, baseURI).href;
-				},
-				getValidFilename(filename, replacedCharacters = DEFAULT_REPLACED_CHARACTERS, replacementCharacter = DEFAULT_REPLACEMENT_CHARACTER) {
-					replacedCharacters.forEach(replacedCharacter => filename = filename.replace(new RegExp("[" + replacedCharacter + "]+", "g"), replacementCharacter));
-					filename = filename
-						.replace(/\.\.\//g, "")
-						.replace(/^\/+/, "")
-						.replace(/\/+/g, "/")
-						.replace(/\/$/, "")
-						.replace(/\.$/, "")
-						.replace(/\.\//g, "." + replacementCharacter)
-						.replace(/\/\./g, "/" + replacementCharacter);
-					return filename;
-				},
-				parseDocContent(content, baseURI) {
-					const doc = (new DOMParser()).parseFromString(content, "text/html");
-					if (!doc.head) {
-						doc.documentElement.insertBefore(doc.createElement("HEAD"), doc.body);
-					}
-					let baseElement = doc.querySelector("base");
-					if (!baseElement || !baseElement.getAttribute("href")) {
-						if (baseElement) {
-							baseElement.remove();
-						}
-						baseElement = doc.createElement("base");
-						baseElement.setAttribute("href", baseURI);
-						doc.head.insertBefore(baseElement, doc.head.firstChild);
+		if (modules.serializer === undefined) {
+			modules.serializer = {
+				process(doc) {
+					const docType = doc.doctype;
+					let docTypeString = "";
+					if (docType) {
+						docTypeString = "<!DOCTYPE " + docType.nodeName;
+						if (docType.publicId) {
+							docTypeString += " PUBLIC \"" + docType.publicId + "\"";
+							if (docType.systemId)
+								docTypeString += " \"" + docType.systemId + "\"";
+						} else if (docType.systemId)
+							docTypeString += " SYSTEM \"" + docType.systemId + "\"";
+						if (docType.internalSubset)
+							docTypeString += " [" + docType.internalSubset + "]";
+						docTypeString += "> ";
 					}
-					return doc;
-				},
-				parseXMLContent(content) {
-					return (new DOMParser()).parseFromString(content, "text/xml");
-				},
-				parseSVGContent(content) {
-					return (new DOMParser()).parseFromString(content, "image/svg+xml");
-				},
-				async digest(algo, text) {
-					try {
-						const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
-						return hex(hash);
-					} catch (error) {
-						return "";
-					}
-				},
-				getContentSize(content) {
-					return new Blob([content]).size;
-				},
-				truncateText(content, maxSize) {
-					const blob = new Blob([content]);
-					const reader = new FileReader();
-					reader.readAsText(blob.slice(0, maxSize));
-					return new Promise((resolve, reject) => {
-						reader.addEventListener("load", () => {
-							if (content.startsWith(reader.result)) {
-								resolve(reader.result);
-							} else {
-								this.truncateText(content, maxSize - 1).then(resolve).catch(reject);
-							}
-						}, false);
-						reader.addEventListener("error", reject, false);
-					});
-				},
-				minifyHTML(doc, options) {
-					return modules.htmlMinifier.process(doc, options);
-				},
-				minifyCSSRules(stylesheets, styles, mediaAllInfo) {
-					return modules.cssRulesMinifier.process(stylesheets, styles, mediaAllInfo);
-				},
-				removeUnusedFonts(doc, stylesheets, styles, options) {
-					return modules.fontsMinifier.process(doc, stylesheets, styles, options);
-				},
-				removeAlternativeFonts(doc, stylesheets, fontURLs, fontTests) {
-					return modules.fontsAltMinifier.process(doc, stylesheets, fontURLs, fontTests);
-				},
-				getMediaAllInfo(doc, stylesheets, styles) {
-					return modules.matchedRules.getMediaAllInfo(doc, stylesheets, styles);
-				},
-				compressCSS(content, options) {
-					return vendor.cssMinifier.processString(content, options);
-				},
-				minifyMedias(stylesheets) {
-					return modules.mediasAltMinifier.process(stylesheets);
-				},
-				removeAlternativeImages(doc) {
-					return modules.imagesAltMinifier.process(doc);
-				},
-				parseSrcset(srcset) {
-					return vendor.srcsetParser.process(srcset);
-				},
-				preProcessDoc(doc, win, options) {
-					return helper.preProcessDoc(doc, win, options);
-				},
-				postProcessDoc(doc, markedElements) {
-					helper.postProcessDoc(doc, markedElements);
-				},
-				serialize(doc, compressHTML) {
-					return modules.serializer.process(doc, compressHTML);
-				},
-				removeQuotes(string) {
-					return helper.removeQuotes(string);
-				},
-				waitForUserScript(eventPrefixName) {
-					if (helper.waitForUserScript) {
-						return helper.waitForUserScript(eventPrefixName);
-					}
-				},
-				ON_BEFORE_CAPTURE_EVENT_NAME: helper.ON_BEFORE_CAPTURE_EVENT_NAME,
-				ON_AFTER_CAPTURE_EVENT_NAME: helper.ON_AFTER_CAPTURE_EVENT_NAME,
-				WIN_ID_ATTRIBUTE_NAME: helper.WIN_ID_ATTRIBUTE_NAME,
-				REMOVED_CONTENT_ATTRIBUTE_NAME: helper.REMOVED_CONTENT_ATTRIBUTE_NAME,
-				HIDDEN_CONTENT_ATTRIBUTE_NAME: helper.HIDDEN_CONTENT_ATTRIBUTE_NAME,
-				HIDDEN_FRAME_ATTRIBUTE_NAME: helper.HIDDEN_FRAME_ATTRIBUTE_NAME,
-				IMAGE_ATTRIBUTE_NAME: helper.IMAGE_ATTRIBUTE_NAME,
-				POSTER_ATTRIBUTE_NAME: helper.POSTER_ATTRIBUTE_NAME,
-				CANVAS_ATTRIBUTE_NAME: helper.CANVAS_ATTRIBUTE_NAME,
-				HTML_IMPORT_ATTRIBUTE_NAME: helper.HTML_IMPORT_ATTRIBUTE_NAME,
-				INPUT_VALUE_ATTRIBUTE_NAME: helper.INPUT_VALUE_ATTRIBUTE_NAME,
-				SHADOW_ROOT_ATTRIBUTE_NAME: helper.SHADOW_ROOT_ATTRIBUTE_NAME,
-				PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
-				STYLESHEET_ATTRIBUTE_NAME: helper.STYLESHEET_ATTRIBUTE_NAME,
-				SELECTED_CONTENT_ATTRIBUTE_NAME: helper.SELECTED_CONTENT_ATTRIBUTE_NAME,
-				COMMENT_HEADER: helper.COMMENT_HEADER,
-				COMMENT_HEADER_LEGACY: helper.COMMENT_HEADER_LEGACY,
-				SINGLE_FILE_UI_ELEMENT_CLASS: helper.SINGLE_FILE_UI_ELEMENT_CLASS
+					return docTypeString + doc.documentElement.outerHTML;
+				}
 			};
+		}
 
-			async function getContent(resourceURL, options) {
-				let response, startTime;
-				const fetchResource = utilOptions.fetch;
-				const fetchFrameResource = utilOptions.frameFetch;
-				if (DEBUG) {
-					startTime = Date.now();
-					log("  // STARTED download url =", resourceURL, "asBinary =", options.asBinary);
+		utilOptions = utilOptions || {};
+		utilOptions.fetch = utilOptions.fetch || fetch;
+		utilOptions.frameFetch = utilOptions.frameFetch || utilOptions.fetch || fetch;
+		return {
+			getContent,
+			parseURL(resourceURL, baseURI) {
+				if (baseURI === undefined) {
+					return new URL(resourceURL);
+				} else {
+					return new URL(resourceURL, baseURI);
 				}
-				try {
-					if (options.frameId) {
-						try {
-							response = await fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer });
-						} catch (error) {
-							response = await fetchResource(resourceURL);
-						}
-					} else {
-						response = await fetchResource(resourceURL, { referrer: options.resourceReferrer });
-					}
-				} catch (error) {
-					return { data: options.asBinary ? "data:null;base64," : "", resourceURL };
+			},
+			resolveURL(resourceURL, baseURI) {
+				return this.parseURL(resourceURL, baseURI).href;
+			},
+			getValidFilename(filename, replacedCharacters = DEFAULT_REPLACED_CHARACTERS, replacementCharacter = DEFAULT_REPLACEMENT_CHARACTER) {
+				replacedCharacters.forEach(replacedCharacter => filename = filename.replace(new RegExp("[" + replacedCharacter + "]+", "g"), replacementCharacter));
+				filename = filename
+					.replace(/\.\.\//g, "")
+					.replace(/^\/+/, "")
+					.replace(/\/+/g, "/")
+					.replace(/\/$/, "")
+					.replace(/\.$/, "")
+					.replace(/\.\//g, "." + replacementCharacter)
+					.replace(/\/\./g, "/" + replacementCharacter);
+				return filename;
+			},
+			parseDocContent(content, baseURI) {
+				const doc = (new DOMParser()).parseFromString(content, "text/html");
+				if (!doc.head) {
+					doc.documentElement.insertBefore(doc.createElement("HEAD"), doc.body);
 				}
-				let buffer;
-				try {
-					buffer = await response.arrayBuffer();
-				} catch (error) {
-					return { data: options.asBinary ? "data:null;base64," : "", resourceURL };
+				let baseElement = doc.querySelector("base");
+				if (!baseElement || !baseElement.getAttribute("href")) {
+					if (baseElement) {
+						baseElement.remove();
+					}
+					baseElement = doc.createElement("base");
+					baseElement.setAttribute("href", baseURI);
+					doc.head.insertBefore(baseElement, doc.head.firstChild);
 				}
-				resourceURL = response.url || resourceURL;
-				let contentType = "", charset;
+				return doc;
+			},
+			parseXMLContent(content) {
+				return (new DOMParser()).parseFromString(content, "text/xml");
+			},
+			parseSVGContent(content) {
+				return (new DOMParser()).parseFromString(content, "image/svg+xml");
+			},
+			async digest(algo, text) {
 				try {
-					const mimeType = new vendor.MIMEType(response.headers.get("content-type"));
-					contentType = mimeType.type + "/" + mimeType.subtype;
-					charset = mimeType.parameters.get("charset");
+					const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
+					return hex(hash);
 				} catch (error) {
-					// ignored
+					return "";
 				}
-				if (!contentType) {
-					contentType = guessMIMEType(options.expectedType, buffer);
-				}
-				if (!charset && options.charset) {
-					charset = options.charset;
-				}
-				if (options.asBinary) {
-					try {
-						if (DEBUG) {
-							log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
-						}
-						if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
-							return { data: "data:null;base64,", resourceURL };
+			},
+			getContentSize(content) {
+				return new Blob([content]).size;
+			},
+			truncateText(content, maxSize) {
+				const blob = new Blob([content]);
+				const reader = new FileReader();
+				reader.readAsText(blob.slice(0, maxSize));
+				return new Promise((resolve, reject) => {
+					reader.addEventListener("load", () => {
+						if (content.startsWith(reader.result)) {
+							resolve(reader.result);
 						} else {
-							const reader = new FileReader();
-							reader.readAsDataURL(new Blob([buffer], { type: contentType + (options.charset ? ";charset=" + options.charset : "") }));
-							const dataUri = await new Promise((resolve, reject) => {
-								reader.addEventListener("load", () => resolve(reader.result), false);
-								reader.addEventListener("error", reject, false);
-							});
-							return { data: dataUri, resourceURL };
+							this.truncateText(content, maxSize - 1).then(resolve).catch(reject);
 						}
+					}, false);
+					reader.addEventListener("error", reject, false);
+				});
+			},
+			minifyHTML(doc, options) {
+				return modules.htmlMinifier.process(doc, options);
+			},
+			minifyCSSRules(stylesheets, styles, mediaAllInfo) {
+				return modules.cssRulesMinifier.process(stylesheets, styles, mediaAllInfo);
+			},
+			removeUnusedFonts(doc, stylesheets, styles, options) {
+				return modules.fontsMinifier.process(doc, stylesheets, styles, options);
+			},
+			removeAlternativeFonts(doc, stylesheets, fontURLs, fontTests) {
+				return modules.fontsAltMinifier.process(doc, stylesheets, fontURLs, fontTests);
+			},
+			getMediaAllInfo(doc, stylesheets, styles) {
+				return modules.matchedRules.getMediaAllInfo(doc, stylesheets, styles);
+			},
+			compressCSS(content, options) {
+				return vendor.cssMinifier.processString(content, options);
+			},
+			minifyMedias(stylesheets) {
+				return modules.mediasAltMinifier.process(stylesheets);
+			},
+			removeAlternativeImages(doc) {
+				return modules.imagesAltMinifier.process(doc);
+			},
+			parseSrcset(srcset) {
+				return vendor.srcsetParser.process(srcset);
+			},
+			preProcessDoc(doc, win, options) {
+				return helper.preProcessDoc(doc, win, options);
+			},
+			postProcessDoc(doc, markedElements) {
+				helper.postProcessDoc(doc, markedElements);
+			},
+			serialize(doc, compressHTML) {
+				return modules.serializer.process(doc, compressHTML);
+			},
+			removeQuotes(string) {
+				return helper.removeQuotes(string);
+			},
+			waitForUserScript(eventPrefixName) {
+				if (helper.waitForUserScript) {
+					return helper.waitForUserScript(eventPrefixName);
+				}
+			},
+			ON_BEFORE_CAPTURE_EVENT_NAME: helper.ON_BEFORE_CAPTURE_EVENT_NAME,
+			ON_AFTER_CAPTURE_EVENT_NAME: helper.ON_AFTER_CAPTURE_EVENT_NAME,
+			WIN_ID_ATTRIBUTE_NAME: helper.WIN_ID_ATTRIBUTE_NAME,
+			REMOVED_CONTENT_ATTRIBUTE_NAME: helper.REMOVED_CONTENT_ATTRIBUTE_NAME,
+			HIDDEN_CONTENT_ATTRIBUTE_NAME: helper.HIDDEN_CONTENT_ATTRIBUTE_NAME,
+			HIDDEN_FRAME_ATTRIBUTE_NAME: helper.HIDDEN_FRAME_ATTRIBUTE_NAME,
+			IMAGE_ATTRIBUTE_NAME: helper.IMAGE_ATTRIBUTE_NAME,
+			POSTER_ATTRIBUTE_NAME: helper.POSTER_ATTRIBUTE_NAME,
+			CANVAS_ATTRIBUTE_NAME: helper.CANVAS_ATTRIBUTE_NAME,
+			HTML_IMPORT_ATTRIBUTE_NAME: helper.HTML_IMPORT_ATTRIBUTE_NAME,
+			INPUT_VALUE_ATTRIBUTE_NAME: helper.INPUT_VALUE_ATTRIBUTE_NAME,
+			SHADOW_ROOT_ATTRIBUTE_NAME: helper.SHADOW_ROOT_ATTRIBUTE_NAME,
+			PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
+			STYLESHEET_ATTRIBUTE_NAME: helper.STYLESHEET_ATTRIBUTE_NAME,
+			SELECTED_CONTENT_ATTRIBUTE_NAME: helper.SELECTED_CONTENT_ATTRIBUTE_NAME,
+			COMMENT_HEADER: helper.COMMENT_HEADER,
+			COMMENT_HEADER_LEGACY: helper.COMMENT_HEADER_LEGACY,
+			SINGLE_FILE_UI_ELEMENT_CLASS: helper.SINGLE_FILE_UI_ELEMENT_CLASS
+		};
+
+		async function getContent(resourceURL, options) {
+			let response, startTime;
+			const fetchResource = utilOptions.fetch;
+			const fetchFrameResource = utilOptions.frameFetch;
+			if (DEBUG) {
+				startTime = Date.now();
+				log("  // STARTED download url =", resourceURL, "asBinary =", options.asBinary);
+			}
+			try {
+				if (options.frameId) {
+					try {
+						response = await fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer });
 					} catch (error) {
-						return { data: "data:null;base64,", resourceURL };
+						response = await fetchResource(resourceURL);
 					}
 				} else {
-					if (response.status >= 400 || (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT))) {
-						return { data: "", resourceURL };
-					}
-					if (!charset) {
-						charset = "utf-8";
-					}
+					response = await fetchResource(resourceURL, { referrer: options.resourceReferrer });
+				}
+			} catch (error) {
+				return { data: options.asBinary ? "data:null;base64," : "", resourceURL };
+			}
+			let buffer;
+			try {
+				buffer = await response.arrayBuffer();
+			} catch (error) {
+				return { data: options.asBinary ? "data:null;base64," : "", resourceURL };
+			}
+			resourceURL = response.url || resourceURL;
+			let contentType = "", charset;
+			try {
+				const mimeType = new vendor.MIMEType(response.headers.get("content-type"));
+				contentType = mimeType.type + "/" + mimeType.subtype;
+				charset = mimeType.parameters.get("charset");
+			} catch (error) {
+				// ignored
+			}
+			if (!contentType) {
+				contentType = guessMIMEType(options.expectedType, buffer);
+			}
+			if (!charset && options.charset) {
+				charset = options.charset;
+			}
+			if (options.asBinary) {
+				try {
 					if (DEBUG) {
 						log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
 					}
 					if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
-						return { data: "", resourceURL, charset };
+						return { data: "data:null;base64,", resourceURL };
 					} else {
+						const reader = new FileReader();
+						reader.readAsDataURL(new Blob([buffer], { type: contentType + (options.charset ? ";charset=" + options.charset : "") }));
+						const dataUri = await new Promise((resolve, reject) => {
+							reader.addEventListener("load", () => resolve(reader.result), false);
+							reader.addEventListener("error", reject, false);
+						});
+						return { data: dataUri, resourceURL };
+					}
+				} catch (error) {
+					return { data: "data:null;base64,", resourceURL };
+				}
+			} else {
+				if (response.status >= 400 || (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT))) {
+					return { data: "", resourceURL };
+				}
+				if (!charset) {
+					charset = "utf-8";
+				}
+				if (DEBUG) {
+					log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
+				}
+				if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
+					return { data: "", resourceURL, charset };
+				} else {
+					try {
+						return { data: new TextDecoder(charset).decode(buffer), resourceURL, charset };
+					} catch (error) {
 						try {
+							charset = "utf-8";
 							return { data: new TextDecoder(charset).decode(buffer), resourceURL, charset };
 						} catch (error) {
-							try {
-								charset = "utf-8";
-								return { data: new TextDecoder(charset).decode(buffer), resourceURL, charset };
-							} catch (error) {
-								return { data: "", resourceURL, charset };
-							}
+							return { data: "", resourceURL, charset };
 						}
 					}
 				}
 			}
 		}
-	};
+	}
 
 	function guessMIMEType(expectedType, buffer) {
 		if (expectedType == "image") {

+ 6 - 4
lib/single-file/single-file.js

@@ -26,10 +26,12 @@ this.singlefile.lib.main = this.singlefile.lib.main || (() => {
 	const singlefile = this.singlefile;
 
 	return {
-		init(initOptions) {
-			const util = singlefile.lib.util.getInstance(initOptions);
-			singlefile.lib.SingleFile = singlefile.lib.core.getClass(util, singlefile.lib.vendor.cssTree);
-		}
+		init
 	};
 
+	function init(initOptions) {
+		const util = singlefile.lib.util.getInstance(initOptions);
+		singlefile.lib.SingleFile = singlefile.lib.core.getClass(util, singlefile.lib.vendor.cssTree);
+	}
+
 })();

+ 70 - 68
lib/single-file/vendor/css-font-property-parser.js

@@ -127,91 +127,93 @@ this.singlefile.lib.vendor.fontPropertyParser = this.singlefile.lib.vendor.fontP
 	const errorPrefix = "[parse-css-font] ";
 
 	return {
-		parse(value) {
-			if (typeof value !== "string") {
-				throw new TypeError(errorPrefix + "Expected a string.");
-			}
-			if (value === "") {
-				throw error("Cannot parse an empty string.");
-			}
-			if (systemFontKeywords.indexOf(value) !== -1) {
-				return { system: value };
-			}
+		parse
+	};
 
-			const font = {
-				lineHeight: "normal",
-				stretch: "normal",
-				style: "normal",
-				variant: "normal",
-				weight: "normal",
-			};
-
-			let isLocked = false;
-			const tokens = cssListHelpers.splitBySpaces(value);
-			let token = tokens.shift();
-			for (; token; token = tokens.shift()) {
-
-				if (token === "normal" || globalKeywords.indexOf(token) !== -1) {
-					["style", "variant", "weight", "stretch"].forEach((prop) => {
-						font[prop] = token;
-					});
-					isLocked = true;
-					continue;
-				}
+	function parse(value) {
+		if (typeof value !== "string") {
+			throw new TypeError(errorPrefix + "Expected a string.");
+		}
+		if (value === "") {
+			throw error("Cannot parse an empty string.");
+		}
+		if (systemFontKeywords.indexOf(value) !== -1) {
+			return { system: value };
+		}
 
-				if (fontWeightKeywords.indexOf(token) !== -1) {
-					if (isLocked) {
-						continue;
-					}
-					font.weight = token;
+		const font = {
+			lineHeight: "normal",
+			stretch: "normal",
+			style: "normal",
+			variant: "normal",
+			weight: "normal",
+		};
+
+		let isLocked = false;
+		const tokens = cssListHelpers.splitBySpaces(value);
+		let token = tokens.shift();
+		for (; token; token = tokens.shift()) {
+
+			if (token === "normal" || globalKeywords.indexOf(token) !== -1) {
+				["style", "variant", "weight", "stretch"].forEach((prop) => {
+					font[prop] = token;
+				});
+				isLocked = true;
+				continue;
+			}
+
+			if (fontWeightKeywords.indexOf(token) !== -1) {
+				if (isLocked) {
 					continue;
 				}
+				font.weight = token;
+				continue;
+			}
 
-				if (fontStyleKeywords.indexOf(token) !== -1) {
-					if (isLocked) {
-						continue;
-					}
-					font.style = token;
+			if (fontStyleKeywords.indexOf(token) !== -1) {
+				if (isLocked) {
 					continue;
 				}
+				font.style = token;
+				continue;
+			}
 
-				if (fontStretchKeywords.indexOf(token) !== -1) {
-					if (isLocked) {
-						continue;
-					}
-					font.stretch = token;
+			if (fontStretchKeywords.indexOf(token) !== -1) {
+				if (isLocked) {
 					continue;
 				}
+				font.stretch = token;
+				continue;
+			}
 
-				if (helpers.isSize(token)) {
-					const parts = cssListHelpers.split(token, ["/"]);
-					font.size = parts[0];
-					if (parts[1]) {
-						font.lineHeight = parseLineHeight(parts[1]);
-					} else if (tokens[0] === "/") {
-						tokens.shift();
-						font.lineHeight = parseLineHeight(tokens.shift());
-					}
-					if (!tokens.length) {
-						throw error("Missing required font-family.");
-					}
-					font.family = cssListHelpers.splitByCommas(tokens.join(" ")).map(removeQuotes);
-					return font;
+			if (helpers.isSize(token)) {
+				const parts = cssListHelpers.split(token, ["/"]);
+				font.size = parts[0];
+				if (parts[1]) {
+					font.lineHeight = parseLineHeight(parts[1]);
+				} else if (tokens[0] === "/") {
+					tokens.shift();
+					font.lineHeight = parseLineHeight(tokens.shift());
 				}
-
-				if (font.variant !== "normal") {
-					throw error("Unknown or unsupported font token: " + font.variant);
+				if (!tokens.length) {
+					throw error("Missing required font-family.");
 				}
+				font.family = cssListHelpers.splitByCommas(tokens.join(" ")).map(removeQuotes);
+				return font;
+			}
 
-				if (isLocked) {
-					continue;
-				}
-				font.variant = token;
+			if (font.variant !== "normal") {
+				throw error("Unknown or unsupported font token: " + font.variant);
 			}
 
-			throw error("Missing required font-size.");
+			if (isLocked) {
+				continue;
+			}
+			font.variant = token;
 		}
-	};
+
+		throw error("Missing required font-size.");
+	}
 
 	function error(message) {
 		return new Error(errorPrefix + message);

+ 18 - 16
lib/single-file/vendor/css-unescape.js

@@ -53,22 +53,24 @@ this.singlefile.lib.vendor.cssUnescape = this.singlefile.lib.vendor.cssUnescape
 	const unescapeRegExp = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig");
 
 	return {
-		process: function (str) {
-			return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
-				const high = "0x" + escaped - 0x10000;
-
-				// NaN means non-codepoint
-				// Workaround erroneous numeric interpretation of +"0x"
-				// eslint-disable-next-line no-self-compare
-				return high !== high || escapedWhitespace
-					? escaped
-					: high < 0
-						? // BMP codepoint
-						String.fromCharCode(high + 0x10000)
-						: // Supplemental Plane codepoint (surrogate pair)
-						String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
-			});
-		}
+		process
 	};
 
+	function process(str) {
+		return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
+			const high = "0x" + escaped - 0x10000;
+
+			// NaN means non-codepoint
+			// Workaround erroneous numeric interpretation of +"0x"
+			// eslint-disable-next-line no-self-compare
+			return high !== high || escapedWhitespace
+				? escaped
+				: high < 0
+					? // BMP codepoint
+					String.fromCharCode(high + 0x10000)
+					: // Supplemental Plane codepoint (surrogate pair)
+					String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
+		});
+	}
+
 })();