Browse Source

refactored docUtil creation pattern to match the one used in modules

Gildas 7 năm trước cách đây
mục cha
commit
0b0f8b12cf

+ 1 - 1
extension/core/bg/core.js

@@ -29,7 +29,7 @@ singlefile.core = (() => {
 		"/lib/single-file/vendor/html-srcset-parser.js",
 		"/lib/single-file/util/timeout.js",
 		"/lib/single-file/util/doc-helper.js",
-		"/lib/single-file/util/doc-util-core.js",
+		"/lib/single-file/util/doc-util.js",
 		"/lib/fetch/content/fetch.js",
 		"/lib/single-file/single-file-core.js",
 		"/lib/single-file/single-file-browser.js",

+ 2 - 3
lib/single-file/single-file-browser.js

@@ -20,7 +20,7 @@
 
 /* global
 	URL,
-	DocUtilCore,
+	docUtil,
 	cssTree,
 	docHelper,
 	crypto, 
@@ -61,8 +61,7 @@ this.SingleFileBrowser = this.SingleFileBrowser || (() => {
 		digestText,
 		parseURL
 	};
-	const DocUtil = DocUtilCore.getClass(modules, domUtil);
-	const SingleFile = SingleFileCore.getClass(DocUtil, cssTree);
+	const SingleFile = SingleFileCore.getClass(docUtil.getInstance(modules, domUtil), cssTree);
 	let fetchResource;
 	return {
 		getClass: () => SingleFile

+ 57 - 57
lib/single-file/single-file-core.js

@@ -27,10 +27,10 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	const SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME = "data-single-file-selected-content-root";
 	const DEBUG = false;
 
-	let DocUtil, cssTree, sessionId = 0;
+	let docUtil, cssTree, sessionId = 0;
 
 	function getClass(...args) {
-		[DocUtil, cssTree] = args;
+		[docUtil, cssTree] = args;
 		return SingleFileClass;
 	}
 
@@ -151,7 +151,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			this.batchRequest = new BatchRequest();
 			this.processor = new Processor(options, this.batchRequest);
 			if (this.options.doc) {
-				const docData = DocUtil.preProcessDoc(this.options.doc, this.options.win, this.options);
+				const docData = docUtil.preProcessDoc(this.options.doc, this.options.win, this.options);
 				this.options.canvasData = docData.canvasData;
 				this.options.fontsData = docData.fontsData;
 				this.options.stylesheetContents = docData.stylesheetContents;
@@ -163,7 +163,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			if (this.options.saveRawPage) {
 				this.options.removeFrames = true;
 			}
-			this.options.content = this.options.content || (this.options.doc ? DocUtil.serialize(this.options.doc, false) : null);
+			this.options.content = this.options.content || (this.options.doc ? docUtil.serialize(this.options.doc, false) : null);
 			this.onprogress = options.onprogress || (() => { });
 		}
 
@@ -178,7 +178,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			await this.executeStage(RESOLVE_URLS_STAGE);
 			this.pendingPromises = this.executeStage(REPLACE_DATA_STAGE);
 			if (this.options.doc) {
-				DocUtil.postProcessDoc(this.options.doc, this.options);
+				docUtil.postProcessDoc(this.options.doc, this.options);
 				this.options.doc = null;
 				this.options.win = null;
 			}
@@ -298,7 +298,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				const [resourceURL, asDataURI] = JSON.parse(requestKey);
 				const resourceRequests = this.requests.get(requestKey);
 				try {
-					const content = await DocUtil.getContent(resourceURL, { asDataURI, maxResourceSize: options.maxResourceSize, maxResourceSizeEnabled: options.maxResourceSizeEnabled });
+					const content = await docUtil.getContent(resourceURL, { asDataURI, maxResourceSize: options.maxResourceSize, maxResourceSizeEnabled: options.maxResourceSizeEnabled });
 					indexResource = indexResource + 1;
 					onloadListener({ index: indexResource, url: resourceURL });
 					resourceRequests.forEach(callbacks => {
@@ -346,10 +346,10 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		async loadPage(pageContent, charset) {
 			let content;
 			if (!pageContent || this.options.saveRawPage) {
-				content = await DocUtil.getContent(this.baseURI, { asDataURI: false, maxResourceSize: this.options.maxResourceSize, maxResourceSizeEnabled: this.options.maxResourceSizeEnabled, charset });
+				content = await docUtil.getContent(this.baseURI, { asDataURI: false, maxResourceSize: this.options.maxResourceSize, maxResourceSizeEnabled: this.options.maxResourceSizeEnabled, charset });
 				pageContent = content.data;
 			}
-			this.doc = DocUtil.parseDocContent(pageContent, this.baseURI);
+			this.doc = docUtil.parseDocContent(pageContent, this.baseURI);
 			if (this.options.saveRawPage) {
 				let charset;
 				this.doc.querySelectorAll("meta[charset], meta[http-equiv=\"content-type\"]").forEach(element => {
@@ -394,8 +394,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		}
 
 		async getPageData() {
-			DocUtil.postProcessDoc(this.doc, this.options);
-			const url = DocUtil.parseURL(this.baseURI);
+			docUtil.postProcessDoc(this.doc, this.options);
+			const url = docUtil.parseURL(this.baseURI);
 			if (this.options.insertSingleFileComment) {
 				const infobarContent = (this.options.infobarContent || "").replace(/\\n/g, "\n").replace(/\\t/g, "\t");
 				const commentNode = this.doc.createComment("\n Page saved with SingleFile" +
@@ -406,11 +406,11 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 			let size;
 			if (this.options.displayStats) {
-				size = DocUtil.getContentSize(this.doc.documentElement.outerHTML);
+				size = docUtil.getContentSize(this.doc.documentElement.outerHTML);
 			}
-			const content = DocUtil.serialize(this.doc, this.options.compressHTML);
+			const content = docUtil.serialize(this.doc, this.options.compressHTML);
 			if (this.options.displayStats) {
-				const contentSize = DocUtil.getContentSize(content);
+				const contentSize = docUtil.getContentSize(content);
 				this.stats.set("processed", "HTML bytes", contentSize);
 				this.stats.add("discarded", "HTML bytes", size - contentSize);
 			}
@@ -442,7 +442,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				this.doc.body.querySelectorAll(":not(svg) title, meta, link[href][rel*=\"icon\"]").forEach(element => element instanceof this.options.win.HTMLElement && this.doc.head.appendChild(element));
 			}
 			if (this.options.imageData) {
-				const dataAttributeName = DocUtil.imagesAttributeName(this.options.sessionId);
+				const dataAttributeName = docUtil.imagesAttributeName(this.options.sessionId);
 				this.doc.querySelectorAll("img").forEach(imgElement => {
 					const imgData = this.options.imageData[Number(imgElement.getAttribute(dataAttributeName))];
 					if (this.options.removeHiddenElements && imgData.size && !imgData.size.pxWidth && !imgData.size.pxHeight) {
@@ -472,13 +472,13 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 
 		insertShadowRootContents() {
 			if (this.options.shadowRootContents) {
-				this.doc.querySelectorAll("[" + DocUtil.shadowRootAttributeName(this.options.sessionId) + "]").forEach((element, elementIndex) => {
+				this.doc.querySelectorAll("[" + docUtil.shadowRootAttributeName(this.options.sessionId) + "]").forEach((element, elementIndex) => {
 					const elementInfo = this.options.shadowRootContents[elementIndex];
 					if (elementInfo) {
 						const frameElement = this.doc.createElement("iframe");
 						frameElement.setAttribute("style", "all:initial!important;border:0!important;width:100%!important;height:" + elementInfo.height + "px!important");
 						const windowId = "shadow-" + this.options.framesData.length;
-						frameElement.setAttribute(DocUtil.windowIdAttributeName(this.options.sessionId), windowId);
+						frameElement.setAttribute(docUtil.windowIdAttributeName(this.options.sessionId), windowId);
 						this.options.framesData.push({ windowId, content: elementInfo.content, baseURI: this.baseURI });
 						element.appendChild(frameElement);
 					}
@@ -625,22 +625,22 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 
 		setInputValues() {
 			this.doc.querySelectorAll("input").forEach(input => {
-				const value = input.getAttribute(DocUtil.inputValueAttributeName(this.options.sessionId));
+				const value = input.getAttribute(docUtil.inputValueAttributeName(this.options.sessionId));
 				input.setAttribute("value", value || "");
 			});
 			this.doc.querySelectorAll("input[type=radio], input[type=checkbox]").forEach(input => {
-				const value = input.getAttribute(DocUtil.inputValueAttributeName(this.options.sessionId));
+				const value = input.getAttribute(docUtil.inputValueAttributeName(this.options.sessionId));
 				if (value == "true") {
 					input.setAttribute("checked", "");
 				}
 			});
 			this.doc.querySelectorAll("textarea").forEach(textarea => {
-				const value = textarea.getAttribute(DocUtil.inputValueAttributeName(this.options.sessionId));
+				const value = textarea.getAttribute(docUtil.inputValueAttributeName(this.options.sessionId));
 				textarea.textContent = value || "";
 			});
 			this.doc.querySelectorAll("select").forEach(select => {
 				select.querySelectorAll("option").forEach(option => {
-					const selected = option.getAttribute(DocUtil.inputValueAttributeName(this.options.sessionId)) != null;
+					const selected = option.getAttribute(docUtil.inputValueAttributeName(this.options.sessionId)) != null;
 					if (selected) {
 						option.setAttribute("selected", "");
 					}
@@ -704,7 +704,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		}
 
 		removeHiddenElements() {
-			const hiddenElements = this.doc.querySelectorAll("[" + DocUtil.removedContentAttributeName(this.options.sessionId) + "]");
+			const hiddenElements = this.doc.querySelectorAll("[" + docUtil.removedContentAttributeName(this.options.sessionId) + "]");
 			this.stats.set("discarded", "hidden elements", hiddenElements.length);
 			this.stats.set("processed", "hidden elements", hiddenElements.length);
 			hiddenElements.forEach(element => element.remove());
@@ -715,7 +715,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				const href = element.getAttribute("href").trim();
 				if (!Util.testIgnoredPath(href) && !href.startsWith("#")) {
 					try {
-						element.setAttribute("href", DocUtil.resolveURL(href, this.options.baseURI || this.options.url));
+						element.setAttribute("href", docUtil.resolveURL(href, this.options.baseURI || this.options.url));
 					} catch (error) {
 						// ignored
 					}
@@ -727,7 +727,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			this.doc.querySelectorAll("[style]").forEach(element => {
 				let styleContent = element.getAttribute("style");
 				if (this.options.compressCSS) {
-					styleContent = DocUtil.compressCSS(styleContent);
+					styleContent = docUtil.compressCSS(styleContent);
 				}
 				styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.options);
 				const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
@@ -775,7 +775,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
 				await Promise.all(frameElements.map(async frameElement => {
 					ProcessorHelper.setFrameEmptySrc(frameElement);
-					const frameWindowId = frameElement.getAttribute(DocUtil.windowIdAttributeName(this.options.sessionId));
+					const frameWindowId = frameElement.getAttribute(docUtil.windowIdAttributeName(this.options.sessionId));
 					if (frameWindowId) {
 						const frameData = this.options.framesData.find(frame => frame.windowId == frameWindowId);
 						if (frameData) {
@@ -833,19 +833,19 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 
 		removeUnusedStyles() {
 			if (!this.mediaAllInfo) {
-				this.mediaAllInfo = DocUtil.getMediaAllInfo(this.doc, this.stylesheets, this.styles);
+				this.mediaAllInfo = docUtil.getMediaAllInfo(this.doc, this.stylesheets, this.styles);
 			}
-			const stats = DocUtil.minifyCSSRules(this.stylesheets, this.styles, this.mediaAllInfo);
+			const stats = docUtil.minifyCSSRules(this.stylesheets, this.styles, this.mediaAllInfo);
 			this.stats.set("processed", "CSS rules", stats.processed);
 			this.stats.set("discarded", "CSS rules", stats.discarded);
 		}
 
 		removeUnusedFonts() {
-			DocUtil.removeUnusedFonts(this.doc, this.stylesheets, this.styles, this.options);
+			docUtil.removeUnusedFonts(this.doc, this.stylesheets, this.styles, this.options);
 		}
 
 		removeAlternativeMedias() {
-			const stats = DocUtil.minifyMedias(this.stylesheets);
+			const stats = docUtil.minifyMedias(this.stylesheets);
 			this.stats.set("processed", "medias", stats.processed);
 			this.stats.set("discarded", "medias", stats.discarded);
 		}
@@ -896,31 +896,31 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				scriptElement.removeAttribute("src");
 				scriptElement.textContent = "";
 				try {
-					resourceURL = DocUtil.resolveURL(scriptSrc, this.baseURI);
+					resourceURL = docUtil.resolveURL(scriptSrc, this.baseURI);
 				} catch (error) {
 					// ignored
 				}
 				if (Util.testValidURL(resourceURL, this.baseURI, this.options.url)) {
 					this.stats.add("processed", "scripts", 1);
-					const content = await DocUtil.getContent(resourceURL, { asDataURI: true, maxResourceSize: this.options.maxResourceSize, maxResourceSizeEnabled: this.options.maxResourceSizeEnabled });
+					const content = await docUtil.getContent(resourceURL, { asDataURI: true, maxResourceSize: this.options.maxResourceSize, maxResourceSizeEnabled: this.options.maxResourceSizeEnabled });
 					scriptElement.setAttribute("src", content.data);
 				}
 			}));
 		}
 
 		removeAlternativeImages() {
-			DocUtil.removeAlternativeImages(this.doc);
+			docUtil.removeAlternativeImages(this.doc);
 		}
 
 		removeAlternativeFonts() {
-			DocUtil.removeAlternativeFonts(this.doc, this.stylesheets);
+			docUtil.removeAlternativeFonts(this.doc, this.stylesheets);
 		}
 
 		async processFrames() {
 			if (this.options.framesData) {
 				const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
 				await Promise.all(frameElements.map(async frameElement => {
-					const frameWindowId = frameElement.getAttribute(DocUtil.windowIdAttributeName(this.options.sessionId));
+					const frameWindowId = frameElement.getAttribute(docUtil.windowIdAttributeName(this.options.sessionId));
 					if (frameWindowId) {
 						const frameData = this.options.framesData.find(frame => frame.windowId == frameWindowId);
 						if (frameData) {
@@ -928,7 +928,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 								this.stats.add("processed", "frames", 1);
 								await frameData.processor.run();
 								const pageData = await frameData.processor.getPageData();
-								frameElement.removeAttribute(DocUtil.windowIdAttributeName(this.options.sessionId));
+								frameElement.removeAttribute(docUtil.windowIdAttributeName(this.options.sessionId));
 								if (pageData.content.match(NOSCRIPT_TAG_FOUND) || pageData.content.match(SCRIPT_TAG_FOUND)) {
 									frameElement.setAttribute("sandbox", "allow-scripts allow-same-origin");
 								} else {
@@ -1025,11 +1025,11 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		compressHTML() {
 			let size;
 			if (this.options.displayStats) {
-				size = DocUtil.getContentSize(this.doc.documentElement.outerHTML);
+				size = docUtil.getContentSize(this.doc.documentElement.outerHTML);
 			}
-			DocUtil.minifyHTML(this.doc, { preservedSpaceAttributeName: DocUtil.preservedSpaceAttributeName(this.options.sessionId) });
+			docUtil.minifyHTML(this.doc, { preservedSpaceAttributeName: docUtil.preservedSpaceAttributeName(this.options.sessionId) });
 			if (this.options.displayStats) {
-				this.stats.add("discarded", "HTML bytes", size - DocUtil.getContentSize(this.doc.documentElement.outerHTML));
+				this.stats.add("discarded", "HTML bytes", size - docUtil.getContentSize(this.doc.documentElement.outerHTML));
 			}
 		}
 	}
@@ -1055,7 +1055,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	class ProcessorHelper {
 		static async evalTemplate(template, options, content, dontReplaceSlash) {
 			const date = new Date();
-			const url = DocUtil.parseURL(options.url);
+			const url = docUtil.parseURL(options.url);
 			template = await Util.evalTemplateVariable(template, "page-title", () => options.title || "No title", dontReplaceSlash);
 			template = await Util.evalTemplateVariable(template, "page-language", () => options.info.lang || "No language", dontReplaceSlash);
 			template = await Util.evalTemplateVariable(template, "page-description", () => options.info.description || "No description", dontReplaceSlash);
@@ -1094,9 +1094,9 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			template = await Util.evalTemplateVariable(template, "tab-id", () => String(options.tabId || "No tab id"), dontReplaceSlash);
 			template = await Util.evalTemplateVariable(template, "url-last-segment", () => Util.getLastSegment(url) || "No last segment", dontReplaceSlash);
 			if (content) {
-				template = await Util.evalTemplateVariable(template, "digest-sha-256", async () => DocUtil.digest("SHA-256", content), dontReplaceSlash);
-				template = await Util.evalTemplateVariable(template, "digest-sha-384", async () => DocUtil.digest("SHA-384", content), dontReplaceSlash);
-				template = await Util.evalTemplateVariable(template, "digest-sha-512", async () => DocUtil.digest("SHA-512", content), dontReplaceSlash);
+				template = await Util.evalTemplateVariable(template, "digest-sha-256", async () => docUtil.digest("SHA-256", content), dontReplaceSlash);
+				template = await Util.evalTemplateVariable(template, "digest-sha-384", async () => docUtil.digest("SHA-384", content), dontReplaceSlash);
+				template = await Util.evalTemplateVariable(template, "digest-sha-512", async () => docUtil.digest("SHA-512", content), dontReplaceSlash);
 			}
 			return template;
 		}
@@ -1204,17 +1204,17 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 					let resourceURL = Util.normalizeURL(match.resourceURL);
 					if (!Util.testIgnoredPath(resourceURL) && Util.testValidPath(resourceURL, baseURI, options.url)) {
 						try {
-							resourceURL = DocUtil.resolveURL(match.resourceURL, baseURI);
+							resourceURL = docUtil.resolveURL(match.resourceURL, baseURI);
 						} catch (error) {
 							// ignored
 						}
 						if (Util.testValidURL(resourceURL, baseURI, options.url)) {
 							const downloadOptions = { asDataURI: false, maxResourceSize: options.maxResourceSize, maxResourceSizeEnabled: options.maxResourceSizeEnabled, validateTextContentType: true };
-							const content = await DocUtil.getContent(resourceURL, downloadOptions);
+							const content = await docUtil.getContent(resourceURL, downloadOptions);
 							resourceURL = content.resourceURL;
 							let importedStylesheetContent = Util.removeCssComments(content.data);
 							if (options.compressCSS) {
-								importedStylesheetContent = DocUtil.compressCSS(importedStylesheetContent);
+								importedStylesheetContent = docUtil.compressCSS(importedStylesheetContent);
 							}
 							importedStylesheetContent = Util.wrapMediaQuery(importedStylesheetContent, match.media);
 							if (stylesheetContent.includes(cssImport)) {
@@ -1242,7 +1242,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 					if (!resourceURL || Util.testValidPath(resourceURL, baseURI, options.url)) {
 						let resolvedURL;
 						try {
-							resolvedURL = DocUtil.resolveURL(resourceURL, baseURI);
+							resolvedURL = docUtil.resolveURL(resourceURL, baseURI);
 						} catch (error) {
 							// ignored
 						}
@@ -1274,11 +1274,11 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			resourceURL = Util.normalizeURL(resourceURL);
 			if (resourceURL && resourceURL != baseURI && resourceURL != ABOUT_BLANK_URI) {
 				const downloadOptions = { asDataURI: false, maxResourceSize: options.maxResourceSize, maxResourceSizeEnabled: options.maxResourceSizeEnabled, charset: options.charset };
-				const content = await DocUtil.getContent(resourceURL, downloadOptions);
+				const content = await docUtil.getContent(resourceURL, downloadOptions);
 				resourceURL = content.resourceURL;
 				let stylesheetContent = Util.removeCssComments(content.data);
 				if (options.compressCSS) {
-					stylesheetContent = DocUtil.compressCSS(stylesheetContent);
+					stylesheetContent = docUtil.compressCSS(stylesheetContent);
 				}
 				stylesheetContent = await ProcessorHelper.resolveImportURLs(stylesheetContent, resourceURL, options, workStylesheet);
 				return stylesheetContent;
@@ -1316,7 +1316,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 								if (Util.testValidURL(resourceURL, baseURI, options.url)) {
 									let { content } = await batchRequest.addURL(resourceURL, true);
 									if (content != EMPTY_DATA_URI && !content.startsWith(PREFIX_DATA_URI_VND) && !content.startsWith(PREFIX_DATA_URI_IMAGE_SVG)) {
-										const validResource = await DocUtil.validFont(urlFunction);
+										const validResource = await docUtil.validFont(urlFunction);
 										if (!validResource) {
 											content = EMPTY_DATA_URI;
 										}
@@ -1324,7 +1324,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 									replaceURLs(declaration, originalResourceURL, content);
 								}
 							} else if (resourceURL.startsWith(DATA_URI_PREFIX) && !resourceURL.startsWith(PREFIX_DATA_URI_VND) && !resourceURL.startsWith(PREFIX_DATA_URI_IMAGE_SVG)) {
-								const validResource = await DocUtil.validFont(urlFunction);
+								const validResource = await docUtil.validFont(urlFunction);
 								if (!validResource) {
 									replaceURLs(declaration, originalResourceURL, EMPTY_DATA_URI);
 								}
@@ -1335,7 +1335,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 
 				function replaceURLs(declaration, oldURL, newURL) {
 					declaration.value.children.forEach(token => {
-						if (token.type == "Url" && DocUtil.removeQuotes(Util.getCSSValue(token.value)) == oldURL) {
+						if (token.type == "Url" && docUtil.removeQuotes(Util.getCSSValue(token.value)) == oldURL) {
 							token.value.value = newURL;
 						}
 					});
@@ -1379,7 +1379,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 					if (token.data.children) {
 						findURLToken(url, token.data.children, callback, depth + 1);
 					}
-					if (token.data.type == "Url" && DocUtil.removeQuotes(Util.getCSSValue(token.data.value)) == url) {
+					if (token.data.type == "Url" && docUtil.removeQuotes(Util.getCSSValue(token.data.value)) == url) {
 						callback(token, children, depth == 0);
 					}
 				}
@@ -1394,7 +1394,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 					resourceElement.setAttribute(attributeName, EMPTY_IMAGE);
 					if (Util.testValidPath(resourceURL, baseURI, options.url)) {
 						try {
-							resourceURL = DocUtil.resolveURL(resourceURL, baseURI);
+							resourceURL = docUtil.resolveURL(resourceURL, baseURI);
 						} catch (error) {
 							// ignored
 						}
@@ -1432,7 +1432,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				if (Util.testValidPath(resourceURL, baseURI, options.url) && !Util.testIgnoredPath(resourceURL)) {
 					resourceElement.setAttribute(attributeName, EMPTY_IMAGE);
 					try {
-						resourceURL = DocUtil.resolveURL(resourceURL, baseURI);
+						resourceURL = docUtil.resolveURL(resourceURL, baseURI);
 					} catch (error) {
 						// ignored
 					}
@@ -1441,7 +1441,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 							const { content } = await batchRequest.addURL(resourceURL);
 							const hashMatch = originalResourceURL.match(REGEXP_URL_HASH);
 							if (hashMatch && hashMatch[0]) {
-								const svgDoc = DocUtil.parseSVGContent(content);
+								const svgDoc = docUtil.parseSVGContent(content);
 								const symbolElement = svgDoc.querySelector(hashMatch[0]);
 								if (symbolElement) {
 									resourceElement.setAttribute(attributeName, hashMatch[0]);
@@ -1462,13 +1462,13 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 
 		static async processSrcset(resourceElements, attributeName, prefixDataURI, baseURI, options, batchRequest) {
 			await Promise.all(Array.from(resourceElements).map(async resourceElement => {
-				const srcset = DocUtil.parseSrcset(resourceElement.getAttribute(attributeName));
+				const srcset = docUtil.parseSrcset(resourceElement.getAttribute(attributeName));
 				const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
 					let resourceURL = Util.normalizeURL(srcsetValue.url);
 					if (!Util.testIgnoredPath(resourceURL)) {
 						if (Util.testValidPath(resourceURL, baseURI, options.url)) {
 							try {
-								resourceURL = DocUtil.resolveURL(resourceURL, baseURI);
+								resourceURL = docUtil.resolveURL(resourceURL, baseURI);
 							} catch (error) {
 								// ignored
 							}
@@ -1647,7 +1647,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		}
 
 		static replaceImageSource(imgElement, variableName, options) {
-			const dataAttributeName = DocUtil.imagesAttributeName(options.sessionId);
+			const dataAttributeName = docUtil.imagesAttributeName(options.sessionId);
 			if (imgElement.getAttribute(dataAttributeName) != null) {
 				const imgData = options.imageData[Number(imgElement.getAttribute(dataAttributeName))];
 				if (imgData.replaceable) {

+ 130 - 154
lib/single-file/util/doc-util-core.js → lib/single-file/util/doc-util.js

@@ -18,14 +18,14 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-this.DocUtilCore = this.DocUtilCore || (() => {
+this.docUtil = this.docUtil || (() => {
 
 	const DEBUG = false;
 	const ONE_MB = 1024 * 1024;
 	const PREFIX_CONTENT_TYPE_TEXT = "text/";
 
 	return {
-		getClass: (modules, domUtil) => {
+		getInstance: (modules, domUtil) => {
 			if (modules.serializer === undefined) {
 				modules.serializer = {
 					process(doc) {
@@ -48,88 +48,15 @@ this.DocUtilCore = this.DocUtilCore || (() => {
 				};
 			}
 
-			return class DocUtil {
-				static async getContent(resourceURL, options) {
-					let resourceContent, startTime;
-					if (DEBUG) {
-						startTime = Date.now();
-						log("  // STARTED download url =", resourceURL, "asDataURI =", options.asDataURI);
-					}
-					try {
-						resourceContent = await domUtil.getResourceContent(resourceURL, options);
-					} catch (error) {
-						return { data: options.asDataURI ? "data:base64," : "", resourceURL };
-					}
-					resourceURL = resourceContent.getUrl();
-					let contentType = resourceContent.getContentType();
-					let charset;
-					if (contentType) {
-						const matchContentType = contentType.toLowerCase().split(";");
-						contentType = matchContentType[0].trim();
-						if (!contentType.includes("/")) {
-							contentType = null;
-						}
-						const charsetValue = matchContentType[1] && matchContentType[1].trim();
-						if (charsetValue) {
-							const matchCharset = charsetValue.match(/^charset=(.*)/);
-							if (matchCharset && matchCharset[1]) {
-								charset = modules.docHelper.removeQuotes(matchCharset[1].trim());
-							}
-						}
-					}
-					if (!charset && options.charset) {
-						charset = options.charset;
-					}
-					if (options.asDataURI) {
-						try {
-							if (DEBUG) {
-								log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
-							}
-							if (options.maxResourceSizeEnabled && resourceContent.getSize() > options.maxResourceSize * ONE_MB) {
-								return { data: "data:base64,", resourceURL };
-							} else {
-								const dataUri = await resourceContent.getDataUri(contentType);
-								return { data: dataUri, resourceURL };
-							}
-						} catch (error) {
-							return { data: "data:base64,", resourceURL };
-						}
-					} else {
-						if (resourceContent.getStatusCode() >= 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 && resourceContent.getSize() > options.maxResourceSize * ONE_MB) {
-							return { data: "", resourceURL, charset };
-						} else {
-							try {
-								return { data: resourceContent.getText(charset), resourceURL, charset };
-							} catch (error) {
-								try {
-									charset = "utf-8";
-									return { data: resourceContent.getText(charset), resourceURL, charset };
-								} catch (error) {
-									return { data: "", resourceURL, charset };
-								}
-							}
-						}
-					}
-				}
-
-				static parseURL(resourceURL, baseURI) {
+			return {
+				getContent,
+				parseURL(resourceURL, baseURI) {
 					return domUtil.parseURL(resourceURL, baseURI);
-				}
-
-				static resolveURL(resourceURL, baseURI) {
+				},
+				resolveURL(resourceURL, baseURI) {
 					return this.parseURL(resourceURL, baseURI).href;
-				}
-
-				static parseDocContent(content, baseURI) {
+				},
+				parseDocContent(content, baseURI) {
 					const doc = domUtil.parseDocContent(content);
 					let baseElement = doc.querySelector("base");
 					if (!baseElement || !baseElement.getAttribute("href")) {
@@ -141,104 +68,153 @@ this.DocUtilCore = this.DocUtilCore || (() => {
 						doc.head.insertBefore(baseElement, doc.head.firstChild);
 					}
 					return doc;
-				}
-
-				static parseSVGContent(content) {
+				},
+				parseSVGContent(content) {
 					return domUtil.parseSVGContent(content);
-				}
-
-				static async digest(algo, text) {
+				},
+				async digest(algo, text) {
 					return domUtil.digestText(algo, text);
-				}
-
-				static getContentSize(content) {
+				},
+				getContentSize(content) {
 					return domUtil.getContentSize(content);
-				}
-
-				static async validFont(urlFunction) {
+				},
+				async validFont(urlFunction) {
 					return domUtil.isValidFontUrl(urlFunction);
-				}
-
-				static minifyHTML(doc, options) {
+				},
+				minifyHTML(doc, options) {
 					return modules.htmlMinifier.process(doc, options);
-				}
-
-				static postMinifyHTML(doc) {
+				},
+				postMinifyHTML(doc) {
 					return modules.htmlMinifier.postProcess(doc);
-				}
-
-				static minifyCSSRules(stylesheets, styles, mediaAllInfo) {
+				},
+				minifyCSSRules(stylesheets, styles, mediaAllInfo) {
 					return modules.cssRulesMinifier.process(stylesheets, styles, mediaAllInfo);
-				}
-
-				static removeUnusedFonts(doc, stylesheets, styles, options) {
+				},
+				removeUnusedFonts(doc, stylesheets, styles, options) {
 					return modules.fontsMinifier.process(doc, stylesheets, styles, options);
-				}
-
-				static removeAlternativeFonts(doc, stylesheets) {
+				},
+				removeAlternativeFonts(doc, stylesheets) {
 					return modules.fontsAltMinifier.process(doc, stylesheets);
-				}
-
-				static getMediaAllInfo(doc, stylesheets, styles) {
+				},
+				getMediaAllInfo(doc, stylesheets, styles) {
 					return modules.matchedRules.getMediaAllInfo(doc, stylesheets, styles);
-				}
-
-				static compressCSS(content, options) {
+				},
+				compressCSS(content, options) {
 					return modules.cssMinifier.processString(content, options);
-				}
-
-				static minifyMedias(stylesheets) {
+				},
+				minifyMedias(stylesheets) {
 					return modules.mediasMinifier.process(stylesheets);
-				}
-
-				static removeAlternativeImages(doc, options) {
+				},
+				removeAlternativeImages(doc, options) {
 					return modules.imagesAltMinifier.process(doc, options);
-				}
-
-				static parseSrcset(srcset) {
+				},
+				parseSrcset(srcset) {
 					return modules.srcsetParser.process(srcset);
-				}
-
-				static preProcessDoc(doc, win, options) {
+				},
+				preProcessDoc(doc, win, options) {
 					return modules.docHelper.preProcessDoc(doc, win, options);
-				}
-
-				static postProcessDoc(doc, options) {
+				},
+				postProcessDoc(doc, options) {
 					modules.docHelper.postProcessDoc(doc, options);
-				}
-
-				static serialize(doc, compressHTML) {
+				},
+				serialize(doc, compressHTML) {
 					return modules.serializer.process(doc, compressHTML);
-				}
-
-				static removeQuotes(string) {
+				},
+				removeQuotes(string) {
 					return modules.docHelper.removeQuotes(string);
-				}
-
-				static windowIdAttributeName(sessionId) {
+				},
+				windowIdAttributeName(sessionId) {
 					return modules.docHelper.windowIdAttributeName(sessionId);
-				}
-
-				static preservedSpaceAttributeName(sessionId) {
+				},
+				preservedSpaceAttributeName(sessionId) {
 					return modules.docHelper.preservedSpaceAttributeName(sessionId);
-				}
-
-				static removedContentAttributeName(sessionId) {
+				},
+				removedContentAttributeName(sessionId) {
 					return modules.docHelper.removedContentAttributeName(sessionId);
-				}
-
-				static imagesAttributeName(sessionId) {
+				},
+				imagesAttributeName(sessionId) {
 					return modules.docHelper.imagesAttributeName(sessionId);
-				}
-
-				static inputValueAttributeName(sessionId) {
+				},
+				inputValueAttributeName(sessionId) {
 					return modules.docHelper.inputValueAttributeName(sessionId);
-				}
-
-				static shadowRootAttributeName(sessionId) {
+				},
+				shadowRootAttributeName(sessionId) {
 					return modules.docHelper.shadowRootAttributeName(sessionId);
 				}
 			};
+
+			async function getContent(resourceURL, options) {
+				let resourceContent, startTime;
+				if (DEBUG) {
+					startTime = Date.now();
+					log("  // STARTED download url =", resourceURL, "asDataURI =", options.asDataURI);
+				}
+				try {
+					resourceContent = await domUtil.getResourceContent(resourceURL, options);
+				} catch (error) {
+					return { data: options.asDataURI ? "data:base64," : "", resourceURL };
+				}
+				resourceURL = resourceContent.getUrl();
+				let contentType = resourceContent.getContentType();
+				let charset;
+				if (contentType) {
+					const matchContentType = contentType.toLowerCase().split(";");
+					contentType = matchContentType[0].trim();
+					if (!contentType.includes("/")) {
+						contentType = null;
+					}
+					const charsetValue = matchContentType[1] && matchContentType[1].trim();
+					if (charsetValue) {
+						const matchCharset = charsetValue.match(/^charset=(.*)/);
+						if (matchCharset && matchCharset[1]) {
+							charset = modules.docHelper.removeQuotes(matchCharset[1].trim());
+						}
+					}
+				}
+				if (!charset && options.charset) {
+					charset = options.charset;
+				}
+				if (options.asDataURI) {
+					try {
+						if (DEBUG) {
+							log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
+						}
+						if (options.maxResourceSizeEnabled && resourceContent.getSize() > options.maxResourceSize * ONE_MB) {
+							return { data: "data:base64,", resourceURL };
+						} else {
+							const dataUri = await resourceContent.getDataUri(contentType);
+							return { data: dataUri, resourceURL };
+						}
+					} catch (error) {
+						return { data: "data:base64,", resourceURL };
+					}
+				} else {
+					if (resourceContent.getStatusCode() >= 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 && resourceContent.getSize() > options.maxResourceSize * ONE_MB) {
+						return { data: "", resourceURL, charset };
+					} else {
+						try {
+							return { data: resourceContent.getText(charset), resourceURL, charset };
+						} catch (error) {
+							try {
+								charset = "utf-8";
+								return { data: resourceContent.getText(charset), resourceURL, charset };
+							} catch (error) {
+								return { data: "", resourceURL, charset };
+							}
+						}
+					}
+				}
+			}
+
 		}
 	};
 

+ 2 - 2
manifest.json

@@ -19,7 +19,7 @@
 				"lib/hooks/hooks-frame.js",
 				"lib/browser-polyfill/chrome-browser-polyfill.js",
 				"lib/single-file/util/doc-helper.js",
-				"lib/single-file/util/doc-util-core.js",
+				"lib/single-file/util/doc-util.js",
 				"lib/single-file/util/timeout.js",
 				"lib/frame-tree/frame-tree.js",
 				"extension/index.js",
@@ -73,7 +73,7 @@
 			"lib/single-file/vendor/html-srcset-parser.js",
 			"lib/single-file/vendor/css-font-property-parser.js",
 			"lib/single-file/util/doc-helper.js",
-			"lib/single-file/util/doc-util-core.js",
+			"lib/single-file/util/doc-util.js",
 			"lib/single-file/modules/css-fonts-minifier.js",
 			"lib/single-file/modules/css-fonts-alt-minifier.js",
 			"lib/single-file/modules/css-medias-alt-minifier.js",

+ 2 - 3
node-jsdom/single-file-jsdom.js

@@ -33,7 +33,7 @@ const iconv = require("iconv-lite");
 const request = require("request-promise-native");
 
 const SCRIPTS = [
-	"./lib/single-file/util/doc-util-core.js",
+	"./lib/single-file/util/doc-util.js",
 	"./lib/single-file/util/doc-helper.js",
 	"./lib/single-file/vendor/css-tree.js",
 	"./lib/single-file/vendor/html-srcset-parser.js",
@@ -75,8 +75,7 @@ const domUtil = {
 	digestText,
 	parseURL
 };
-const DocUtil = this.DocUtilCore.getClass(modules, domUtil);
-const SingleFile = this.SingleFileCore.getClass(DocUtil, this.cssTree);
+const SingleFile = this.SingleFileCore.getClass(this.docUtil.getInstance(modules, domUtil), this.cssTree);
 exports.getClass = () => SingleFile;
 exports.getPageData = async options => {
 	const pageContent = (await request({