From 530c876b97c606507aad1fb28273cbedb47125d2 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Sat, 17 Oct 2026 00:17:43 +0800 Subject: [PATCH] Memoize getUserConfig to avoid repeated storage reads Cache config per context and share concurrent reads. Return independent deep copies with cloneDeep for older Safari, invalidate on settings and locale changes, and exclude conversation history writes. Read fresh settings at action and request boundaries, and pass the same snapshot through ChatGPT Web requests and cleanup. Prevent older reads from replacing newer cache entries, and retry failed migrations. Keep Escape and Go Back failures contained so the actions remain retryable. Keep useConfig's existing storage.local.onChanged listener unchanged; its browser compatibility remains a separate follow-up. --- src/background/index.mjs | 9 +- src/background/menus.mjs | 9 +- src/components/ConversationCard/index.jsx | 12 +- .../WebJumpBackNotification/index.jsx | 16 +- src/config/index.mjs | 174 +++++++-- src/config/language.mjs | 6 +- src/config/storage-keys.mjs | 19 + src/content-script/index.jsx | 46 ++- src/content-script/menu-tools/index.mjs | 2 +- src/content-script/selection-tools/index.mjs | 7 +- src/hooks/config-storage-listener.mjs | 9 +- src/services/apis/chatgpt-web.mjs | 95 +++-- src/services/local-session.mjs | 6 +- src/services/wrappers.mjs | 32 +- tests/setup/browser-shim.mjs | 112 +++++- tests/setup/chatgpt-web-loader-hooks.mjs | 10 + ...-script-selection-toolbar-loader-hooks.mjs | 10 +- ...nversation-card-lifecycle-loader-hooks.mjs | 3 +- tests/setup/menu-loader-hooks.mjs | 10 + tests/setup/web-jump-back-loader-hooks.mjs | 47 +++ tests/unit/background/menus.test.mjs | 43 +++ .../conversation-card-lifecycle.test.mjs | 102 ++++++ .../web-jump-back-notification.test.mjs | 147 ++++++++ tests/unit/config/language-config.test.mjs | 8 + tests/unit/config/user-config-cache.test.mjs | 329 ++++++++++++++++++ .../config/user-config-cross-context.test.mjs | 233 +++++++++++++ .../user-config-set-invalidation.test.mjs | 22 ++ tests/unit/content-script/menu-tools.test.mjs | 24 ++ .../selection-toolbar-lifecycle.test.mjs | 58 +-- .../content-script/selection-tools.test.mjs | 24 ++ .../services/apis/chatgpt-web-config.test.mjs | 120 +++++++ .../unit/services/wrappers-register.test.mjs | 50 +++ tests/unit/setup/browser-shim.test.mjs | 98 ++++++ 33 files changed, 1744 insertions(+), 148 deletions(-) create mode 100644 src/config/storage-keys.mjs create mode 100644 tests/setup/chatgpt-web-loader-hooks.mjs create mode 100644 tests/setup/menu-loader-hooks.mjs create mode 100644 tests/setup/web-jump-back-loader-hooks.mjs create mode 100644 tests/unit/background/menus.test.mjs create mode 100644 tests/unit/components/web-jump-back-notification.test.mjs create mode 100644 tests/unit/config/user-config-cache.test.mjs create mode 100644 tests/unit/config/user-config-cross-context.test.mjs create mode 100644 tests/unit/config/user-config-set-invalidation.test.mjs create mode 100644 tests/unit/services/apis/chatgpt-web-config.test.mjs diff --git a/src/background/index.mjs b/src/background/index.mjs index 79b4f0c0d..a91b13e79 100644 --- a/src/background/index.mjs +++ b/src/background/index.mjs @@ -546,7 +546,7 @@ async function executeApi( console.debug('[background] Skipping a superseded direct ChatGPT Web session request.') return } - await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken) + await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken, config) } } else if (isUsingClaudeWebModel(session)) { console.debug('[background] Using Claude Web Model') @@ -656,7 +656,7 @@ Browser.runtime.onMessage.addListener(async (message, sender) => { break case 'OPEN_CHAT_WINDOW': { console.log('[background] Processing OPEN_CHAT_WINDOW message') - const config = await getUserConfig() + const config = await getUserConfig({ fresh: true }) const url = Browser.runtime.getURL('IndependentPanel.html') const tabs = await Browser.tabs.query({ url: url, windowType: 'popup' }) if (!config.alwaysCreateNewConversationWindow && tabs.length > 0) { @@ -675,7 +675,7 @@ Browser.runtime.onMessage.addListener(async (message, sender) => { } case 'REFRESH_MENU': console.log('[background] Processing REFRESH_MENU message') - refreshMenu() + await refreshMenu() break case 'PIN_TAB': { console.log('[background] Processing PIN_TAB message:', message.data) @@ -1050,7 +1050,8 @@ try { try { refreshMenu() - console.log('[background] Menu refreshed successfully.') + .then(() => console.log('[background] Menu refreshed successfully.')) + .catch((error) => console.error('[background] Error refreshing menu:', error)) } catch (error) { console.error('[background] Error refreshing menu:', error) } diff --git a/src/background/menus.mjs b/src/background/menus.mjs index d3a61ee1e..d39c11709 100644 --- a/src/background/menus.mjs +++ b/src/background/menus.mjs @@ -110,12 +110,11 @@ const onClickMenu = (info, tab) => { export function refreshMenu() { if (Browser.contextMenus.onClicked.hasListener(onClickMenu)) Browser.contextMenus.onClicked.removeListener(onClickMenu) - Browser.contextMenus.removeAll().then(async () => { - if ((await getUserConfig()).hideContextMenu) return + return Browser.contextMenus.removeAll().then(async () => { + const config = await getUserConfig({ fresh: true }) + if (config.hideContextMenu) return - await getPreferredLanguageKey().then((lang) => { - changeLanguage(lang) - }) + await changeLanguage(await getPreferredLanguageKey(config)) Browser.contextMenus.create({ id: menuId, title: 'ChatGPTBox', diff --git a/src/components/ConversationCard/index.jsx b/src/components/ConversationCard/index.jsx index 7458a0d89..e9c2be346 100644 --- a/src/components/ConversationCard/index.jsx +++ b/src/components/ConversationCard/index.jsx @@ -384,7 +384,7 @@ function ConversationCard(props) { } foregroundPortsRef.current.add(fakePort) try { - const bingToken = (await getUserConfig()).bingAccessToken + const bingToken = (await getUserConfig({ fresh: true })).bingAccessToken if ( disposedRef.current || disconnected || @@ -445,9 +445,13 @@ function ConversationCard(props) { } const closeChatsEscListener = async (e) => { if (e.key !== 'Escape') return - const { allowEscToCloseAll } = await getUserConfig() - if (disposedRef.current || !allowEscToCloseAll) return - closeChatsMessageListener({ type: 'CLOSE_CHATS' }) + try { + const { allowEscToCloseAll } = await getUserConfig({ fresh: true }) + if (disposedRef.current || !allowEscToCloseAll) return + closeChatsMessageListener({ type: 'CLOSE_CHATS' }) + } catch (error) { + console.warn('[ConversationCard] Failed to handle Escape:', error) + } } if (props.closeable) { diff --git a/src/components/WebJumpBackNotification/index.jsx b/src/components/WebJumpBackNotification/index.jsx index cece185da..527d07246 100644 --- a/src/components/WebJumpBackNotification/index.jsx +++ b/src/components/WebJumpBackNotification/index.jsx @@ -56,12 +56,16 @@ const WebJumpBackNotification = (props) => {