From 0d52930c5b3a873fb32768c25f75ffeba5a1c16c Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Sat, 12 Feb 2022 18:41:13 +0100 Subject: [PATCH 01/10] Create right-click menu --- README.md | 8 +++++ src/background.js | 64 ++++++++++++++++++++++++++++++++++++++ src/manifest-chromium.json | 3 +- src/manifest-firefox.json | 3 +- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48450ef8..fc499c7d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ In order to use Browserpass you must also install a [companion native messaging - [Organizing password store](#organizing-password-store) - [First steps in browser extension](#first-steps-in-browser-extension) - [Available keyboard shortcuts](#available-keyboard-shortcuts) + - [Usage via right-click menu](#usage-via-right-click-menu) - [Password matching and sorting](#password-matching-and-sorting) - [Searching password entries](#searching-password-entries) - [OpenID authentication](#openid-authentication) @@ -160,6 +161,12 @@ Note: If the cursor is located in the search input field, every shortcut that wo | Ctrl+Shift+G | Open URL in the new tab | | Backspace (with no search text entered) | Search passwords in the entire password store | +### Usage via right-click menu + +You can right-click anywhere a visited website and there will appear a menu with an option `Browserpass - entries`, where `n` is the number of entries that match the host of the visited website. When you select an entry, that one gets automatically filled in, equivalent to the behavior when an entry is selected from the Browserpass popup. This can be helpful if you want to fill credentials in a browser popup window without extension buttons. Selecting single form fields and choosing values to fill in is currently not supported + +![The right-click menu of browserpass](https://user-images.githubusercontent.com/15818773/153720814-66b99653-3b8a-456d-a55a-8dd208e66028.gif) + ### Password matching and sorting When you first open the Browserpass popup, you will see a badge with the current domain name in the search input field: @@ -300,6 +307,7 @@ Browserpass extension requests the following permissions: | `tabs` | To get URL of a given tab, used for example to set count of the matching passwords for a given tab | | `clipboardRead` | To ensure only copied credentials and not other content is cleared from the clipboard after 60 seconds | | `clipboardWrite` | For "Copy password" and "Copy username" functionality | +| `contextMenus` | To create a context menu, also called right-click menu | | `nativeMessaging` | To allow communication with the native app | | `notifications` | To show browser notifications on install or update | | `webRequest` | For modal HTTP authentication | diff --git a/src/background.js b/src/background.js index 70031c5a..7b48885f 100644 --- a/src/background.js +++ b/src/background.js @@ -41,6 +41,8 @@ chrome.browserAction.setBadgeBackgroundColor({ chrome.tabs.onUpdated.addListener((tabId, info) => { // unregister any auth listeners for this tab if (info.status === "complete") { + createContextMenu(); + if (authListeners[tabId]) { chrome.webRequest.onAuthRequired.removeListener(authListeners[tabId]); delete authListeners[tabId]; @@ -51,6 +53,10 @@ chrome.tabs.onUpdated.addListener((tabId, info) => { updateMatchingPasswordsCount(tabId); }); +chrome.tabs.onActivated.addListener(() => { + createContextMenu(); +}); + // handle incoming messages chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { receiveMessage(message, sender, sendResponse); @@ -1156,3 +1162,61 @@ function onExtensionInstalled(details) { }); } } + +/** + * Create a context menu, also called right-click menu + * + * @since 3.8.0 + * + * @return void + */ +async function createContextMenu() { + await chrome.contextMenus.removeAll(); + + const menuEntryProps = { + contexts: ["all"], + type: "normal", + }; + const menuEntryId = "menuEntry"; + + const settings = await getFullSettings(); + const response = await hostAction(settings, "list"); + + if (response.status != "ok") { + throw new Error(JSON.stringify(response)); + } + const files = helpers.ignoreFiles(response.data.files, settings); + const logins = helpers.prepareLogins(files, settings); + const loginsForThisHost = helpers.filterSortLogins(logins, "", true); + const numberOfLoginsForThisHost = loginsForThisHost.length; + const singularOrPlural = numberOfLoginsForThisHost === 1 ? 'entry' : 'entries' + + await chrome.contextMenus.create({ + ...menuEntryProps, + title: `Browserpass - ${numberOfLoginsForThisHost} ${singularOrPlural}`, + id: menuEntryId, + }); + + for (let i = 0; i < numberOfLoginsForThisHost; i++) { + await chrome.contextMenus.create({ + ...menuEntryProps, + parentId: menuEntryId, + id: "login" + i, + title: loginsForThisHost[i].login, + onclick: () => clickMenuEntry(settings, loginsForThisHost[i]), + }); + } +} + +/** + * Handle the click of a context menu item + * + * @since 3.8.0 + * + * @param object settings Full settings object + * @param array login Filtered and sorted list of logins + * @return void + */ +async function clickMenuEntry(settings, login) { + await handleMessage(settings, { action: "fill", login }, () => {}); +} diff --git a/src/manifest-chromium.json b/src/manifest-chromium.json index c3e5134f..f5b70dfa 100644 --- a/src/manifest-chromium.json +++ b/src/manifest-chromium.json @@ -37,7 +37,8 @@ "webRequest", "webRequestBlocking", "http://*/*", - "https://*/*" + "https://*/*", + "contextMenus" ], "content_security_policy": "default-src 'none'; font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self'", "commands": { diff --git a/src/manifest-firefox.json b/src/manifest-firefox.json index a7481aa2..3214cbef 100644 --- a/src/manifest-firefox.json +++ b/src/manifest-firefox.json @@ -35,7 +35,8 @@ "webRequest", "webRequestBlocking", "http://*/*", - "https://*/*" + "https://*/*", + "contextMenus" ], "content_security_policy": "default-src 'none'; font-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self'", "applications": { From 8874b79a5aa0be94537dccf907f91359da78d412 Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Sat, 12 Feb 2022 21:07:27 +0100 Subject: [PATCH 02/10] Don't show menu entry when there are zero matching logins --- src/background.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/background.js b/src/background.js index 7b48885f..45fa97fe 100644 --- a/src/background.js +++ b/src/background.js @@ -1189,22 +1189,24 @@ async function createContextMenu() { const logins = helpers.prepareLogins(files, settings); const loginsForThisHost = helpers.filterSortLogins(logins, "", true); const numberOfLoginsForThisHost = loginsForThisHost.length; - const singularOrPlural = numberOfLoginsForThisHost === 1 ? 'entry' : 'entries' + const singularOrPlural = numberOfLoginsForThisHost === 1 ? "entry" : "entries"; - await chrome.contextMenus.create({ - ...menuEntryProps, - title: `Browserpass - ${numberOfLoginsForThisHost} ${singularOrPlural}`, - id: menuEntryId, - }); - - for (let i = 0; i < numberOfLoginsForThisHost; i++) { + if (numberOfLoginsForThisHost > 0) { await chrome.contextMenus.create({ ...menuEntryProps, - parentId: menuEntryId, - id: "login" + i, - title: loginsForThisHost[i].login, - onclick: () => clickMenuEntry(settings, loginsForThisHost[i]), + title: `Browserpass - ${numberOfLoginsForThisHost} ${singularOrPlural}`, + id: menuEntryId, }); + + for (let i = 0; i < numberOfLoginsForThisHost; i++) { + await chrome.contextMenus.create({ + ...menuEntryProps, + parentId: menuEntryId, + id: "login" + i, + title: loginsForThisHost[i].login, + onclick: () => clickMenuEntry(settings, loginsForThisHost[i]), + }); + } } } From 646c7e53071e55421cd5ad1e02835cf7b5886752 Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Sat, 12 Feb 2022 21:12:17 +0100 Subject: [PATCH 03/10] Static menu title --- src/background.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/background.js b/src/background.js index 45fa97fe..1c580e6e 100644 --- a/src/background.js +++ b/src/background.js @@ -1189,12 +1189,11 @@ async function createContextMenu() { const logins = helpers.prepareLogins(files, settings); const loginsForThisHost = helpers.filterSortLogins(logins, "", true); const numberOfLoginsForThisHost = loginsForThisHost.length; - const singularOrPlural = numberOfLoginsForThisHost === 1 ? "entry" : "entries"; if (numberOfLoginsForThisHost > 0) { await chrome.contextMenus.create({ ...menuEntryProps, - title: `Browserpass - ${numberOfLoginsForThisHost} ${singularOrPlural}`, + title: `Browserpass`, id: menuEntryId, }); From 3a7f61dcca971425a08baeb741febbdad291eb63 Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Sat, 12 Feb 2022 21:25:28 +0100 Subject: [PATCH 04/10] trow error + fix docs --- src/background.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/background.js b/src/background.js index 1c580e6e..868b3be4 100644 --- a/src/background.js +++ b/src/background.js @@ -1215,9 +1215,13 @@ async function createContextMenu() { * @since 3.8.0 * * @param object settings Full settings object - * @param array login Filtered and sorted list of logins + * @param object login Login object * @return void */ async function clickMenuEntry(settings, login) { - await handleMessage(settings, { action: "fill", login }, () => {}); + await handleMessage(settings, { action: "fill", login }, (response) => { + if (response.status != "ok") { + throw new Error(JSON.stringify(response)); + } + }); } From 025c98ef11b13fd73a75dd024e376e3a616425b4 Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Mon, 21 Feb 2022 21:27:13 +0100 Subject: [PATCH 05/10] Fix slow load & tab switch issue --- src/background.js | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/background.js b/src/background.js index 868b3be4..b2c9c36a 100644 --- a/src/background.js +++ b/src/background.js @@ -30,6 +30,11 @@ var badgeCache = { isRefreshing: false, }; +let contextMenu = { + activeTabId: null, + isRefreshing: false, +}; + // the last text copied to the clipboard is stored here in order to be cleared after 60 seconds let lastCopiedText = null; @@ -41,8 +46,6 @@ chrome.browserAction.setBadgeBackgroundColor({ chrome.tabs.onUpdated.addListener((tabId, info) => { // unregister any auth listeners for this tab if (info.status === "complete") { - createContextMenu(); - if (authListeners[tabId]) { chrome.webRequest.onAuthRequired.removeListener(authListeners[tabId]); delete authListeners[tabId]; @@ -51,10 +54,14 @@ chrome.tabs.onUpdated.addListener((tabId, info) => { // redraw badge counter updateMatchingPasswordsCount(tabId); + + // create right-click menu + createContextMenu(tabId); }); -chrome.tabs.onActivated.addListener(() => { - createContextMenu(); +chrome.tabs.onActivated.addListener((parameters) => { + contextMenu.activeTabId = parameters.tabId; + createContextMenu(parameters.tabId); }); // handle incoming messages @@ -1170,21 +1177,27 @@ function onExtensionInstalled(details) { * * @return void */ -async function createContextMenu() { +async function createContextMenu(tabId) { + if ( + (contextMenu.isRefreshing && tabId === contextMenu.activeTabId) || + contextMenu.activeTabId !== tabId + ) { + return; + } + contextMenu.isRefreshing = true; await chrome.contextMenus.removeAll(); - const menuEntryProps = { contexts: ["all"], type: "normal", }; - const menuEntryId = "menuEntry"; - + const parentId = "parent"; const settings = await getFullSettings(); const response = await hostAction(settings, "list"); if (response.status != "ok") { throw new Error(JSON.stringify(response)); } + const files = helpers.ignoreFiles(response.data.files, settings); const logins = helpers.prepareLogins(files, settings); const loginsForThisHost = helpers.filterSortLogins(logins, "", true); @@ -1193,20 +1206,21 @@ async function createContextMenu() { if (numberOfLoginsForThisHost > 0) { await chrome.contextMenus.create({ ...menuEntryProps, - title: `Browserpass`, - id: menuEntryId, + title: "Browserpass", + id: parentId, }); for (let i = 0; i < numberOfLoginsForThisHost; i++) { await chrome.contextMenus.create({ ...menuEntryProps, - parentId: menuEntryId, - id: "login" + i, + parentId: parentId, + id: "child" + i, title: loginsForThisHost[i].login, onclick: () => clickMenuEntry(settings, loginsForThisHost[i]), }); } } + contextMenu.isRefreshing = false; } /** From fcc3d3955af7ea688cf256714bbbdeec058dd64a Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Mon, 21 Feb 2022 21:55:21 +0100 Subject: [PATCH 06/10] Update GIF --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc499c7d..827d578a 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Note: If the cursor is located in the search input field, every shortcut that wo You can right-click anywhere a visited website and there will appear a menu with an option `Browserpass - entries`, where `n` is the number of entries that match the host of the visited website. When you select an entry, that one gets automatically filled in, equivalent to the behavior when an entry is selected from the Browserpass popup. This can be helpful if you want to fill credentials in a browser popup window without extension buttons. Selecting single form fields and choosing values to fill in is currently not supported -![The right-click menu of browserpass](https://user-images.githubusercontent.com/15818773/153720814-66b99653-3b8a-456d-a55a-8dd208e66028.gif) +![The right-click menu of browserpass](https://user-images.githubusercontent.com/15818773/155025065-15cdc54e-2d24-46fc-886d-c83881d2ea76.gif) ### Password matching and sorting From 48a3d3598f91ada1dbcdde1bb85205ce76e9ebe6 Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Tue, 28 Mar 2023 15:27:17 +0200 Subject: [PATCH 07/10] Cover edge cases for context menu creation --- src/background.js | 284 +++++++++++++++++++++++++++++++++------------- 1 file changed, 202 insertions(+), 82 deletions(-) diff --git a/src/background.js b/src/background.js index 651eef74..d165c7db 100644 --- a/src/background.js +++ b/src/background.js @@ -10,6 +10,10 @@ const helpers = require("./helpers"); // native application id var appID = "com.github.browserpass.native"; +const INTERNAL_PAGES = /^(chrome|about):/; +const CACHE_TTL_MS = 60 * 1000; +const CONTEXT_MENU_PARENT = "ContextMenuParent"; + // default settings var defaultSettings = { autoSubmit: false, @@ -30,10 +34,8 @@ var badgeCache = { isRefreshing: false, }; -let contextMenu = { - activeTabId: null, - isRefreshing: false, -}; +// stores login data per tab, for use in context menu +let contextMenuCache = {}; // the last text copied to the clipboard is stored here in order to be cleared after 60 seconds let lastCopiedText = null; @@ -43,7 +45,7 @@ chrome.browserAction.setBadgeBackgroundColor({ }); // watch for tab updates -chrome.tabs.onUpdated.addListener((tabId, info) => { +chrome.tabs.onUpdated.addListener(async (tabId, info) => { // unregister any auth listeners for this tab if (info.status === "complete") { if (authListeners[tabId]) { @@ -55,15 +57,204 @@ chrome.tabs.onUpdated.addListener((tabId, info) => { // redraw badge counter updateMatchingPasswordsCount(tabId); - // create right-click menu - createContextMenu(tabId); + // update context menu + await updateContextMenu(tabId.toString()); +}); + +chrome.contextMenus.create({ + contexts: ["all"], + id: CONTEXT_MENU_PARENT, + title: "Browserpass", + type: "normal", + visible: false, }); -chrome.tabs.onActivated.addListener((parameters) => { - contextMenu.activeTabId = parameters.tabId; - createContextMenu(parameters.tabId); +chrome.tabs.onActivated.addListener(async (activeInfo) => { + await chrome.contextMenus.update(CONTEXT_MENU_PARENT, { + visible: false, + }); + await updateContextMenu(activeInfo.tabId.toString()); }); +/** + * Update the context menu + * + * @since 3.8.0 + * + * @param string tabId ID of the Tab + * @return void + */ + +async function updateContextMenu(tabId) { + let tabUrl = null; + + await chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) { + tabUrl = tabs[0].url; + }); + + if ( + (contextMenuCache[tabId]?.tabUrl !== tabUrl && tabUrl.match(INTERNAL_PAGES)) || + tabUrl.match(INTERNAL_PAGES) + ) { + await chrome.contextMenus.update(CONTEXT_MENU_PARENT, { + visible: false, + }); + return; + } + + if (contextMenuCache[tabId]?.isRefreshing || tabUrl === "") { + return; + } + + contextMenuCache[tabId] = { ...contextMenuCache[tabId], isRefreshing: true }; + + if (contextMenuCache[tabId]?.tabUrl !== tabUrl && contextMenuCache[tabId]?.children) { + const oldChildren = contextMenuCache[tabId].children; + + if (oldChildren?.length) { + Promise.all( + oldChildren.map(async (children) => { + await chrome.contextMenus.remove(children.id); + }) + ); + } + contextMenuCache[tabId].children = []; + contextMenuCache[tabId].expires = Date.now(); + } + + if (Date.now() < contextMenuCache[tabId]?.expires) { + await changeContextMenuChildrenVisibility(tabId); + contextMenuCache[tabId].isRefreshing = false; + return; + } + + contextMenuCache[tabId] = { + ...contextMenuCache[tabId], + expires: Date.now() + CACHE_TTL_MS, + tabUrl, + }; + + const settings = await getFullSettings(); + const response = await hostAction(settings, "list"); + + if (response.status != "ok") { + throw new Error(JSON.stringify(response)); + } + + const files = helpers.ignoreFiles(response.data.files, settings); + const logins = helpers.prepareLogins(files, settings); + const loginsForThisHost = helpers.filterSortLogins(logins, "", true); + + contextMenuCache[tabId] = { + ...contextMenuCache[tabId], + loginsForThisHost, + settings, + }; + + await createContextMenuChildren(tabId); + contextMenuCache[tabId].isRefreshing = false; +} + +/** + * Create context menu children + * + * @since 3.8.0 + * + * @param string tabId ID of the Tab + * @return void + */ +async function createContextMenuChildren(tabId) { + const loginsForThisHost = contextMenuCache[tabId].loginsForThisHost; + const settings = contextMenuCache[tabId].settings; + + if (loginsForThisHost.length > 0) { + try { + contextMenuCache[tabId].children = []; + + await Promise.all( + loginsForThisHost.map(async (logins, index) => { + const contextMenuChild = { + contexts: ["all"], + id: `child_${tabId}_${index}`, + onclick: () => clickMenuEntry(settings, logins), + parentId: CONTEXT_MENU_PARENT, + title: logins.login, + type: "normal", + visible: true, + }; + + await chrome.contextMenus.create(contextMenuChild); + contextMenuCache[tabId].children.push(contextMenuChild); + }) + ); + } catch (e) { + console.log(e); + } + } + + await changeContextMenuChildrenVisibility(tabId); +} + +/** + * Change the visibility of the context menu's child items + * + * @since 3.8.0 + * + * @param string tabId ID of the Tab + * @return void + */ +async function changeContextMenuChildrenVisibility(tabId) { + const keys = Object.keys(contextMenuCache); + let isParentVisible = false; + + await Promise.all( + keys.map(async (key) => { + const children = contextMenuCache[key].children; + if (children === undefined || children?.length === 0) { + return; + } + + const visible = key === tabId; + + if (visible) { + await chrome.contextMenus.update(CONTEXT_MENU_PARENT, { + visible, + }); + isParentVisible = true; + } + + await Promise.all( + children.map(async (c) => { + await chrome.contextMenus.update(c.id, { visible }); + }) + ); + }) + ); + + if (!isParentVisible) { + await chrome.contextMenus.update(CONTEXT_MENU_PARENT, { + visible: false, + }); + } +} + +/** + * Handle the click of a context menu item + * + * @since 3.8.0 + * + * @param object settings Full settings object + * @param object login Login object + * @return void + */ +async function clickMenuEntry(settings, login) { + await handleMessage(settings, { action: "fill", login }, (response) => { + if (response.status != "ok") { + throw new Error(JSON.stringify(response)); + } + }); +} + // handle incoming messages chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { receiveMessage(message, sender, sendResponse); @@ -78,7 +269,7 @@ chrome.commands.onCommand.addListener(async (command) => { case "fillBest": try { const settings = await getFullSettings(); - if (settings.tab.url.match(/^(chrome|about):/)) { + if (settings.tab.url.match(INTERNAL_PAGES)) { // only fill on real domains return; } @@ -134,7 +325,6 @@ async function updateMatchingPasswordsCount(tabId, forceRefresh = false) { throw new Error(JSON.stringify(response)); } - const CACHE_TTL_MS = 60 * 1000; badgeCache = { files: response.data.files, settings: settings, @@ -1150,73 +1340,3 @@ function onExtensionInstalled(details) { }); } } - -/** - * Create a context menu, also called right-click menu - * - * @since 3.8.0 - * - * @return void - */ -async function createContextMenu(tabId) { - if ( - (contextMenu.isRefreshing && tabId === contextMenu.activeTabId) || - contextMenu.activeTabId !== tabId - ) { - return; - } - contextMenu.isRefreshing = true; - await chrome.contextMenus.removeAll(); - const menuEntryProps = { - contexts: ["all"], - type: "normal", - }; - const parentId = "parent"; - const settings = await getFullSettings(); - const response = await hostAction(settings, "list"); - - if (response.status != "ok") { - throw new Error(JSON.stringify(response)); - } - - const files = helpers.ignoreFiles(response.data.files, settings); - const logins = helpers.prepareLogins(files, settings); - const loginsForThisHost = helpers.filterSortLogins(logins, "", true); - const numberOfLoginsForThisHost = loginsForThisHost.length; - - if (numberOfLoginsForThisHost > 0) { - await chrome.contextMenus.create({ - ...menuEntryProps, - title: "Browserpass", - id: parentId, - }); - - for (let i = 0; i < numberOfLoginsForThisHost; i++) { - await chrome.contextMenus.create({ - ...menuEntryProps, - parentId: parentId, - id: "child" + i, - title: loginsForThisHost[i].login, - onclick: () => clickMenuEntry(settings, loginsForThisHost[i]), - }); - } - } - contextMenu.isRefreshing = false; -} - -/** - * Handle the click of a context menu item - * - * @since 3.8.0 - * - * @param object settings Full settings object - * @param object login Login object - * @return void - */ -async function clickMenuEntry(settings, login) { - await handleMessage(settings, { action: "fill", login }, (response) => { - if (response.status != "ok") { - throw new Error(JSON.stringify(response)); - } - }); -} From 434affdf4d55c1d8890b9c534aa9732aecc78892 Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Tue, 28 Mar 2023 15:58:33 +0200 Subject: [PATCH 08/10] Handle cache expired --- src/background.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/background.js b/src/background.js index d165c7db..31b0d240 100644 --- a/src/background.js +++ b/src/background.js @@ -108,11 +108,14 @@ async function updateContextMenu(tabId) { contextMenuCache[tabId] = { ...contextMenuCache[tabId], isRefreshing: true }; - if (contextMenuCache[tabId]?.tabUrl !== tabUrl && contextMenuCache[tabId]?.children) { + if ( + (contextMenuCache[tabId]?.tabUrl !== tabUrl && contextMenuCache[tabId]?.children) || + Date.now() >= contextMenuCache[tabId]?.expires + ) { const oldChildren = contextMenuCache[tabId].children; if (oldChildren?.length) { - Promise.all( + await Promise.all( oldChildren.map(async (children) => { await chrome.contextMenus.remove(children.id); }) @@ -122,7 +125,10 @@ async function updateContextMenu(tabId) { contextMenuCache[tabId].expires = Date.now(); } - if (Date.now() < contextMenuCache[tabId]?.expires) { + if ( + contextMenuCache[tabId]?.tabUrl === tabUrl && + Date.now() < contextMenuCache[tabId]?.expires + ) { await changeContextMenuChildrenVisibility(tabId); contextMenuCache[tabId].isRefreshing = false; return; @@ -159,7 +165,7 @@ async function updateContextMenu(tabId) { * Create context menu children * * @since 3.8.0 - * + * * @param string tabId ID of the Tab * @return void */ @@ -199,7 +205,7 @@ async function createContextMenuChildren(tabId) { * Change the visibility of the context menu's child items * * @since 3.8.0 - * + * * @param string tabId ID of the Tab * @return void */ From f880d43560a246de93343d2a65d339f584d10dca Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Tue, 28 Mar 2023 16:31:43 +0200 Subject: [PATCH 09/10] Refactor --- src/background.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/background.js b/src/background.js index 31b0d240..69f5af91 100644 --- a/src/background.js +++ b/src/background.js @@ -151,13 +151,7 @@ async function updateContextMenu(tabId) { const logins = helpers.prepareLogins(files, settings); const loginsForThisHost = helpers.filterSortLogins(logins, "", true); - contextMenuCache[tabId] = { - ...contextMenuCache[tabId], - loginsForThisHost, - settings, - }; - - await createContextMenuChildren(tabId); + await createContextMenuChildren(tabId, loginsForThisHost, settings); contextMenuCache[tabId].isRefreshing = false; } @@ -167,12 +161,11 @@ async function updateContextMenu(tabId) { * @since 3.8.0 * * @param string tabId ID of the Tab + * @param object settings Full settings object + * @param object login Login object * @return void */ -async function createContextMenuChildren(tabId) { - const loginsForThisHost = contextMenuCache[tabId].loginsForThisHost; - const settings = contextMenuCache[tabId].settings; - +async function createContextMenuChildren(tabId, loginsForThisHost, settings) { if (loginsForThisHost.length > 0) { try { contextMenuCache[tabId].children = []; From f9b23ee8fe82eeb88097b6b75123efe952162c6f Mon Sep 17 00:00:00 2001 From: Florian Kneist Date: Tue, 28 Mar 2023 16:37:19 +0200 Subject: [PATCH 10/10] Adjust inline documentation --- src/background.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/background.js b/src/background.js index 69f5af91..3da00ebd 100644 --- a/src/background.js +++ b/src/background.js @@ -151,7 +151,7 @@ async function updateContextMenu(tabId) { const logins = helpers.prepareLogins(files, settings); const loginsForThisHost = helpers.filterSortLogins(logins, "", true); - await createContextMenuChildren(tabId, loginsForThisHost, settings); + await createContextMenuChildren(tabId, settings, loginsForThisHost); contextMenuCache[tabId].isRefreshing = false; } @@ -160,12 +160,12 @@ async function updateContextMenu(tabId) { * * @since 3.8.0 * - * @param string tabId ID of the Tab - * @param object settings Full settings object - * @param object login Login object + * @param string tabId ID of the Tab + * @param object settings Full settings object + * @param object loginsForThisHost Login object * @return void */ -async function createContextMenuChildren(tabId, loginsForThisHost, settings) { +async function createContextMenuChildren(tabId, settings, loginsForThisHost) { if (loginsForThisHost.length > 0) { try { contextMenuCache[tabId].children = [];