Просмотр исходного кода

added "save frame" feature (via the context menu)

Gildas 7 лет назад
Родитель
Сommit
82c5acab07

+ 10 - 2
extension/core/bg/bg.js

@@ -32,7 +32,11 @@ singlefile.core = (() => {
 			options.insertFaviconLink = true;
 			return new Promise(async (resolve, reject) => {
 				const errorTimeout = setTimeout(reject, TIMEOUT_PROCESS_START_MESSAGE);
-				await processStart(tab, options);
+				try {
+					await processStart(tab, options);
+				} catch (error) {
+					reject(error);
+				}
 				clearTimeout(errorTimeout);
 				resolve();
 			});
@@ -43,7 +47,11 @@ singlefile.core = (() => {
 		if (!options.removeFrames) {
 			await FrameTree.initialize(tab.id);
 		}
-		await browser.tabs.sendMessage(tab.id, { processStart: true, options });
+		if (options.frameId) {
+			await browser.tabs.sendMessage(tab.id, { processStart: true, options }, { frameId: options.frameId });
+		} else {
+			await browser.tabs.sendMessage(tab.id, { processStart: true, options });
+		}
 	}
 
 })();

+ 55 - 0
extension/core/content/content-frame.js

@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* global browser, window, top, document */
+
+(() => {
+
+	if (window != top) {
+		browser.runtime.onMessage.addListener(async message => {
+			if (message.processStart) {
+				message.options.content = getDoctype(document) + document.documentElement.outerHTML;
+				message.options.frameId = null;
+				message.options.url = document.location.href;
+				top.postMessage("__SingleFile__::" + JSON.stringify(message), "*");
+				return {};
+			}
+		});
+	}
+
+	function getDoctype(doc) {
+		const docType = doc.doctype;
+		let docTypeString;
+		if (docType) {
+			docTypeString = "<!DOCTYPE " + docType.nodeName;
+			if (docType.publicId) {
+				docTypeString += " PUBLIC \"" + docType.publicId + "\"";
+				if (docType.systemId)
+					docTypeString += " \"" + docType.systemId + "\"";
+			} else if (docType.systemId)
+				docTypeString += " SYSTEM \"" + docType.systemId + "\"";
+			if (docType.internalSubset)
+				docTypeString += " [" + docType.internalSubset + "]";
+			return docTypeString + ">\n";
+		}
+		return "";
+	}
+
+})();

+ 16 - 8
extension/core/content/content.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, SingleFile, singlefile, FrameTree, document, Blob, MouseEvent, getSelection, getComputedStyle, prompt */
+/* global browser, SingleFile, singlefile, FrameTree, document, Blob, MouseEvent, getSelection, getComputedStyle, prompt, addEventListener */
 
 (() => {
 
@@ -31,14 +31,22 @@
 		return {};
 	});
 
+	addEventListener("message", event => {
+		if (typeof event.data === "string" && event.data.startsWith("__SingleFile__::")) {
+			const message = JSON.parse(event.data.substring("__SingleFile__".length + 2));
+			savePage(message);
+		}
+	});
+
 	async function savePage(message) {
-		if (message.processStart && !processing) {
+		if (message.processStart && !processing && !message.options.frameId) {
 			processing = true;
 			try {
 				const page = await processMessage(message);
 				downloadPage(page, message.options);
 				revokeDownloadURL(page);
 			} catch (error) {
+				console.error(error); // eslint-disable-line no-console
 				browser.runtime.sendMessage({ processError: true, error });
 			}
 			processing = false;
@@ -59,20 +67,20 @@
 		if (options.removeHiddenElements) {
 			selectRemovedElements(processor.REMOVED_CONTENT_ATTRIBUTE_NAME);
 		}
-		options.url = document.location.href;
-		options.content = getDoctype(document) + document.documentElement.outerHTML;
+		options.url = options.url || document.location.href;
+		options.content = options.content || getDoctype(document) + document.documentElement.outerHTML;
+		await processor.initialize();
 		if (options.removeHiddenElements) {
 			unselectRemovedElements(processor.REMOVED_CONTENT_ATTRIBUTE_NAME);
 		}
-		if (options.selected) {
-			unselectSelectedContent(processor.SELECTED_CONTENT_ATTRIBUTE_NAME);
-		}
-		await processor.initialize();
 		if (options.shadowEnabled) {
 			singlefile.ui.init();
 		}
 		await processor.preparePageData();
 		const page = processor.getPageData();
+		if (options.selected) {
+			unselectSelectedContent(processor.SELECTED_CONTENT_ATTRIBUTE_NAME);
+		}
 		const date = new Date();
 		page.filename = page.title + (options.appendSaveDate ? " (" + date.toISOString().split("T")[0] + " " + date.toLocaleTimeString() + ")" : "") + ".html";
 		page.url = URL.createObjectURL(new Blob([page.content], { type: "text/html" })); // TODO: revoke after download

+ 22 - 11
extension/ui/bg/ui.js

@@ -30,6 +30,7 @@ singlefile.ui = (() => {
 	const STORE_URLS = ["https://chrome.google.com", "https://addons.mozilla.org"];
 	const MENU_ID_SAVE_PAGE = "save-page";
 	const MENU_ID_SAVE_SELECTED = "save-selected";
+	const MENU_ID_SAVE_FRAME = "save-frame";
 
 	const tabs = {};
 	const badgeTabs = {};
@@ -43,6 +44,9 @@ singlefile.ui = (() => {
 		if (event.menuItemId == MENU_ID_SAVE_SELECTED) {
 			processTab(tab, { selected: true });
 		}
+		if (event.menuItemId == MENU_ID_SAVE_FRAME) {
+			processTab(tab, { frameId: event.frameId });
+		}
 	});
 	browser.browserAction.onClicked.addListener(async tab => {
 		if (isAllowedURL(tab.url)) {
@@ -91,6 +95,11 @@ singlefile.ui = (() => {
 				contexts: ["selection"],
 				title: "Save selection"
 			});
+			browser.menus.create({
+				id: MENU_ID_SAVE_FRAME,
+				contexts: ["frame"],
+				title: "Save frame"
+			});
 		} else {
 			await browser.menus.removeAll();
 		}
@@ -110,17 +119,19 @@ singlefile.ui = (() => {
 				barProgress: -1
 			};
 			refreshBadge(tabId);
-		} catch (e) {
-			tabs[tabId] = {
-				id: tabId,
-				text: "↻",
-				color: [255, 141, 1, 255],
-				title: "reload the page",
-				path: DEFAULT_ICON_PATH,
-				progress: -1,
-				barProgress: -1
-			};
-			refreshBadge(tabId);
+		} catch (error) {
+			if (!error) {
+				tabs[tabId] = {
+					id: tabId,
+					text: "↻",
+					color: [255, 141, 1, 255],
+					title: "reload the page",
+					path: DEFAULT_ICON_PATH,
+					progress: -1,
+					barProgress: -1
+				};
+				refreshBadge(tabId);
+			}
 		}
 	}
 

+ 22 - 2
lib/browser-polyfill/custom-browser-polyfill.js

@@ -1,3 +1,23 @@
+/*
+ * Copyright 2018 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
 /* global navigator, chrome */
 
 (() => {
@@ -136,8 +156,8 @@
 				onRemoved: {
 					addListener: listener => chrome.tabs.onRemoved.addListener(listener)
 				},
-				sendMessage: (tabId, message) => new Promise((resolve, reject) =>
-					chrome.tabs.sendMessage(tabId, message, response => {
+				sendMessage: (tabId, message, options = {}) => new Promise((resolve, reject) =>
+					chrome.tabs.sendMessage(tabId, message, options, response => {
 						if (chrome.runtime.lastError) {
 							reject(chrome.runtime.lastError);
 						} else {

+ 4 - 2
lib/single-file/single-file-core.js

@@ -258,8 +258,10 @@ this.SingleFileCore = (() => {
 		getPageData() {
 			if (this.options.selected) {
 				const selectedElement = this.doc.querySelector("[" + SELECTED_CONTENT_ATTRIBUTE_NAME + "]");
-				DomProcessorHelper.isolateElement(selectedElement.parentElement, selectedElement);
-				selectedElement.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
+				if (selectedElement) {
+					DomProcessorHelper.isolateElement(selectedElement.parentElement, selectedElement);
+					selectedElement.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
+				}
 			}
 			const titleElement = this.doc.querySelector("title");
 			let title;

+ 2 - 1
manifest.json

@@ -55,7 +55,8 @@
             ],
             "js": [
                 "lib/browser-polyfill/custom-browser-polyfill.js",
-                "lib/frame-tree/content/frame-tree.js"
+                "lib/frame-tree/content/frame-tree.js",
+                "extension/core/content/content-frame.js"
             ],
             "run_at": "document_start",
             "all_frames": true