1
0
Gildas 6 жил өмнө
parent
commit
82bd8674d7
49 өөрчлөгдсөн 648 нэмэгдсэн , 598 устгасан
  1. 18 17
      cli/back-ends/jsdom.js
  2. 11 10
      cli/back-ends/puppeteer.js
  3. 10 9
      cli/back-ends/webdriver-chromium.js
  4. 10 9
      cli/back-ends/webdriver-gecko.js
  5. 28 25
      extension/core/bg/autosave.js
  6. 30 26
      extension/core/bg/business.js
  7. 17 14
      extension/core/bg/config.js
  8. 2 2
      extension/core/bg/downloads.js
  9. 11 11
      extension/core/bg/messages.js
  10. 15 14
      extension/core/bg/tabs-data.js
  11. 9 8
      extension/core/bg/tabs.js
  12. 1 1
      extension/core/bg/util.js
  13. 21 15
      extension/core/content/content-bootstrap.js
  14. 36 28
      extension/core/content/content-main.js
  15. 14 13
      extension/ui/bg/ui-button.js
  16. 15 15
      extension/ui/bg/ui-main.js
  17. 62 54
      extension/ui/bg/ui-menu.js
  18. 5 5
      extension/ui/bg/ui-options.js
  19. 2 2
      extension/ui/content/content-ui-infobar.js
  20. 3 3
      extension/ui/content/content-ui-main.js
  21. 31 1
      index.js
  22. 5 4
      lib/fetch/bg/fetch-resources.js
  23. 2 3
      lib/fetch/content/content-fetch-resources.js
  24. 3 2
      lib/frame-tree/bg/frame-tree.js
  25. 15 12
      lib/frame-tree/content/content-frame-tree.js
  26. 3 3
      lib/hooks/content/content-hooks-frame.js
  27. 1 1
      lib/hooks/content/content-hooks-web.js
  28. 2 3
      lib/hooks/content/content-hooks.js
  29. 2 2
      lib/lazy/bg/lazy-timeout.js
  30. 6 3
      lib/lazy/content/content-lazy-loader.js
  31. 45 46
      lib/single-file/modules/css-fonts-alt-minifier.js
  32. 53 56
      lib/single-file/modules/css-fonts-minifier.js
  33. 8 10
      lib/single-file/modules/css-matched-rules.js
  34. 15 20
      lib/single-file/modules/css-medias-alt-minifier.js
  35. 33 36
      lib/single-file/modules/css-rules-minifier.js
  36. 20 25
      lib/single-file/modules/html-images-alt-minifier.js
  37. 1 1
      lib/single-file/modules/html-minifier.js
  38. 1 1
      lib/single-file/modules/html-serializer.js
  39. 1 1
      lib/single-file/single-file-core.js
  40. 4 4
      lib/single-file/single-file-helper.js
  41. 18 18
      lib/single-file/single-file-util.js
  42. 21 29
      lib/single-file/single-file.js
  43. 1 1
      lib/single-file/vendor/css-font-property-parser.js
  44. 1 1
      lib/single-file/vendor/css-media-query-parser.js
  45. 1 1
      lib/single-file/vendor/css-minifier.js
  46. 1 1
      lib/single-file/vendor/css-tree.js
  47. 1 1
      lib/single-file/vendor/html-srcset-parser.js
  48. 10 8
      maff2html/back-ends/webdriver-gecko.js
  49. 23 23
      manifest.json

+ 18 - 17
cli/back-ends/jsdom.js

@@ -32,9 +32,10 @@ const iconv = require("iconv-lite");
 const request = require("request-promise-native");
 
 const SCRIPTS = [
+	"../../index.js",
 	"../../lib/frame-tree/content/content-frame-tree.js",
-	"../../lib/single-file/util/doc-util.js",
-	"../../lib/single-file/util/doc-helper.js",
+	"../../lib/single-file/single-file-util.js",
+	"../../lib/single-file/single-file-helper.js",
 	"../../lib/single-file/vendor/css-tree.js",
 	"../../lib/single-file/vendor/html-srcset-parser.js",
 	"../../lib/single-file/vendor/css-minifier.js",
@@ -83,7 +84,7 @@ exports.getPageData = async options => {
 		const win = dom.window;
 		const doc = win.document;
 		let scripts = (await Promise.all(SCRIPTS.concat(options.browserScripts).map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString()))).join("\n");
-		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/hooks-web.js")) + "`;" + scripts;
+		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/content/content-hooks-web.js")) + "`;" + scripts;
 		dom.window.eval(scripts);
 		if (dom.window.document.readyState == "loading") {
 			await new Promise(resolve => win.document.onload = resolve);
@@ -91,7 +92,7 @@ exports.getPageData = async options => {
 		win.Element.prototype.getBoundingClientRect = undefined;
 		executeFrameScripts(doc, scripts);
 		if (!options.saveRawPage && !options.removeFrames) {
-			options.framesData = await win.frameTree.getAsync(options);
+			options.framesData = await win.singlefile.lib.frameTree.content.frames.getAsync(options);
 		}
 		options.win = win;
 		options.doc = doc;
@@ -121,26 +122,26 @@ function executeFrameScripts(doc, scripts) {
 }
 
 function getSingleFileClass(win) {
-	const docHelper = win.docHelper;
+	const helper = win.singlefile.lib.helper;
 	const modules = {
-		docHelper: docHelper,
-		srcsetParser: win.srcsetParser,
-		cssMinifier: win.cssMinifier,
-		htmlMinifier: win.htmlMinifier,
-		serializer: win.serializer,
-		fontsMinifier: win.fontsMinifier.getInstance(win.cssTree, win.fontPropertyParser, docHelper),
-		fontsAltMinifier: win.fontsAltMinifier.getInstance(win.cssTree),
-		cssRulesMinifier: win.cssRulesMinifier.getInstance(win.cssTree),
-		matchedRules: win.matchedRules.getInstance(win.cssTree),
-		mediasAltMinifier: win.mediasAltMinifier.getInstance(win.cssTree, win.mediaQueryParser),
-		imagesAltMinifier: win.imagesAltMinifier.getInstance(win.srcsetParser)
+		helper: helper,
+		srcsetParser: win.singlefile.lib.vendor.srcsetParser,
+		cssMinifier: win.singlefile.lib.vendor.cssMinifier,
+		htmlMinifier: win.singlefile.lib.modules.htmlMinifier,
+		serializer: win.singlefile.lib.modules.serializer,
+		fontsMinifier: win.singlefile.lib.modules.fontsMinifier,
+		fontsAltMinifier: win.singlefile.lib.modules.fontsAltMinifier,
+		cssRulesMinifier: win.singlefile.lib.modules.cssRulesMinifier,
+		matchedRules: win.singlefile.lib.modules.matchedRules,
+		mediasAltMinifier: win.singlefile.lib.modules.mediasAltMinifier,
+		imagesAltMinifier: win.singlefile.lib.modules.imagesAltMinifier
 	};
 	const domUtil = {
 		getResourceContent,
 		isValidFontUrl,
 		digestText
 	};
-	return win.SingleFileCore.getClass(win.docUtil.getInstance(modules, domUtil), win.cssTree);
+	return win.singlefile.lib.core.getClass(win.singlefile.lib.util.getInstance(modules, domUtil), win.singlefile.lib.vendor.cssTree);
 }
 
 async function digestText(algo, text) {

+ 11 - 10
cli/back-ends/puppeteer.js

@@ -21,18 +21,19 @@
  *   Source.
  */
 
-/* global require, exports, SingleFileBrowser, frameTree, lazyLoader, docHelper, document, window */
+/* global singlefile, require, exports, document, window */
 
 const fs = require("fs");
 
 const puppeteer = require("puppeteer-core");
 
 const SCRIPTS = [
-	"../../lib/hooks/hooks-frame.js",
+	"../../index.js",
+	"../../lib/hooks/content/content-hooks-frame.js",
 	"../../lib/frame-tree/content/content-frame-tree.js",
 	"../../lib/lazy/content/content-lazy-loader.js",
-	"../../lib/single-file/util/doc-util.js",
-	"../../lib/single-file/util/doc-helper.js",
+	"../../lib/single-file/single-file-util.js",
+	"../../lib/single-file/single-file-helper.js",
 	"../../lib/single-file/vendor/css-tree.js",
 	"../../lib/single-file/vendor/html-srcset-parser.js",
 	"../../lib/single-file/vendor/css-minifier.js",
@@ -47,7 +48,7 @@ const SCRIPTS = [
 	"../../lib/single-file/modules/html-images-alt-minifier.js",
 	"../../lib/single-file/modules/html-serializer.js",
 	"../../lib/single-file/single-file-core.js",
-	"../../lib/single-file/single-file-browser.js"
+	"../../lib/single-file/single-file.js"
 ];
 
 exports.getPageData = async options => {
@@ -86,7 +87,7 @@ exports.getPageData = async options => {
 			await page.setBypassCSP(true);
 		}
 		let scripts = SCRIPTS.concat(options.browserScripts).map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString()).join("\n");
-		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/hooks-web.js")) + "`;" + scripts;
+		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/content/content-hooks-web.js")) + "`;" + scripts;
 		await page.evaluateOnNewDocument(scripts);
 		if (options.browserDebug) {
 			await page.waitFor(3000);
@@ -96,22 +97,22 @@ exports.getPageData = async options => {
 			waitUntil: options.browserWaitUntil || "networkidle0"
 		});
 		return await page.evaluate(async options => {
-			docHelper.initDoc(document);
+			singlefile.lib.helper.initDoc(document);
 			options.insertSingleFileComment = true;
 			options.insertFaviconLink = true;
 			const preInitializationPromises = [];
 			if (!options.saveRawPage) {
 				if (!options.removeFrames) {
-					preInitializationPromises.push(frameTree.getAsync(options));
+					preInitializationPromises.push(singlefile.lib.frameTree.content.frames.getAsync(options));
 				}
 				if (options.loadDeferredImages) {
-					preInitializationPromises.push(lazyLoader.process(options));
+					preInitializationPromises.push(singlefile.lib.lazy.content.loader.process(options));
 				}
 			}
 			[options.framesData] = await Promise.all(preInitializationPromises);
 			options.doc = document;
 			options.win = window;
-			const SingleFile = SingleFileBrowser.getClass();
+			const SingleFile = singlefile.lib.getClass();
 			const singleFile = new SingleFile(options);
 			await singleFile.run();
 			return await singleFile.getPageData();

+ 10 - 9
cli/back-ends/webdriver-chromium.js

@@ -30,11 +30,12 @@ const chrome = require("selenium-webdriver/chrome");
 const { Builder } = require("selenium-webdriver");
 
 const SCRIPTS = [
-	"../../lib/hooks/hooks-frame.js",
+	"../../index.js",
+	"../../lib/hooks/content/content-hooks-frame.js",
 	"../../lib/frame-tree/content/content-frame-tree.js",
 	"../../lib/lazy/content/content-lazy-loader.js",
-	"../../lib/single-file/util/doc-util.js",
-	"../../lib/single-file/util/doc-helper.js",
+	"../../lib/single-file/single-file-util.js",
+	"../../lib/single-file/single-file-helper.js",
 	"../../lib/single-file/vendor/css-tree.js",
 	"../../lib/single-file/vendor/html-srcset-parser.js",
 	"../../lib/single-file/vendor/css-minifier.js",
@@ -49,7 +50,7 @@ const SCRIPTS = [
 	"../../lib/single-file/modules/html-images-alt-minifier.js",
 	"../../lib/single-file/modules/html-serializer.js",
 	"../../lib/single-file/single-file-core.js",
-	"../../lib/single-file/single-file-browser.js"
+	"../../lib/single-file/single-file.js"
 ];
 
 exports.getPageData = async options => {
@@ -107,7 +108,7 @@ exports.getPageData = async options => {
 			}
 		}
 		let scripts = SCRIPTS.concat(options.browserScripts).map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString()).join("\n");
-		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/hooks-web.js")) + "`;" + scripts;
+		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/content/content-hooks-web.js")) + "`;" + scripts;
 		if (options.browserDebug) {
 			await driver.sleep(3000);
 		}
@@ -158,22 +159,22 @@ function getPageDataScript() {
 		.catch(error => callback({ error: error.toString() }));
 
 	async function getPageData() {
-		docHelper.initDoc(document);
+		singlefile.lib.helper.initDoc(document);
 		options.insertSingleFileComment = true;
 		options.insertFaviconLink = true;
 		const preInitializationPromises = [];
 		if (!options.saveRawPage) {
 			if (!options.removeFrames) {
-				preInitializationPromises.push(frameTree.getAsync(options));
+				preInitializationPromises.push(singlefile.lib.frameTree.content.frames.getAsync(options));
 			}
 			if (options.loadDeferredImages) {
-				preInitializationPromises.push(lazyLoader.process(options));
+				preInitializationPromises.push(singlefile.lib.lazy.content.loader.process(options));
 			}
 		}
 		[options.framesData] = await Promise.all(preInitializationPromises);
 		options.doc = document;
 		options.win = window;
-		const SingleFile = SingleFileBrowser.getClass();
+		const SingleFile = singlefile.lib.getClass();
 		const singleFile = new SingleFile(options);
 		await singleFile.run();
 		return await singleFile.getPageData();

+ 10 - 9
cli/back-ends/webdriver-gecko.js

@@ -30,11 +30,12 @@ const firefox = require("selenium-webdriver/firefox");
 const { Builder, By, Key } = require("selenium-webdriver");
 
 const SCRIPTS = [
-	"../../lib/hooks/hooks-frame.js",
+	"../../index.js",
+	"../../lib/hooks/content/content-hooks-frame.js",
 	"../../lib/frame-tree/content/content-frame-tree.js",
 	"../../lib/lazy/content/content-lazy-loader.js",
-	"../../lib/single-file/util/doc-util.js",
-	"../../lib/single-file/util/doc-helper.js",
+	"../../lib/single-file/single-file-util.js",
+	"../../lib/single-file/single-file-helper.js",
 	"../../lib/single-file/vendor/css-tree.js",
 	"../../lib/single-file/vendor/html-srcset-parser.js",
 	"../../lib/single-file/vendor/css-minifier.js",
@@ -49,7 +50,7 @@ const SCRIPTS = [
 	"../../lib/single-file/modules/html-images-alt-minifier.js",
 	"../../lib/single-file/modules/html-serializer.js",
 	"../../lib/single-file/single-file-core.js",
-	"../../lib/single-file/single-file-browser.js"
+	"../../lib/single-file/single-file.js"
 ];
 
 exports.getPageData = async options => {
@@ -97,7 +98,7 @@ exports.getPageData = async options => {
 			}
 		}
 		let scripts = SCRIPTS.concat(options.browserScripts).map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString().replace(/\n(this)\.([^ ]+) = (this)\.([^ ]+) \|\|/g, "\nwindow.$2 = window.$4 ||")).join("\n");
-		scripts = "this.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/hooks-web.js")) + "`;" + scripts;
+		scripts = "window.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/content/content-hooks-web.js")) + "`;" + scripts;
 		if (options.browserDebug) {
 			await driver.findElement(By.css("html")).sendKeys(Key.SHIFT + Key.F5);
 			await driver.sleep(3000);
@@ -165,22 +166,22 @@ function getPageDataScript() {
 		.catch(error => callback({ error: error.toString() }));
 
 	async function getPageData() {
-		docHelper.initDoc(document);
+		singlefile.lib.helper.initDoc(document);
 		options.insertSingleFileComment = true;
 		options.insertFaviconLink = true;
 		const preInitializationPromises = [];
 		if (!options.saveRawPage) {
 			if (!options.removeFrames) {
-				preInitializationPromises.push(frameTree.getAsync(options));
+				preInitializationPromises.push(singlefile.lib.frameTree.content.frames.getAsync(options));
 			}
 			if (options.loadDeferredImages) {
-				preInitializationPromises.push(lazyLoader.process(options));
+				preInitializationPromises.push(singlefile.lib.lazy.content.loader.process(options));
 			}
 		}
 		[options.framesData] = await Promise.all(preInitializationPromises);
 		options.doc = document;
 		options.win = window;
-		const SingleFile = SingleFileBrowser.getClass();
+		const SingleFile = singlefile.lib.getClass();
 		const singleFile = new SingleFile(options);
 		await singleFile.run();
 		return await singleFile.getPageData();

+ 28 - 25
extension/core/bg/autosave.js

@@ -21,9 +21,9 @@
  *   Source.
  */
 
-/* global singlefile, SingleFileBrowser, URL, Blob */
+/* global singlefile, URL, Blob */
 
-singlefile.autosave = (() => {
+singlefile.extension.core.bg.autosave = (() => {
 
 	return {
 		onMessage,
@@ -35,7 +35,7 @@ singlefile.autosave = (() => {
 
 	async function onMessage(message, sender) {
 		if (message.method.endsWith(".init")) {
-			const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]);
+			const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]);
 			return { options, autoSaveEnabled };
 		}
 		if (message.method.endsWith(".save")) {
@@ -44,11 +44,12 @@ singlefile.autosave = (() => {
 	}
 
 	async function onMessageExternal(message, currentTab) {
+		const tabsData = singlefile.extension.core.bg.tabsData;
 		if (message.method == "enableAutoSave") {
-			const tabsData = await singlefile.tabsData.get(currentTab.id);
-			tabsData[currentTab.id].autoSave = message.enabled;
-			await singlefile.tabsData.set(tabsData);
-			singlefile.ui.refreshTab(currentTab);
+			const allTabsData = await tabsData.get(currentTab.id);
+			allTabsData[currentTab.id].autoSave = message.enabled;
+			await tabsData.set(allTabsData);
+			singlefile.extension.ui.bg.main.refreshTab(currentTab);
 		}
 		if (message.method == "isAutoSaveEnabled") {
 			return await isEnabled(currentTab);
@@ -56,35 +57,37 @@ singlefile.autosave = (() => {
 	}
 
 	async function onTabUpdated(tabId, changeInfo, tab) {
-		const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tab.url, true), isEnabled(tab)]);
+		const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(tab.url, true), isEnabled(tab)]);
 		if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) {
 			if (changeInfo.status == "complete") {
-				singlefile.core.saveTab(tab, { autoSave: true });
+				singlefile.extension.core.bg.business.saveTab(tab, { autoSave: true });
 			}
 		}
 	}
 
 	async function isEnabled(tab) {
+		const config = singlefile.extension.core.bg.config;
 		if (tab) {
-			const [tabsData, rule] = await Promise.all([singlefile.tabsData.get(), singlefile.config.getRule(tab.url)]);
-			return singlefile.util.isAllowedURL(tab.url) &&
-				Boolean(tabsData.autoSaveAll ||
-					(tabsData.autoSaveUnpinned && !tab.pinned) ||
-					(tabsData[tab.id] && tabsData[tab.id].autoSave)) &&
-				(!rule || rule.autoSaveProfile != singlefile.config.DISABLED_PROFILE_NAME);
+			const [allTabsData, rule] = await Promise.all([singlefile.extension.core.bg.tabsData.get(), config.getRule(tab.url)]);
+			return singlefile.extension.core.bg.util.isAllowedURL(tab.url) &&
+				Boolean(allTabsData.autoSaveAll ||
+					(allTabsData.autoSaveUnpinned && !tab.pinned) ||
+					(allTabsData[tab.id] && allTabsData[tab.id].autoSave)) &&
+				(!rule || rule.autoSaveProfile != config.DISABLED_PROFILE_NAME);
 		}
 	}
 
 	async function refreshTabs() {
-		const tabs = (await singlefile.tabs.get({})).filter(tab => singlefile.util.isAllowedURL(tab.url));
-		return Promise.all(tabs.map(async tab => {
-			const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tab.url, true), isEnabled(tab)]);
-			singlefile.tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options }).catch(() => { /* ignored */ });
+		const tabs = singlefile.extension.core.bg.tabs;
+		const allTabs = (await tabs.get({})).filter(tab => singlefile.extension.core.bg.util.isAllowedURL(tab.url));
+		return Promise.all(allTabs.map(async tab => {
+			const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(tab.url, true), isEnabled(tab)]);
+			tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options }).catch(() => { /* ignored */ });
 		}));
 	}
 
 	async function saveContent(message, tab) {
-		const options = await singlefile.config.getOptions(tab.url, true);
+		const options = await singlefile.extension.core.bg.config.getOptions(tab.url, true);
 		const tabId = tab.id;
 		options.content = message.content;
 		options.url = message.url;
@@ -108,20 +111,20 @@ singlefile.autosave = (() => {
 		options.onprogress = async event => {
 			if (event.type == event.RESOURCES_INITIALIZED) {
 				maxIndex = event.detail.max;
-				singlefile.ui.onProgress(tabId, index, maxIndex, { autoSave: true });
+				singlefile.extension.ui.bg.main.onProgress(tabId, index, maxIndex, { autoSave: true });
 			}
 			if (event.type == event.RESOURCE_LOADED) {
 				index++;
-				singlefile.ui.onProgress(tabId, index, maxIndex, { autoSave: true });
+				singlefile.extension.ui.bg.main.onProgress(tabId, index, maxIndex, { autoSave: true });
 			} else if (event.type == event.PAGE_ENDED) {
-				singlefile.ui.onEnd(tabId, { autoSave: true });
+				singlefile.extension.ui.bg.main.onEnd(tabId, { autoSave: true });
 			}
 		};
-		const processor = new (SingleFileBrowser.getClass())(options);
+		const processor = new (singlefile.lib.getClass())(options);
 		await processor.run();
 		const page = await processor.getPageData();
 		page.url = URL.createObjectURL(new Blob([page.content], { type: "text/html" }));
-		return singlefile.download.downloadPage(page, options);
+		return singlefile.extension.core.bg.downloads.downloadPage(page, options);
 	}
 
 })();

+ 30 - 26
extension/core/bg/core.js → extension/core/bg/business.js

@@ -23,31 +23,31 @@
 
 /* global browser, singlefile, fetch, TextDecoder */
 
-singlefile.core = (() => {
+singlefile.extension.core.bg.business = (() => {
 
 	let contentScript, frameScript, modulesScript;
 
 	const contentScriptFiles = [
-		"/lib/hooks/hooks.js",
+		"/index.js",
+		"/lib/hooks/content/content-hooks.js",
 		"/lib/browser-polyfill/chrome-browser-polyfill.js",
 		"/lib/single-file/vendor/css-tree.js",
 		"/lib/single-file/vendor/html-srcset-parser.js",
-		"/lib/single-file/util/doc-helper.js",
-		"/lib/single-file/util/doc-util.js",
-		"/lib/fetch/content/fetch.js",
+		"/lib/single-file/single-file-util.js",
+		"/lib/single-file/single-file-helper.js",
+		"/lib/fetch/content/content-fetch-resources.js",
 		"/lib/single-file/single-file-core.js",
-		"/lib/single-file/single-file-browser.js",
-		"/extension/index.js",
-		"/extension/ui/content/content-ui.js",
-		"/extension/core/content/content.js"
+		"/lib/single-file/single-file.js",
+		"/extension/ui/content/content-ui-main.js",
+		"/extension/core/content/content-main.js"
 	];
 
 	const frameScriptFiles = [
-		"/lib/hooks/hooks-frame.js",
+		"/index.js",
+		"/lib/hooks/content/content-hooks-frame.js",
 		"/lib/browser-polyfill/chrome-browser-polyfill.js",
-		"/extension/index.js",
-		"/lib/single-file/util/doc-helper.js",
-		"/lib/fetch/content/fetch.js",
+		"/lib/single-file/single-file-helper.js",
+		"/lib/fetch/content/content-fetch-resources.js",
 		"/lib/frame-tree/content/content-frame-tree.js"
 	];
 
@@ -71,49 +71,53 @@ singlefile.core = (() => {
 	return { saveTab };
 
 	async function saveTab(tab, options = {}) {
-		if (singlefile.util.isAllowedURL(tab.url)) {
+		const config = singlefile.extension.core.bg.config;
+		const autosave = singlefile.extension.core.bg.autosave;
+		const tabs = singlefile.extension.core.bg.tabs;
+		const ui = singlefile.extension.ui.bg.main;
+		if (singlefile.extension.core.bg.util.isAllowedURL(tab.url)) {
 			await initScripts();
 			const tabId = tab.id;
 			options.tabId = tabId;
 			options.tabIndex = tab.index;
 			try {
 				if (options.autoSave) {
-					const options = await singlefile.config.getOptions(tab.url, true);
-					if (singlefile.autosave.isEnabled(tab)) {
-						await singlefile.tabs.sendMessage(tabId, { method: "content.autosave", options });
+					const options = await config.getOptions(tab.url, true);
+					if (autosave.isEnabled(tab)) {
+						await tabs.sendMessage(tabId, { method: "content.autosave", options });
 					}
 				} else {
-					singlefile.ui.onInitialize(tabId, options, 1);
-					const mergedOptions = await singlefile.config.getOptions(tab.url);
+					ui.onInitialize(tabId, options, 1);
+					const mergedOptions = await config.getOptions(tab.url);
 					Object.keys(options).forEach(key => mergedOptions[key] = options[key]);
 					let scriptsInjected;
 					if (!mergedOptions.removeFrames) {
 						try {
-							await browser.tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
+							await tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
 						} catch (error) {
 							// ignored
 						}
 					}
 					try {
 						await initScripts();
-						await browser.tabs.executeScript(tabId, { code: modulesScript + "\n" + contentScript, allFrames: false, runAt: "document_idle" });
+						await tabs.executeScript(tabId, { code: modulesScript + "\n" + contentScript, allFrames: false, runAt: "document_idle" });
 						scriptsInjected = true;
 					} catch (error) {
 						// ignored
 					}
 					if (scriptsInjected) {
-						singlefile.ui.onInitialize(tabId, options, 2);
+						ui.onInitialize(tabId, options, 2);
 						if (mergedOptions.frameId) {
-							await browser.tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: mergedOptions.frameId, matchAboutBlank: true, runAt: "document_start" });
+							await tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: mergedOptions.frameId, matchAboutBlank: true, runAt: "document_start" });
 						}
-						await singlefile.tabs.sendMessage(tabId, { method: "content.save", options: mergedOptions });
+						await tabs.sendMessage(tabId, { method: "content.save", options: mergedOptions });
 					} else {
-						singlefile.ui.onForbiddenDomain(tab, options);
+						ui.onForbiddenDomain(tab, options);
 					}
 				}
 			} catch (error) {
 				console.log(error); // eslint-disable-line no-console
-				singlefile.ui.onError(tabId, options);
+				ui.onError(tabId, options);
 			}
 		}
 	}

+ 17 - 14
extension/core/bg/data/config.js → extension/core/bg/config.js

@@ -23,7 +23,7 @@
 
 /* global browser, singlefile, URL, Blob */
 
-singlefile.config = (() => {
+singlefile.extension.core.bg.config = (() => {
 
 	const CURRENT_PROFILE_NAME = "-";
 	const DEFAULT_PROFILE_NAME = "__Default_Settings__";
@@ -211,8 +211,8 @@ singlefile.config = (() => {
 	}
 
 	async function getOptions(url, autoSave) {
-		const [config, rule, tabsData] = await Promise.all([getConfig(), getRule(url), singlefile.tabsData.get()]);
-		const tabProfileName = tabsData.profileName || DEFAULT_PROFILE_NAME;
+		const [config, rule, allTabsData] = await Promise.all([getConfig(), getRule(url), singlefile.extension.core.bg.tabsData.get()]);
+		const tabProfileName = allTabsData.profileName || DEFAULT_PROFILE_NAME;
 		if (rule) {
 			const profileName = rule[autoSave ? "autoSaveProfile" : "profile"];
 			return config.profiles[profileName == CURRENT_PROFILE_NAME ? tabProfileName : profileName];
@@ -231,7 +231,8 @@ singlefile.config = (() => {
 	}
 
 	async function renameProfile(oldProfileName, profileName) {
-		const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
+		const tabsData = singlefile.extension.core.bg.tabsData;
+		const [config, allTabsData] = await Promise.all([getConfig(), tabsData.get()]);
 		if (!Object.keys(config.profiles).includes(oldProfileName)) {
 			throw new Error("Profile not found");
 		}
@@ -241,9 +242,9 @@ singlefile.config = (() => {
 		if (oldProfileName == DEFAULT_PROFILE_NAME) {
 			throw new Error("Default settings cannot be renamed");
 		}
-		if (tabsData.profileName == oldProfileName) {
-			tabsData.profileName = profileName;
-			await singlefile.tabsData.set(tabsData);
+		if (allTabsData.profileName == oldProfileName) {
+			allTabsData.profileName = profileName;
+			await tabsData.set(allTabsData);
 		}
 		config.profiles[profileName] = config.profiles[oldProfileName];
 		config.rules.forEach(rule => {
@@ -259,16 +260,17 @@ singlefile.config = (() => {
 	}
 
 	async function deleteProfile(profileName) {
-		const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
+		const tabsData = singlefile.extension.core.bg.tabsData;
+		const [config, allTabsData] = await Promise.all([getConfig(), tabsData.get()]);
 		if (!Object.keys(config.profiles).includes(profileName)) {
 			throw new Error("Profile not found");
 		}
 		if (profileName == DEFAULT_PROFILE_NAME) {
 			throw new Error("Default settings cannot be deleted");
 		}
-		if (tabsData.profileName == profileName) {
-			delete tabsData.profileName;
-			await singlefile.tabsData.set(tabsData);
+		if (allTabsData.profileName == profileName) {
+			delete allTabsData.profileName;
+			await tabsData.set(allTabsData);
 		}
 		config.rules.forEach(rule => {
 			if (rule.profile == profileName) {
@@ -337,10 +339,11 @@ singlefile.config = (() => {
 	}
 
 	async function resetProfiles() {
+		const tabsData = singlefile.extension.core.bg.tabsData;
 		await pendingUpgradePromise;
-		const tabsData = await singlefile.tabsData.get();
-		delete tabsData.profileName;
-		await singlefile.tabsData.set(tabsData);
+		const allTabsData = await tabsData.get();
+		delete allTabsData.profileName;
+		await tabsData.set(allTabsData);
 		await browser.storage.local.remove(["profiles", "rules"]);
 		await browser.storage.local.set({ profiles: { [DEFAULT_PROFILE_NAME]: DEFAULT_CONFIG }, rules: [] });
 	}

+ 2 - 2
extension/core/bg/download.js → extension/core/bg/downloads.js

@@ -23,7 +23,7 @@
 
 /* global browser, singlefile, Blob, URL, document */
 
-singlefile.download = (() => {
+singlefile.extension.core.bg.downloads = (() => {
 
 	const partialContents = new Map();
 
@@ -62,7 +62,7 @@ singlefile.download = (() => {
 					return await downloadPage(message, { confirmFilename: message.confirmFilename, incognito: tab.incognito, filenameConflictAction: message.filenameConflictAction });
 				} catch (error) {
 					console.error(error); // eslint-disable-line no-console
-					singlefile.ui.onError(sender.tab.id, {});
+					singlefile.extension.ui.bg.main.onError(sender.tab.id, {});
 					return {};
 				} finally {
 					URL.revokeObjectURL(message.url);

+ 11 - 11
extension/core/bg/messages.js

@@ -23,34 +23,34 @@
 
 /* global browser, singlefile, */
 
-singlefile.messages = (() => {
+singlefile.extension.core.bg.messages = (() => {
 
 	browser.runtime.onMessage.addListener((message, sender) => {
 		if (message.method.startsWith("tabs.")) {
-			return singlefile.tabs.onMessage(message, sender);
+			return singlefile.extension.core.bg.tabs.onMessage(message, sender);
 		}
 		if (message.method.startsWith("downloads.")) {
-			return singlefile.download.onMessage(message, sender);
+			return singlefile.extension.core.bg.downloads.onMessage(message, sender);
 		}
 		if (message.method.startsWith("autosave.")) {
-			return singlefile.autosave.onMessage(message, sender);
+			return singlefile.extension.core.bg.autosave.onMessage(message, sender);
 		}
 		if (message.method.startsWith("ui.")) {
-			return singlefile.ui.onMessage(message, sender);
+			return singlefile.extension.ui.bg.main.onMessage(message, sender);
 		}
 		if (message.method.startsWith("config.")) {
-			return singlefile.config.onMessage(message, sender);
+			return singlefile.extension.core.bg.config.onMessage(message, sender);
 		}
 		if (message.method.startsWith("tabsData.")) {
-			return singlefile.tabsData.onMessage(message, sender);
+			return singlefile.extension.core.bg.tabsData.onMessage(message, sender);
 		}
 	});
 	if (browser.runtime.onMessageExternal) {
 		browser.runtime.onMessageExternal.addListener(async (message, sender) => {
-			const tabs = await singlefile.tabs.get({ currentWindow: true, active: true });
-			const currentTab = tabs[0];
-			if (currentTab && singlefile.util.isAllowedURL(currentTab.url)) {
-				return singlefile.autosave.onMessageExternal(message, currentTab, sender);
+			const allTabs = await singlefile.extension.core.bg.tabs.get({ currentWindow: true, active: true });
+			const currentTab = allTabs[0];
+			if (currentTab && singlefile.extension.core.bg.util.isAllowedURL(currentTab.url)) {
+				return singlefile.extension.core.bg.autosave.onMessageExternal(message, currentTab, sender);
 			} else {
 				return false;
 			}

+ 15 - 14
extension/core/bg/data/tabs-data.js → extension/core/bg/tabs-data.js

@@ -21,12 +21,12 @@
  *   Source.
  */
 
-/* global browser, singlefile */
+/* global browser, singlefile, setTimeout */
 
-singlefile.tabsData = (() => {
+singlefile.extension.core.bg.tabsData = (() => {
 
-	let persistentData, temporaryData;
-	getPersistent().then(tabsData => persistentData = tabsData);
+	let persistentData, temporaryData, cleanedUp;
+	setTimeout(() => getPersistent().then(allTabsData => persistentData = allTabsData), 0);
 	return {
 		onMessage,
 		onTabRemoved,
@@ -48,9 +48,9 @@ singlefile.tabsData = (() => {
 		if (temporaryData) {
 			delete temporaryData[tabId];
 		}
-		const tabsData = await getPersistent();
-		delete tabsData[tabId];
-		setPersistent(tabsData);
+		const allTabsData = await getPersistent();
+		delete allTabsData[tabId];
+		setPersistent(allTabsData);
 	}
 
 	function getTemporary(desiredTabId) {
@@ -67,25 +67,26 @@ singlefile.tabsData = (() => {
 		if (!persistentData) {
 			const config = await browser.storage.local.get();
 			persistentData = config.tabsData || {};
-			cleanup();
 		}
+		cleanup();
 		if (desiredTabId !== undefined && !persistentData[desiredTabId]) {
 			persistentData[desiredTabId] = {};
 		}
 		return persistentData;
 	}
 
-	async function setPersistent(tabsData) {
-		persistentData = tabsData;
-		await browser.storage.local.set({ tabsData });
+	async function setPersistent(allTabsData) {
+		persistentData = allTabsData;
+		await browser.storage.local.set({ tabsData: allTabsData });
 	}
 
 	async function cleanup() {
-		if (persistentData) {
-			const tabs = await browser.tabs.query({});
+		if (!cleanedUp && singlefile.extension.core.bg.tabs) {
+			cleanedUp = true;
+			const allTabs = await singlefile.extension.core.bg.tabs.get({ currentWindow: true, highlighted: true });
 			Object.keys(persistentData).filter(key => {
 				if (key != "autoSaveAll" && key != "autoSaveUnpinned" && key != "profileName") {
-					return !tabs.find(tab => tab.id == key);
+					return !allTabs.find(tab => tab.id == key);
 				}
 			}).forEach(tabId => delete persistentData[tabId]);
 			await browser.storage.local.set({ tabsData: persistentData });

+ 9 - 8
extension/core/bg/tabs.js

@@ -22,7 +22,7 @@
  */
 /* global browser, singlefile */
 
-singlefile.tabs = (() => {
+singlefile.extension.core.bg.tabs = (() => {
 
 	browser.tabs.onCreated.addListener(tab => onTabCreated(tab));
 	browser.tabs.onActivated.addListener(activeInfo => onTabActivated(activeInfo));
@@ -31,31 +31,32 @@ singlefile.tabs = (() => {
 	return {
 		onMessage,
 		get: options => browser.tabs.query(options),
-		sendMessage: (tabId, message, options) => browser.tabs.sendMessage(tabId, message, options)
+		sendMessage: (tabId, message, options) => browser.tabs.sendMessage(tabId, message, options),
+		executeScript: (tabId, scriptData) => browser.tabs.executeScript(tabId, scriptData)
 	};
 
 	async function onMessage(message) {
 		if (message.method.endsWith(".getOptions")) {
-			return singlefile.config.getOptions(message.url);
+			return singlefile.extension.core.bg.config.getOptions(message.url);
 		}
 	}
 
 	function onTabCreated(tab) {
-		singlefile.ui.onTabCreated(tab);
+		singlefile.extension.ui.bg.main.onTabCreated(tab);
 	}
 
 	async function onTabActivated(activeInfo) {
 		const tab = await browser.tabs.get(activeInfo.tabId);
-		singlefile.ui.onTabActivated(tab, activeInfo);
+		singlefile.extension.ui.bg.main.onTabActivated(tab, activeInfo);
 	}
 
 	function onTabUpdated(tabId, changeInfo, tab) {
-		singlefile.autosave.onTabUpdated(tabId, changeInfo, tab);
-		singlefile.ui.onTabUpdated(tabId, changeInfo, tab);
+		singlefile.extension.core.bg.autosave.onTabUpdated(tabId, changeInfo, tab);
+		singlefile.extension.ui.bg.main.onTabUpdated(tabId, changeInfo, tab);
 	}
 
 	function onTabRemoved(tabId) {
-		singlefile.tabsData.onTabRemoved(tabId);
+		singlefile.extension.core.bg.tabsData.onTabRemoved(tabId);
 	}
 
 })();

+ 1 - 1
extension/core/bg/util.js

@@ -23,7 +23,7 @@
 
 /* global singlefile */
 
-singlefile.util = (() => {
+singlefile.extension.core.bg.util = (() => {
 
 	const FORBIDDEN_DOMAINS = [
 		"chrome.google.com/webstore/",

+ 21 - 15
extension/core/content/content-bootstrap.js

@@ -21,11 +21,13 @@
  *   Source.
  */
 
-/* global singlefile, frameTree, browser, window, addEventListener, removeEventListener, document, location, docHelper, setTimeout */
+/* global browser, window, addEventListener, removeEventListener, document, location, setTimeout */
 
-this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
+this.singlefile.extension.core.content.bootstrap = this.singlefile.extension.core.content.bootstrap || (async () => {
 
-	let unloadListenerAdded, options, autoSaveEnabled, autoSaveTimeout, autoSavingPage;
+	const singlefile = this.singlefile;
+
+	let unloadListenerAdded, options, autoSaveEnabled, autoSaveTimeout, autoSavingPage, pageAutoSaved;
 	browser.runtime.sendMessage({ method: "autosave.init" }).then(message => {
 		options = message.options;
 		autoSaveEnabled = message.autoSaveEnabled;
@@ -53,22 +55,24 @@ this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
 	}
 
 	async function autoSavePage() {
-		if ((!autoSavingPage || autoSaveTimeout) && !singlefile.pageAutoSaved) {
+		const helper = singlefile.lib.helper;
+		const frames = singlefile.lib.frameTree.content.frames;
+		if ((!autoSavingPage || autoSaveTimeout) && !pageAutoSaved) {
 			autoSavingPage = true;
 			if (options.autoSaveDelay && !autoSaveTimeout) {
 				autoSaveTimeout = setTimeout(() => {
 					autoSavePage();
 				}, options.autoSaveDelay * 1000);
 			} else {
-				const docData = docHelper.preProcessDoc(document, window, options);
+				const docData = helper.preProcessDoc(document, window, options);
 				let framesData = [];
 				autoSaveTimeout = null;
-				if (!options.removeFrames && this.frameTree) {
-					framesData = await frameTree.getAsync(options);
+				if (!options.removeFrames && frames) {
+					framesData = await frames.getAsync(options);
 				}
 				browser.runtime.sendMessage({
 					method: "autosave.save",
-					content: docHelper.serialize(document, false),
+					content: helper.serialize(document, false),
 					canvasData: docData.canvasData,
 					fontsData: docData.fontsData,
 					stylesheetsData: docData.stylesheetsData,
@@ -80,8 +84,8 @@ this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
 					framesData,
 					url: location.href
 				});
-				docHelper.postProcessDoc(document, options);
-				singlefile.pageAutoSaved = true;
+				helper.postProcessDoc(document, options);
+				pageAutoSaved = true;
 				autoSavingPage = false;
 			}
 		}
@@ -102,15 +106,16 @@ this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
 	}
 
 	function onUnload() {
-		if (!singlefile.pageAutoSaved || options.autoSaveUnload) {
-			const docData = docHelper.preProcessDoc(document, window, options);
+		const helper = singlefile.lib.helper;
+		if (!pageAutoSaved || options.autoSaveUnload) {
+			const docData = helper.preProcessDoc(document, window, options);
 			let framesData = [];
-			if (this.frameTree && !options.removeFrames) {
-				framesData = frameTree.getSync(options);
+			if (!options.removeFrames && singlefile.lib.frameTree.content.frames) {
+				framesData = singlefile.lib.frameTree.content.frames.getSync(options);
 			}
 			browser.runtime.sendMessage({
 				method: "autosave.save",
-				content: docHelper.serialize(document),
+				content: helper.serialize(document),
 				canvasData: docData.canvasData,
 				fontsData: docData.fontsData,
 				stylesheetsData: docData.stylesheetsData,
@@ -124,4 +129,5 @@ this.singlefile.bootstrap = this.singlefile.bootstrap || (async () => {
 			});
 		}
 	}
+
 })();

+ 36 - 28
extension/core/content/content.js → extension/core/content/content-main.js

@@ -21,29 +21,36 @@
  *   Source.
  */
 
-/* global browser, SingleFileBrowser, singlefile, frameTree, document, window, lazyLoader, setTimeout, docHelper */
+/* global browser, document, window, setTimeout */
 
-this.singlefile.top = this.singlefile.top || (() => {
+this.singlefile.extension.core.content.main = this.singlefile.extension.core.content.main || (() => {
+
+	const singlefile = this.singlefile;
+
+	let ui;
 
 	const MAX_CONTENT_SIZE = 64 * (1024 * 1024);
 	const DOWNLOADER_FRAME_ID = "single-file-downloader";
-	const SingleFile = SingleFileBrowser.getClass();
+	const SingleFile = singlefile.lib.getClass();
 
 	let processing = false;
 
 	browser.runtime.onMessage.addListener(message => {
+		if (!ui) {
+			ui = singlefile.extension.ui.content.main;
+		}
 		if (message.method == "content.save") {
 			savePage(message);
 		}
 	});
-	return true;
+	return {};
 
 	async function savePage(message) {
 		const options = message.options;
 		if (!processing) {
 			let selectionFound;
 			if (options.selected) {
-				selectionFound = await singlefile.ui.markSelection();
+				selectionFound = await ui.markSelection();
 			}
 			if (!options.selected || selectionFound) {
 				processing = true;
@@ -62,31 +69,32 @@ this.singlefile.top = this.singlefile.top || (() => {
 	}
 
 	async function processPage(options) {
-		docHelper.initDoc(document);
+		const frames = singlefile.lib.frameTree.content.frames;
+		singlefile.lib.helper.initDoc(document);
 		const iframe = document.getElementById(DOWNLOADER_FRAME_ID);
 		if (iframe) {
 			iframe.remove();
 		}
-		singlefile.ui.onStartPage(options);
+		ui.onStartPage(options);
 		const processor = new SingleFile(options);
 		const preInitializationPromises = [];
 		options.insertSingleFileComment = true;
 		if (!options.saveRawPage) {
-			if (!options.removeFrames && this.frameTree) {
+			if (!options.removeFrames && frames) {
 				let frameTreePromise;
 				if (options.loadDeferredImages) {
-					frameTreePromise = new Promise(resolve => setTimeout(() => resolve(frameTree.getAsync(options)), options.loadDeferredImagesMaxIdleTime - frameTree.TIMEOUT_INIT_REQUEST_MESSAGE));
+					frameTreePromise = new Promise(resolve => setTimeout(() => resolve(frames.getAsync(options)), options.loadDeferredImagesMaxIdleTime - frames.TIMEOUT_INIT_REQUEST_MESSAGE));
 				} else {
-					frameTreePromise = frameTree.getAsync(options);
+					frameTreePromise = frames.getAsync(options);
 				}
-				singlefile.ui.onLoadingFrames();
-				frameTreePromise.then(() => singlefile.ui.onLoadFrames());
+				ui.onLoadingFrames();
+				frameTreePromise.then(() => ui.onLoadFrames());
 				preInitializationPromises.push(frameTreePromise);
 			}
 			if (options.loadDeferredImages) {
-				const lazyLoadPromise = lazyLoader.process(options);
-				singlefile.ui.onLoadingDeferResources();
-				lazyLoadPromise.then(() => singlefile.ui.onLoadDeferResources());
+				const lazyLoadPromise = singlefile.lib.lazy.content.loader.process(options);
+				ui.onLoadingDeferResources();
+				lazyLoadPromise.then(() => ui.onLoadDeferResources());
 				preInitializationPromises.push(lazyLoadPromise);
 			}
 		}
@@ -100,26 +108,26 @@ this.singlefile.top = this.singlefile.top || (() => {
 					index++;
 				}
 				browser.runtime.sendMessage({ method: "ui.processProgress", index, maxIndex, options: {} });
-				singlefile.ui.onLoadResource(index, maxIndex, options);
+				ui.onLoadResource(index, maxIndex, options);
 			} if (event.type == event.PAGE_ENDED) {
 				browser.runtime.sendMessage({ method: "ui.processEnd", options: {} });
 			} else if (!event.detail.frame) {
 				if (event.type == event.PAGE_LOADING) {
-					singlefile.ui.onPageLoading();
+					ui.onPageLoading();
 				} else if (event.type == event.PAGE_LOADED) {
-					singlefile.ui.onLoadPage();
+					ui.onLoadPage();
 				} else if (event.type == event.STAGE_STARTED) {
 					if (event.detail.step < 3) {
-						singlefile.ui.onStartStage(event.detail.step);
+						ui.onStartStage(event.detail.step);
 					}
 				} else if (event.type == event.STAGE_ENDED) {
 					if (event.detail.step < 3) {
-						singlefile.ui.onEndStage(event.detail.step);
+						ui.onEndStage(event.detail.step);
 					}
 				} else if (event.type == event.STAGE_TASK_STARTED) {
-					singlefile.ui.onStartStageTask(event.detail.step, event.detail.task);
+					ui.onStartStageTask(event.detail.step, event.detail.task);
 				} else if (event.type == event.STAGE_TASK_ENDED) {
-					singlefile.ui.onEndStageTask(event.detail.step, event.detail.task);
+					ui.onEndStageTask(event.detail.step, event.detail.task);
 				}
 			}
 		};
@@ -140,17 +148,17 @@ this.singlefile.top = this.singlefile.top || (() => {
 			options.doc = document;
 		}
 		await processor.run();
-		if (!options.saveRawPage && !options.removeFrames && this.frameTree) {
-			this.frameTree.cleanup(options);
+		if (!options.saveRawPage && !options.removeFrames && frames) {
+			frames.cleanup(options);
 		}
 		if (options.confirmInfobarContent) {
-			options.infobarContent = singlefile.ui.prompt("Infobar content", options.infobarContent) || "";
+			options.infobarContent = ui.prompt("Infobar content", options.infobarContent) || "";
 		}
 		const page = await processor.getPageData();
 		if (options.selected) {
-			singlefile.ui.unmarkSelection();
+			ui.unmarkSelection();
 		}
-		singlefile.ui.onEndPage(options);
+		ui.onEndPage(options);
 		if (options.displayStats) {
 			console.log("SingleFile stats"); // eslint-disable-line no-console
 			console.table(page.stats); // eslint-disable-line no-console
@@ -182,7 +190,7 @@ this.singlefile.top = this.singlefile.top || (() => {
 
 	function downloadPageForeground(page, options) {
 		if (options.confirmFilename) {
-			page.filename = singlefile.ui.prompt("File name", page.filename);
+			page.filename = ui.prompt("File name", page.filename);
 		}
 		if (page.filename && page.filename.length) {
 			const iframe = document.createElement("iframe");

+ 14 - 13
extension/ui/bg/ui-button.js

@@ -23,7 +23,7 @@
 
 /* global browser, singlefile */
 
-singlefile.ui.button = (() => {
+singlefile.extension.ui.bg.button = (() => {
 
 	const DEFAULT_ICON_PATH = "/extension/ui/resources/icon_128.png";
 	const WAIT_ICON_PATH_PREFIX = "/extension/ui/resources/icon_128_wait";
@@ -40,11 +40,12 @@ singlefile.ui.button = (() => {
 	const DEFAULT_COLOR = [2, 147, 20, 192];
 
 	browser.browserAction.onClicked.addListener(async tab => {
-		const tabs = await singlefile.tabs.get({ currentWindow: true, highlighted: true });
-		if (!tabs.length) {
-			singlefile.core.saveTab(tab);
+		const business = singlefile.extension.core.bg.business;
+		const allTabs = await singlefile.extension.core.bg.tabs.get({ currentWindow: true, highlighted: true });
+		if (!allTabs.length) {
+			business.saveTab(tab);
 		} else {
-			tabs.forEach(tab => (tab.active || tab.highlighted) && singlefile.core.saveTab(tab));
+			allTabs.forEach(tab => (tab.active || tab.highlighted) && business.saveTab(tab));
 		}
 	});
 
@@ -114,7 +115,7 @@ singlefile.ui.button = (() => {
 	}
 
 	function onForbiddenDomain(tab, options) {
-		if (singlefile.util.isAllowedProtocol(tab.url)) {
+		if (singlefile.extension.core.bg.util.isAllowedProtocol(tab.url)) {
 			refresh(tab.id, getProperties(options, BUTTON_BLOCKED_BADGE_MESSAGE, [255, 255, 255, 1], BUTTON_BLOCKED_TOOLTIP_MESSAGE));
 		}
 	}
@@ -135,9 +136,9 @@ singlefile.ui.button = (() => {
 	}
 
 	async function refreshTab(tab) {
-		const options = { autoSave: await singlefile.autosave.isEnabled(tab) };
+		const options = { autoSave: await singlefile.extension.core.bg.autosave.isEnabled(tab) };
 		const properties = getCurrentProperties(tab.id, options);
-		if (singlefile.util.isAllowedURL(tab.url)) {
+		if (singlefile.extension.core.bg.util.isAllowedURL(tab.url)) {
 			await refresh(tab.id, properties);
 		} else {
 			try {
@@ -152,8 +153,8 @@ singlefile.ui.button = (() => {
 		if (options.autoSave) {
 			return getProperties(options);
 		} else {
-			const tabsData = singlefile.tabsData.getTemporary(tabId);
-			delete tabsData[tabId].button;
+			const allTabsData = singlefile.extension.core.bg.tabsData.getTemporary(tabId);
+			delete allTabsData[tabId].button;
 			return getProperties(options);
 		}
 	}
@@ -168,9 +169,9 @@ singlefile.ui.button = (() => {
 	}
 
 	async function refresh(tabId, tabData) {
-		const tabsData = singlefile.tabsData.getTemporary(tabId);
-		const oldTabData = tabsData[tabId].button || {};
-		tabsData[tabId].button = tabData;
+		const allTabsData = singlefile.extension.core.bg.tabsData.getTemporary(tabId);
+		const oldTabData = allTabsData[tabId].button || {};
+		allTabsData[tabId].button = tabData;
 		if (!tabData.pendingRefresh) {
 			tabData.pendingRefresh = Promise.resolve();
 		}

+ 15 - 15
extension/ui/bg/bg-ui.js → extension/ui/bg/ui-main.js

@@ -23,45 +23,45 @@
 
 /* global singlefile */
 
-singlefile.ui = (() => {
+singlefile.extension.ui.bg.main = (() => {
 
 	return {
 		onMessage(message, sender) {
 			if (message.method.endsWith(".refreshMenu")) {
-				return singlefile.ui.menu.onMessage(message, sender);
+				return singlefile.extension.ui.bg.menu.onMessage(message, sender);
 			} else {
-				return singlefile.ui.button.onMessage(message, sender);
+				return singlefile.extension.ui.bg.button.onMessage(message, sender);
 			}
 		},
 		async refreshTab(tab) {
-			return Promise.all([singlefile.ui.menu.refreshTab(tab), singlefile.ui.button.refreshTab(tab)]);
+			return Promise.all([singlefile.extension.ui.bg.menu.refreshTab(tab), singlefile.extension.ui.bg.button.refreshTab(tab)]);
 		},
 		onForbiddenDomain(tab, options) {
-			singlefile.ui.button.onForbiddenDomain(tab, options);
+			singlefile.extension.ui.bg.button.onForbiddenDomain(tab, options);
 		},
 		onInitialize(tabId, options, step) {
-			singlefile.ui.button.onInitialize(tabId, options, step);
+			singlefile.extension.ui.bg.button.onInitialize(tabId, options, step);
 		},
 		onProgress(tabId, index, maxIndex, options) {
-			singlefile.ui.button.onProgress(tabId, index, maxIndex, options);
+			singlefile.extension.ui.bg.button.onProgress(tabId, index, maxIndex, options);
 		},
 		onError(tabId, options) {
-			singlefile.ui.button.onError(tabId, options);
+			singlefile.extension.ui.bg.button.onError(tabId, options);
 		},
 		onEnd(tabId, options) {
-			singlefile.ui.button.onEnd(tabId, options);
+			singlefile.extension.ui.bg.button.onEnd(tabId, options);
 		},
 		onTabCreated(tab) {
-			singlefile.ui.button.onTabCreated(tab);
-			singlefile.ui.menu.onTabCreated(tab);
+			singlefile.extension.ui.bg.button.onTabCreated(tab);
+			singlefile.extension.ui.bg.menu.onTabCreated(tab);
 		},
 		onTabActivated(tab, activeInfo) {
-			singlefile.ui.menu.onTabActivated(tab, activeInfo);
-			singlefile.ui.button.onTabActivated(tab);
+			singlefile.extension.ui.bg.menu.onTabActivated(tab, activeInfo);
+			singlefile.extension.ui.bg.button.onTabActivated(tab);
 		},
 		onTabUpdated(tabId, changeInfo, tab) {
-			singlefile.ui.menu.onTabUpdated(tabId, changeInfo, tab);
-			singlefile.ui.button.onTabUpdated(tabId, changeInfo, tab);
+			singlefile.extension.ui.bg.menu.onTabUpdated(tabId, changeInfo, tab);
+			singlefile.extension.ui.bg.button.onTabUpdated(tabId, changeInfo, tab);
 		}
 	};
 

+ 62 - 54
extension/ui/bg/ui-menu.js

@@ -23,7 +23,7 @@
 
 /* global browser, singlefile, URL */
 
-singlefile.ui.menu = (() => {
+singlefile.extension.ui.bg.menu = (() => {
 
 	const menus = browser.menus || browser.contextMenus;
 	const BROWSER_MENUS_API_SUPPORTED = menus && menus.onClicked && menus.create && menus.update && menus.removeAll;
@@ -86,8 +86,10 @@ singlefile.ui.menu = (() => {
 	}
 
 	async function createMenus(tab) {
-		const [profiles, tabsData] = await Promise.all([singlefile.config.getProfiles(), singlefile.tabsData.get()]);
-		const options = await singlefile.config.getOptions(tab && tab.url, true);
+		const config = singlefile.extension.core.bg.config;
+		const tabsData = singlefile.extension.core.bg.tabsData;
+		const [profiles, allTabsData] = await Promise.all([config.getProfiles(), tabsData.get()]);
+		const options = await config.getOptions(tab && tab.url, true);
 		if (BROWSER_MENUS_API_SUPPORTED && options) {
 			const pageContextsEnabled = ["page", "frame", "image", "link", "video", "audio"];
 			const defaultContextsDisabled = ["browser_action"];
@@ -168,7 +170,7 @@ singlefile.ui.menu = (() => {
 					contexts: defaultContexts,
 				});
 				const defaultProfileId = MENU_ID_SELECT_PROFILE_PREFIX + "default";
-				const defaultProfileChecked = !tabsData.profileName || tabsData.profileName == singlefile.config.DEFAULT_PROFILE_NAME;
+				const defaultProfileChecked = !allTabsData.profileName || allTabsData.profileName == config.DEFAULT_PROFILE_NAME;
 				menus.create({
 					id: defaultProfileId,
 					type: "radio",
@@ -186,22 +188,22 @@ singlefile.ui.menu = (() => {
 				menusTitleState.set(MENU_ID_ASSOCIATE_WITH_PROFILE, MENU_CREATE_DOMAIN_RULE_MESSAGE);
 				let rule;
 				if (tab && tab.url) {
-					rule = await singlefile.config.getRule(tab.url);
+					rule = await config.getRule(tab.url);
 				}
 				const currentProfileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "current";
-				const currentProfileIChecked = !rule || (rule.profile == singlefile.config.CURRENT_PROFILE_NAME);
+				const currentProfileIChecked = !rule || (rule.profile == config.CURRENT_PROFILE_NAME);
 				menus.create({
 					id: currentProfileId,
 					type: "radio",
 					contexts: defaultContexts,
-					title: singlefile.config.CURRENT_PROFILE_NAME,
+					title: config.CURRENT_PROFILE_NAME,
 					checked: currentProfileIChecked,
 					parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
 				});
 				menusCheckedState.set(currentProfileId, currentProfileIChecked);
 
 				const associatedDefaultProfileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
-				const associatedDefaultProfileChecked = Boolean(rule) && (rule.profile == singlefile.config.DEFAULT_PROFILE_NAME);
+				const associatedDefaultProfileChecked = Boolean(rule) && (rule.profile == config.DEFAULT_PROFILE_NAME);
 				menus.create({
 					id: associatedDefaultProfileId,
 					type: "radio",
@@ -216,9 +218,9 @@ singlefile.ui.menu = (() => {
 
 				profileIndexes = new Map();
 				Object.keys(profiles).forEach((profileName, profileIndex) => {
-					if (profileName != singlefile.config.DEFAULT_PROFILE_NAME) {
+					if (profileName != config.DEFAULT_PROFILE_NAME) {
 						let profileId = MENU_ID_SELECT_PROFILE_PREFIX + profileIndex;
-						let profileChecked = tabsData.profileName == profileName;
+						let profileChecked = allTabsData.profileName == profileName;
 						menus.create({
 							id: profileId,
 							type: "radio",
@@ -295,111 +297,117 @@ singlefile.ui.menu = (() => {
 	}
 
 	async function initialize() {
+		const business = singlefile.extension.core.bg.business;
+		const tabs = singlefile.extension.core.bg.tabs;
+		const tabsData = singlefile.extension.core.bg.tabsData;
+		const config = singlefile.extension.core.bg.config;
+		const autosave = singlefile.extension.core.bg.autosave;
 		if (BROWSER_MENUS_API_SUPPORTED) {
 			createMenus();
 			menus.onClicked.addListener(async (event, tab) => {
 				if (event.menuItemId == MENU_ID_SAVE_PAGE) {
-					singlefile.core.saveTab(tab);
+					business.saveTab(tab);
 				}
 				if (event.menuItemId == MENU_ID_SAVE_SELECTED) {
-					singlefile.core.saveTab(tab, { selected: true });
+					business.saveTab(tab, { selected: true });
 				}
 				if (event.menuItemId == MENU_ID_SAVE_FRAME) {
-					singlefile.core.saveTab(tab, { frameId: event.frameId });
+					business.saveTab(tab, { frameId: event.frameId });
 				}
 				if (event.menuItemId == MENU_ID_SAVE_SELECTED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_SELECTED_TABS) {
-					const tabs = await singlefile.tabs.get({ currentWindow: true, highlighted: true });
-					tabs.forEach(tab => singlefile.core.saveTab(tab));
+					const allTabs = await tabs.get({ currentWindow: true, highlighted: true });
+					allTabs.forEach(tab => business.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_SAVE_UNPINNED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_UNPINNED_TABS) {
-					const tabs = await singlefile.tabs.get({ currentWindow: true, pinned: false });
-					tabs.forEach(tab => singlefile.core.saveTab(tab));
+					const allTabs = await tabs.get({ currentWindow: true, pinned: false });
+					allTabs.forEach(tab => business.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_SAVE_ALL_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_ALL_TABS) {
-					const tabs = await singlefile.tabs.get({ currentWindow: true });
-					tabs.forEach(tab => singlefile.core.saveTab(tab));
+					const allTabs = await tabs.get({ currentWindow: true });
+					allTabs.forEach(tab => business.saveTab(tab));
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) {
-					const tabsData = await singlefile.tabsData.get(tab.id);
-					tabsData[tab.id].autoSave = true;
-					await singlefile.tabsData.set(tabsData);
+					const allTabsData = await tabsData.get(tab.id);
+					allTabsData[tab.id].autoSave = true;
+					await tabsData.set(allTabsData);
 					refreshExternalComponents(tab, { autoSave: true });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_DISABLED) {
-					const tabsData = await singlefile.tabsData.get();
-					Object.keys(tabsData).forEach(tabId => tabsData[tabId].autoSave = false);
-					tabsData.autoSaveUnpinned = tabsData.autoSaveAll = false;
-					await singlefile.tabsData.set(tabsData);
+					const allTabsData = await tabsData.get();
+					Object.keys(allTabsData).forEach(tabId => allTabsData[tabId].autoSave = false);
+					allTabsData.autoSaveUnpinned = allTabsData.autoSaveAll = false;
+					await tabsData.set(allTabsData);
 					refreshExternalComponents(tab, {});
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_ALL) {
-					const tabsData = await singlefile.tabsData.get();
-					tabsData.autoSaveAll = event.checked;
-					await singlefile.tabsData.set(tabsData);
+					const allTabsData = await tabsData.get();
+					allTabsData.autoSaveAll = event.checked;
+					await tabsData.set(allTabsData);
 					refreshExternalComponents(tab, { autoSave: true });
 				}
 				if (event.menuItemId == MENU_ID_AUTO_SAVE_UNPINNED) {
-					const tabsData = await singlefile.tabsData.get();
-					tabsData.autoSaveUnpinned = event.checked;
-					await singlefile.tabsData.set(tabsData);
+					const allTabsData = await tabsData.get();
+					allTabsData.autoSaveUnpinned = event.checked;
+					await tabsData.set(allTabsData);
 					refreshExternalComponents(tab, { autoSave: true });
 				}
 				if (event.menuItemId.startsWith(MENU_ID_SELECT_PROFILE_PREFIX)) {
-					const [profiles, tabsData] = await Promise.all([singlefile.config.getProfiles(), singlefile.tabsData.get()]);
+					const [profiles, allTabsData] = await Promise.all([config.getProfiles(), tabsData.get()]);
 					const profileId = event.menuItemId.split(MENU_ID_SELECT_PROFILE_PREFIX)[1];
 					if (profileId == "default") {
-						tabsData.profileName = singlefile.config.DEFAULT_PROFILE_NAME;
+						allTabsData.profileName = config.DEFAULT_PROFILE_NAME;
 					} else {
 						const profileIndex = Number(profileId);
-						tabsData.profileName = Object.keys(profiles)[profileIndex];
+						allTabsData.profileName = Object.keys(profiles)[profileIndex];
 					}
-					await singlefile.tabsData.set(tabsData);
-					refreshExternalComponents(tab, { autoSave: await singlefile.autosave.isEnabled() });
+					await tabsData.set(allTabsData);
+					refreshExternalComponents(tab, { autoSave: await autosave.isEnabled() });
 				}
 				if (event.menuItemId.startsWith(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)) {
-					const [profiles, rule] = await Promise.all([singlefile.config.getProfiles(), singlefile.config.getRule(tab.url)]);
+					const [profiles, rule] = await Promise.all([config.getProfiles(), config.getRule(tab.url)]);
 					const profileId = event.menuItemId.split(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)[1];
 					let profileName;
 					if (profileId == "default") {
-						profileName = singlefile.config.DEFAULT_PROFILE_NAME;
+						profileName = config.DEFAULT_PROFILE_NAME;
 					} else if (profileId == "current") {
-						profileName = singlefile.config.CURRENT_PROFILE_NAME;
+						profileName = config.CURRENT_PROFILE_NAME;
 					} else {
 						const profileIndex = Number(profileId);
 						profileName = Object.keys(profiles)[profileIndex];
 					}
 					if (rule) {
-						await singlefile.config.updateRule(rule.url, rule.url, profileName, profileName);
+						await config.updateRule(rule.url, rule.url, profileName, profileName);
 					} else {
 						await updateTitleValue(MENU_ID_ASSOCIATE_WITH_PROFILE, MENU_UPDATE_RULE_MESSAGE);
-						await singlefile.config.addRule(new URL(tab.url).hostname, profileName, profileName);
+						await config.addRule(new URL(tab.url).hostname, profileName, profileName);
 					}
 				}
 			});
-			(await singlefile.tabs.get({})).forEach(tab => refreshTab(tab));
+			(await tabs.get({})).forEach(tab => refreshTab(tab));
 		}
 	}
 
 	async function refreshExternalComponents(tab, options) {
-		const tabsData = await singlefile.tabsData.get(tab.id);
-		await singlefile.autosave.refreshTabs();
-		singlefile.ui.button.refresh(tab, options);
-		browser.runtime.sendMessage({ method: "options.refresh", profileName: tabsData.profileName }).catch(() => { /* ignored */ });
+		const allTabsData = await singlefile.extension.core.bg.tabsData.get(tab.id);
+		await singlefile.extension.core.bg.autosave.refreshTabs();
+		singlefile.extension.ui.bg.button.refresh(tab, options);
+		browser.runtime.sendMessage({ method: "options.refresh", profileName: allTabsData.profileName }).catch(() => { /* ignored */ });
 	}
 
 	async function refreshTab(tab) {
+		const config = singlefile.extension.core.bg.config;
 		if (BROWSER_MENUS_API_SUPPORTED) {
-			const tabsData = await singlefile.tabsData.get(tab.id);
+			const allTabsData = await singlefile.extension.core.bg.tabsData.get(tab.id);
 			const promises = [];
 			try {
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, !tabsData[tab.id].autoSave));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, tabsData[tab.id].autoSave));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_UNPINNED, Boolean(tabsData.autoSaveUnpinned)));
-				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_ALL, Boolean(tabsData.autoSaveAll)));
+				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, !allTabsData[tab.id].autoSave));
+				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, allTabsData[tab.id].autoSave));
+				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_UNPINNED, Boolean(allTabsData.autoSaveUnpinned)));
+				promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_ALL, Boolean(allTabsData.autoSaveAll)));
 				if (tab && tab.url) {
 					let selectedEntryId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
 					let title = MENU_CREATE_DOMAIN_RULE_MESSAGE;
-					const [profiles, rule] = await Promise.all([singlefile.config.getProfiles(), singlefile.config.getRule(tab.url)]);
+					const [profiles, rule] = await Promise.all([config.getProfiles(), config.getRule(tab.url)]);
 					if (rule) {
 						const profileIndex = profileIndexes.get(rule.profile);
 						if (profileIndex) {
@@ -408,7 +416,7 @@ singlefile.ui.menu = (() => {
 						}
 					}
 					Object.keys(profiles).forEach((profileName, profileIndex) => {
-						if (profileName == singlefile.config.DEFAULT_PROFILE_NAME) {
+						if (profileName == config.DEFAULT_PROFILE_NAME) {
 							promises.push(updateCheckedValue(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default", selectedEntryId == MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default"));
 						} else {
 							promises.push(updateCheckedValue(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex, selectedEntryId == MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex));

+ 5 - 5
extension/ui/bg/ui-options.js

@@ -344,9 +344,9 @@
 			if (target == profileNamesInput) {
 				await refresh(profileNamesInput.value);
 				if (sidePanelDisplay) {
-					const tabsData = await browser.runtime.sendMessage({ method: "tabsData.get" });
-					tabsData.profileName = profileNamesInput.value;
-					await browser.runtime.sendMessage({ method: "tabsData.set", tabsData });
+					const allTabsData = await browser.runtime.sendMessage({ method: "tabsData.get" });
+					allTabsData.profileName = profileNamesInput.value;
+					await browser.runtime.sendMessage({ method: "tabsData.set", tabsData: allTabsData });
 					await browser.runtime.sendMessage({ method: "ui.refreshMenu" });
 				}
 			} else {
@@ -434,8 +434,8 @@
 		sidePanelDisplay = true;
 		document.querySelector(".options-title").remove();
 		document.documentElement.classList.add("side-panel");
-		const tabsData = await browser.runtime.sendMessage({ method: "tabsData.get" });
-		refresh(tabsData.profileName);
+		const allTabsData = await browser.runtime.sendMessage({ method: "tabsData.get" });
+		refresh(allTabsData.profileName);
 	} else {
 		refresh();
 	}

+ 2 - 2
extension/ui/content/infobar.js → extension/ui/content/content-ui-infobar.js

@@ -23,7 +23,7 @@
 
 /* global browser, document, Node, window, top, getComputedStyle, location, setTimeout */
 
-this.singlefile.infobar = this.singlefile.infobar || (() => {
+this.singlefile.extension.ui.content.infobar = this.singlefile.extension.ui.content.infobar || (() => {
 
 	const INFOBAR_TAGNAME = "singlefile-infobar";
 	const LINK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAABmJLR0QABQDuAACS38mlAAAACXBIWXMAACfuAAAn7gExzuVDAAAAB3RJTUUH4ggCDDcMnYqGGAAAATtJREFUOMvNk19LwlAYxp+zhOoqpxJ1la3patFVINk/oRDBLuyreiPFMmcj/QQRSOOwpEINDCpwRr7d1HBMc4sufO7Oe877e5/zcA4wbWLDi8urGr2+vXsOFfJZdnPboDtuueoRcQEH6RQDgNBP8bxcpfvmA0QxPHF6u/MMInLVHFDP7kMUwyjks2xU8+ZGkgGAbtSp1e5gRhBc+0KQHHSjTg2TY0tVEItF/wYqV6+pYXKoiox0atvjOuQXYnILqiJj/ztceXUlGEirGGRyC0pCciDDmfm6mlYxiFtNKAkJmb0dV2OxpFGxpNFE0NmFTtxqQpbiHsgojQX1bBuyFMfR4S7zk+PYjE5PcizI0xD+6685jubnZvH41MJwgL+p233B8tKiF7SeXMPnYIB+/8OXg2hERO44wzC1+gJYGGpVbtoqiAAAAABJRU5ErkJggg==";
@@ -37,7 +37,7 @@ this.singlefile.infobar = this.singlefile.infobar || (() => {
 			displayIcon();
 		}
 	}
-	return true;
+	return {};
 
 	async function displayIcon() {
 		let singleFileComment = document.documentElement.childNodes[0];

+ 3 - 3
extension/ui/content/content-ui.js → extension/ui/content/content-ui-main.js

@@ -21,11 +21,11 @@
  *   Source.
  */
 
-/* global SingleFileBrowser, browser, document, prompt, getComputedStyle, addEventListener, removeEventListener, requestAnimationFrame, setTimeout, getSelection, Node */
+/* global browser, document, prompt, getComputedStyle, addEventListener, removeEventListener, requestAnimationFrame, setTimeout, getSelection, Node */
 
-this.singlefile.ui = this.singlefile.ui || (() => {
+this.singlefile.extension.ui.content.main = this.singlefile.extension.ui.content.main || (() => {
 
-	const SingleFile = SingleFileBrowser.getClass();
+	const SingleFile = this.singlefile.lib.getClass();
 
 	const MASK_TAGNAME = "singlefile-mask";
 	const PROGRESS_BAR_TAGNAME = "singlefile-progress-bar";

+ 31 - 1
extension/index.js → index.js

@@ -24,5 +24,35 @@
 /* global */
 
 if (!this.singlefile) {
-	this.singlefile = {};
+	this.singlefile = {
+		extension: {
+			core: {
+				bg: {},
+				content: {}
+			},
+			ui: {
+				bg: {},
+				content: {}
+			}
+		},
+		lib: {
+			fetch: {
+				bg: {},
+				content: {}
+			},
+			frameTree: {
+				bg: {},
+				content: {}
+			},
+			hooks: {
+				content: {}
+			},
+			lazy: {
+				bg: {},
+				content: {}
+			},
+			vendor: {},
+			modules: {}
+		}
+	};
 }

+ 5 - 4
lib/fetch/bg/fetch.js → lib/fetch/bg/fetch-resources.js

@@ -21,9 +21,9 @@
  *   Source.
  */
 
-/* global browser, XMLHttpRequest */
+/* global singlefile, browser, XMLHttpRequest */
 
-(() => {
+singlefile.lib.fetch.bg.resources = (() => {
 
 	const responses = new Map();
 
@@ -38,12 +38,13 @@
 			});
 		}
 	});
+	return {};
 
 	async function onRequest(message) {
 		if (message.method == "fetch") {
 			const responseId = requestId;
 			requestId = requestId + 1;
-			const response = await superFetch(message.url);
+			const response = await fetchResource(message.url);
 			responses.set(responseId, response);
 			response.responseId = responseId;
 			return { responseId, headers: response.headers };
@@ -65,7 +66,7 @@
 		return { array: Array.from(new Uint8Array(xhrRequest.response)) };
 	}
 
-	async function superFetch(url) {
+	async function fetchResource(url) {
 		return new Promise((resolve, reject) => {
 			const xhrRequest = new XMLHttpRequest();
 			xhrRequest.withCredentials = true;

+ 2 - 3
lib/fetch/content/fetch.js → lib/fetch/content/content-fetch-resources.js

@@ -23,9 +23,9 @@
 
 /* global browser, fetch, XMLHttpRequest */
 
-this.superFetch = this.superFetch || (() => {
+this.singlefile.lib.fetch.content.resources = this.singlefile.lib.fetch.content.resources || (() => {
 
-	const superFetch = {
+	return {
 		fetch: async url => {
 			try {
 				let response = await fetch(url, { cache: "force-cache" });
@@ -62,7 +62,6 @@ this.superFetch = this.superFetch || (() => {
 			}
 		}
 	};
-	return superFetch;
 
 	async function sendMessage(message) {
 		const response = await browser.runtime.sendMessage(message);

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

@@ -21,9 +21,9 @@
  *   Source.
  */
 
-/* global browser */
+/* global singlefile, browser */
 
-this.frameTree = (() => {
+singlefile.lib.frameTree.bg.main = (() => {
 
 	"use strict";
 
@@ -32,5 +32,6 @@ this.frameTree = (() => {
 			browser.tabs.sendMessage(sender.tab.id, message, { frameId: 0 });
 		}
 	});
+	return {};
 
 })();

+ 15 - 12
lib/frame-tree/content/content-frame-tree.js

@@ -21,9 +21,11 @@
  *   Source.
  */
 
-/* global window, top, document, addEventListener, docHelper, MessageChannel, lazyLoader, browser, setTimeout */
+/* global window, top, document, addEventListener, MessageChannel, browser, setTimeout */
 
-this.frameTree = this.frameTree || (() => {
+this.singlefile.lib.frameTree.content.frames = this.singlefile.lib.frameTree.content.frames || (() => {
+
+	const singlefile = this.singlefile;
 
 	const MESSAGE_PREFIX = "__frameTree__::";
 	const FRAMES_CSS_SELECTOR = "iframe, frame, object[type=\"text/html\"][data]";
@@ -52,12 +54,14 @@ this.frameTree = this.frameTree || (() => {
 	}
 	addEventListener("message", event => {
 		if (typeof event.data == "string" && event.data.startsWith(MESSAGE_PREFIX)) {
+			event.preventDefault();
+			event.stopPropagation();
 			const message = JSON.parse(event.data.substring(MESSAGE_PREFIX.length));
 			if (!TOP_WINDOW && message.method == INIT_REQUEST_MESSAGE) {
 				window.stop();
 				initRequest(message);
-				if (message.options.loadDeferredImages && window.lazyLoader) {
-					lazyLoader.process(message.options);
+				if (message.options.loadDeferredImages && singlefile.lib.lazy.content.loader) {
+					singlefile.lib.lazy.content.loader.process(message.options);
 				}
 			} else if (message.method == CLEANUP_REQUEST_MESSAGE) {
 				cleanupRequest(message);
@@ -65,8 +69,6 @@ this.frameTree = this.frameTree || (() => {
 				const port = event.ports[0];
 				port.onmessage = event => initResponse(event.data);
 			}
-			event.preventDefault();
-			event.stopPropagation();
 		}
 	}, true);
 	return {
@@ -164,7 +166,7 @@ this.frameTree = this.frameTree || (() => {
 		const framesData = [];
 		frameElements.forEach((frameElement, frameIndex) => {
 			const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-			frameElement.setAttribute(docHelper.WIN_ID_ATTRIBUTE_NAME, windowId);
+			frameElement.setAttribute(singlefile.lib.helper.WIN_ID_ATTRIBUTE_NAME, windowId);
 			framesData.push({ windowId });
 			try {
 				sendMessage(frameElement.contentWindow, { method: INIT_REQUEST_MESSAGE, windowId, sessionId, options });
@@ -205,7 +207,7 @@ this.frameTree = this.frameTree || (() => {
 	function cleanupFrames(frameElements, parentWindowId, sessionId) {
 		frameElements.forEach((frameElement, frameIndex) => {
 			const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-			frameElement.removeAttribute(docHelper.WIN_ID_ATTRIBUTE_NAME);
+			frameElement.removeAttribute(singlefile.lib.helper.WIN_ID_ATTRIBUTE_NAME);
 			try {
 				sendMessage(frameElement.contentWindow, { method: CLEANUP_REQUEST_MESSAGE, windowId, sessionId });
 			} catch (error) {
@@ -233,7 +235,7 @@ this.frameTree = this.frameTree || (() => {
 	function sendInitResponse(message) {
 		message.method = INIT_RESPONSE_MESSAGE;
 		try {
-			top.frameTree.initResponse(message);
+			top.singlefile.lib.frameTree.content.frames.initResponse(message);
 		} catch (error) {
 			sendMessage(top, message, true);
 		}
@@ -254,9 +256,10 @@ this.frameTree = this.frameTree || (() => {
 	}
 
 	function getFrameData(document, window, windowId, options) {
-		const docData = docHelper.preProcessDoc(document, window, options);
-		const content = docHelper.serialize(document);
-		docHelper.postProcessDoc(document, options);
+		const helper = singlefile.lib.helper;
+		const docData = helper.preProcessDoc(document, window, options);
+		const content = helper.serialize(document);
+		helper.postProcessDoc(document, options);
 		const baseURI = document.baseURI.split("#")[0];
 		return {
 			windowId,

+ 3 - 3
lib/hooks/hooks-frame.js → lib/hooks/content/content-hooks-frame.js

@@ -23,7 +23,7 @@
 
 /* global browser, window, addEventListener, dispatchEvent, CustomEvent, document, HTMLDocument, FileReader, Blob, getFileContent */
 
-this.hooksFrame = this.hooksFrame || (() => {
+this.singlefile.lib.hooks.content.frame = this.singlefile.lib.hooks.content.frame || (() => {
 
 	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
 	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
@@ -35,9 +35,9 @@ this.hooksFrame = this.hooksFrame || (() => {
 	if (document instanceof HTMLDocument) {
 		let scriptElement = document.createElement("script");
 		if (typeof browser !== "undefined" && browser.runtime && browser.runtime.getURL) {
-			scriptElement.src = browser.runtime.getURL("/lib/hooks/hooks-web.js");
+			scriptElement.src = browser.runtime.getURL("/lib/hooks/content/content-hooks-web.js");
 		} else if (typeof getFileContent !== "undefined") {
-			scriptElement.textContent = getFileContent("/lib/hooks/hooks-web.js");
+			scriptElement.textContent = getFileContent("/lib/hooks/content/content-hooks-web.js");
 		}
 		(document.documentElement || document).appendChild(scriptElement);
 		scriptElement.remove();

+ 1 - 1
lib/hooks/hooks-web.js → lib/hooks/content/content-hooks-web.js

@@ -23,7 +23,7 @@
 
 /* global window, addEventListener, dispatchEvent, CustomEvent, document, screen, Element, UIEvent */
 
-this.hooksFrame = this.hooksFrame || (() => {
+(() => {
 
 	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
 	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";

+ 2 - 3
lib/hooks/hooks.js → lib/hooks/content/content-hooks.js

@@ -23,7 +23,7 @@
 
 /* global history, dispatchEvent, CustomEvent, document, HTMLDocument */
 
-this.hooks = this.hooks || (() => {
+this.singlefile.lib.hooks.content.main = this.singlefile.lib.hooks.content.main || (() => {
 
 	if (document instanceof HTMLDocument) {
 		const scriptElement = document.createElement("script");
@@ -31,8 +31,7 @@ this.hooks = this.hooks || (() => {
 		(document.documentElement || document).appendChild(scriptElement);
 		scriptElement.remove();
 	}
-
-	return true;
+	return {};
 
 	function hookPushState() {
 		const pushState = history.pushState;

+ 2 - 2
lib/lazy/bg/bg-lazy-timeout.js → lib/lazy/bg/lazy-timeout.js

@@ -21,9 +21,9 @@
  *   Source.
  */
 
-/* global browser, setTimeout, clearTimeout */
+/* global singlefile, browser, setTimeout, clearTimeout */
 
-this.lazyTimeout = (() => {
+singlefile.lib.lazy.bg.main = (() => {
 
 	"use strict";
 

+ 6 - 3
lib/lazy/content/content-lazy-loader.js

@@ -21,9 +21,11 @@
  *   Source.
  */
 
-/* global browser, document, MutationObserver, setTimeout, clearTimeout, hooksFrame, addEventListener, removeEventListener, scrollY, scrollX */
+/* global browser, document, MutationObserver, setTimeout, clearTimeout, addEventListener, removeEventListener, scrollY, scrollX */
 
-this.lazyLoader = this.lazyLoader || (() => {
+this.singlefile.lib.lazy.content.loader = this.singlefile.lib.lazy.content.loader || (() => {
+
+	const singlefile = this.singlefile;
 
 	const ATTRIBUTES_MUTATION_TYPE = "attributes";
 	const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
@@ -42,6 +44,7 @@ this.lazyLoader = this.lazyLoader || (() => {
 	};
 
 	function process(options) {
+		const hooksFrame = singlefile.lib.hooks.content.frame;
 		return new Promise(async resolve => {
 			let timeoutId, idleTimeoutId, maxTimeoutId, loadingImages;
 			const pendingImages = new Set();
@@ -120,7 +123,7 @@ this.lazyLoader = this.lazyLoader || (() => {
 	function lazyLoadEnd(idleTimeoutId, observer, resolve) {
 		clearAsyncTimeout(idleTimeoutId);
 		if (typeof hooksFrame != "undefined") {
-			hooksFrame.loadDeferredImagesEnd();
+			singlefile.lib.hooks.content.frame.loadDeferredImagesEnd();
 		}
 		setAsyncTimeout(resolve, 100);
 		observer.disconnect();

+ 45 - 46
lib/single-file/modules/css-fonts-alt-minifier.js

@@ -21,7 +21,9 @@
  *   Source.
  */
 
-this.fontsAltMinifier = this.fontsAltMinifier || (() => {
+this.singlefile.lib.modules.fontsAltMinifier = this.singlefile.lib.modules.fontsAltMinifier || (() => {
+
+	const singlefile = this.singlefile;
 
 	const REGEXP_URL_SIMPLE_QUOTES_FN = /url\s*\(\s*'(.*?)'\s*\)/i;
 	const REGEXP_URL_DOUBLE_QUOTES_FN = /url\s*\(\s*"(.*?)"\s*\)/i;
@@ -53,57 +55,51 @@ this.fontsAltMinifier = this.fontsAltMinifier || (() => {
 		"ultra-expanded": "200%"
 	};
 
-	let cssTree;
-
 	return {
-		getInstance(...args) {
-			[cssTree] = args;
-			return {
-				process: (doc, stylesheets) => {
-					const fontsDetails = {
-						fonts: new Map(),
-						medias: new Map(),
-						supports: new Map()
-					};
-					const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
-					let sheetIndex = 0;
-					stylesheets.forEach(stylesheetInfo => {
-						const cssRules = stylesheetInfo.stylesheet.children;
-						if (cssRules) {
-							stats.rules.processed += cssRules.getSize();
-							stats.rules.discarded += cssRules.getSize();
-							if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
-								const mediaFontsDetails = createFontsDetailsInfo();
-								fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
-								getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
-							} else {
-								getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
-							}
-						}
-						sheetIndex++;
-					});
-					processFontDetails(fontsDetails);
-					sheetIndex = 0;
-					stylesheets.forEach(stylesheetInfo => {
-						const cssRules = stylesheetInfo.stylesheet.children;
-						const media = stylesheetInfo.mediaText;
-						if (cssRules) {
-							if (media && media != MEDIA_ALL) {
-								processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), stats);
-							} else {
-								processFontFaceRules(cssRules, sheetIndex, fontsDetails, stats);
-							}
-							stats.rules.discarded -= cssRules.getSize();
-						}
-						sheetIndex++;
-					});
-					return stats;
-				}
+		process: (doc, stylesheets) => {
+			const fontsDetails = {
+				fonts: new Map(),
+				medias: new Map(),
+				supports: new Map()
 			};
+			const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
+			let sheetIndex = 0;
+			stylesheets.forEach(stylesheetInfo => {
+				const cssRules = stylesheetInfo.stylesheet.children;
+				if (cssRules) {
+					stats.rules.processed += cssRules.getSize();
+					stats.rules.discarded += cssRules.getSize();
+					if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
+						const mediaFontsDetails = createFontsDetailsInfo();
+						fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
+						getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
+					} else {
+						getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
+					}
+				}
+				sheetIndex++;
+			});
+			processFontDetails(fontsDetails);
+			sheetIndex = 0;
+			stylesheets.forEach(stylesheetInfo => {
+				const cssRules = stylesheetInfo.stylesheet.children;
+				const media = stylesheetInfo.mediaText;
+				if (cssRules) {
+					if (media && media != MEDIA_ALL) {
+						processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), stats);
+					} else {
+						processFontFaceRules(cssRules, sheetIndex, fontsDetails, stats);
+					}
+					stats.rules.discarded -= cssRules.getSize();
+				}
+				sheetIndex++;
+			});
+			return stats;
 		}
 	};
 
 	function getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		let mediaIndex = 0, supportsIndex = 0;
 		cssRules.forEach(ruleData => {
 			if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
@@ -172,6 +168,7 @@ this.fontsAltMinifier = this.fontsAltMinifier || (() => {
 	}
 
 	function processFontFaceRules(cssRules, sheetIndex, fontsDetails, stats) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		const removedRules = [];
 		let mediaIndex = 0, supportsIndex = 0;
 		for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
@@ -198,6 +195,7 @@ this.fontsAltMinifier = this.fontsAltMinifier || (() => {
 	}
 
 	function processFontFaceRule(ruleData, fontInfo, stats) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		const findSource = fontFormat => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat);
 		const filterSource = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
 		stats.fonts.processed += fontInfo.length;
@@ -238,6 +236,7 @@ this.fontsAltMinifier = this.fontsAltMinifier || (() => {
 	}
 
 	function getPropertyValue(ruleData, propertyName) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		let property;
 		if (ruleData.block.children) {
 			property = ruleData.block.children.filter(node => node.property == propertyName).tail;

+ 53 - 56
lib/single-file/modules/css-fonts-minifier.js

@@ -21,7 +21,9 @@
  *   Source.
  */
 
-this.fontsMinifier = this.fontsMinifier || (() => {
+this.singlefile.lib.modules.fontsMinifier = this.singlefile.lib.modules.fontsMinifier || (() => {
+
+	const singlefile = this.singlefile;
 
 	const REGEXP_COMMA = /\s*,\s*/;
 	const REGEXP_DASH = /-/;
@@ -36,60 +38,53 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		lighter: "100"
 	};
 
-	let cssTree, fontPropertyParser, docHelper;
-
 	return {
-		getInstance(...args) {
-			[cssTree, fontPropertyParser, docHelper] = args;
-			return {
-				process: (doc, stylesheets, styles, options) => {
-					const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
-					const fontsInfo = { declared: [], used: [] };
-					const workStyleElement = doc.createElement("style");
-					let docContent = "";
-					doc.body.appendChild(workStyleElement);
-					stylesheets.forEach(stylesheetInfo => {
-						const cssRules = stylesheetInfo.stylesheet.children;
-						if (cssRules) {
-							stats.processed += cssRules.getSize();
-							stats.discarded += cssRules.getSize();
-							getFontsInfo(cssRules, fontsInfo);
-							docContent = getRulesTextContent(doc, cssRules, workStyleElement, docContent);
-						}
-					});
-					styles.forEach(declarations => {
-						const fontFamilyNames = getFontFamilyNames(declarations);
-						if (fontFamilyNames.length) {
-							fontsInfo.used.push(fontFamilyNames);
-						}
-						docContent = getDeclarationsTextContent(declarations.children, workStyleElement, docContent);
-					});
-					workStyleElement.remove();
-					docContent += doc.body.innerText;
-					const variableFound = fontsInfo.used.find(fontNames => fontNames.find(fontName => fontName.startsWith("var(--")));
-					let unusedFonts, filteredUsedFonts;
-					if (variableFound) {
-						unusedFonts = [];
-					} else {
-						filteredUsedFonts = new Map();
-						fontsInfo.used.forEach(fontNames => fontNames.forEach(familyName => {
-							if (fontsInfo.declared.find(fontInfo => fontInfo.fontFamily == familyName)) {
-								const optionalData = options.usedFonts && options.usedFonts.filter(fontInfo => fontInfo[0] == familyName);
-								filteredUsedFonts.set(familyName, optionalData);
-							}
-						}));
-						unusedFonts = fontsInfo.declared.filter(fontInfo => !filteredUsedFonts.has(fontInfo.fontFamily));
+		process: (doc, stylesheets, styles, options) => {
+			const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
+			const fontsInfo = { declared: [], used: [] };
+			const workStyleElement = doc.createElement("style");
+			let docContent = "";
+			doc.body.appendChild(workStyleElement);
+			stylesheets.forEach(stylesheetInfo => {
+				const cssRules = stylesheetInfo.stylesheet.children;
+				if (cssRules) {
+					stats.processed += cssRules.getSize();
+					stats.discarded += cssRules.getSize();
+					getFontsInfo(cssRules, fontsInfo);
+					docContent = getRulesTextContent(doc, cssRules, workStyleElement, docContent);
+				}
+			});
+			styles.forEach(declarations => {
+				const fontFamilyNames = getFontFamilyNames(declarations);
+				if (fontFamilyNames.length) {
+					fontsInfo.used.push(fontFamilyNames);
+				}
+				docContent = getDeclarationsTextContent(declarations.children, workStyleElement, docContent);
+			});
+			workStyleElement.remove();
+			docContent += doc.body.innerText;
+			const variableFound = fontsInfo.used.find(fontNames => fontNames.find(fontName => fontName.startsWith("var(--")));
+			let unusedFonts, filteredUsedFonts;
+			if (variableFound) {
+				unusedFonts = [];
+			} else {
+				filteredUsedFonts = new Map();
+				fontsInfo.used.forEach(fontNames => fontNames.forEach(familyName => {
+					if (fontsInfo.declared.find(fontInfo => fontInfo.fontFamily == familyName)) {
+						const optionalData = options.usedFonts && options.usedFonts.filter(fontInfo => fontInfo[0] == familyName);
+						filteredUsedFonts.set(familyName, optionalData);
 					}
-					stylesheets.forEach(stylesheetInfo => {
-						const cssRules = stylesheetInfo.stylesheet.children;
-						if (cssRules) {
-							filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docContent);
-							stats.rules.discarded -= cssRules.getSize();
-						}
-					});
-					return stats;
+				}));
+				unusedFonts = fontsInfo.declared.filter(fontInfo => !filteredUsedFonts.has(fontInfo.fontFamily));
+			}
+			stylesheets.forEach(stylesheetInfo => {
+				const cssRules = stylesheetInfo.stylesheet.children;
+				if (cssRules) {
+					filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docContent);
+					stats.rules.discarded -= cssRules.getSize();
 				}
-			};
+			});
+			return stats;
 		}
 	};
 
@@ -170,7 +165,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		}
 		if (property) {
 			try {
-				return cssTree.generate(property.data.value);
+				return singlefile.lib.vendor.cssTree.generate(property.data.value);
 			} catch (error) {
 				// ignored
 			}
@@ -178,6 +173,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 	}
 
 	function getFontFamilyNames(declarations) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		let fontFamilyName = declarations.children.filter(node => node.property == "font-family").tail;
 		let fontFamilyNames = [];
 		if (fontFamilyName) {
@@ -201,7 +197,7 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 		const font = declarations.children.filter(node => node.property == "font").tail;
 		if (font && font.data && font.data.value) {
 			try {
-				const parsedFont = fontPropertyParser.parse(cssTree.generate(font.data.value));
+				const parsedFont = singlefile.lib.vendor.fontPropertyParser.parse(cssTree.generate(font.data.value));
 				parsedFont.family.forEach(familyName => fontFamilyNames.push(getFontFamily(familyName)));
 			} catch (error) {
 				// ignored				
@@ -270,10 +266,11 @@ this.fontsMinifier = this.fontsMinifier || (() => {
 	}
 
 	function getDeclarationUnescapedValue(declarations, property, workStylesheet) {
-		const rawValue = docHelper.removeQuotes(getDeclarationValue(declarations, property) || "");
+		const helper = singlefile.lib.helper;
+		const rawValue = helper.removeQuotes(getDeclarationValue(declarations, property) || "");
 		if (rawValue) {
 			workStylesheet.textContent = "tmp { content:\"" + rawValue + "\"}";
-			return docHelper.removeQuotes(workStylesheet.sheet.cssRules[0].style.getPropertyValue("content"));
+			return helper.removeQuotes(workStylesheet.sheet.cssRules[0].style.getPropertyValue("content"));
 		}
 		return "";
 	}

+ 8 - 10
lib/single-file/modules/css-matched-rules.js

@@ -21,15 +21,15 @@
  *   Source.
  */
 
-this.matchedRules = this.matchedRules || (() => {
+this.singlefile.lib.modules.matchedRules = this.singlefile.lib.modules.matchedRules || (() => {
+
+	const singlefile = this.singlefile;
 
 	const MEDIA_ALL = "all";
 	const IGNORED_PSEUDO_ELEMENTS = ["after", "before", "first-letter", "first-line", "placeholder", "selection"];
 	const REGEXP_VENDOR_IDENTIFIER = /-(ms|webkit|moz|o)-/;
 	const DEBUG = false;
 
-	let cssTree;
-
 	class MatchedRules {
 		constructor(doc, stylesheets, styles) {
 			this.doc = doc;
@@ -77,13 +77,8 @@ this.matchedRules = this.matchedRules || (() => {
 	}
 
 	return {
-		getInstance(...args) {
-			[cssTree] = args;
-			return {
-				getMediaAllInfo(doc, stylesheets, styles) {
-					return new MatchedRules(doc, stylesheets, styles).getMediaAllInfo();
-				}
-			};
+		getMediaAllInfo(doc, stylesheets, styles) {
+			return new MatchedRules(doc, stylesheets, styles).getMediaAllInfo();
 		}
 	};
 
@@ -96,6 +91,7 @@ this.matchedRules = this.matchedRules || (() => {
 	}
 
 	function getMatchedElementsRules(doc, cssRules, mediaInfo, sheetIndex, styles, matchedElementsCache, workStylesheet) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		let mediaIndex = 0;
 		let ruleIndex = 0;
 		let startTime;
@@ -168,6 +164,7 @@ this.matchedRules = this.matchedRules || (() => {
 	}
 
 	function getFilteredSelector(selector, selectorText) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		const removedSelectors = [];
 		selector = { data: cssTree.parse(cssTree.generate(selector.data), { context: "selector" }) };
 		filterPseudoClasses(selector);
@@ -265,6 +262,7 @@ this.matchedRules = this.matchedRules || (() => {
 	}
 
 	function processDeclarations(declarationsInfo, declarations, selectorInfo, processedProperties, workStylesheet) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		for (let declaration = declarations.tail; declaration; declaration = declaration.prev) {
 			const declarationData = declaration.data;
 			const declarationText = cssTree.generate(declarationData);

+ 15 - 20
lib/single-file/modules/css-medias-alt-minifier.js

@@ -21,30 +21,25 @@
  *   Source.
  */
 
-this.mediasAltMinifier = this.mediasAltMinifier || (() => {
+this.singlefile.lib.modules.mediasAltMinifier = this.singlefile.lib.modules.mediasAltMinifier || (() => {
+	
+	const singlefile = this.singlefile;
 
 	const MEDIA_ALL = "all";
 	const MEDIA_SCREEN = "screen";
 
-	let cssTree, mediaQueryParser;
-
 	return {
-		getInstance(...args) {
-			[cssTree, mediaQueryParser] = args;
-			return {
-				process: stylesheets => {
-					const stats = { processed: 0, discarded: 0 };
-					stylesheets.forEach((stylesheetInfo, element) => {
-						if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL, MEDIA_SCREEN) && stylesheetInfo.stylesheet.children) {
-							const removedRules = processRules(stylesheetInfo.stylesheet.children, stats);
-							removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
-						} else {
-							stylesheets.delete(element);
-						}
-					});
-					return stats;
+		process: stylesheets => {
+			const stats = { processed: 0, discarded: 0 };
+			stylesheets.forEach((stylesheetInfo, element) => {
+				if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL, MEDIA_SCREEN) && stylesheetInfo.stylesheet.children) {
+					const removedRules = processRules(stylesheetInfo.stylesheet.children, stats);
+					removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
+				} else {
+					stylesheets.delete(element);
 				}
-			};
+			});
+			return stats;
 		}
 	};
 
@@ -53,7 +48,7 @@ this.mediasAltMinifier = this.mediasAltMinifier || (() => {
 			const ruleData = cssRule.data;
 			if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
 				stats.processed++;
-				if (matchesMediaType(cssTree.generate(ruleData.prelude), MEDIA_SCREEN)) {
+				if (matchesMediaType(singlefile.lib.vendor.cssTree.generate(ruleData.prelude), MEDIA_SCREEN)) {
 					processRules(ruleData.block.children, stats, removedRules);
 				} else {
 					removedRules.push({ cssRules, cssRule });
@@ -69,7 +64,7 @@ this.mediasAltMinifier = this.mediasAltMinifier || (() => {
 	}
 
 	function matchesMediaType(mediaText, mediaType) {
-		const foundMediaTypes = flatten(mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node, mediaType)));
+		const foundMediaTypes = flatten(singlefile.lib.vendor.mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node, mediaType)));
 		return foundMediaTypes.find(mediaTypeInfo => (!mediaTypeInfo.not && (mediaTypeInfo.value == mediaType || mediaTypeInfo.value == MEDIA_ALL))
 			|| (mediaTypeInfo.not && (mediaTypeInfo.value == MEDIA_ALL || mediaTypeInfo.value != mediaType)));
 	}

+ 33 - 36
lib/single-file/modules/css-rules-minifier.js

@@ -21,53 +21,49 @@
  *   Source.
  */
 
-this.cssRulesMinifier = this.cssRulesMinifier || (() => {
+this.singlefile.lib.modules.cssRulesMinifier = this.singlefile.lib.modules.cssRulesMinifier || (() => {
 
-	const DEBUG = false;
+	const singlefile = this.singlefile;
 
-	let cssTree;
+	const DEBUG = false;
 
 	return {
-		getInstance(...args) {
-			[cssTree] = args;
-			return {
-				process: (stylesheets, styles, mediaAllInfo) => {
-					const stats = { processed: 0, discarded: 0 };
-					let sheetIndex = 0;
-					stylesheets.forEach(stylesheetInfo => {
-						if (!stylesheetInfo.scoped) {
-							const cssRules = stylesheetInfo.stylesheet.children;
-							if (cssRules) {
-								stats.processed += cssRules.getSize();
-								stats.discarded += cssRules.getSize();
-								let mediaInfo;
-								if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != "all") {
-									mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.mediaText);
-								} else {
-									mediaInfo = mediaAllInfo;
-								}
-								processRules(cssRules, sheetIndex, mediaInfo);
-								stats.discarded -= cssRules.getSize();
-							}
+		process: (stylesheets, styles, mediaAllInfo) => {
+			const stats = { processed: 0, discarded: 0 };
+			let sheetIndex = 0;
+			stylesheets.forEach(stylesheetInfo => {
+				if (!stylesheetInfo.scoped) {
+					const cssRules = stylesheetInfo.stylesheet.children;
+					if (cssRules) {
+						stats.processed += cssRules.getSize();
+						stats.discarded += cssRules.getSize();
+						let mediaInfo;
+						if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != "all") {
+							mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.mediaText);
+						} else {
+							mediaInfo = mediaAllInfo;
 						}
-						sheetIndex++;
-					});
-					let startTime;
-					if (DEBUG) {
-						startTime = Date.now();
-						log("  -- STARTED processStyleAttribute");
-					}
-					styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
-					if (DEBUG) {
-						log("  -- ENDED   processStyleAttribute delay =", Date.now() - startTime);
+						processRules(cssRules, sheetIndex, mediaInfo);
+						stats.discarded -= cssRules.getSize();
 					}
-					return stats;
 				}
-			};
+				sheetIndex++;
+			});
+			let startTime;
+			if (DEBUG) {
+				startTime = Date.now();
+				log("  -- STARTED processStyleAttribute");
+			}
+			styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
+			if (DEBUG) {
+				log("  -- ENDED   processStyleAttribute delay =", Date.now() - startTime);
+			}
+			return stats;
 		}
 	};
 
 	function processRules(cssRules, sheetIndex, mediaInfo) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		let mediaRuleIndex = 0, startTime;
 		if (DEBUG && cssRules.getSize() > 1) {
 			startTime = Date.now();
@@ -109,6 +105,7 @@ this.cssRulesMinifier = this.cssRulesMinifier || (() => {
 	}
 
 	function processRuleInfo(ruleData, ruleInfo, pseudoSelectors) {
+		const cssTree = singlefile.lib.vendor.cssTree;
 		const removedDeclarations = [];
 		const removedSelectors = [];
 		let pseudoSelectorFound;

+ 20 - 25
lib/single-file/modules/html-images-alt-minifier.js

@@ -21,36 +21,31 @@
  *   Source.
  */
 
-this.imagesAltMinifier = this.imagesAltMinifier || (() => {
+this.singlefile.lib.modules.imagesAltMinifier = this.singlefile.lib.modules.imagesAltMinifier || (() => {
 
-	const EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+	const singlefile = this.singlefile;
 
-	let srcsetParser;
+	const EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
 
 	return {
-		getInstance(...args) {
-			[srcsetParser] = args;
-			return {
-				process(doc) {
-					doc.querySelectorAll("picture").forEach(pictureElement => {
-						const imgElement = pictureElement.querySelector("img");
-						if (imgElement) {
-							let srcData = getImgSrcData(imgElement);
-							let { src, srcset } = srcData;
-							if (!src) {
-								const sources = Array.from(pictureElement.querySelectorAll("source")).reverse();
-								const data = getSourceSrcData(Array.from(sources));
-								src = data.src;
-								if (!srcset) {
-									srcset = data.srcset;
-								}
-							}
-							setSrc({ src, srcset }, imgElement, pictureElement);
+		process(doc) {
+			doc.querySelectorAll("picture").forEach(pictureElement => {
+				const imgElement = pictureElement.querySelector("img");
+				if (imgElement) {
+					let srcData = getImgSrcData(imgElement);
+					let { src, srcset } = srcData;
+					if (!src) {
+						const sources = Array.from(pictureElement.querySelectorAll("source")).reverse();
+						const data = getSourceSrcData(Array.from(sources));
+						src = data.src;
+						if (!srcset) {
+							srcset = data.srcset;
 						}
-					});
-					doc.querySelectorAll(":not(picture) > img[srcset]").forEach(imgElement => setSrc(getImgSrcData(imgElement), imgElement));
+					}
+					setSrc({ src, srcset }, imgElement, pictureElement);
 				}
-			};
+			});
+			doc.querySelectorAll(":not(picture) > img[srcset]").forEach(imgElement => setSrc(getImgSrcData(imgElement), imgElement));
 		}
 	};
 
@@ -108,7 +103,7 @@ this.imagesAltMinifier = this.imagesAltMinifier || (() => {
 
 	function getSourceSrc(sourceSrcSet) {
 		if (sourceSrcSet) {
-			const srcset = srcsetParser.process(sourceSrcSet);
+			const srcset = singlefile.lib.vendor.srcsetParser.process(sourceSrcSet);
 			return (srcset.find(srcset => srcset.url)).url;
 		}
 	}

+ 1 - 1
lib/single-file/modules/html-minifier.js

@@ -23,7 +23,7 @@
 
 // Derived from the work of Kirill Maltsev - https://github.com/posthtml/htmlnano
 
-this.htmlMinifier = this.htmlMinifier || (() => {
+this.singlefile.lib.modules.htmlMinifier = this.singlefile.lib.modules.htmlMinifier || (() => {
 
 	// Source: https://github.com/kangax/html-minifier/issues/63
 	const booleanAttributes = [

+ 1 - 1
lib/single-file/modules/html-serializer.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-this.serializer = this.serializer || (() => {
+this.singlefile.lib.modules.serializer = this.singlefile.lib.modules.serializer || (() => {
 
 	const SELF_CLOSED_TAG_NAMES = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];
 

+ 1 - 1
lib/single-file/single-file-core.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-this.SingleFileCore = this.SingleFileCore || (() => {
+this.singlefile.lib.core = this.singlefile.lib.core || (() => {
 
 	const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-single-file-selected-content";
 	const DEBUG = false;

+ 4 - 4
lib/single-file/util/doc-helper.js → lib/single-file/single-file-helper.js

@@ -21,9 +21,9 @@
  *   Source.
  */
 
-/* global hooksFrame */
+this.singlefile.lib.helper = this.singlefile.lib.helper || (() => {
 
-this.docHelper = this.docHelper || (() => {
+	const singlefile = this.singlefile;
 
 	const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-single-file-removed-content";
 	const PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME = "data-single-file-preserved-space-element";
@@ -324,8 +324,8 @@ this.docHelper = this.docHelper || (() => {
 	}
 
 	function getFontsData() {
-		if (typeof hooksFrame != "undefined") {
-			return hooksFrame.getFontsData();
+		if (singlefile.lib.hooks.content.frame) {
+			return singlefile.lib.hooks.content.frame.getFontsData();
 		}
 	}
 

+ 18 - 18
lib/single-file/util/doc-util.js → lib/single-file/single-file-util.js

@@ -23,14 +23,14 @@
 
 /* global DOMParser, URL, Blob, FileReader */
 
-this.docUtil = this.docUtil || (() => {
+this.singlefile.lib.util = this.singlefile.lib.util || (() => {
 
 	const DEBUG = false;
 	const ONE_MB = 1024 * 1024;
 	const PREFIX_CONTENT_TYPE_TEXT = "text/";
 
 	return {
-		getInstance: (modules, domUtil) => {
+		getInstance: (modules, util) => {
 			if (modules.serializer === undefined) {
 				modules.serializer = {
 					process(doc) {
@@ -84,10 +84,10 @@ this.docUtil = this.docUtil || (() => {
 					return (new DOMParser()).parseFromString(content, "image/svg+xml");
 				},
 				async digest(algo, text) {
-					return domUtil.digestText(algo, text);
+					return util.digestText(algo, text);
 				},
 				async validFont(urlFunction) {
-					return domUtil.isValidFontUrl(urlFunction);
+					return util.isValidFontUrl(urlFunction);
 				},
 				getContentSize(content) {
 					return new Blob([content]).size;
@@ -138,25 +138,25 @@ this.docUtil = this.docUtil || (() => {
 					return modules.srcsetParser.process(srcset);
 				},
 				preProcessDoc(doc, win, options) {
-					return modules.docHelper.preProcessDoc(doc, win, options);
+					return modules.helper.preProcessDoc(doc, win, options);
 				},
 				postProcessDoc(doc, options) {
-					modules.docHelper.postProcessDoc(doc, options);
+					modules.helper.postProcessDoc(doc, options);
 				},
 				serialize(doc, compressHTML) {
 					return modules.serializer.process(doc, compressHTML);
 				},
 				removeQuotes(string) {
-					return modules.docHelper.removeQuotes(string);
-				},
-				WIN_ID_ATTRIBUTE_NAME: modules.docHelper.WIN_ID_ATTRIBUTE_NAME,
-				REMOVED_CONTENT_ATTRIBUTE_NAME: modules.docHelper.REMOVED_CONTENT_ATTRIBUTE_NAME,
-				IMAGE_ATTRIBUTE_NAME: modules.docHelper.IMAGE_ATTRIBUTE_NAME,
-				POSTER_ATTRIBUTE_NAME: modules.docHelper.POSTER_ATTRIBUTE_NAME,
-				CANVAS_ATTRIBUTE_NAME: modules.docHelper.CANVAS_ATTRIBUTE_NAME,
-				INPUT_VALUE_ATTRIBUTE_NAME: modules.docHelper.INPUT_VALUE_ATTRIBUTE_NAME,
-				SHADOW_ROOT_ATTRIBUTE_NAME: modules.docHelper.SHADOW_ROOT_ATTRIBUTE_NAME,
-				PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: modules.docHelper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME
+					return modules.helper.removeQuotes(string);
+				},
+				WIN_ID_ATTRIBUTE_NAME: modules.helper.WIN_ID_ATTRIBUTE_NAME,
+				REMOVED_CONTENT_ATTRIBUTE_NAME: modules.helper.REMOVED_CONTENT_ATTRIBUTE_NAME,
+				IMAGE_ATTRIBUTE_NAME: modules.helper.IMAGE_ATTRIBUTE_NAME,
+				POSTER_ATTRIBUTE_NAME: modules.helper.POSTER_ATTRIBUTE_NAME,
+				CANVAS_ATTRIBUTE_NAME: modules.helper.CANVAS_ATTRIBUTE_NAME,
+				INPUT_VALUE_ATTRIBUTE_NAME: modules.helper.INPUT_VALUE_ATTRIBUTE_NAME,
+				SHADOW_ROOT_ATTRIBUTE_NAME: modules.helper.SHADOW_ROOT_ATTRIBUTE_NAME,
+				PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: modules.helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME
 			};
 
 			async function getContent(resourceURL, options) {
@@ -166,7 +166,7 @@ this.docUtil = this.docUtil || (() => {
 					log("  // STARTED download url =", resourceURL, "asDataURI =", options.asDataURI);
 				}
 				try {
-					resourceContent = await domUtil.getResourceContent(resourceURL, options);
+					resourceContent = await util.getResourceContent(resourceURL, options);
 				} catch (error) {
 					return { data: options.asDataURI ? "data:base64," : "", resourceURL };
 				}
@@ -183,7 +183,7 @@ this.docUtil = this.docUtil || (() => {
 					if (charsetValue) {
 						const matchCharset = charsetValue.match(/^charset=(.*)/);
 						if (matchCharset && matchCharset[1]) {
-							charset = modules.docHelper.removeQuotes(matchCharset[1].trim());
+							charset = modules.helper.removeQuotes(matchCharset[1].trim());
 						}
 					}
 				}

+ 21 - 29
lib/single-file/single-file-browser.js → lib/single-file/single-file.js

@@ -21,53 +21,45 @@
  *   Source.
  */
 
-/* global
-	docUtil,
-	cssTree,
+/* global	
 	crypto,
-	docHelper,
 	fetch, 
 	setTimeout, 
-	superFetch, 
 	Blob, 
 	FileReader, 
-	FontFace
-	SingleFileCore, 
+	FontFace,	
 	TextDecoder,
 	TextEncoder */
 
-this.SingleFileBrowser = this.SingleFileBrowser || (() => {
+this.singlefile.lib.getClass = this.singlefile.lib.getClass || (() => {
+
+	const singlefile = this.singlefile;
 
 	const FONT_FACE_TEST_MAX_DELAY = 1000;
 
 	const modules = {
-		srcsetParser: this.srcsetParser,
-		cssMinifier: this.cssMinifier,
-		docHelper: docHelper,
-		htmlMinifier: this.htmlMinifier,
-		serializer: this.serializer,
-		fontsMinifier: this.fontsMinifier && this.fontsMinifier.getInstance(cssTree, this.fontPropertyParser, docHelper),
-		fontsAltMinifier: this.fontsAltMinifier && this.fontsAltMinifier.getInstance(cssTree),
-		cssRulesMinifier: this.cssRulesMinifier && this.cssRulesMinifier.getInstance(cssTree),
-		matchedRules: this.matchedRules && this.matchedRules.getInstance(cssTree),
-		mediasAltMinifier: this.mediasAltMinifier && this.mediasAltMinifier.getInstance(cssTree, this.mediaQueryParser),
-		imagesAltMinifier: this.imagesAltMinifier && this.imagesAltMinifier.getInstance(this.srcsetParser)
+		helper: singlefile.lib.helper,
+		srcsetParser: singlefile.lib.vendor.srcsetParser,
+		cssMinifier: singlefile.lib.vendor.cssMinifier,
+		htmlMinifier: singlefile.lib.modules.htmlMinifier,
+		serializer: singlefile.lib.modules.serializer,
+		fontsMinifier: singlefile.lib.modules.fontsMinifier,
+		fontsAltMinifier: singlefile.lib.modules.fontsAltMinifier,
+		cssRulesMinifier: singlefile.lib.modules.cssRulesMinifier,
+		matchedRules: singlefile.lib.modules.matchedRules,
+		mediasAltMinifier: singlefile.lib.modules.mediasAltMinifier,
+		imagesAltMinifier: singlefile.lib.modules.imagesAltMinifier
 	};
-	const domUtil = {
+	const util = {
 		getResourceContent,
 		isValidFontUrl,
 		digestText
 	};
-	const SingleFile = SingleFileCore.getClass(docUtil.getInstance(modules, domUtil), cssTree);
-	let fetchResource;
-	return {
-		getClass: () => SingleFile
-	};
+	const SingleFile = singlefile.lib.core.getClass(singlefile.lib.util.getInstance(modules, util), singlefile.lib.vendor.cssTree);
+	const fetchResource = (singlefile.lib.fetch.content.resources && singlefile.lib.fetch.content.resources.fetch) || fetch;
+	return () => SingleFile;
 
-	async function getResourceContent(resourceURL) {		
-		if (!fetchResource) {
-			fetchResource = typeof superFetch == "undefined" ? fetch : superFetch.fetch;
-		}
+	async function getResourceContent(resourceURL) {
 		const resourceContent = await fetchResource(resourceURL);
 		const buffer = await resourceContent.arrayBuffer();
 		return {

+ 1 - 1
lib/single-file/vendor/css-font-property-parser.js

@@ -24,7 +24,7 @@
 
 // derived from https://github.com/jedmao/parse-css-font/
 
-this.fontPropertyParser = (() => {
+this.singlefile.lib.vendor.fontPropertyParser = this.singlefile.lib.vendor.fontPropertyParser || (() => {
 
 	const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
 	const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;

+ 1 - 1
lib/single-file/vendor/css-media-query-parser.js

@@ -22,7 +22,7 @@
 
 // derived from https://github.com/dryoma/postcss-media-query-parser
 
-this.mediaQueryParser = (() => {
+this.singlefile.lib.vendor.mediaQueryParser = this.singlefile.lib.vendor.mediaQueryParser || (() => {
 
 	/**
 	 * Parses a media feature expression, e.g. `max-width: 10px`, `(color)`

+ 1 - 1
lib/single-file/vendor/css-minifier.js

@@ -26,7 +26,7 @@
 
 // derived from https://github.com/fmarcia/UglifyCSS
 
-this.cssMinifier = this.cssMinifier || (() => {
+this.singlefile.lib.vendor.cssMinifier = this.singlefile.lib.vendor.cssMinifier || (() => {
 
 	/**
 	 * @type {string} - placeholder prefix

+ 1 - 1
lib/single-file/vendor/css-tree.js

@@ -23,7 +23,7 @@
 
 // derived from https://github.com/csstree/csstree
 
-this.cssTree = this.cssTree || (() => {
+this.singlefile.lib.vendor.cssTree = this.singlefile.lib.vendor.cssTree || (() => {
 
 	function createItem(data) {
 		return {

+ 1 - 1
lib/single-file/vendor/html-srcset-parser.js

@@ -16,7 +16,7 @@
 
 // derived from https://github.com/albell/parse-srcset
 
-this.srcsetParser = this.srcsetParser || (() => {
+this.singlefile.lib.vendor.srcsetParser = this.singlefile.lib.vendor.srcsetParser || (() => {
 
 	return {
 		process

+ 10 - 8
maff2html/back-ends/webdriver-gecko.js

@@ -30,11 +30,12 @@ const firefox = require("selenium-webdriver/firefox");
 const { Builder, By, Key } = require("selenium-webdriver");
 
 const SCRIPTS = [
-	"../../lib/hooks/hooks-frame.js",
+	"../../index.js",
+	"../../lib/hooks/content/content-hooks-frame.js",
 	"../../lib/frame-tree/content/content-frame-tree.js",
 	"../../lib/lazy/content/content-lazy-loader.js",
-	"../../lib/single-file/util/doc-util.js",
-	"../../lib/single-file/util/doc-helper.js",
+	"../../lib/single-file/single-file-util.js",
+	"../../lib/single-file/single-file-helper.js",
 	"../../lib/single-file/vendor/css-tree.js",
 	"../../lib/single-file/vendor/html-srcset-parser.js",
 	"../../lib/single-file/vendor/css-minifier.js",
@@ -49,7 +50,7 @@ const SCRIPTS = [
 	"../../lib/single-file/modules/html-images-alt-minifier.js",
 	"../../lib/single-file/modules/html-serializer.js",
 	"../../lib/single-file/single-file-core.js",
-	"../../lib/single-file/single-file-browser.js"
+	"../../lib/single-file/single-file.js"
 ];
 
 exports.getPageData = async options => {
@@ -97,6 +98,7 @@ exports.getPageData = async options => {
 			}
 		}
 		let scripts = SCRIPTS.map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString().replace(/\n(this)\.([^ ]+) = (this)\.([^ ]+) \|\|/g, "\nwindow.$2 = window.$4 ||")).join("\n");
+		scripts = "window.getFileContent = () => `" + fs.readFileSync(require.resolve("../../lib/hooks/content/content-hooks-web.js")) + "`;" + scripts;
 		if (options.browserDebug) {
 			await driver.findElement(By.css("html")).sendKeys(Key.SHIFT + Key.F5);
 			await driver.sleep(3000);
@@ -144,22 +146,22 @@ function getPageDataScript() {
 		.catch(error => callback({ error: error.toString() }));
 
 	async function getPageData() {
-		docHelper.initDoc(document);
+		singlefile.lib.helper.initDoc(document);
 		options.insertSingleFileComment = true;
 		options.insertFaviconLink = true;
 		const preInitializationPromises = [];
 		if (!options.saveRawPage) {
 			if (!options.removeFrames) {
-				preInitializationPromises.push(frameTree.getAsync(options));
+				preInitializationPromises.push(singlefile.lib.frameTree.content.frames.(options));
 			}
 			if (options.loadDeferredImages) {
-				preInitializationPromises.push(lazyLoader.process(options));
+				preInitializationPromises.push(singlefile.lib.lazy.content.loader.process(options));
 			}
 		}
 		[options.framesData] = await Promise.all(preInitializationPromises);
 		options.doc = document;
 		options.win = window;
-		const SingleFile = SingleFileBrowser.getClass();
+		const SingleFile = singlefile.lib.getClass();
 		const singleFile = new SingleFile(options);
 		await singleFile.run();
 		return await singleFile.getPageData();

+ 23 - 23
manifest.json

@@ -17,12 +17,12 @@
 			],
 			"run_at": "document_start",
 			"js": [
+				"index.js",
 				"lib/browser-polyfill/chrome-browser-polyfill.js",
-				"lib/hooks/hooks-frame.js",
-				"lib/single-file/util/doc-helper.js",
-				"lib/single-file/util/doc-util.js",
-				"lib/frame-tree/content/content-frame-tree.js",
-				"extension/index.js"
+				"lib/hooks/content/content-hooks-frame.js",
+				"lib/single-file/single-file-util.js",
+				"lib/single-file/single-file-helper.js",
+				"lib/frame-tree/content/content-frame-tree.js"
 			],
 			"all_frames": true,
 			"match_about_blank": true
@@ -32,8 +32,8 @@
 				"<all_urls>"
 			],
 			"js": [
-				"extension/index.js",
-				"extension/ui/content/infobar.js"
+				"index.js",
+				"extension/ui/content/content-ui-infobar.js"
 			]
 		},
 		{
@@ -42,38 +42,38 @@
 			],
 			"run_at": "document_start",
 			"js": [
-				"extension/index.js",
-				"lib/hooks/hooks.js",
+				"index.js",
+				"lib/hooks/content/content-hooks.js",
 				"extension/core/content/content-bootstrap.js"
 			]
 		}
 	],
 	"background": {
 		"scripts": [
+			"index.js",
 			"lib/browser-polyfill/chrome-browser-polyfill.js",
-			"lib/fetch/bg/fetch.js",
-			"lib/fetch/content/fetch.js",
-			"lib/frame-tree/bg/bg-frame-tree.js",
-			"extension/index.js",
-			"extension/core/bg/data/config.js",
-			"extension/core/bg/data/tabs-data.js",
+			"lib/fetch/bg/fetch-resources.js",
+			"lib/fetch/content/content-fetch-resources.js",
+			"lib/frame-tree/bg/frame-tree.js",
+			"extension/core/bg/config.js",
+			"extension/core/bg/tabs-data.js",
 			"extension/core/bg/util.js",
-			"extension/core/bg/core.js",
+			"extension/core/bg/business.js",
 			"extension/core/bg/messages.js",
 			"extension/core/bg/tabs.js",
-			"extension/core/bg/download.js",
+			"extension/core/bg/downloads.js",
 			"extension/core/bg/autosave.js",
-			"extension/ui/bg/bg-ui.js",
+			"extension/ui/bg/ui-main.js",
 			"extension/ui/bg/ui-menu.js",
 			"extension/ui/bg/ui-button.js",
-			"lib/lazy/bg/bg-lazy-timeout.js",
+			"lib/lazy/bg/lazy-timeout.js",
 			"lib/single-file/vendor/css-minifier.js",
 			"lib/single-file/vendor/css-tree.js",
 			"lib/single-file/vendor/css-media-query-parser.js",
 			"lib/single-file/vendor/html-srcset-parser.js",
 			"lib/single-file/vendor/css-font-property-parser.js",
-			"lib/single-file/util/doc-helper.js",
-			"lib/single-file/util/doc-util.js",
+			"lib/single-file/single-file-util.js",
+			"lib/single-file/single-file-helper.js",
 			"lib/single-file/modules/css-fonts-minifier.js",
 			"lib/single-file/modules/css-fonts-alt-minifier.js",
 			"lib/single-file/modules/css-medias-alt-minifier.js",
@@ -83,7 +83,7 @@
 			"lib/single-file/modules/html-serializer.js",
 			"lib/single-file/modules/html-images-alt-minifier.js",
 			"lib/single-file/single-file-core.js",
-			"lib/single-file/single-file-browser.js"
+			"lib/single-file/single-file.js"
 		],
 		"persistent": false
 	},
@@ -120,7 +120,7 @@
 		}
 	},
 	"web_accessible_resources": [
-		"lib/hooks/hooks-web.js",
+		"lib/hooks/content/content-hooks-web.js",
 		"extension/core/content/content-download.js",
 		"extension/core/pages/downloader.html"
 	],