Gildas 7 лет назад
Родитель
Сommit
6a05b3e061

+ 1 - 0
.vscode/settings.json

@@ -7,6 +7,7 @@
         "gmail",
         "graytext",
         "iframes",
+        "polyfill",
         "prefetch",
         "singlefile",
         "srcdoc",

+ 6 - 9
extension/core/bg/bg.js

@@ -18,11 +18,10 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global singlefile, FrameTree */
+/* global browser, singlefile, FrameTree */
 
 singlefile.core = (() => {
 
-	const browser = this.browser || this.chrome;
 	const TIMEOUT_PROCESS_START_MESSAGE = 3000;
 
 	return {
@@ -31,13 +30,11 @@ singlefile.core = (() => {
 			Object.keys(processOptions).forEach(key => options[key] = processOptions[key]);
 			options.insertSingleFileComment = true;
 			options.insertFaviconLink = true;
-			return new Promise((resolve, reject) => {
+			return new Promise(async (resolve, reject) => {
 				const errorTimeout = setTimeout(reject, TIMEOUT_PROCESS_START_MESSAGE);
-				processStart(tab, options)
-					.then(() => {
-						clearTimeout(errorTimeout);
-						resolve();
-					});
+				await processStart(tab, options);
+				clearTimeout(errorTimeout);
+				resolve();
 			});
 		}
 	};
@@ -46,7 +43,7 @@ singlefile.core = (() => {
 		if (!options.removeFrames) {
 			await FrameTree.initialize(tab.id);
 		}
-		await new Promise(resolve => browser.tabs.sendMessage(tab.id, { processStart: true, options }, resolve));
+		await browser.tabs.sendMessage(tab.id, { processStart: true, options });
 	}
 
 })();

+ 10 - 12
extension/core/bg/config.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global singlefile, localStorage */
+/* global browser, singlefile, localStorage */
 
 singlefile.config = (() => {
 
@@ -39,22 +39,19 @@ singlefile.config = (() => {
 		maxResourceSize: 10
 	};
 
-	const browser = this.browser || this.chrome;
-
 	let pendingUpgradePromise;
 	upgrade();
 
-	function upgrade() {
+	async function upgrade() {
 		if (localStorage.config) {
 			const config = JSON.parse(localStorage.config);
 			upgradeConfig(config);
 			delete localStorage.config;
-			pendingUpgradePromise = new Promise(resolve => browser.storage.local.set(config, resolve));
+			pendingUpgradePromise = browser.storage.local.set(config);
 		} else {
-			pendingUpgradePromise = new Promise(resolve => browser.storage.local.get(config => {
-				upgradeConfig(config);
-				browser.storage.local.set(config, resolve);
-			}));
+			const config = await browser.storage.local.get();
+			upgradeConfig(config);
+			pendingUpgradePromise = browser.storage.local.set(config);
 		}
 	}
 
@@ -97,15 +94,16 @@ singlefile.config = (() => {
 	return {
 		async set(config) {
 			await pendingUpgradePromise;
-			return new Promise(resolve => browser.storage.local.set(config, resolve));
+			await browser.storage.local.set(config);
 		},
 		async get() {
 			await pendingUpgradePromise;
-			return new Promise(resolve => browser.storage.local.get(config => resolve(Object.keys(config).length ? config : DEFAULT_CONFIG)));
+			const config = await browser.storage.local.get();
+			return Object.keys(config).length ? config : DEFAULT_CONFIG;
 		},
 		async reset() {
 			await pendingUpgradePromise;
-			return new Promise(resolve => browser.storage.local.clear(resolve));
+			await browser.storage.local.clear();
 		}
 	};
 

+ 3 - 5
extension/core/content/content.js

@@ -18,19 +18,17 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global SingleFile, singlefile, FrameTree, document, Blob, MouseEvent, getSelection, getComputedStyle, prompt */
+/* global browser, SingleFile, singlefile, FrameTree, document, Blob, MouseEvent, getSelection, getComputedStyle, prompt */
 
 (() => {
 
-	const browser = this.browser || this.chrome;
-
 	const PROGRESS_LOADED_COEFFICIENT = 2;
 
 	let processing = false;
 
-	browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
-		sendResponse({});
+	browser.runtime.onMessage.addListener(async message => {
 		savePage(message);
+		return {};
 	});
 
 	async function savePage(message) {

+ 65 - 68
extension/ui/bg/options.js

@@ -18,77 +18,74 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global document */
+/* global browser, document */
 
-(() => {
+(async () => {
 
-	const browser = this.browser || this.chrome;
+	const bgPage = await browser.runtime.getBackgroundPage();
+	const removeHiddenElementsInput = document.getElementById("removeHiddenElementsInput");
+	const removeUnusedCSSRulesInput = document.getElementById("removeUnusedCSSRulesInput");
+	const removeFramesInput = document.getElementById("removeFramesInput");
+	const removeImportsInput = document.getElementById("removeImportsInput");
+	const removeScriptsInput = document.getElementById("removeScriptsInput");
+	const saveRawPageInput = document.getElementById("saveRawPageInput");
+	const compressInput = document.getElementById("compressInput");
+	const lazyLoadImagesInput = document.getElementById("lazyLoadImagesInput");
+	const contextMenuEnabledInput = document.getElementById("contextMenuEnabledInput");
+	const appendSaveDateInput = document.getElementById("appendSaveDateInput");
+	const shadowEnabledInput = document.getElementById("shadowEnabledInput");
+	const maxResourceSizeInput = document.getElementById("maxResourceSizeInput");
+	const maxResourceSizeEnabledInput = document.getElementById("maxResourceSizeEnabledInput");
+	const confirmFilenameInput = document.getElementById("confirmFilenameInput");
+	let pendingSave = Promise.resolve();
+	document.getElementById("resetButton").addEventListener("click", async () => {
+		await bgPage.singlefile.config.reset();
+		await refresh();
+		await update();
+	}, false);
+	maxResourceSizeEnabledInput.addEventListener("click", () => maxResourceSizeInput.disabled = !maxResourceSizeEnabledInput.checked, false);
+	document.getElementById("popupContent").onchange = update;
+	refresh();
 
-	browser.runtime.getBackgroundPage(bgPage => {
-		const removeHiddenElementsInput = document.getElementById("removeHiddenElementsInput");
-		const removeUnusedCSSRulesInput = document.getElementById("removeUnusedCSSRulesInput");
-		const removeFramesInput = document.getElementById("removeFramesInput");
-		const removeImportsInput = document.getElementById("removeImportsInput");
-		const removeScriptsInput = document.getElementById("removeScriptsInput");
-		const saveRawPageInput = document.getElementById("saveRawPageInput");
-		const compressInput = document.getElementById("compressInput");
-		const lazyLoadImagesInput = document.getElementById("lazyLoadImagesInput");
-		const contextMenuEnabledInput = document.getElementById("contextMenuEnabledInput");
-		const appendSaveDateInput = document.getElementById("appendSaveDateInput");
-		const shadowEnabledInput = document.getElementById("shadowEnabledInput");
-		const maxResourceSizeInput = document.getElementById("maxResourceSizeInput");
-		const maxResourceSizeEnabledInput = document.getElementById("maxResourceSizeEnabledInput");
-		const confirmFilenameInput = document.getElementById("confirmFilenameInput");
-		let pendingSave = Promise.resolve();
-		document.getElementById("resetButton").addEventListener("click", async () => {
-			await bgPage.singlefile.config.reset();
-			await refresh();
-			await update();
-		}, false);
-		maxResourceSizeEnabledInput.addEventListener("click", () => maxResourceSizeInput.disabled = !maxResourceSizeEnabledInput.checked, false);
-		document.getElementById("popupContent").onchange = update;
-		refresh();
+	async function refresh() {
+		const config = await bgPage.singlefile.config.get();
+		removeHiddenElementsInput.checked = config.removeHiddenElements;
+		removeUnusedCSSRulesInput.checked = config.removeUnusedCSSRules;
+		removeFramesInput.checked = config.removeFrames;
+		removeImportsInput.checked = config.removeImports;
+		removeScriptsInput.checked = config.removeScripts;
+		saveRawPageInput.checked = config.saveRawPage;
+		compressInput.checked = config.compress;
+		lazyLoadImagesInput.checked = config.lazyLoadImages;
+		contextMenuEnabledInput.checked = config.contextMenuEnabled;
+		appendSaveDateInput.checked = config.appendSaveDate;
+		shadowEnabledInput.checked = config.shadowEnabled;
+		maxResourceSizeEnabledInput.checked = config.maxResourceSizeEnabled;
+		maxResourceSizeInput.value = config.maxResourceSize;
+		maxResourceSizeInput.disabled = !config.maxResourceSizeEnabled;
+		confirmFilenameInput.checked = config.confirmFilename;
+	}
 
-		async function refresh() {
-			const config = await bgPage.singlefile.config.get();
-			removeHiddenElementsInput.checked = config.removeHiddenElements;
-			removeUnusedCSSRulesInput.checked = config.removeUnusedCSSRules;
-			removeFramesInput.checked = config.removeFrames;
-			removeImportsInput.checked = config.removeImports;
-			removeScriptsInput.checked = config.removeScripts;
-			saveRawPageInput.checked = config.saveRawPage;
-			compressInput.checked = config.compress;
-			lazyLoadImagesInput.checked = config.lazyLoadImages;
-			contextMenuEnabledInput.checked = config.contextMenuEnabled;
-			appendSaveDateInput.checked = config.appendSaveDate;
-			shadowEnabledInput.checked = config.shadowEnabled;
-			maxResourceSizeEnabledInput.checked = config.maxResourceSizeEnabled;
-			maxResourceSizeInput.value = config.maxResourceSize;
-			maxResourceSizeInput.disabled = !config.maxResourceSizeEnabled;
-			confirmFilenameInput.checked = config.confirmFilename;
-		}
-
-		async function update() {
-			await pendingSave;
-			pendingSave = bgPage.singlefile.config.set({
-				removeHiddenElements: removeHiddenElementsInput.checked,
-				removeUnusedCSSRules: removeUnusedCSSRulesInput.checked,
-				removeFrames: removeFramesInput.checked,
-				removeImports: removeImportsInput.checked,
-				removeScripts: removeScriptsInput.checked,
-				saveRawPage: saveRawPageInput.checked,
-				compress: compressInput.checked,
-				lazyLoadImages: lazyLoadImagesInput.checked,
-				contextMenuEnabled: contextMenuEnabledInput.checked,
-				appendSaveDate: appendSaveDateInput.checked,
-				shadowEnabled: shadowEnabledInput.checked,
-				maxResourceSizeEnabled: maxResourceSizeEnabledInput.checked,
-				maxResourceSize: maxResourceSizeInput.value,
-				confirmFilename: confirmFilenameInput.checked
-			});
-			await pendingSave;
-			await bgPage.singlefile.ui.update();
-		}
-	});
+	async function update() {
+		await pendingSave;
+		pendingSave = bgPage.singlefile.config.set({
+			removeHiddenElements: removeHiddenElementsInput.checked,
+			removeUnusedCSSRules: removeUnusedCSSRulesInput.checked,
+			removeFrames: removeFramesInput.checked,
+			removeImports: removeImportsInput.checked,
+			removeScripts: removeScriptsInput.checked,
+			saveRawPage: saveRawPageInput.checked,
+			compress: compressInput.checked,
+			lazyLoadImages: lazyLoadImagesInput.checked,
+			contextMenuEnabled: contextMenuEnabledInput.checked,
+			appendSaveDate: appendSaveDateInput.checked,
+			shadowEnabled: shadowEnabledInput.checked,
+			maxResourceSizeEnabled: maxResourceSizeEnabledInput.checked,
+			maxResourceSize: maxResourceSizeInput.value,
+			confirmFilename: confirmFilenameInput.checked
+		});
+		await pendingSave;
+		await bgPage.singlefile.ui.update();
+	}
 
 })();

+ 18 - 27
extension/ui/bg/ui.js

@@ -18,18 +18,15 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global singlefile, navigator */
+/* global browser, singlefile */
 
 singlefile.ui = (() => {
 
-	const browser = this.browser || this.chrome;
-
 	const DEFAULT_ICON_PATH = "/extension/ui/resources/icon_16.png";
 	const WAIT_ICON_PATH_PREFIX = "/extension/ui/resources/icon_16_wait";
 	const DEFAULT_TITLE = "Process this page with SingleFile";
 	const DEFAULT_COLOR = [2, 147, 20, 255];
 	const BADGE_PROPERTIES = [{ name: "text", browserActionMethod: "setBadgeText" }, { name: "color", browserActionMethod: "setBadgeBackgroundColor" }, { name: "title", browserActionMethod: "setTitle" }, { name: "path", browserActionMethod: "setIcon" }];
-	const RUNNING_IN_EDGE = navigator.userAgent.includes("Edge");
 	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";
@@ -39,7 +36,7 @@ singlefile.ui = (() => {
 	let badgeRefreshPending = [];
 
 	browser.runtime.onInstalled.addListener(refreshContextMenu);
-	browser.contextMenus.onClicked.addListener((event, tab) => {
+	browser.menus.onClicked.addListener((event, tab) => {
 		if (event.menuItemId == MENU_ID_SAVE_PAGE) {
 			processTab(tab);
 		}
@@ -47,19 +44,20 @@ singlefile.ui = (() => {
 			processTab(tab, { selected: true });
 		}
 	});
-	browser.browserAction.onClicked.addListener(tab => {
+	browser.browserAction.onClicked.addListener(async tab => {
 		if (isAllowedURL(tab.url)) {
-			browser.tabs.query({ currentWindow: true, highlighted: true }, tabs => {
-				tabs = tabs.filter(tab => tab.highlighted);
-				if (!tabs.length) {
-					processTab(tab);
-				} else {
-					tabs.forEach(processTab);
-				}
-			});
+			const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
+			if (!tabs.length) {
+				processTab(tab);
+			} else {
+				tabs.forEach(processTab);
+			}
 		}
 	});
-	browser.tabs.onActivated.addListener(activeInfo => browser.tabs.get(activeInfo.tabId, tab => onTabActivated(tab.id, isAllowedURL(tab.url))));
+	browser.tabs.onActivated.addListener(async activeInfo => {
+		const tab = browser.tabs.get(activeInfo.tabId);
+		onTabActivated(tab.id, isAllowedURL(tab.url));
+	});
 	browser.tabs.onCreated.addListener(tab => onTabActivated(tab.id, isAllowedURL(tab.url)));
 	browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => onTabActivated(tab.id, isAllowedURL(tab.url)));
 	browser.tabs.onRemoved.addListener(tabId => onTabRemoved(tabId));
@@ -76,25 +74,25 @@ singlefile.ui = (() => {
 			}
 			onTabError(sender.tab.id);
 		}
-		return false;
 	});
 	return { update: refreshContextMenu };
 
 	async function refreshContextMenu() {
 		const config = await singlefile.config.get();
 		if (config.contextMenuEnabled) {
-			browser.contextMenus.create({
+			await browser.menus.removeAll();
+			browser.menus.create({
 				id: MENU_ID_SAVE_PAGE,
 				contexts: ["page"],
 				title: "Save page with SingleFile"
 			});
-			browser.contextMenus.create({
+			browser.menus.create({
 				id: MENU_ID_SAVE_SELECTED,
 				contexts: ["selection"],
 				title: "Save selection"
 			});
 		} else {
-			browser.contextMenus.removeAll();
+			await browser.menus.removeAll();
 		}
 	}
 
@@ -204,14 +202,7 @@ singlefile.ui = (() => {
 		if (JSON.stringify(badgeTabs[tabId][property]) != JSON.stringify(value)) {
 			const browserActionParameter = { tabId };
 			badgeTabs[tabId][property] = browserActionParameter[property] = value;
-			return new Promise(resolve => {
-				if (RUNNING_IN_EDGE) {
-					browser.browserAction[browserActionMethod](browserActionParameter);
-					resolve();
-				} else {
-					browser.browserAction[browserActionMethod](browserActionParameter, resolve);
-				}
-			});
+			await browser.browserAction[browserActionMethod](browserActionParameter);
 		}
 	}
 

+ 1 - 0
extension/ui/pages/options.html

@@ -76,6 +76,7 @@
 			</div>
 		</div>
 	</div>
+	<script type="text/javascript" src="/lib/browser-polyfill/custom-browser-polyfill.js"></script>
 	<script type="text/javascript" src="../bg/options.js"></script>
 </body>
 

+ 170 - 0
lib/browser-polyfill/custom-browser-polyfill.js

@@ -0,0 +1,170 @@
+/* global navigator, chrome */
+
+(() => {
+
+	const isChrome = navigator.userAgent.includes("Chrome");
+
+	if (isChrome) {
+		this.browser = {
+			browserAction: {
+				onClicked: {
+					addListener: listener => chrome.browserAction.onClicked.addListener(listener)
+				},
+				enable: tabId => chrome.browserAction.enable(tabId),
+				disable: tabId => chrome.browserAction.disable(tabId),
+				setBadgeText: options => new Promise((resolve, reject) => {
+					if (chrome.runtime.lastError) {
+						reject(chrome.runtime.lastError);
+					} else {
+						chrome.browserAction.setBadgeText(options, resolve);
+					}
+				}),
+				setBadgeBackgroundColor: options => new Promise((resolve, reject) => {
+					if (chrome.runtime.lastError) {
+						reject(chrome.runtime.lastError);
+					} else {
+						chrome.browserAction.setBadgeBackgroundColor(options, resolve);
+					}
+				}),
+				setTitle: options => new Promise((resolve, reject) => {
+					if (chrome.runtime.lastError) {
+						reject(chrome.runtime.lastError);
+					} else {
+						chrome.browserAction.setTitle(options, resolve);
+					}
+				}),
+				setIcon: options => new Promise((resolve, reject) => {
+					if (chrome.runtime.lastError) {
+						reject(chrome.runtime.lastError);
+					} else {
+						chrome.browserAction.setIcon(options, resolve);
+					}
+				})
+			},
+			menus: {
+				onClicked: {
+					addListener: listener => chrome.contextMenus.onClicked.addListener(listener)
+				},
+				create: options => chrome.contextMenus.create(options),
+				removeAll: () => new Promise((resolve, reject) => {
+					chrome.contextMenus.removeAll(() => {
+						if (chrome.runtime.lastError) {
+							reject(chrome.runtime.lastError);
+						} else {
+							resolve();
+						}
+					});
+				})
+			},
+			runtime: {
+				onMessage: {
+					addListener: listener => chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+						const response = listener(message, sender);
+						if (response && typeof response.then == "function") {
+							response.then(sendResponse);
+							return true;
+						}
+					}),
+					removeListener: () => { }
+				},
+				sendMessage: message => new Promise((resolve, reject) =>
+					chrome.runtime.sendMessage(message, response => {
+						if (chrome.runtime.lastError) {
+							reject(chrome.runtime.lastError);
+						} else {
+							resolve(response);
+						}
+					})
+				),
+				getBackgroundPage: () => new Promise((resolve, reject) =>
+					chrome.runtime.getBackgroundPage(bgPage => {
+						if (chrome.runtime.lastError) {
+							reject(chrome.runtime.lastError);
+						} else {
+							resolve(bgPage);
+						}
+					})
+				),
+				onInstalled: {
+					addListener: listener => chrome.runtime.onInstalled.addListener(listener)
+				},
+				get lastError() {
+					return chrome.runtime.last;
+				}
+			},
+			storage: {
+				local: {
+					set: value => new Promise((resolve, reject) => {
+						chrome.storage.local.set(value, () => {
+							if (chrome.runtime.lastError) {
+								reject(chrome.runtime.lastError);
+							} else {
+								resolve();
+							}
+						});
+					}),
+					get: () => new Promise((resolve, reject) => {
+						chrome.storage.local.get(value => {
+							if (chrome.runtime.lastError) {
+								reject(chrome.runtime.lastError);
+							} else {
+								resolve(value);
+							}
+						});
+					}),
+					clear: () => new Promise((resolve, reject) => {
+						chrome.storage.local.clear(() => {
+							if (chrome.runtime.lastError) {
+								reject(chrome.runtime.lastError);
+							} else {
+								resolve();
+							}
+						});
+					})
+				}
+			},
+			tabs: {
+				onCreated: {
+					addListener: listener => chrome.tabs.onCreated.addListener(listener)
+				},
+				onActivated: {
+					addListener: listener => chrome.tabs.onActivated.addListener(listener)
+				},
+				onUpdated: {
+					addListener: listener => chrome.tabs.onUpdated.addListener(listener)
+				},
+				onRemoved: {
+					addListener: listener => chrome.tabs.onRemoved.addListener(listener)
+				},
+				sendMessage: (tabId, message) => new Promise((resolve, reject) =>
+					chrome.tabs.sendMessage(tabId, message, response => {
+						if (chrome.runtime.lastError) {
+							reject(chrome.runtime.lastError);
+						} else {
+							resolve(response);
+						}
+					})
+				),
+				query: options => new Promise((resolve, reject) => {
+					chrome.tabs.query(options, tabs => {
+						if (chrome.runtime.lastError) {
+							reject(chrome.runtime.lastError);
+						} else {
+							resolve(tabs);
+						}
+					});
+				}),
+				get: options => new Promise((resolve, reject) => {
+					chrome.tabs.get(options, tab => {
+						if (chrome.runtime.lastError) {
+							reject(chrome.runtime.lastError);
+						} else {
+							resolve(tab);
+						}
+					});
+				})
+			}
+		};
+	}
+
+})();

+ 15 - 18
lib/fetch/bg/fetch.js

@@ -18,22 +18,21 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global fetch */
+/* global browser, fetch */
 
 (() => {
 
-	const browser = this.browser || this.chrome;
-
 	const fetchResponses = new Map();
 
 	let requestId = 1;
 
-	browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
+	browser.runtime.onMessage.addListener(async request => {
 		if (request.method) {
-			onRequest(request)
-				.then(sendResponse)
-				.catch(error => sendResponse({ error: error.toString() }));
-			return true;
+			try {
+				return await onRequest(request);
+			} catch (error) {
+				return { error: error.toString() };
+			}
 		}
 	});
 
@@ -42,17 +41,15 @@
 			const responseId = requestId;
 			requestId = requestId + 1;
 			const response = await fetch(request.url, request.options);
-			if (response) {
-				if (response.status >= 400) {
-					throw new Error(response.statusText || response.status);
-				} else {
-					fetchResponses.set(responseId, response);
-					const headers = {};
-					for (let headerName of response.headers.keys()) {
-						headers[headerName] = response.headers.get(headerName);
-					}
-					return { responseId, headers };
+			if (response.status >= 400) {
+				throw new Error(response.statusText || response.status);
+			} else {
+				fetchResponses.set(responseId, response);
+				const headers = {};
+				for (let headerName of response.headers.keys()) {
+					headers[headerName] = response.headers.get(headerName);
 				}
+				return { responseId, headers };
 			}
 		} else {
 			const content = fetchResponses.get(request.requestId);

+ 8 - 13
lib/fetch/content/fetch.js

@@ -18,12 +18,10 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global Blob */
+/* global browser, Blob */
 
 this.superFetch = (() => {
 
-	const browser = this.browser || this.chrome;
-
 	return async (url, options) => {
 		const responseFetch = await sendMessage({ method: "fetch", url, options });
 		return {
@@ -43,16 +41,13 @@ this.superFetch = (() => {
 		};
 	};
 
-	function sendMessage(message) {
-		return new Promise((resolve, reject) => {
-			browser.runtime.sendMessage(message, response => {
-				if (!response || response.error) {
-					reject(response && response.error);
-				} else {
-					resolve(response);
-				}
-			});
-		});
+	async function sendMessage(message) {
+		const response = await browser.runtime.sendMessage(message);
+		if (!response || response.error) {
+			throw new Error(response && response.error.toString());
+		} else {
+			return response;
+		}
 	}
 
 })();

+ 2 - 2
lib/frame-tree/bg/frame-tree.js

@@ -18,9 +18,9 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-this.FrameTree = (() => {
+/* global browser */
 
-	const browser = this.browser || this.chrome;
+this.FrameTree = (() => {
 
 	browser.runtime.onMessage.addListener((message, sender) => {
 		if (message.method == "FrameTree.getDataRequest") {

+ 1 - 3
lib/frame-tree/content/frame-tree.js

@@ -18,12 +18,10 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global window, top, document, HTMLHtmlElement, addEventListener */
+/* global browser, window, top, document, HTMLHtmlElement, addEventListener */
 
 this.FrameTree = (() => {
 
-	const browser = this.browser || this.chrome;
-
 	const MESSAGE_PREFIX = "__FrameTree__";
 	const TIMEOUT_INIT_REQUEST_MESSAGE = 1000;
 	const TIMEOUT_DATA_RESPONSE_MESSAGE = 1000;

+ 4 - 0
manifest.json

@@ -10,6 +10,7 @@
     "description": "Archive a complete page into a single HTML file",
     "background": {
         "scripts": [
+            "lib/browser-polyfill/custom-browser-polyfill.js",
             "lib/frame-tree/bg/frame-tree.js",
             "lib/fetch/bg/fetch.js",
             "extension/index.js",
@@ -34,6 +35,7 @@
                 "<all_urls>"
             ],
             "js": [
+                "lib/browser-polyfill/custom-browser-polyfill.js",
                 "extension/index.js",
                 "extension/ui/content/ui.js",
                 "lib/single-file/base64.js",
@@ -52,6 +54,7 @@
                 "<all_urls>"
             ],
             "js": [
+                "lib/browser-polyfill/custom-browser-polyfill.js",
                 "lib/frame-tree/content/frame-tree.js"
             ],
             "run_at": "document_start",
@@ -61,6 +64,7 @@
     "permissions": [
         "tabs",
         "contextMenus",
+        "menus",
         "storage",
         "http://*/*",
         "https://*/*"