Explorar el Código

added "save the page of a newly created bookmark" option (cf issue #320)

Former-commit-id: 24d62117a1fb4c86c9913217e6b187f2b943650b
Gildas hace 6 años
padre
commit
6535a376a6

+ 4 - 0
_locales/de/messages.json

@@ -395,6 +395,10 @@
 		"message": "In Google Drive speichern",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "Die Seite eines neu angelegten Bookmark speichern",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "Hilfe",
 		"description": "Options help link"

+ 4 - 0
_locales/en/messages.json

@@ -395,6 +395,10 @@
 		"message": "save to Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "help",
 		"description": "Options help link"

+ 4 - 0
_locales/es/messages.json

@@ -395,6 +395,10 @@
 		"message": "save en Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "guardar la página de un marcador recién creado",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "ayuda",
 		"description": "Options help link"

+ 4 - 0
_locales/fr/messages.json

@@ -395,6 +395,10 @@
 		"message": "sauvegarder dans Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "sauvegarder la page d'un signet nouvellement créé",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "aide (anglais)",
 		"description": "Options help link"

+ 4 - 0
_locales/ja/messages.json

@@ -395,6 +395,10 @@
 		"message": "Google Drive に保存",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "ヘルプ",
 		"description": "Options help link"

+ 4 - 0
_locales/pl/messages.json

@@ -395,6 +395,10 @@
 		"message": "zapisuj na Dysku Google",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "pomoc (w języku angielskim)",
 		"description": "Options help link"

+ 4 - 0
_locales/ru/messages.json

@@ -395,6 +395,10 @@
 		"message": "сохранить на Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "помощь",
 		"description": "Options help link"

+ 4 - 0
_locales/uk/messages.json

@@ -395,6 +395,10 @@
 		"message": "save to Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "допомога",
 		"description": "Options help link"

+ 4 - 0
_locales/zh_CN/messages.json

@@ -395,6 +395,10 @@
 		"message": "save to Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "帮助",
 		"description": "选项页帮助链接"

+ 4 - 0
_locales/zh_TW/messages.json

@@ -395,6 +395,10 @@
 		"message": "save to Google Drive",
 		"description": "Options page label: 'save to Google Drive'"
 	},
+	"optionSaveCreatedBookmarks": {
+		"message": "save the page of a newly created bookmark",
+		"description": "Options page label: 'save the page of a newly created bookmark'"
+	},
 	"optionsHelpLink": {
 		"message": "幫助",
 		"description": "選項頁幫助鏈接"

+ 95 - 0
extension/core/bg/bookmarks.js

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010-2019 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   The code in this file is free software: you can redistribute it and/or 
+ *   modify it under the terms of the GNU Affero General Public License 
+ *   (GNU AGPL) as published by the Free Software Foundation, either version 3
+ *   of the License, or (at your option) any later version.
+ * 
+ *   The code in this file is distributed in the hope that it will be useful, 
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
+ *   General Public License for more details.
+ *
+ *   As additional permission under GNU AGPL version 3 section 7, you may 
+ *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
+ *   AGPL normally required by section 4, provided you include this license 
+ *   notice and a URL through which recipients can access the Corresponding 
+ *   Source.
+ */
+
+/* global singlefile, browser */
+
+singlefile.extension.core.bg.bookmarks = (() => {
+
+	onInit();
+	return {
+		onMessage,
+		saveCreatedBookmarks: enable,
+		disable
+	};
+
+	async function onInit() {
+		enable();
+	}
+
+	async function onMessage(message) {
+		if (message.method.endsWith(".saveCreatedBookmarks")) {
+			enable();
+			return {};
+		}
+		if (message.method.endsWith(".disable")) {
+			disable();
+			return {};
+		}
+	}
+
+	async function enable() {
+		try {
+			browser.bookmarks.onCreated.removeListener(onCreated);
+		} catch (error) {
+			// ignored
+		}
+		let enabled;
+		const profiles = await singlefile.extension.core.bg.config.getProfiles();
+		Object.keys(profiles).forEach(profileName => {
+			if (profiles[profileName].saveCreatedBookmarks) {
+				enabled = true;
+			}
+		});
+		if (enabled) {
+			browser.bookmarks.onCreated.addListener(onCreated);
+		}
+	}
+
+	async function disable() {
+		let disabled;
+		const profiles = await singlefile.extension.core.bg.config.getProfiles();
+		Object.keys(profiles).forEach(profileName => disabled = disabled || !profiles[profileName].saveCreatedBookmarks);
+		if (disabled) {
+			browser.bookmarks.onCreated.removeListener(onCreated);
+		}
+	}
+
+	async function onCreated(id, bookmarkInfo) {
+		const tabs = await singlefile.extension.core.bg.tabs.get({ lastFocusedWindow: true, active: true });
+		const options = await singlefile.extension.core.bg.config.getOptions(bookmarkInfo.url);
+		if (options.saveCreatedBookmarks) {
+			if (tabs.length && tabs[0].url == bookmarkInfo.url) {
+				singlefile.extension.core.bg.business.saveTabs(tabs);
+			} else {
+				const tabs = await singlefile.extension.core.bg.tabs.get({});
+				if (tabs.length) {
+					const tab = tabs.find(tab => tab.url == bookmarkInfo.url);
+					if (tab) {
+						singlefile.extension.core.bg.business.saveTabs([tab]);
+					}
+				}
+			}
+		}
+	}
+
+})();

+ 2 - 1
extension/core/bg/config.js

@@ -85,7 +85,8 @@ singlefile.extension.core.bg.config = (() => {
 		resolveFragmentIdentifierURLs: false,
 		userScriptEnabled: false,
 		openEditor: false,
-		autoOpenEditor: false
+		autoOpenEditor: false,
+		saveCreatedBookmarks: false
 	};
 
 	let configStorage;

+ 3 - 0
extension/core/bg/messages.js

@@ -50,6 +50,9 @@ singlefile.extension.core.bg.messages = (() => {
 		if (message.method.startsWith("editor.")) {
 			return singlefile.extension.core.bg.editor.onMessage(message, sender);
 		}
+		if (message.method.startsWith("bookmarks.")) {
+			return singlefile.extension.core.bg.bookmarks.onMessage(message, sender);
+		}
 	});
 	if (browser.runtime.onMessageExternal) {
 		browser.runtime.onMessageExternal.addListener(async (message, sender) => {

+ 41 - 35
extension/lib/single-file/browser-polyfill/chrome-browser-polyfill.js

@@ -37,7 +37,7 @@
 						try {
 							nativeAPI.browserAction.setBadgeText(options, () => {
 								if (nativeAPI.runtime.lastError) {
-									reject(nativeAPI.runtime.lastError);
+									reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 								} else {
 									resolve();
 								}
@@ -49,7 +49,7 @@
 					if (FEATURE_TESTS["browserAction.setBadgeText"] && FEATURE_TESTS["browserAction.setBadgeText"].callbackNotSupported) {
 						nativeAPI.browserAction.setBadgeText(options);
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -60,7 +60,7 @@
 						try {
 							nativeAPI.browserAction.setBadgeBackgroundColor(options, () => {
 								if (nativeAPI.runtime.lastError) {
-									reject(nativeAPI.runtime.lastError);
+									reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 								} else {
 									resolve();
 								}
@@ -72,7 +72,7 @@
 					if (FEATURE_TESTS["browserAction.setBadgeBackgroundColor"] && FEATURE_TESTS["browserAction.setBadgeBackgroundColor"].callbackNotSupported) {
 						nativeAPI.browserAction.setBadgeBackgroundColor(options);
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -83,7 +83,7 @@
 						try {
 							nativeAPI.browserAction.setTitle(options, () => {
 								if (nativeAPI.runtime.lastError) {
-									reject(nativeAPI.runtime.lastError);
+									reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 								} else {
 									resolve();
 								}
@@ -95,7 +95,7 @@
 					if (FEATURE_TESTS["browserAction.setTitle"] && FEATURE_TESTS["browserAction.setTitle"].callbackNotSupported) {
 						nativeAPI.browserAction.setTitle(options);
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -106,7 +106,7 @@
 						try {
 							nativeAPI.browserAction.setIcon(options, () => {
 								if (nativeAPI.runtime.lastError) {
-									reject(nativeAPI.runtime.lastError);
+									reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 								} else {
 									resolve();
 								}
@@ -118,13 +118,19 @@
 					if (FEATURE_TESTS["browserAction.setIcon"] && FEATURE_TESTS["browserAction.setIcon"].callbackNotSupported) {
 						nativeAPI.browserAction.setIcon(options);
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
 					}
 				})
 			},
+			bookmarks: {
+				onCreated: {
+					addListener: listener => nativeAPI.bookmarks.onCreated.addListener(listener),
+					removeListener: listener => nativeAPI.bookmarks.onCreated.removeListener(listener)
+				}
+			},
 			commands: {
 				onCommand: {
 					addListener: listener => nativeAPI.commands.onCommand.addListener(listener)
@@ -134,7 +140,7 @@
 				download: options => new Promise((resolve, reject) => {
 					nativeAPI.downloads.download(options, downloadId => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(downloadId);
 						}
@@ -153,7 +159,7 @@
 					return nativeAPI.identity && nativeAPI.identity.getAuthToken && (details => new Promise((resolve, reject) =>
 						nativeAPI.identity.getAuthToken(details, token => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve(token);
 							}
@@ -164,7 +170,7 @@
 					return nativeAPI.identity && nativeAPI.identity.launchWebAuthFlow && (options => new Promise((resolve, reject) => {
 						nativeAPI.identity.launchWebAuthFlow(options, responseUrl => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve(responseUrl);
 							}
@@ -175,7 +181,7 @@
 					return nativeAPI.identity && nativeAPI.identity.removeCachedAuthToken && (details => new Promise((resolve, reject) =>
 						nativeAPI.identity.removeCachedAuthToken(details, () => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -191,7 +197,7 @@
 				update: (menuItemId, options) => new Promise((resolve, reject) => {
 					nativeAPI.contextMenus.update(menuItemId, options, () => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -200,7 +206,7 @@
 				removeAll: () => new Promise((resolve, reject) => {
 					nativeAPI.contextMenus.removeAll(() => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -209,11 +215,11 @@
 			},
 			permissions: {
 				request: permissions => new Promise((resolve, reject) => {
-					nativeAPI.permissions.request(permissions, () => {
+					nativeAPI.permissions.request(permissions, result => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
-							resolve();
+							resolve(result);
 						}
 					});
 				})
@@ -260,13 +266,13 @@
 				sendMessage: message => new Promise((resolve, reject) => {
 					nativeAPI.runtime.sendMessage(message, response => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(response);
 						}
 					});
 					if (nativeAPI.runtime.lastError) {
-						reject(nativeAPI.runtime.lastError);
+						reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 					}
 				}),
 				getURL: (path) => nativeAPI.runtime.getURL(path),
@@ -279,7 +285,7 @@
 					set: value => new Promise((resolve, reject) => {
 						nativeAPI.storage.local.set(value, () => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -288,7 +294,7 @@
 					get: () => new Promise((resolve, reject) => {
 						nativeAPI.storage.local.get(null, value => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve(value);
 							}
@@ -297,7 +303,7 @@
 					clear: () => new Promise((resolve, reject) => {
 						nativeAPI.storage.local.clear(() => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -306,7 +312,7 @@
 					remove: keys => new Promise((resolve, reject) => {
 						nativeAPI.storage.local.remove(keys, () => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -317,7 +323,7 @@
 					set: value => new Promise((resolve, reject) => {
 						nativeAPI.storage.sync.set(value, () => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -326,7 +332,7 @@
 					get: () => new Promise((resolve, reject) => {
 						nativeAPI.storage.sync.get(null, value => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve(value);
 							}
@@ -335,7 +341,7 @@
 					clear: () => new Promise((resolve, reject) => {
 						nativeAPI.storage.sync.clear(() => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -344,7 +350,7 @@
 					remove: keys => new Promise((resolve, reject) => {
 						nativeAPI.storage.sync.remove(keys, () => {
 							if (nativeAPI.runtime.lastError) {
-								reject(nativeAPI.runtime.lastError);
+								reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 							} else {
 								resolve();
 							}
@@ -370,7 +376,7 @@
 				executeScript: (tabId, details) => new Promise((resolve, reject) => {
 					nativeAPI.tabs.executeScript(tabId, details, () => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -379,19 +385,19 @@
 				sendMessage: (tabId, message, options = {}) => new Promise((resolve, reject) => {
 					nativeAPI.tabs.sendMessage(tabId, message, options, response => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(response);
 						}
 					});
 					if (nativeAPI.runtime.lastError) {
-						reject(nativeAPI.runtime.lastError);
+						reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 					}
 				}),
 				query: options => new Promise((resolve, reject) => {
 					nativeAPI.tabs.query(options, tabs => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(tabs);
 						}
@@ -400,7 +406,7 @@
 				create: createProperties => new Promise((resolve, reject) => {
 					nativeAPI.tabs.create(createProperties, tab => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(tab);
 						}
@@ -409,7 +415,7 @@
 				get: options => new Promise((resolve, reject) => {
 					nativeAPI.tabs.get(options, tab => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(tab);
 						}
@@ -418,7 +424,7 @@
 				remove: tabId => new Promise((resolve, reject) => {
 					nativeAPI.tabs.remove(tabId, () => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve();
 						}
@@ -427,7 +433,7 @@
 				update: (tabId, updateProperties) => new Promise((resolve, reject) => {
 					nativeAPI.tabs.update(tabId, updateProperties, tab => {
 						if (nativeAPI.runtime.lastError) {
-							reject(nativeAPI.runtime.lastError);
+							reject(nativeAPI.runtime.lastError.message || nativeAPI.runtime.lastError);
 						} else {
 							resolve(tab);
 						}

+ 39 - 1
extension/ui/bg/ui-options.js

@@ -66,6 +66,7 @@
 	const removeAlternativeFontsLabel = document.getElementById("removeAlternativeFontsLabel");
 	const removeAlternativeImagesLabel = document.getElementById("removeAlternativeImagesLabel");
 	const removeAlternativeMediasLabel = document.getElementById("removeAlternativeMediasLabel");
+	const saveCreatedBookmarksLabel = document.getElementById("saveCreatedBookmarksLabel");
 	const titleLabel = document.getElementById("titleLabel");
 	const userInterfaceLabel = document.getElementById("userInterfaceLabel");
 	const filenameLabel = document.getElementById("filenameLabel");
@@ -134,6 +135,7 @@
 	const removeAlternativeFontsInput = document.getElementById("removeAlternativeFontsInput");
 	const removeAlternativeImagesInput = document.getElementById("removeAlternativeImagesInput");
 	const removeAlternativeMediasInput = document.getElementById("removeAlternativeMediasInput");
+	const saveCreatedBookmarksInput = document.getElementById("saveCreatedBookmarksInput");
 	const groupDuplicateImagesInput = document.getElementById("groupDuplicateImagesInput");
 	const infobarTemplateInput = document.getElementById("infobarTemplateInput");
 	const includeInfobarInput = document.getElementById("includeInfobarInput");
@@ -354,6 +356,7 @@
 			removeUnusedStylesInput.checked = false;
 		}
 	}, false);
+	saveCreatedBookmarksInput.addEventListener("click", saveCreatedBookmarks, false);
 	saveToGDriveInput.addEventListener("click", async () => {
 		if (!saveToGDriveInput.checked) {
 			await browser.runtime.sendMessage({ method: "downloads.disableGDrive" });
@@ -380,7 +383,14 @@
 	}, false);
 	document.body.onchange = async event => {
 		let target = event.target;
-		if (target != ruleUrlInput && target != ruleProfileInput && target != ruleAutoSaveProfileInput && target != ruleEditUrlInput && target != ruleEditProfileInput && target != ruleEditAutoSaveProfileInput && target != showAutoSaveProfileInput) {
+		if (target != ruleUrlInput &&
+			target != ruleProfileInput &&
+			target != ruleAutoSaveProfileInput &&
+			target != ruleEditUrlInput &&
+			target != ruleEditProfileInput &&
+			target != ruleEditAutoSaveProfileInput &&
+			target != showAutoSaveProfileInput &&
+			target != saveCreatedBookmarksInput) {
 			if (target != profileNamesInput && target != showAllProfilesInput) {
 				await update();
 			}
@@ -444,6 +454,7 @@
 	removeAlternativeFontsLabel.textContent = browser.i18n.getMessage("optionRemoveAlternativeFonts");
 	removeAlternativeImagesLabel.textContent = browser.i18n.getMessage("optionRemoveAlternativeImages");
 	removeAlternativeMediasLabel.textContent = browser.i18n.getMessage("optionRemoveAlternativeMedias");
+	saveCreatedBookmarksLabel.textContent = browser.i18n.getMessage("optionSaveCreatedBookmarks");
 	groupDuplicateImagesLabel.textContent = browser.i18n.getMessage("optionGroupDuplicateImages");
 	titleLabel.textContent = browser.i18n.getMessage("optionsTitle");
 	userInterfaceLabel.textContent = browser.i18n.getMessage("optionsUserInterfaceSubTitle");
@@ -631,6 +642,7 @@
 		removeAlternativeImagesInput.checked = profileOptions.removeAlternativeImages;
 		groupDuplicateImagesInput.checked = profileOptions.groupDuplicateImages;
 		removeAlternativeMediasInput.checked = profileOptions.removeAlternativeMedias;
+		saveCreatedBookmarksInput.checked = profileOptions.saveCreatedBookmarks;
 		infobarTemplateInput.value = profileOptions.infobarTemplate;
 		includeInfobarInput.checked = profileOptions.includeInfobar;
 		confirmInfobarInput.checked = profileOptions.confirmInfobarContent;
@@ -690,6 +702,7 @@
 				removeAlternativeFonts: removeAlternativeFontsInput.checked,
 				removeAlternativeImages: removeAlternativeImagesInput.checked,
 				removeAlternativeMedias: removeAlternativeMediasInput.checked,
+				saveCreatedBookmarks: saveCreatedBookmarksInput.checked,
 				groupDuplicateImages: groupDuplicateImagesInput.checked,
 				infobarTemplate: infobarTemplateInput.value,
 				includeInfobar: includeInfobarInput.checked,
@@ -715,6 +728,31 @@
 		}
 	}
 
+	async function saveCreatedBookmarks() {
+		if (saveCreatedBookmarksInput.checked) {
+			saveCreatedBookmarksInput.checked = false;
+			try {
+				const permissionGranted = await browser.permissions.request({ permissions: ["bookmarks"] });
+				if (permissionGranted) {
+					saveCreatedBookmarksInput.checked = true;
+					await update();
+					await browser.runtime.sendMessage({ method: "bookmarks.saveCreatedBookmarks" });
+				} else {
+					await disableOption();
+				}
+			} catch (error) {
+				await disableOption();
+			}
+		} else {
+			await disableOption();
+		}
+
+		async function disableOption() {
+			await update();
+			await browser.runtime.sendMessage({ method: "bookmarks.disable" });
+		}
+	}
+
 	async function confirm(message, position = "center") {
 		document.getElementById("confirmLabel").textContent = message;
 		document.getElementById("formConfirmContainer").style.setProperty("display", "flex");

+ 9 - 0
extension/ui/pages/help.html

@@ -412,6 +412,15 @@
 						</p>
 						<p class="notice">It is recommended to <u>uncheck</u> this option</p>
 					</li>
+					<li data-options-label="saveCreatedBookmarksLabel"> <span class="option">Option: save the page of a
+							newly created bookmark</span>
+						<p>
+							Check this option to save pages that you add into your bookmarks. Note that a page will be
+							saved if and only if the page is already displayed in a tab when adding it into the
+							bookmarks.
+						</p>
+						<p class="notice">It is recommended to <u>uncheck</u> this option</p>
+					</li>
 				</ul>
 				<p id="auto-settings-rules">Auto-settings rules</p>
 				<ul>

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

@@ -222,6 +222,10 @@
 				<label for="saveToGDriveInput" id="saveToGDriveLabel"></label>
 				<input type="checkbox" id="saveToGDriveInput">
 			</div>
+			<div class="option">
+				<label for="saveCreatedBookmarksInput" id="saveCreatedBookmarksLabel"></label>
+				<input type="checkbox" id="saveCreatedBookmarksInput">
+			</div>
 		</details>
 		<details>
 			<summary id="autoSettingsLabel"></summary>

+ 3 - 1
manifest.json

@@ -94,6 +94,7 @@
 			"extension/core/bg/autosave.js",
 			"extension/core/bg/devtools.js",
 			"extension/core/bg/editor.js",
+			"extension/core/bg/bookmarks.js",
 			"extension/ui/bg/ui-main.js",
 			"extension/ui/bg/ui-menus.js",
 			"extension/ui/bg/ui-commands.js",
@@ -165,7 +166,8 @@
 		"<all_urls>"
 	],
 	"optional_permissions": [
-		"identity"
+		"identity",
+		"bookmarks"
 	],
 	"applications": {
 		"gecko": {