Przeglądaj źródła

re-ordered Processor methods

Gildas 7 lat temu
rodzic
commit
819b355f34
1 zmienionych plików z 342 dodań i 342 usunięć
  1. 342 342
      lib/single-file/single-file-core.js

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

@@ -409,63 +409,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			};
 		}
 
-		removeUnselectedElements() {
-			const rootElement = this.doc.querySelector("[" + SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME + "]");
-			if (rootElement) {
-				ProcessorHelper.isolateElements(rootElement);
-				rootElement.removeAttribute(SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME);
-				rootElement.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
-			}
-		}
-
-		setInputValues() {
-			this.doc.querySelectorAll("input").forEach(input => {
-				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));
-				if (value == "true") {
-					input.setAttribute("checked", "");
-				}
-			});
-			this.doc.querySelectorAll("textarea").forEach(textarea => {
-				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;
-					if (selected) {
-						option.setAttribute("selected", "");
-					}
-				});
-			});
-		}
-
-		removeDiscardedResources() {
-			this.doc.querySelectorAll("singlefile-infobar, singlefile-mask, singlefile-logs-window").forEach(element => element.remove());
-			const objectElements = this.doc.querySelectorAll("applet, meta[http-equiv=refresh], object[data]:not([type=\"image/svg+xml\"]):not([type=\"image/svg-xml\"]):not([type=\"text/html\"]), embed[src]:not([src*=\".svg\"])");
-			this.stats.set("discarded", "objects", objectElements.length);
-			this.stats.set("processed", "objects", objectElements.length);
-			objectElements.forEach(element => element.remove());
-			const replacedAttributeValue = this.doc.querySelectorAll("link[rel~=preconnect], link[rel~=prerender], link[rel~=dns-prefetch], link[rel~=preload], link[rel~=prefetch]");
-			replacedAttributeValue.forEach(element => {
-				const relValue = element.getAttribute("rel").replace(/(preconnect|prerender|dns-prefetch|preload|prefetch)/g, "").trim();
-				if (relValue.length) {
-					element.setAttribute("rel", relValue);
-				} else {
-					element.remove();
-				}
-			});
-			this.doc.querySelectorAll("link[rel*=stylesheet][rel*=alternate][title]").forEach(element => element.remove());
-			this.doc.querySelectorAll("meta[http-equiv=\"content-security-policy\"]").forEach(element => element.remove());
-			if (this.options.compressHTML) {
-				this.doc.querySelectorAll("input[type=hidden]").forEach(element => element.remove());
-			}
-			this.doc.querySelectorAll("a[ping]").forEach(element => element.removeAttribute("ping"));
-		}
-
 		async end() {
 			const metaCharset = this.doc.head.querySelector("meta[charset]");
 			if (metaCharset) {
@@ -524,6 +467,71 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
+		insertShadowRootContents() {
+			if (this.options.shadowRootContents) {
+				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);
+						this.options.framesData.push({ windowId, content: elementInfo.content, baseURI: this.baseURI });
+						element.appendChild(frameElement);
+					}
+				});
+			}
+		}
+
+		replaceStyleContents() {
+			if (this.options.stylesheetContents) {
+				this.doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
+					if (this.options.stylesheetContents[styleIndex]) {
+						styleElement.textContent = this.options.stylesheetContents[styleIndex];
+					}
+				});
+			}
+		}
+
+		removeUnselectedElements() {
+			const rootElement = this.doc.querySelector("[" + SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME + "]");
+			if (rootElement) {
+				ProcessorHelper.isolateElements(rootElement);
+				rootElement.removeAttribute(SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME);
+				rootElement.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
+			}
+		}
+
+		insertVideoPosters() {
+			if (this.options.postersData) {
+				this.doc.querySelectorAll("video[src], video > source[src]").forEach((element, videoIndex) => {
+					let videoElement;
+					if (element.tagName == "VIDEO") {
+						videoElement = element;
+					} else {
+						videoElement = element.parentElement;
+					}
+					if (!videoElement.poster && this.options.postersData[videoIndex]) {
+						videoElement.setAttribute("poster", this.options.postersData[videoIndex]);
+					}
+				});
+			}
+		}
+
+		removeFrames() {
+			const frameElements = this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]");
+			this.stats.set("discarded", "frames", frameElements.length);
+			this.stats.set("processed", "frames", frameElements.length);
+			this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]").forEach(element => element.remove());
+		}
+
+		removeImports() {
+			const importElements = this.doc.querySelectorAll("link[rel=import]");
+			this.stats.set("discarded", "HTML imports", importElements.length);
+			this.stats.set("processed", "HTML imports", importElements.length);
+			importElements.forEach(element => element.remove());
+		}
+
 		removeScripts() {
 			this.onEventAttributeNames.forEach(attributeName => this.doc.querySelectorAll("[" + attributeName + "]").forEach(element => element.removeAttribute(attributeName)));
 			this.doc.querySelectorAll("[href]").forEach(element => {
@@ -542,6 +550,19 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			scriptElements.forEach(element => element.remove());
 		}
 
+		removeVideoSources() {
+			const videoSourceElements = this.doc.querySelectorAll("video[src], video > source");
+			this.stats.set("discarded", "video sources", videoSourceElements.length);
+			this.stats.set("processed", "video sources", videoSourceElements.length);
+			videoSourceElements.forEach(element => {
+				if (element.tagName == "SOURCE") {
+					element.remove();
+				} else {
+					videoSourceElements.forEach(element => element.removeAttribute("src"));
+				}
+			});
+		}
+
 		removeAudioSources() {
 			const audioSourceElements = this.doc.querySelectorAll("audio[src], audio > source[src]");
 			this.stats.set("discarded", "audio sources", audioSourceElements.length);
@@ -555,31 +576,27 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			});
 		}
 
-		removeVideoSources() {
-			const videoSourceElements = this.doc.querySelectorAll("video[src], video > source");
-			this.stats.set("discarded", "video sources", videoSourceElements.length);
-			this.stats.set("processed", "video sources", videoSourceElements.length);
-			videoSourceElements.forEach(element => {
-				if (element.tagName == "SOURCE") {
-					element.remove();
+		removeDiscardedResources() {
+			this.doc.querySelectorAll("singlefile-infobar, singlefile-mask, singlefile-logs-window").forEach(element => element.remove());
+			const objectElements = this.doc.querySelectorAll("applet, meta[http-equiv=refresh], object[data]:not([type=\"image/svg+xml\"]):not([type=\"image/svg-xml\"]):not([type=\"text/html\"]), embed[src]:not([src*=\".svg\"])");
+			this.stats.set("discarded", "objects", objectElements.length);
+			this.stats.set("processed", "objects", objectElements.length);
+			objectElements.forEach(element => element.remove());
+			const replacedAttributeValue = this.doc.querySelectorAll("link[rel~=preconnect], link[rel~=prerender], link[rel~=dns-prefetch], link[rel~=preload], link[rel~=prefetch]");
+			replacedAttributeValue.forEach(element => {
+				const relValue = element.getAttribute("rel").replace(/(preconnect|prerender|dns-prefetch|preload|prefetch)/g, "").trim();
+				if (relValue.length) {
+					element.setAttribute("rel", relValue);
 				} else {
-					videoSourceElements.forEach(element => element.removeAttribute("src"));
+					element.remove();
 				}
 			});
-		}
-
-		removeFrames() {
-			const frameElements = this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]");
-			this.stats.set("discarded", "frames", frameElements.length);
-			this.stats.set("processed", "frames", frameElements.length);
-			this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]").forEach(element => element.remove());
-		}
-
-		removeImports() {
-			const importElements = this.doc.querySelectorAll("link[rel=import]");
-			this.stats.set("discarded", "HTML imports", importElements.length);
-			this.stats.set("processed", "HTML imports", importElements.length);
-			importElements.forEach(element => element.remove());
+			this.doc.querySelectorAll("link[rel*=stylesheet][rel*=alternate][title]").forEach(element => element.remove());
+			this.doc.querySelectorAll("meta[http-equiv=\"content-security-policy\"]").forEach(element => element.remove());
+			if (this.options.compressHTML) {
+				this.doc.querySelectorAll("input[type=hidden]").forEach(element => element.remove());
+			}
+			this.doc.querySelectorAll("a[ping]").forEach(element => element.removeAttribute("ping"));
 		}
 
 		resetCharsetMeta() {
@@ -603,6 +620,31 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
+		setInputValues() {
+			this.doc.querySelectorAll("input").forEach(input => {
+				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));
+				if (value == "true") {
+					input.setAttribute("checked", "");
+				}
+			});
+			this.doc.querySelectorAll("textarea").forEach(textarea => {
+				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;
+					if (selected) {
+						option.setAttribute("selected", "");
+					}
+				});
+			});
+		}
+
 		insertFaviconLink() {
 			let faviconElement = this.doc.querySelector("link[href][rel=\"icon\"]");
 			if (!faviconElement) {
@@ -617,6 +659,54 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			this.doc.head.appendChild(faviconElement);
 		}
 
+		replaceCanvasElements() {
+			if (this.options.canvasData) {
+				this.doc.querySelectorAll("canvas").forEach((canvasElement, indexCanvasElement) => {
+					const canvasData = this.options.canvasData[indexCanvasElement];
+					if (canvasData) {
+						ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")");
+						this.stats.add("processed", "canvas", 1);
+					}
+				});
+			}
+		}
+
+		insertFonts() {
+			if (this.options.fontsData && this.options.fontsData.length) {
+				let stylesheetContent = "";
+				this.options.fontsData.forEach(fontStyles => {
+					if (fontStyles["font-family"] && fontStyles.src) {
+						stylesheetContent += "@font-face{";
+						let stylesContent = "";
+						Object.keys(fontStyles).forEach(fontStyle => {
+							if (stylesContent) {
+								stylesContent += ";";
+							}
+							stylesContent += fontStyle + ":" + fontStyles[fontStyle];
+						});
+						stylesheetContent += stylesContent + "}";
+					}
+				});
+				if (stylesheetContent) {
+					const styleElement = this.doc.createElement("style");
+					styleElement.textContent = stylesheetContent;
+					const existingStyleElement = this.doc.querySelector("style");
+					if (existingStyleElement) {
+						existingStyleElement.parentElement.insertBefore(styleElement, existingStyleElement);
+					} else {
+						this.doc.head.insertBefore(styleElement, this.doc.head.firstChild);
+					}
+				}
+			}
+		}
+
+		removeHiddenElements() {
+			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());
+		}
+
 		resolveHrefs() {
 			this.doc.querySelectorAll("a[href], area[href], link[href]").forEach(element => {
 				const href = element.getAttribute("href").trim();
@@ -630,159 +720,16 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			});
 		}
 
-		removeUnusedStyles() {
-			if (!this.mediaAllInfo) {
-				this.mediaAllInfo = DocUtil.getMediaAllInfo(this.doc, this.stylesheets, this.styles);
-			}
-			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);
-		}
-
-		removeAlternativeFonts() {
-			DocUtil.removeAlternativeFonts(this.doc, this.stylesheets);
-		}
-
-		removeAlternativeImages() {
-			DocUtil.removeAlternativeImages(this.doc);
-		}
-
-		removeHiddenElements() {
-			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());
-		}
-
-		compressHTML() {
-			let size;
-			if (this.options.displayStats) {
-				size = DocUtil.getContentSize(this.doc.documentElement.outerHTML);
-			}
-			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));
-			}
-		}
-
-		removeAlternativeMedias() {
-			const stats = DocUtil.minifyMedias(this.stylesheets);
-			this.stats.set("processed", "medias", stats.processed);
-			this.stats.set("discarded", "medias", stats.discarded);
-		}
-
-		replaceCanvasElements() {
-			if (this.options.canvasData) {
-				this.doc.querySelectorAll("canvas").forEach((canvasElement, indexCanvasElement) => {
-					const canvasData = this.options.canvasData[indexCanvasElement];
-					if (canvasData) {
-						ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")");
-						this.stats.add("processed", "canvas", 1);
-					}
-				});
-			}
-		}
-
-		insertFonts() {
-			if (this.options.fontsData && this.options.fontsData.length) {
-				let stylesheetContent = "";
-				this.options.fontsData.forEach(fontStyles => {
-					if (fontStyles["font-family"] && fontStyles.src) {
-						stylesheetContent += "@font-face{";
-						let stylesContent = "";
-						Object.keys(fontStyles).forEach(fontStyle => {
-							if (stylesContent) {
-								stylesContent += ";";
-							}
-							stylesContent += fontStyle + ":" + fontStyles[fontStyle];
-						});
-						stylesheetContent += stylesContent + "}";
-					}
-				});
-				if (stylesheetContent) {
-					const styleElement = this.doc.createElement("style");
-					styleElement.textContent = stylesheetContent;
-					const existingStyleElement = this.doc.querySelector("style");
-					if (existingStyleElement) {
-						existingStyleElement.parentElement.insertBefore(styleElement, existingStyleElement);
-					} else {
-						this.doc.head.insertBefore(styleElement, this.doc.head.firstChild);
-					}
+		resolveStyleAttributeURLs() {
+			this.doc.querySelectorAll("[style]").forEach(element => {
+				let styleContent = element.getAttribute("style");
+				if (this.options.compressCSS) {
+					styleContent = DocUtil.compressCSS(styleContent);
 				}
-			}
-		}
-
-		replaceStyleContents() {
-			if (this.options.stylesheetContents) {
-				this.doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
-					if (this.options.stylesheetContents[styleIndex]) {
-						styleElement.textContent = this.options.stylesheetContents[styleIndex];
-					}
-				});
-			}
-		}
-
-		insertShadowRootContents() {
-			if (this.options.shadowRootContents) {
-				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);
-						this.options.framesData.push({ windowId, content: elementInfo.content, baseURI: this.baseURI });
-						element.appendChild(frameElement);
-					}
-				});
-			}
-		}
-
-		insertVideoPosters() {
-			if (this.options.postersData) {
-				this.doc.querySelectorAll("video[src], video > source[src]").forEach((element, videoIndex) => {
-					let videoElement;
-					if (element.tagName == "VIDEO") {
-						videoElement = element;
-					} else {
-						videoElement = element.parentElement;
-					}
-					if (!videoElement.poster && this.options.postersData[videoIndex]) {
-						videoElement.setAttribute("poster", this.options.postersData[videoIndex]);
-					}
-				});
-			}
-		}
-
-		async processPageResources() {
-			const processAttributeArgs = [
-				["link[href][rel*=\"icon\"]", "href", "data:", false, true],
-				["object[type=\"image/svg+xml\"], object[type=\"image/svg-xml\"]", "data", PREFIX_DATA_URI_IMAGE_SVG],
-				["img[src], input[src][type=image]", "src", PREFIX_DATA_URI_IMAGE, true],
-				["embed[src*=\".svg\"]", "src", PREFIX_DATA_URI_IMAGE_SVG],
-				["video[poster]", "poster", PREFIX_DATA_URI_IMAGE],
-				["*[background]", "background", PREFIX_DATA_URI_IMAGE],
-				["image", "xlink:href", PREFIX_DATA_URI_IMAGE]
-			];
-			let resourcePromises = processAttributeArgs.map(([selector, attributeName, prefixDataURI, processDuplicates, removeElementIfMissing]) =>
-				ProcessorHelper.processAttribute(this.doc.querySelectorAll(selector), attributeName, prefixDataURI, this.baseURI, this.options, this.cssVariables, this.styles, this.batchRequest, processDuplicates, removeElementIfMissing)
-			);
-			resourcePromises = resourcePromises.concat([
-				ProcessorHelper.processXLinks(this.doc.querySelectorAll("use"), this.baseURI, this.options, this.batchRequest),
-				ProcessorHelper.processSrcset(this.doc.querySelectorAll("img[srcset], source[srcset]"), "srcset", PREFIX_DATA_URI_IMAGE, this.baseURI, this.options, this.batchRequest)
-			]);
-			if (!this.options.removeAudioSrc) {
-				resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("audio[src], audio > source[src]"), "src", PREFIX_DATA_URI_AUDIO, this.baseURI, this.options, this.cssVariables, this.styles, this.batchRequest));
-			}
-			if (!this.options.removeVideoSrc) {
-				resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("video[src], video > source[src]"), "src", PREFIX_DATA_URI_VIDEO, this.baseURI, this.options, this.cssVariables, this.styles, this.batchRequest));
-			}
-			await Promise.all(resourcePromises);
-			ProcessorHelper.processShortcutIcons(this.doc);
+				styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.options);
+				const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
+				this.styles.set(element, declarationList);
+			});
 		}
 
 		async resolveStylesheetURLs() {
@@ -820,80 +767,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				}));
 		}
 
-		async processStylesheets() {
-			await Promise.all(Array.from(this.stylesheets).map(([, stylesheetInfo]) =>
-				ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)
-			));
-		}
-
-		replaceStylesheets() {
-			this.doc.querySelectorAll("style").forEach(styleElement => {
-				const stylesheetInfo = this.stylesheets.get(styleElement);
-				if (stylesheetInfo) {
-					let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
-					styleElement.textContent = stylesheetContent;
-					if (stylesheetInfo.mediaText) {
-						styleElement.media = stylesheetInfo.mediaText;
-					}
-				} else {
-					styleElement.remove();
-				}
-			});
-			this.doc.querySelectorAll("link[rel*=stylesheet]").forEach(linkElement => {
-				const stylesheetInfo = this.stylesheets.get(linkElement);
-				if (stylesheetInfo) {
-					const styleElement = this.doc.createElement("style");
-					if (stylesheetInfo.mediaText) {
-						styleElement.media = stylesheetInfo.mediaText;
-					}
-					let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
-					styleElement.textContent = stylesheetContent;
-					linkElement.parentElement.replaceChild(styleElement, linkElement);
-				} else {
-					linkElement.remove();
-				}
-			});
-		}
-
-		insertVariables() {
-			if (this.cssVariables.size) {
-				const styleElement = this.doc.createElement("style");
-				const firstStyleElement = this.doc.head.querySelector("style");
-				if (firstStyleElement) {
-					this.doc.head.insertBefore(styleElement, firstStyleElement);
-				} else {
-					this.doc.head.appendChild(styleElement);
-				}
-				let stylesheetContent = "";
-				this.cssVariables.forEach((content, indexResource) => {
-					if (stylesheetContent) {
-						stylesheetContent += ";";
-					}
-					stylesheetContent += `${SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource}:url("${content}")`;
-				});
-				styleElement.textContent = ":root{" + stylesheetContent + "}";
-			}
-		}
-
-		async processScripts() {
-			await Promise.all(Array.from(this.doc.querySelectorAll("script[src]")).map(async scriptElement => {
-				let resourceURL;
-				const scriptSrc = scriptElement.getAttribute("src");
-				scriptElement.removeAttribute("src");
-				scriptElement.textContent = "";
-				try {
-					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 });
-					scriptElement.setAttribute("src", content.data);
-				}
-			}));
-		}
-
 		async resolveFrameURLs() {
 			if (!this.options.saveRawPage && this.options.framesData) {
 				const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
@@ -932,6 +805,114 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
+		async resolveHtmlImportURLs() {
+			const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
+			if (!this.relImportProcessors) {
+				this.relImportProcessors = new Map();
+			}
+			await Promise.all(linkElements.map(async linkElement => {
+				const resourceURL = linkElement.href;
+				linkElement.removeAttribute("href");
+				const options = Object.create(this.options);
+				options.insertSingleFileComment = false;
+				options.insertFaviconLink = false;
+				options.doc = null;
+				options.win = null;
+				options.url = resourceURL;
+				if (!Util.testIgnoredPath(resourceURL) && Util.testValidPath(resourceURL, this.baseURI, this.options.url)) {
+					const processor = new Runner(options);
+					this.relImportProcessors.set(linkElement, processor);
+					await processor.loadPage();
+					return processor.initialize();
+				}
+			}));
+		}
+
+		removeUnusedStyles() {
+			if (!this.mediaAllInfo) {
+				this.mediaAllInfo = DocUtil.getMediaAllInfo(this.doc, this.stylesheets, this.styles);
+			}
+			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);
+		}
+
+		removeAlternativeMedias() {
+			const stats = DocUtil.minifyMedias(this.stylesheets);
+			this.stats.set("processed", "medias", stats.processed);
+			this.stats.set("discarded", "medias", stats.discarded);
+		}
+
+		async processStylesheets() {
+			await Promise.all(Array.from(this.stylesheets).map(([, stylesheetInfo]) =>
+				ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)
+			));
+		}
+
+		async processStyleAttributes() {
+			return Promise.all(Array.from(this.styles).map(([, declarationList]) =>
+				ProcessorHelper.processStyle(declarationList.children.toArray(), this.baseURI, this.options, this.cssVariables, this.batchRequest)
+			));
+		}
+
+		async processPageResources() {
+			const processAttributeArgs = [
+				["link[href][rel*=\"icon\"]", "href", "data:", false, true],
+				["object[type=\"image/svg+xml\"], object[type=\"image/svg-xml\"]", "data", PREFIX_DATA_URI_IMAGE_SVG],
+				["img[src], input[src][type=image]", "src", PREFIX_DATA_URI_IMAGE, true],
+				["embed[src*=\".svg\"]", "src", PREFIX_DATA_URI_IMAGE_SVG],
+				["video[poster]", "poster", PREFIX_DATA_URI_IMAGE],
+				["*[background]", "background", PREFIX_DATA_URI_IMAGE],
+				["image", "xlink:href", PREFIX_DATA_URI_IMAGE]
+			];
+			let resourcePromises = processAttributeArgs.map(([selector, attributeName, prefixDataURI, processDuplicates, removeElementIfMissing]) =>
+				ProcessorHelper.processAttribute(this.doc.querySelectorAll(selector), attributeName, prefixDataURI, this.baseURI, this.options, this.cssVariables, this.styles, this.batchRequest, processDuplicates, removeElementIfMissing)
+			);
+			resourcePromises = resourcePromises.concat([
+				ProcessorHelper.processXLinks(this.doc.querySelectorAll("use"), this.baseURI, this.options, this.batchRequest),
+				ProcessorHelper.processSrcset(this.doc.querySelectorAll("img[srcset], source[srcset]"), "srcset", PREFIX_DATA_URI_IMAGE, this.baseURI, this.options, this.batchRequest)
+			]);
+			if (!this.options.removeAudioSrc) {
+				resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("audio[src], audio > source[src]"), "src", PREFIX_DATA_URI_AUDIO, this.baseURI, this.options, this.cssVariables, this.styles, this.batchRequest));
+			}
+			if (!this.options.removeVideoSrc) {
+				resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("video[src], video > source[src]"), "src", PREFIX_DATA_URI_VIDEO, this.baseURI, this.options, this.cssVariables, this.styles, this.batchRequest));
+			}
+			await Promise.all(resourcePromises);
+			ProcessorHelper.processShortcutIcons(this.doc);
+		}
+
+		async processScripts() {
+			await Promise.all(Array.from(this.doc.querySelectorAll("script[src]")).map(async scriptElement => {
+				let resourceURL;
+				const scriptSrc = scriptElement.getAttribute("src");
+				scriptElement.removeAttribute("src");
+				scriptElement.textContent = "";
+				try {
+					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 });
+					scriptElement.setAttribute("src", content.data);
+				}
+			}));
+		}
+
+		removeAlternativeImages() {
+			DocUtil.removeAlternativeImages(this.doc);
+		}
+
+		removeAlternativeFonts() {
+			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]"));
@@ -961,29 +942,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
-		async resolveHtmlImportURLs() {
-			const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
-			if (!this.relImportProcessors) {
-				this.relImportProcessors = new Map();
-			}
-			await Promise.all(linkElements.map(async linkElement => {
-				const resourceURL = linkElement.href;
-				linkElement.removeAttribute("href");
-				const options = Object.create(this.options);
-				options.insertSingleFileComment = false;
-				options.insertFaviconLink = false;
-				options.doc = null;
-				options.win = null;
-				options.url = resourceURL;
-				if (!Util.testIgnoredPath(resourceURL) && Util.testValidPath(resourceURL, this.baseURI, this.options.url)) {
-					const processor = new Runner(options);
-					this.relImportProcessors.set(linkElement, processor);
-					await processor.loadPage();
-					return processor.initialize();
-				}
-			}));
-		}
-
 		async processHtmlImports() {
 			const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
 			await Promise.all(linkElements.map(async linkElement => {
@@ -1000,22 +958,33 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}));
 		}
 
-		resolveStyleAttributeURLs() {
-			this.doc.querySelectorAll("[style]").forEach(element => {
-				let styleContent = element.getAttribute("style");
-				if (this.options.compressCSS) {
-					styleContent = DocUtil.compressCSS(styleContent);
+		replaceStylesheets() {
+			this.doc.querySelectorAll("style").forEach(styleElement => {
+				const stylesheetInfo = this.stylesheets.get(styleElement);
+				if (stylesheetInfo) {
+					let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
+					styleElement.textContent = stylesheetContent;
+					if (stylesheetInfo.mediaText) {
+						styleElement.media = stylesheetInfo.mediaText;
+					}
+				} else {
+					styleElement.remove();
+				}
+			});
+			this.doc.querySelectorAll("link[rel*=stylesheet]").forEach(linkElement => {
+				const stylesheetInfo = this.stylesheets.get(linkElement);
+				if (stylesheetInfo) {
+					const styleElement = this.doc.createElement("style");
+					if (stylesheetInfo.mediaText) {
+						styleElement.media = stylesheetInfo.mediaText;
+					}
+					let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
+					styleElement.textContent = stylesheetContent;
+					linkElement.parentElement.replaceChild(styleElement, linkElement);
+				} else {
+					linkElement.remove();
 				}
-				styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.options);
-				const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
-				this.styles.set(element, declarationList);
 			});
-		}
-
-		async processStyleAttributes() {
-			return Promise.all(Array.from(this.styles).map(([, declarationList]) =>
-				ProcessorHelper.processStyle(declarationList.children.toArray(), this.baseURI, this.options, this.cssVariables, this.batchRequest)
-			));
 		}
 
 		replaceStyleAttributes() {
@@ -1029,6 +998,37 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				}
 			});
 		}
+
+		insertVariables() {
+			if (this.cssVariables.size) {
+				const styleElement = this.doc.createElement("style");
+				const firstStyleElement = this.doc.head.querySelector("style");
+				if (firstStyleElement) {
+					this.doc.head.insertBefore(styleElement, firstStyleElement);
+				} else {
+					this.doc.head.appendChild(styleElement);
+				}
+				let stylesheetContent = "";
+				this.cssVariables.forEach((content, indexResource) => {
+					if (stylesheetContent) {
+						stylesheetContent += ";";
+					}
+					stylesheetContent += `${SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource}:url("${content}")`;
+				});
+				styleElement.textContent = ":root{" + stylesheetContent + "}";
+			}
+		}
+
+		compressHTML() {
+			let size;
+			if (this.options.displayStats) {
+				size = DocUtil.getContentSize(this.doc.documentElement.outerHTML);
+			}
+			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));
+			}
+		}
 	}
 
 	// ---------------