|
|
@@ -220,38 +220,13 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
// ------------
|
|
|
const ESCAPED_FRAGMENT = "_escaped_fragment_=";
|
|
|
const EMPTY_DATA_URI = "data:base64,";
|
|
|
- const STATS_DEFAULT_VALUES = {
|
|
|
- discarded: {
|
|
|
- htmlBytes: 0,
|
|
|
- hiddenElements: 0,
|
|
|
- imports: 0,
|
|
|
- scripts: 0,
|
|
|
- objects: 0,
|
|
|
- audioSource: 0,
|
|
|
- videoSource: 0,
|
|
|
- frames: 0,
|
|
|
- cssRules: 0
|
|
|
- },
|
|
|
- processed: {
|
|
|
- htmlBytes: 0,
|
|
|
- imports: 0,
|
|
|
- scripts: 0,
|
|
|
- frames: 0,
|
|
|
- cssRules: 0,
|
|
|
- canvas: 0,
|
|
|
- styleSheets: 0,
|
|
|
- resources: 0
|
|
|
- }
|
|
|
- };
|
|
|
|
|
|
const batchRequest = new BatchRequest();
|
|
|
|
|
|
class DOMProcessor {
|
|
|
constructor(options) {
|
|
|
this.options = options;
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats = JSON.parse(JSON.stringify(STATS_DEFAULT_VALUES));
|
|
|
- }
|
|
|
+ this.stats = new Stats(options);
|
|
|
this.baseURI = options.url;
|
|
|
}
|
|
|
|
|
|
@@ -276,9 +251,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
|
|
|
async retrieveResources(onloadListener) {
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.processed.resources = batchRequest.getMaxResources();
|
|
|
- }
|
|
|
+ this.stats.set("processed", "resources", batchRequest.getMaxResources());
|
|
|
await batchRequest.run(onloadListener, this.options);
|
|
|
}
|
|
|
|
|
|
@@ -305,11 +278,12 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
const content = DOM.serialize(this.doc, this.options.compressHTML);
|
|
|
if (this.options.displayStats) {
|
|
|
- this.stats.processed.htmlBytes = DOM.getContentSize(content);
|
|
|
- this.stats.discarded.htmlBytes += size - this.stats.processed.htmlBytes;
|
|
|
+ const contentSize = DOM.getContentSize(content);
|
|
|
+ this.stats.set("processed", "htmlBytes", contentSize);
|
|
|
+ this.stats.add("discarded", "htmlBytes", size - contentSize);
|
|
|
}
|
|
|
return {
|
|
|
- stats: this.stats,
|
|
|
+ stats: this.stats.data,
|
|
|
title: title || (this.baseURI && matchTitle ? matchTitle[1] : (url.hostname ? url.hostname : "Untitled page")),
|
|
|
content
|
|
|
};
|
|
|
@@ -338,24 +312,18 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
removeDiscardedResources() {
|
|
|
const objectElements = this.doc.querySelectorAll("applet, meta[http-equiv=refresh], object:not([type=\"image/svg+xml\"]):not([type=\"image/svg-xml\"]):not([type=\"text/html\"]), embed:not([src*=\".svg\"]), link[rel*=preload], link[rel*=prefetch]");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.objects = objectElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "objects", objectElements.length);
|
|
|
objectElements.forEach(element => element.remove());
|
|
|
this.doc.querySelectorAll("[onload]").forEach(element => element.removeAttribute("onload"));
|
|
|
this.doc.querySelectorAll("[onerror]").forEach(element => element.removeAttribute("onerror"));
|
|
|
if (this.options.removeAudioSrc) {
|
|
|
const audioSourceElements = this.doc.querySelectorAll("audio[src], audio > source[src]");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.audioSource = objectElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "audioSource", audioSourceElements.length);
|
|
|
audioSourceElements.forEach(element => element.removeAttribute("src"));
|
|
|
}
|
|
|
if (this.options.removeVideoSrc) {
|
|
|
const videoSourceElements = this.doc.querySelectorAll("video[src], video > source[src]");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.videoSource = objectElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "videoSource", videoSourceElements.length);
|
|
|
videoSourceElements.forEach(element => element.removeAttribute("src"));
|
|
|
}
|
|
|
}
|
|
|
@@ -373,25 +341,19 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
removeScripts() {
|
|
|
const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"])");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.scripts = scriptElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "scripts", scriptElements.length);
|
|
|
scriptElements.forEach(element => element.remove());
|
|
|
}
|
|
|
|
|
|
removeFrames() {
|
|
|
const frameElements = this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.frames = frameElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "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]");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.imports = importElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "imports", importElements.length);
|
|
|
importElements.forEach(element => element.remove());
|
|
|
}
|
|
|
|
|
|
@@ -423,18 +385,14 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
|
|
|
removeUnusedCSSRules() {
|
|
|
- const stats = DOM.rulesMinifier(this.doc);
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.processed.cssRules = stats.processed;
|
|
|
- this.stats.discarded.cssRules = stats.discarded;
|
|
|
- }
|
|
|
+ const removedRules = DOM.rulesMinifier(this.doc);
|
|
|
+ this.stats.set("processed", "cssRules", removedRules.processed);
|
|
|
+ this.stats.set("discarded", "cssRules", removedRules.discarded);
|
|
|
}
|
|
|
|
|
|
removeHiddenElements() {
|
|
|
const hiddenElements = this.doc.querySelectorAll("[" + DOM.removedContentAttributeName() + "]");
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.discarded.hiddenElements = hiddenElements.length;
|
|
|
- }
|
|
|
+ this.stats.set("discarded", "hiddenElements", hiddenElements.length);
|
|
|
hiddenElements.forEach(element => element.remove());
|
|
|
}
|
|
|
|
|
|
@@ -446,7 +404,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
DOM.htmlminiPostProcess(this.doc);
|
|
|
if (this.options.displayStats) {
|
|
|
- this.stats.discarded.htmlBytes += size - DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
+ this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
|
|
|
}
|
|
|
} else {
|
|
|
let size;
|
|
|
@@ -455,7 +413,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
DOM.htmlminiProcess(this.doc, { preservedSpaceAttributeName: DOM.preservedSpaceAttributeName() });
|
|
|
if (this.options.displayStats) {
|
|
|
- this.stats.discarded.htmlBytes += size - DOM.getContentSize(this.doc.documentElement.outerHTML);
|
|
|
+ this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -484,9 +442,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
imgElement.style.pixelHeight = canvasData.height;
|
|
|
}
|
|
|
canvasElement.parentElement.replaceChild(imgElement, canvasElement);
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.processed.canvas++;
|
|
|
- }
|
|
|
+ this.stats.add("processed", "canvas", 1);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
@@ -529,8 +485,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
async inlineStylesheets(initialization) {
|
|
|
await Promise.all(Array.from(this.doc.querySelectorAll("style")).map(async styleElement => {
|
|
|
- if (!initialization && this.options.displayStats) {
|
|
|
- this.stats.processed.styleSheets++;
|
|
|
+ if (!initialization) {
|
|
|
+ this.stats.add("processed", "styleSheets", 1);
|
|
|
}
|
|
|
const stylesheetContent = initialization ? await DomProcessorHelper.resolveImportURLs(styleElement.textContent, this.baseURI, { maxResourceSize: this.options.maxResourceSize, maxResourceSizeEnabled: this.options.maxResourceSizeEnabled }) : await DomProcessorHelper.processStylesheet(styleElement.textContent, this.baseURI);
|
|
|
styleElement.textContent = !initialization && this.options.compressCSS ? DOM.uglifycss(stylesheetContent) : stylesheetContent;
|
|
|
@@ -540,9 +496,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
async scripts() {
|
|
|
await Promise.all(Array.from(this.doc.querySelectorAll("script[src]")).map(async scriptElement => {
|
|
|
if (scriptElement.src) {
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.processed.scripts++;
|
|
|
- }
|
|
|
+ this.stats.add("processed", "scripts", 1);
|
|
|
const scriptContent = await Download.getContent(scriptElement.src, { asDataURI: false, maxResourceSize: this.options.maxResourceSize, maxResourceSizeEnabled: this.options.maxResourceSizeEnabled });
|
|
|
scriptElement.textContent = scriptContent.replace(/<\/script>/gi, "<\\/script>");
|
|
|
}
|
|
|
@@ -578,19 +532,17 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
} else {
|
|
|
if (frameData.processor) {
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.processed.frames++;
|
|
|
- }
|
|
|
+ this.stats.add("processed", "frames", 1);
|
|
|
await frameData.processor.preparePageData();
|
|
|
const pageData = await frameData.processor.getPageData();
|
|
|
frameElement.removeAttribute(DOM.winIdAttributeName());
|
|
|
DomProcessorHelper.setFrameContent(frameElement, pageData.content);
|
|
|
if (this.options.displayStats) {
|
|
|
- Object.keys(this.stats.discarded).forEach(key => this.stats.discarded[key] += (pageData.stats.discarded[key] || 0));
|
|
|
- Object.keys(this.stats.processed).forEach(key => this.stats.processed[key] += (pageData.stats.processed[key] || 0));
|
|
|
+ Object.keys(this.stats.data.discarded).forEach(key => this.stats.add("discarded", key, pageData.stats.discarded[key] || 0));
|
|
|
+ Object.keys(this.stats.data.processed).forEach(key => this.stats.add("processed", key, pageData.stats.processed[key] || 0));
|
|
|
}
|
|
|
- } else if (this.options.displayStats) {
|
|
|
- this.stats.discarded.frames++;
|
|
|
+ } else {
|
|
|
+ this.stats.add("discarded", "frames", 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -624,18 +576,16 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
linkElement.setAttribute("href", EMPTY_DATA_URI);
|
|
|
const processor = this.relImportProcessors.get(linkElement);
|
|
|
if (processor) {
|
|
|
- if (this.options.displayStats) {
|
|
|
- this.stats.processed.imports++;
|
|
|
- }
|
|
|
+ this.stats.add("processed", "imports", 1);
|
|
|
this.relImportProcessors.delete(linkElement);
|
|
|
const pageData = await processor.getPageData();
|
|
|
linkElement.setAttribute("href", "data:text/html," + pageData.content);
|
|
|
if (this.options.displayStats) {
|
|
|
- Object.keys(this.stats.discarded).forEach(key => this.stats.discarded[key] += (pageData.stats.discarded[key] || 0));
|
|
|
- Object.keys(this.stats.processed).forEach(key => this.stats.processed[key] += (pageData.stats.processed[key] || 0));
|
|
|
+ Object.keys(this.stats.data.discarded).forEach(key => this.stats.add("discarded", key, pageData.stats.discarded[key] || 0));
|
|
|
+ Object.keys(this.stats.data.processed).forEach(key => this.stats.add("processed", key, pageData.stats.processed[key] || 0));
|
|
|
}
|
|
|
- } else if (this.options.displayStats) {
|
|
|
- this.stats.discarded.imports++;
|
|
|
+ } else {
|
|
|
+ this.stats.add("discarded", "imports", 1);
|
|
|
}
|
|
|
}
|
|
|
}));
|
|
|
@@ -878,6 +828,52 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // -----
|
|
|
+ // Stats
|
|
|
+ // -----
|
|
|
+ const STATS_DEFAULT_VALUES = {
|
|
|
+ discarded: {
|
|
|
+ htmlBytes: 0,
|
|
|
+ hiddenElements: 0,
|
|
|
+ imports: 0,
|
|
|
+ scripts: 0,
|
|
|
+ objects: 0,
|
|
|
+ audioSource: 0,
|
|
|
+ videoSource: 0,
|
|
|
+ frames: 0,
|
|
|
+ cssRules: 0
|
|
|
+ },
|
|
|
+ processed: {
|
|
|
+ htmlBytes: 0,
|
|
|
+ imports: 0,
|
|
|
+ scripts: 0,
|
|
|
+ frames: 0,
|
|
|
+ cssRules: 0,
|
|
|
+ canvas: 0,
|
|
|
+ styleSheets: 0,
|
|
|
+ resources: 0
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ class Stats {
|
|
|
+ constructor(options) {
|
|
|
+ this.options = options;
|
|
|
+ if (options.displayStats) {
|
|
|
+ this.data = JSON.parse(JSON.stringify(STATS_DEFAULT_VALUES));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ set(type, subType, value) {
|
|
|
+ if (this.options.displayStats) {
|
|
|
+ this.data[type][subType] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ add(type, subType, value) {
|
|
|
+ if (this.options.displayStats) {
|
|
|
+ this.data[type][subType] += value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return { getClass };
|
|
|
|
|
|
})();
|