|
|
@@ -68,6 +68,58 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
// -------------
|
|
|
// PageProcessor
|
|
|
// -------------
|
|
|
+ const STAGES = [{
|
|
|
+ sync: [
|
|
|
+ { action: "removeUIElements" },
|
|
|
+ { action: "replaceStyleContents" },
|
|
|
+ { option: "removeFrames", action: "removeFrames" },
|
|
|
+ { option: "removeImports", action: "removeImports" },
|
|
|
+ { option: "removeScripts", action: "removeScripts" },
|
|
|
+ { action: "removeDiscardedResources" },
|
|
|
+ { action: "resetCharsetMeta" },
|
|
|
+ { option: "compressHTML", action: "compressHTML" },
|
|
|
+ { action: "insertFaviconLink" },
|
|
|
+ { action: "resolveHrefs" },
|
|
|
+ { action: "replaceCanvasElements" },
|
|
|
+ { option: "removeHiddenElements", action: "removeHiddenElements" }
|
|
|
+ ],
|
|
|
+ async: [
|
|
|
+ { action: "inlineStylesheets" },
|
|
|
+ { action: "linkStylesheets" },
|
|
|
+ { action: "attributeStyles" },
|
|
|
+ { option: "!removeFrames", action: "frames" },
|
|
|
+ { option: "!removeImports", action: "htmlImports" }
|
|
|
+ ]
|
|
|
+ }, {
|
|
|
+ sync: [
|
|
|
+ { option: "removeUnusedStyles", action: "removeUnusedStyles" },
|
|
|
+ { option: "compressHTML", action: "compressHTML" },
|
|
|
+ { option: "removeAlternativeFonts", action: "removeAlternativeFonts" },
|
|
|
+ { option: "compressCSS", action: "compressCSS" },
|
|
|
+ ],
|
|
|
+ async: [
|
|
|
+ { action: "inlineStylesheets" },
|
|
|
+ { action: "attributeStyles" },
|
|
|
+ { action: "pageResources" },
|
|
|
+ { option: "!removeScripts", action: "scripts" }
|
|
|
+ ]
|
|
|
+ }, {
|
|
|
+ sync: [
|
|
|
+ { option: "lazyLoadImages", action: "lazyLoadImages" },
|
|
|
+ { option: "removeAlternativeFonts", action: "postRemoveAlternativeFonts" }
|
|
|
+ ],
|
|
|
+ async: [
|
|
|
+ { option: "!removeFrames", action: "frames" },
|
|
|
+ { option: "!removeImports", action: "htmlImports" },
|
|
|
+ ]
|
|
|
+ }, {
|
|
|
+ sync: [
|
|
|
+ { option: "compressHTML", action: "postCompressHTML" },
|
|
|
+ { option: "insertSingleFileComment", action: "insertSingleFileComment" },
|
|
|
+ { action: "removeDefaultHeadTags" }
|
|
|
+ ]
|
|
|
+ }];
|
|
|
+
|
|
|
class PageProcessor {
|
|
|
constructor(options) {
|
|
|
this.options = options;
|
|
|
@@ -90,54 +142,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
async initialize() {
|
|
|
this.onprogress(new ProgressEvent(RESOURCES_INITIALIZING, { pageURL: this.options.url }));
|
|
|
- this.processor.removeUIElements();
|
|
|
- this.processor.replaceStyleContents();
|
|
|
- if (this.options.removeFrames) {
|
|
|
- this.processor.removeFrames();
|
|
|
- }
|
|
|
- if (this.options.removeImports) {
|
|
|
- this.processor.removeImports();
|
|
|
- }
|
|
|
- if (this.options.removeScripts) {
|
|
|
- this.processor.removeScripts();
|
|
|
- }
|
|
|
- this.processor.removeDiscardedResources();
|
|
|
- this.processor.resetCharsetMeta();
|
|
|
- if (this.options.compressHTML) {
|
|
|
- this.processor.compressHTML();
|
|
|
- }
|
|
|
- if (this.options.insertFaviconLink) {
|
|
|
- this.processor.insertFaviconLink();
|
|
|
- }
|
|
|
- this.processor.resolveHrefs();
|
|
|
- this.processor.replaceCanvasElements();
|
|
|
- if (this.options.removeHiddenElements) {
|
|
|
- this.processor.removeHiddenElements(this.options.sessionId);
|
|
|
- }
|
|
|
- const initializationPromises = [this.processor.inlineStylesheets(true), this.processor.linkStylesheets(), this.processor.attributeStyles(true)];
|
|
|
- if (!this.options.removeFrames && this.options.framesData) {
|
|
|
- initializationPromises.push(this.processor.frames(true));
|
|
|
- }
|
|
|
- await Promise.all(initializationPromises);
|
|
|
- if (this.options.removeUnusedStyles) {
|
|
|
- this.processor.removeUnusedStyles();
|
|
|
- }
|
|
|
- if (!this.options.removeImports) {
|
|
|
- initializationPromises.push(this.processor.htmlImports(true));
|
|
|
- }
|
|
|
- if (this.options.compressHTML) {
|
|
|
- this.processor.compressHTML();
|
|
|
- }
|
|
|
- if (this.options.removeAlternativeFonts) {
|
|
|
- this.processor.removeAlternativeFonts();
|
|
|
- }
|
|
|
- if (this.options.compressCSS) {
|
|
|
- this.processor.compressCSS();
|
|
|
- }
|
|
|
- this.pendingPromises = [this.processor.inlineStylesheets(), this.processor.attributeStyles(), this.processor.pageResources()];
|
|
|
- if (!this.options.removeScripts) {
|
|
|
- this.pendingPromises.push(this.processor.scripts());
|
|
|
- }
|
|
|
+ await this.executeStage(0);
|
|
|
+ this.pendingPromises = this.executeStage(1);
|
|
|
if (this.options.doc) {
|
|
|
DOM.postProcessDoc(this.options.doc, this.options);
|
|
|
this.options.doc = null;
|
|
|
@@ -147,40 +153,35 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
|
|
|
async preparePageData() {
|
|
|
- await this.processor.retrieveResources(
|
|
|
- details => {
|
|
|
- details.pageURL = this.options.url;
|
|
|
- this.onprogress(new ProgressEvent(RESOURCE_LOADED, details));
|
|
|
- });
|
|
|
- await this.pendingPromises;
|
|
|
- if (this.options.lazyLoadImages) {
|
|
|
- this.processor.lazyLoadImages();
|
|
|
- }
|
|
|
- if (this.options.removeAlternativeFonts) {
|
|
|
- this.processor.removeAlternativeFonts(true);
|
|
|
- if (this.options.compressCSS) {
|
|
|
- this.processor.compressCSS();
|
|
|
- }
|
|
|
- }
|
|
|
- if (!this.options.removeFrames && this.options.framesData) {
|
|
|
- await this.processor.frames();
|
|
|
- }
|
|
|
- if (!this.options.removeImports) {
|
|
|
- await this.processor.htmlImports();
|
|
|
- }
|
|
|
- if (this.options.compressHTML) {
|
|
|
- this.processor.compressHTML(true);
|
|
|
- }
|
|
|
- if (this.options.insertSingleFileComment) {
|
|
|
- this.processor.insertSingleFileCommentNode();
|
|
|
+ if (!this.options.windowId) {
|
|
|
+ await this.processor.retrieveResources(
|
|
|
+ details => {
|
|
|
+ details.pageURL = this.options.url;
|
|
|
+ this.onprogress(new ProgressEvent(RESOURCE_LOADED, details));
|
|
|
+ });
|
|
|
}
|
|
|
- this.processor.removeDefaultHeadTags();
|
|
|
+ await this.pendingPromises;
|
|
|
+ await this.executeStage(2);
|
|
|
+ await this.executeStage(3);
|
|
|
}
|
|
|
|
|
|
getPageData() {
|
|
|
this.onprogress(new ProgressEvent(PAGE_ENDED, { pageURL: this.options.url }));
|
|
|
return this.processor.getPageData();
|
|
|
}
|
|
|
+
|
|
|
+ async executeStage(step) {
|
|
|
+ STAGES[step].sync.forEach(task => this.executeTask(task, !step));
|
|
|
+ if (STAGES[step].async) {
|
|
|
+ return Promise.all(STAGES[step].async.map(task => this.executeTask(task, !step)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ executeTask(task, initialization) {
|
|
|
+ if (!task.option || ((task.option.startsWith("!") && !this.options[task.option]) || this.options[task.option])) {
|
|
|
+ return this.processor[task.action](initialization);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// --------
|
|
|
@@ -388,35 +389,42 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
this.stats.set("discarded", "cssRules", stats.discarded);
|
|
|
}
|
|
|
|
|
|
- removeAlternativeFonts(secondPass) {
|
|
|
- DOM.minifyFonts(this.doc, secondPass);
|
|
|
+ removeAlternativeFonts() {
|
|
|
+ DOM.minifyFonts(this.doc);
|
|
|
+ }
|
|
|
+
|
|
|
+ postRemoveAlternativeFonts() {
|
|
|
+ DOM.minifyFonts(this.doc, true);
|
|
|
+ if (this.options.compressCSS) {
|
|
|
+ this.compressCSS();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- removeHiddenElements(sessionId) {
|
|
|
- const hiddenElements = this.doc.querySelectorAll("[" + DOM.removedContentAttributeName(sessionId) + "]");
|
|
|
+ removeHiddenElements() {
|
|
|
+ const hiddenElements = this.doc.querySelectorAll("[" + DOM.removedContentAttributeName(this.options.sessionId) + "]");
|
|
|
this.stats.set("discarded", "hiddenElements", hiddenElements.length);
|
|
|
hiddenElements.forEach(element => element.remove());
|
|
|
}
|
|
|
|
|
|
- compressHTML(postProcess) {
|
|
|
- if (postProcess) {
|
|
|
- let size;
|
|
|
- if (this.options.displayStats) {
|
|
|
- size = DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
- }
|
|
|
- DOM.postMinifyHTML(this.doc);
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
|
|
|
- }
|
|
|
- } else {
|
|
|
- let size;
|
|
|
- if (this.options.displayStats) {
|
|
|
- size = DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
- }
|
|
|
- DOM.minifyHTML(this.doc, { preservedSpaceAttributeName: DOM.preservedSpaceAttributeName(this.options.sessionId) });
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
|
|
|
- }
|
|
|
+ compressHTML() {
|
|
|
+ let size;
|
|
|
+ if (this.options.displayStats) {
|
|
|
+ size = DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
+ }
|
|
|
+ DOM.minifyHTML(this.doc, { preservedSpaceAttributeName: DOM.preservedSpaceAttributeName(this.options.sessionId) });
|
|
|
+ if (this.options.displayStats) {
|
|
|
+ this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ postCompressHTML() {
|
|
|
+ let size;
|
|
|
+ if (this.options.displayStats) {
|
|
|
+ size = DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
+ }
|
|
|
+ DOM.postMinifyHTML(this.doc);
|
|
|
+ if (this.options.displayStats) {
|
|
|
+ this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -431,7 +439,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- insertSingleFileCommentNode() {
|
|
|
+ insertSingleFileComment() {
|
|
|
const commentNode = this.doc.createComment("\n Archive processed by SingleFile \n url: " + this.baseURI + " \n saved date: " + new Date() + " \n");
|
|
|
this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
|
|
|
}
|
|
|
@@ -522,46 +530,48 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
|
|
|
async frames(initialization) {
|
|
|
- const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
|
|
|
- await Promise.all(frameElements.map(async frameElement => {
|
|
|
- DomProcessorHelper.setFrameEmptySrc(frameElement);
|
|
|
- frameElement.setAttribute("sandbox", "");
|
|
|
- const frameWindowId = frameElement.getAttribute(DOM.windowIdAttributeName(this.options.sessionId));
|
|
|
- if (frameWindowId) {
|
|
|
- const frameData = this.options.framesData.find(frame => frame.windowId == frameWindowId);
|
|
|
- if (frameData) {
|
|
|
- if (initialization) {
|
|
|
- const options = Object.create(this.options);
|
|
|
- options.insertSingleFileComment = false;
|
|
|
- options.insertFaviconLink = false;
|
|
|
- options.doc = null;
|
|
|
- options.win = null;
|
|
|
- options.url = frameData.baseURI;
|
|
|
- options.windowId = frameWindowId;
|
|
|
- if (frameData.content) {
|
|
|
- options.content = frameData.content;
|
|
|
- options.canvasData = frameData.canvasData;
|
|
|
- options.stylesheetContents = frameData.stylesheetContents;
|
|
|
- frameData.processor = new PageProcessor(options);
|
|
|
- frameData.frameElement = frameElement;
|
|
|
- await frameData.processor.loadPage();
|
|
|
- return frameData.processor.initialize();
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (frameData.processor) {
|
|
|
- this.stats.add("processed", "frames", 1);
|
|
|
- await frameData.processor.preparePageData();
|
|
|
- const pageData = await frameData.processor.getPageData();
|
|
|
- frameElement.removeAttribute(DOM.windowIdAttributeName(this.options.sessionId));
|
|
|
- DomProcessorHelper.setFrameContent(frameElement, pageData.content);
|
|
|
- this.stats.addAll(pageData);
|
|
|
+ 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 => {
|
|
|
+ DomProcessorHelper.setFrameEmptySrc(frameElement);
|
|
|
+ frameElement.setAttribute("sandbox", "");
|
|
|
+ const frameWindowId = frameElement.getAttribute(DOM.windowIdAttributeName(this.options.sessionId));
|
|
|
+ if (frameWindowId) {
|
|
|
+ const frameData = this.options.framesData.find(frame => frame.windowId == frameWindowId);
|
|
|
+ if (frameData) {
|
|
|
+ if (initialization) {
|
|
|
+ const options = Object.create(this.options);
|
|
|
+ options.insertSingleFileComment = false;
|
|
|
+ options.insertFaviconLink = false;
|
|
|
+ options.doc = null;
|
|
|
+ options.win = null;
|
|
|
+ options.url = frameData.baseURI;
|
|
|
+ options.windowId = frameWindowId;
|
|
|
+ if (frameData.content) {
|
|
|
+ options.content = frameData.content;
|
|
|
+ options.canvasData = frameData.canvasData;
|
|
|
+ options.stylesheetContents = frameData.stylesheetContents;
|
|
|
+ frameData.processor = new PageProcessor(options);
|
|
|
+ frameData.frameElement = frameElement;
|
|
|
+ await frameData.processor.loadPage();
|
|
|
+ return frameData.processor.initialize();
|
|
|
+ }
|
|
|
} else {
|
|
|
- this.stats.add("discarded", "frames", 1);
|
|
|
+ if (frameData.processor) {
|
|
|
+ this.stats.add("processed", "frames", 1);
|
|
|
+ await frameData.processor.preparePageData();
|
|
|
+ const pageData = await frameData.processor.getPageData();
|
|
|
+ frameElement.removeAttribute(DOM.windowIdAttributeName(this.options.sessionId));
|
|
|
+ DomProcessorHelper.setFrameContent(frameElement, pageData.content);
|
|
|
+ this.stats.addAll(pageData);
|
|
|
+ } else {
|
|
|
+ this.stats.add("discarded", "frames", 1);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- }));
|
|
|
+ }));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async htmlImports(initialization) {
|