|
|
@@ -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));
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ---------------
|