Procházet zdrojové kódy

ensure concurrent saves won't interfer with each other

Gildas před 7 roky
rodič
revize
c2f75084bf

+ 18 - 23
extension/core/content/content.js

@@ -70,13 +70,28 @@ this.singlefile.top = this.singlefile.top || (() => {
 	}
 
 	async function processPage(options) {
-		options.insertSingleFileComment = true;
-		options.insertFaviconLink = true;
 		if (options.shadowEnabled && !options.autoSave) {
 			singlefile.ui.init();
 		}
-		options = await getOptions(options);
 		const processor = new (SingleFile.getClass())(options);
+		options.insertSingleFileComment = true;
+		options.insertFaviconLink = true;
+		options.jsEnabled = true;
+		if (!options.removeFrames) {
+			options.framesData = await frameTree.get(options);
+		}
+		options.doc = document;
+		options.win = window;
+		options.onprogress = event => {
+			if (event.type == event.RESOURCES_INITIALIZED || event.type == event.RESOURCE_LOADED) {
+				browser.runtime.sendMessage({ processProgress: true, index: event.details.index, maxIndex: event.details.max, options: { autoSave: options.autoSave } });
+				if (options.shadowEnabled && !options.autoSave) {
+					singlefile.ui.onprogress(event);
+				}
+			} else if (event.type == event.PAGE_ENDED) {
+				browser.runtime.sendMessage({ processEnd: true, options: { autoSave: options.autoSave } });
+			}
+		};
 		if (options.selected) {
 			markSelectedContent(processor.SELECTED_CONTENT_ATTRIBUTE_NAME, processor.SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME);
 		}
@@ -126,26 +141,6 @@ this.singlefile.top = this.singlefile.top || (() => {
 		document.querySelectorAll("[" + SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME + "]").forEach(selectedContent => selectedContent.removeAttribute(SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME));
 	}
 
-	async function getOptions(options) {
-		if (!options.removeFrames) {
-			options.framesData = await frameTree.get(options);
-		}
-		options.doc = document;
-		options.win = window;
-		options.jsEnabled = true;
-		options.onprogress = event => {
-			if (event.type == event.RESOURCES_INITIALIZED || event.type == event.RESOURCE_LOADED) {
-				browser.runtime.sendMessage({ processProgress: true, index: event.details.index, maxIndex: event.details.max, options: { autoSave: options.autoSave } });
-				if (options.shadowEnabled && !options.autoSave) {
-					singlefile.ui.onprogress(event);
-				}
-			} else if (event.type == event.PAGE_ENDED) {
-				browser.runtime.sendMessage({ processEnd: true, options: { autoSave: options.autoSave } });
-			}
-		};
-		return options;
-	}
-
 	async function downloadPage(page, options) {
 		if (options.backgroundSave) {
 			const response = await browser.runtime.sendMessage({ download: true, url: page.url, confirmFilename: options.confirmFilename, filename: page.filename });

+ 12 - 12
lib/single-file/doc-helper.js

@@ -33,7 +33,7 @@ this.docHelper = this.docHelper || (() => {
 		removedContentAttributeName
 	};
 
-	function preProcessDoc(doc, win, options) {
+	function preProcessDoc(doc, win, options) {	
 		doc.querySelectorAll("script").forEach(element => element.textContent = element.textContent.replace(/<\/script>/gi, "<\\/script>"));
 		doc.head.querySelectorAll("noscript").forEach(element => {
 			const disabledNoscriptElement = doc.createElement("disabled-noscript");
@@ -46,7 +46,7 @@ this.docHelper = this.docHelper || (() => {
 			doc.querySelectorAll("html > body *:not(style):not(script):not(link):not(frame):not(iframe):not(object)").forEach(element => {
 				const style = win.getComputedStyle(element);
 				if (element instanceof win.HTMLElement && style && (element.hidden || style.display == "none" || ((style.opacity === 0 || style.visibility == "hidden") && !element.clientWidth && !element.clientHeight)) && !element.querySelector("iframe, frame, object[type=\"text/html\"][data]")) {
-					element.setAttribute(removedContentAttributeName(), "");
+					element.setAttribute(removedContentAttributeName(options.sessionId), "");
 				}
 			});
 		}
@@ -54,7 +54,7 @@ this.docHelper = this.docHelper || (() => {
 			doc.querySelectorAll("*").forEach(element => {
 				const style = win.getComputedStyle(element);
 				if (style && style.whiteSpace.startsWith("pre")) {
-					element.setAttribute(preservedSpaceAttributeName(), "");
+					element.setAttribute(preservedSpaceAttributeName(options.sessionId), "");
 				}
 			});
 		}
@@ -72,24 +72,24 @@ this.docHelper = this.docHelper || (() => {
 		});
 		doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.removeAttribute("hidden"));
 		if (options.removeHiddenElements) {
-			doc.querySelectorAll("[" + removedContentAttributeName() + "]").forEach(element => element.removeAttribute(removedContentAttributeName()));
+			doc.querySelectorAll("[" + removedContentAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(removedContentAttributeName(options.sessionId)));
 		}
 		if (options.compressHTML) {
-			doc.querySelectorAll("[" + preservedSpaceAttributeName() + "]").forEach(element => element.removeAttribute(preservedSpaceAttributeName()));
+			doc.querySelectorAll("[" + preservedSpaceAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(preservedSpaceAttributeName(options.sessionId)));
 		}
-		doc.querySelectorAll("[" + windowIdAttributeName() + "]").forEach(element => element.removeAttribute(windowIdAttributeName()));
+		doc.querySelectorAll("[" + windowIdAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(windowIdAttributeName(options.sessionId)));
 	}
 
-	function preservedSpaceAttributeName() {
-		return PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME;
+	function preservedSpaceAttributeName(sessionId) {
+		return PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME + (sessionId ? "-" + sessionId : "");
 	}
 
-	function removedContentAttributeName() {
-		return REMOVED_CONTENT_ATTRIBUTE_NAME;
+	function removedContentAttributeName(sessionId) {
+		return REMOVED_CONTENT_ATTRIBUTE_NAME + (sessionId ? "-" + sessionId : "");
 	}
 
-	function windowIdAttributeName() {
-		return WIN_ID_ATTRIBUTE_NAME;
+	function windowIdAttributeName(sessionId) {
+		return WIN_ID_ATTRIBUTE_NAME + (sessionId ? "-" + sessionId : "");
 	}
 
 	function getCanvasData(doc) {

+ 5 - 7
lib/single-file/frame-tree.js

@@ -29,11 +29,10 @@ this.frameTree = this.frameTree || (() => {
 	const TIMEOUT_INIT_REQUEST_MESSAGE = 500;
 	const TOP_WINDOW = isTopWindow(window);
 
-	let sessionId, sessions, windowId;
+	let sessions, windowId;
 
 	if (TOP_WINDOW) {
 		sessions = new Map();
-		sessionId = 0;
 	}
 	addEventListener("message", event => {
 		if (typeof event.data == "string" && event.data.startsWith(MESSAGE_PREFIX + "::")) {
@@ -47,13 +46,12 @@ this.frameTree = this.frameTree || (() => {
 	}, false);
 	return {
 		get: async options => {
+			const sessionId = options.sessionId;
 			options = JSON.parse(JSON.stringify(options));
-			options.sessionId = sessionId;
 			options.win = null;
-			sessionId++;
 			return new Promise(resolve => {
-				sessions.set(options.sessionId, { frames: [], resolve });
-				initRequest({ windowId: "0", sessionId: options.sessionId, options });
+				sessions.set(sessionId, { frames: [], resolve });
+				initRequest({ windowId: "0", sessionId, options });
 			});
 		}
 	};
@@ -116,7 +114,7 @@ this.frameTree = this.frameTree || (() => {
 		let framesData = [];
 		frameElements.forEach((frameElement, frameIndex) => {
 			const frameWindowId = windowId + "." + frameIndex;
-			frameElement.setAttribute(docHelper.windowIdAttributeName(), frameWindowId);
+			frameElement.setAttribute(docHelper.windowIdAttributeName(options.sessionId), frameWindowId);
 			framesData.push({ windowId: frameWindowId });
 			if (!frameElement.contentDocument) {
 				try {

+ 6 - 6
lib/single-file/single-file-browser.js

@@ -146,16 +146,16 @@ this.SingleFile = this.SingleFile || (() => {
 			docHelper.postProcessDoc(doc, options);
 		}
 
-		static windowIdAttributeName() {
-			return docHelper.windowIdAttributeName();
+		static windowIdAttributeName(sessionId) {
+			return docHelper.windowIdAttributeName(sessionId);
 		}
 
-		static preservedSpaceAttributeName() {
-			return docHelper.preservedSpaceAttributeName();
+		static preservedSpaceAttributeName(sessionId) {
+			return docHelper.preservedSpaceAttributeName(sessionId);
 		}
 
-		static removedContentAttributeName() {
-			return docHelper.removedContentAttributeName();
+		static removedContentAttributeName(sessionId) {
+			return docHelper.removedContentAttributeName(sessionId);
 		}
 	}
 

+ 9 - 7
lib/single-file/single-file-core.js

@@ -23,13 +23,15 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-single-file-selected-content";
 	const SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME = "data-single-file-selected-content-root";
 
-	let Download, DOM, URL;
+	let Download, DOM, URL, sessionId = 0;
 
 	function getClass(...args) {
 		[Download, DOM, URL] = args;
 		return class {
 			constructor(options) {
 				this.options = options;
+				options.sessionId = sessionId;
+				sessionId++;
 				this.SELECTED_CONTENT_ATTRIBUTE_NAME = SELECTED_CONTENT_ATTRIBUTE_NAME;
 				this.SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME = SELECTED_CONTENT_ROOT_ATTRIBUTE_NAME;
 			}
@@ -113,7 +115,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			this.processor.resolveHrefs();
 			this.processor.replaceCanvasElements();
 			if (this.options.removeHiddenElements) {
-				this.processor.removeHiddenElements();
+				this.processor.removeHiddenElements(this.options.sessionId);
 			}
 			const initializationPromises = [this.processor.inlineStylesheets(true), this.processor.linkStylesheets(), this.processor.attributeStyles(true)];
 			if (!this.options.removeFrames) {
@@ -390,8 +392,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			this.stats.set("discarded", "cssRules", removedRules.discarded);
 		}
 
-		removeHiddenElements() {
-			const hiddenElements = this.doc.querySelectorAll("[" + DOM.removedContentAttributeName() + "]");
+		removeHiddenElements(sessionId) {
+			const hiddenElements = this.doc.querySelectorAll("[" + DOM.removedContentAttributeName(sessionId) + "]");
 			this.stats.set("discarded", "hiddenElements", hiddenElements.length);
 			hiddenElements.forEach(element => element.remove());
 		}
@@ -411,7 +413,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				if (this.options.displayStats) {
 					size = DOM.getContentSize(this.doc.documentElement.outerHTML);
 				}
-				DOM.htmlminiProcess(this.doc, { preservedSpaceAttributeName: DOM.preservedSpaceAttributeName() });
+				DOM.htmlminiProcess(this.doc, { preservedSpaceAttributeName: DOM.preservedSpaceAttributeName(this.options.sessionId) });
 				if (this.options.displayStats) {
 					this.stats.add("discarded", "htmlBytes", size - DOM.getContentSize(this.doc.documentElement.outerHTML));
 				}
@@ -509,7 +511,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			await Promise.all(frameElements.map(async frameElement => {
 				DomProcessorHelper.setFrameEmptySrc(frameElement);
 				frameElement.setAttribute("sandbox", "");
-				const frameWindowId = frameElement.getAttribute(DOM.windowIdAttributeName());
+				const frameWindowId = frameElement.getAttribute(DOM.windowIdAttributeName(this.options.sessionId));
 				if (frameWindowId) {
 					const frameData = this.options.framesData.find(frame => frame.windowId == frameWindowId);
 					if (frameData) {
@@ -535,7 +537,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 								this.stats.add("processed", "frames", 1);
 								await frameData.processor.preparePageData();
 								const pageData = await frameData.processor.getPageData();
-								frameElement.removeAttribute(DOM.windowIdAttributeName());
+								frameElement.removeAttribute(DOM.windowIdAttributeName(this.options.sessionId));
 								DomProcessorHelper.setFrameContent(frameElement, pageData.content);
 								this.stats.addAll(pageData);
 							} else {