diff --git a/packages/extension/public/_locales/de/messages.json b/packages/extension/public/_locales/de/messages.json
index a21d8a1d..10475ea9 100644
--- a/packages/extension/public/_locales/de/messages.json
+++ b/packages/extension/public/_locales/de/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Zeigt die verbleibende Zeit bis zur Vorschau an."
},
+ "Option_windowSettings": {
+ "message": "Fenstereinstellungen"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Konfigurieren Sie das Fensterverhalten und Popup-Einstellungen."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Popup-Autoschluss-Verzögerung"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Stellen Sie die Verzögerungszeit ein, bevor das Popup nach dem Verlust des Fokus automatisch geschlossen wird. Stellen Sie 0 ein oder lassen Sie es leer für sofortiges Schließen.\nMaximum: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (sofort schließen)"
+ },
"Option_folders": {
"message": "Ordner"
},
diff --git a/packages/extension/public/_locales/en/messages.json b/packages/extension/public/_locales/en/messages.json
index 1ac8502a..fc3f0541 100644
--- a/packages/extension/public/_locales/en/messages.json
+++ b/packages/extension/public/_locales/en/messages.json
@@ -203,6 +203,15 @@
"Option_popupAnimation": {
"message": "Menu Display Animation"
},
+ "Option_popupAutoCloseDelay": {
+ "message": "Popup Auto-Close Delay"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Set the delay time before the popup automatically closes after losing focus. Set to 0 or leave empty for immediate close.\nMaximum: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (close immediately)"
+ },
"Option_inherit": {
"message": "Inherit"
},
@@ -503,6 +512,12 @@
"Option_showIndicator_desc": {
"message": "Displays the remaining to the preview."
},
+ "Option_windowSettings": {
+ "message": "Window Settings"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Configure window behavior and popup settings."
+ },
"Option_folders": {
"message": "Folders"
},
diff --git a/packages/extension/public/_locales/es/messages.json b/packages/extension/public/_locales/es/messages.json
index fe25ac49..003912d3 100644
--- a/packages/extension/public/_locales/es/messages.json
+++ b/packages/extension/public/_locales/es/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Muestra el tiempo restante hasta la vista previa."
},
+ "Option_windowSettings": {
+ "message": "Configuración de Ventana"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Configure el comportamiento de la ventana y la configuración de ventanas emergentes."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Retardo de Cierre Automático de Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Establezca el tiempo de retardo antes de que el popup se cierre automáticamente después de perder el foco. Establezca 0 o déjelo vacío para cierre inmediato.\nMáximo: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (cerrar inmediatamente)"
+ },
"Option_folders": {
"message": "Carpetas"
},
diff --git a/packages/extension/public/_locales/fr/messages.json b/packages/extension/public/_locales/fr/messages.json
index 877041df..50133e3e 100644
--- a/packages/extension/public/_locales/fr/messages.json
+++ b/packages/extension/public/_locales/fr/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Affiche le temps restant jusqu'à l'aperçu."
},
+ "Option_windowSettings": {
+ "message": "Paramètres de Fenêtre"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Configurez le comportement de la fenêtre et les paramètres de popup."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Délai de Fermeture Automatique du Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Définissez le délai avant que le popup se ferme automatiquement après avoir perdu le focus. Définissez 0 ou laissez vide pour une fermeture immédiate.\nMaximum: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (fermer immédiatement)"
+ },
"Option_folders": {
"message": "Dossiers"
},
diff --git a/packages/extension/public/_locales/hi/messages.json b/packages/extension/public/_locales/hi/messages.json
index 53333a7e..1e4640d3 100644
--- a/packages/extension/public/_locales/hi/messages.json
+++ b/packages/extension/public/_locales/hi/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "प्रीव्यू तक शेष समय दिखाएं।"
},
+ "Option_windowSettings": {
+ "message": "विंडो सेटिंग्स"
+ },
+ "Option_windowSettings_desc": {
+ "message": "विंडो व्यवहार और पॉपअप सेटिंग्स को कॉन्फ़िगर करें।"
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "पॉपअप ऑटो-क्लोज़ विलंब"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "फोकस खोने के बाद पॉपअप के स्वचालित रूप से बंद होने से पहले विलंब का समय सेट करें। तुरंत बंद करने के लिए 0 सेट करें या खाली छोड़ दें।\nअधिकतम: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (तुरंत बंद करें)"
+ },
"Option_folders": {
"message": "फ़ोल्डर्स"
},
diff --git a/packages/extension/public/_locales/id/messages.json b/packages/extension/public/_locales/id/messages.json
index 2f5e66dd..b3685a0f 100644
--- a/packages/extension/public/_locales/id/messages.json
+++ b/packages/extension/public/_locales/id/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Menampilkan sisa ke pratinjau."
},
+ "Option_windowSettings": {
+ "message": "Pengaturan Jendela"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Konfigurasi perilaku jendela dan pengaturan popup."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Penundaan Penutupan Otomatis Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Atur waktu penundaan sebelum popup secara otomatis menutup setelah kehilangan fokus. Atur ke 0 atau biarkan kosong untuk penutupan segera.\nMaksimum: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (tutup segera)"
+ },
"Option_folders": {
"message": "Folder"
},
diff --git a/packages/extension/public/_locales/it/messages.json b/packages/extension/public/_locales/it/messages.json
index 2f72f722..97b4c3e6 100644
--- a/packages/extension/public/_locales/it/messages.json
+++ b/packages/extension/public/_locales/it/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Mostra il tempo rimanente fino all'anteprima."
},
+ "Option_windowSettings": {
+ "message": "Impostazioni Finestra"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Configura il comportamento della finestra e le impostazioni popup."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Ritardo Chiusura Automatica Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Imposta il tempo di ritardo prima che il popup si chiuda automaticamente dopo aver perso il focus. Imposta 0 o lascia vuoto per la chiusura immediata.\nMassimo: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (chiudi immediatamente)"
+ },
"Option_folders": {
"message": "Cartelle"
},
diff --git a/packages/extension/public/_locales/ja/messages.json b/packages/extension/public/_locales/ja/messages.json
index 905fef14..69b3350f 100644
--- a/packages/extension/public/_locales/ja/messages.json
+++ b/packages/extension/public/_locales/ja/messages.json
@@ -203,6 +203,15 @@
"Option_popupAnimation": {
"message": "メニュー表示アニメーション"
},
+ "Option_popupAutoCloseDelay": {
+ "message": "ポップアップ自動クローズまでの時間"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "ポップアップウィンドウのフォーカスが外れてから自動的に閉じるまでの時間を設定します。0または未設定の場合は即座に閉じます。 \n最大:10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (即座に閉じる)"
+ },
"Option_inherit": {
"message": "継承"
},
@@ -590,6 +599,12 @@
"Option_showIndicator_desc": {
"message": "プレビューまでの残りを表示します。"
},
+ "Option_windowSettings": {
+ "message": "ウィンドウ設定"
+ },
+ "Option_windowSettings_desc": {
+ "message": "ウィンドウやポップアップの設定を行います。"
+ },
"Option_folders": {
"message": "フォルダ"
},
diff --git a/packages/extension/public/_locales/ko/messages.json b/packages/extension/public/_locales/ko/messages.json
index f680f23f..3dd5dea4 100644
--- a/packages/extension/public/_locales/ko/messages.json
+++ b/packages/extension/public/_locales/ko/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "미리보기까지 남은 시간을 표시합니다."
},
+ "Option_windowSettings": {
+ "message": "창 설정"
+ },
+ "Option_windowSettings_desc": {
+ "message": "창 동작 및 팝업 설정을 구성합니다."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "팝업 자동 닫기 지연"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "포커스를 잃은 후 팝업이 자동으로 닫히기 전 지연 시간을 설정합니다. 즉시 닫으려면 0으로 설정하거나 비워 두십시오.\n최대: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (즉시 닫기)"
+ },
"Option_folders": {
"message": "폴더"
},
diff --git a/packages/extension/public/_locales/ms/messages.json b/packages/extension/public/_locales/ms/messages.json
index 01a3d6ec..3fc0f4b9 100644
--- a/packages/extension/public/_locales/ms/messages.json
+++ b/packages/extension/public/_locales/ms/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Memaparkan baki ke pratonton."
},
+ "Option_windowSettings": {
+ "message": "Tetapan Tetingkap"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Konfigurasi tingkah laku tetingkap dan tetapan popup."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Kelewatan Penutupan Auto Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Tetapkan masa kelewatan sebelum popup menutup secara automatik selepas kehilangan fokus. Tetapkan ke 0 atau biarkan kosong untuk penutupan segera.\nMaksimum: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (tutup segera)"
+ },
"Option_folders": {
"message": "Folder"
},
diff --git a/packages/extension/public/_locales/pt_BR/messages.json b/packages/extension/public/_locales/pt_BR/messages.json
index 60d3069b..54b60b85 100644
--- a/packages/extension/public/_locales/pt_BR/messages.json
+++ b/packages/extension/public/_locales/pt_BR/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Exibe o restante para a visualização."
},
+ "Option_windowSettings": {
+ "message": "Configurações da Janela"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Configure o comportamento da janela e as configurações de popup."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Atraso de Fechamento Automático do Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Defina o tempo de atraso antes que o popup feche automaticamente após perder o foco. Defina como 0 ou deixe vazio para fechamento imediato.\nMáximo: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (fechar imediatamente)"
+ },
"Option_folders": {
"message": "Pastas"
},
diff --git a/packages/extension/public/_locales/pt_PT/messages.json b/packages/extension/public/_locales/pt_PT/messages.json
index 90f83ec5..5a5796b7 100644
--- a/packages/extension/public/_locales/pt_PT/messages.json
+++ b/packages/extension/public/_locales/pt_PT/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Exibe o restante para a visualização."
},
+ "Option_windowSettings": {
+ "message": "Definições da Janela"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Configure o comportamento da janela e as definições de popup."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Atraso de Fecho Automático do Popup"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Defina o tempo de atraso antes que o popup feche automaticamente após perder o foco. Defina como 0 ou deixe vazio para fecho imediato.\nMáximo: 10000 ms"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (fechar imediatamente)"
+ },
"Option_folders": {
"message": "Pastas"
},
diff --git a/packages/extension/public/_locales/ru/messages.json b/packages/extension/public/_locales/ru/messages.json
index 4e8b3a8e..ffc12c76 100644
--- a/packages/extension/public/_locales/ru/messages.json
+++ b/packages/extension/public/_locales/ru/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "Показывать оставшееся время до предпросмотра."
},
+ "Option_windowSettings": {
+ "message": "Настройки Окна"
+ },
+ "Option_windowSettings_desc": {
+ "message": "Настройте поведение окна и параметры всплывающих окон."
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "Задержка Автозакрытия Всплывающего Окна"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "Установите время задержки перед автоматическим закрытием всплывающего окна после потери фокуса. Установите 0 или оставьте пустым для немедленного закрытия.\nМаксимум: 10000 мс"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0 (закрыть немедленно)"
+ },
"Option_folders": {
"message": "Папки"
},
diff --git a/packages/extension/public/_locales/zh_CN/messages.json b/packages/extension/public/_locales/zh_CN/messages.json
index 89dea19d..6a8c2d1f 100644
--- a/packages/extension/public/_locales/zh_CN/messages.json
+++ b/packages/extension/public/_locales/zh_CN/messages.json
@@ -461,6 +461,21 @@
"Option_showIndicator_desc": {
"message": "显示预览前的剩余时间。"
},
+ "Option_windowSettings": {
+ "message": "窗口设置"
+ },
+ "Option_windowSettings_desc": {
+ "message": "配置窗口行为和弹出窗口设置。"
+ },
+ "Option_popupAutoCloseDelay": {
+ "message": "弹出窗口自动关闭延迟"
+ },
+ "Option_popupAutoCloseDelay_desc": {
+ "message": "设置弹出窗口失去焦点后自动关闭前的延迟时间。设置为0或留空表示立即关闭。\n最大值:10000毫秒"
+ },
+ "Option_popupAutoCloseDelay_placeholder": {
+ "message": "0(立即关闭)"
+ },
"Option_folders": {
"message": "文件夹"
},
diff --git a/packages/extension/src/background_script.test.ts b/packages/extension/src/background_script.test.ts
index 5efa0473..93c9aa3e 100644
--- a/packages/extension/src/background_script.test.ts
+++ b/packages/extension/src/background_script.test.ts
@@ -7,6 +7,7 @@ import { POPUP_ENABLED, LINK_COMMAND_ENABLED } from "@/const"
// Mock dependencies
vi.mock("@/services/settings/enhancedSettings")
vi.mock("@/services/settings/settings")
+vi.mock("@/services/settings/settingsCache")
vi.mock("@/services/storage")
vi.mock("@/services/chrome")
vi.mock("@/services/backgroundData")
@@ -378,3 +379,224 @@ describe("Background Script Migration", () => {
)
})
})
+
+describe("Popup Auto-Close Delay", () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ vi.useFakeTimers()
+ })
+
+ afterEach(() => {
+ vi.useRealTimers()
+ })
+
+ it("PAC-01: should close popups immediately when delay is not set", async () => {
+ // Mock WindowStackManager
+ const mockGetWindowsToClose = vi.fn().mockResolvedValue([
+ { id: 100, commandId: "test", srcWindowId: 1 },
+ ])
+ const mockRemoveWindow = vi.fn().mockResolvedValue(undefined)
+
+ vi.doMock("@/services/windowStackManager", () => ({
+ WindowStackManager: {
+ getWindowsToClose: mockGetWindowsToClose,
+ removeWindow: mockRemoveWindow,
+ },
+ }))
+
+ // Mock closeWindow
+ const mockCloseWindow = vi.fn().mockResolvedValue(undefined)
+ vi.doMock("@/services/chrome", () => ({
+ closeWindow: mockCloseWindow,
+ windowExists: vi.fn(),
+ }))
+
+ // Mock enhancedSettings to return no delay
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: undefined,
+ },
+ } as any)
+
+ // Mock Storage
+ const mockStorageSet = vi.fn().mockResolvedValue(undefined)
+ vi.doMock("@/services/storage", () => ({
+ Storage: {
+ set: mockStorageSet,
+ },
+ SESSION_STORAGE_KEY: {
+ SELECTION_TEXT: "selectionText",
+ },
+ }))
+
+ // Mock updateActiveScreenId
+ vi.doMock("@/services/screen", () => ({
+ updateActiveScreenId: vi.fn().mockResolvedValue(undefined),
+ }))
+
+ // Clear module cache and re-import
+ vi.resetModules()
+ await import("./background_script")
+
+ // Get the registered listener
+ const listenerCalls = (chrome.windows.onFocusChanged.addListener as any)
+ .mock.calls
+ expect(listenerCalls.length).toBeGreaterThan(0)
+ const focusChangedListener = listenerCalls[listenerCalls.length - 1][0]
+
+ // Trigger focus change
+ await focusChangedListener(200)
+
+ // Wait for async operations
+ await vi.runAllTimersAsync()
+
+ // Verify window was closed immediately (no setTimeout)
+ expect(mockCloseWindow).toHaveBeenCalledWith(100, "onFocusChanged")
+ expect(mockRemoveWindow).toHaveBeenCalledWith(100)
+ })
+
+ it("PAC-02: should delay popup close when delay is set", async () => {
+ const delay = 1000 // 1 second
+
+ // Mock WindowStackManager
+ const mockGetWindowsToClose = vi.fn().mockResolvedValue([
+ { id: 100, commandId: "test", srcWindowId: 1 },
+ ])
+ const mockRemoveWindow = vi.fn().mockResolvedValue(undefined)
+
+ vi.doMock("@/services/windowStackManager", () => ({
+ WindowStackManager: {
+ getWindowsToClose: mockGetWindowsToClose,
+ removeWindow: mockRemoveWindow,
+ },
+ }))
+
+ // Mock closeWindow
+ const mockCloseWindow = vi.fn().mockResolvedValue(undefined)
+ vi.doMock("@/services/chrome", () => ({
+ closeWindow: mockCloseWindow,
+ windowExists: vi.fn(),
+ }))
+
+ // Mock enhancedSettings to return delay
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: delay,
+ },
+ } as any)
+
+ // Mock Storage
+ const mockStorageSet = vi.fn().mockResolvedValue(undefined)
+ vi.doMock("@/services/storage", () => ({
+ Storage: {
+ set: mockStorageSet,
+ },
+ SESSION_STORAGE_KEY: {
+ SELECTION_TEXT: "selectionText",
+ },
+ }))
+
+ // Mock updateActiveScreenId
+ vi.doMock("@/services/screen", () => ({
+ updateActiveScreenId: vi.fn().mockResolvedValue(undefined),
+ }))
+
+ // Clear module cache and re-import
+ vi.resetModules()
+ await import("./background_script")
+
+ // Get the registered listener
+ const listenerCalls = (chrome.windows.onFocusChanged.addListener as any)
+ .mock.calls
+ const focusChangedListener = listenerCalls[listenerCalls.length - 1][0]
+
+ // Trigger focus change
+ await focusChangedListener(200)
+
+ // Window should not be closed immediately
+ expect(mockCloseWindow).not.toHaveBeenCalled()
+
+ // Advance timers by the delay amount
+ await vi.advanceTimersByTimeAsync(delay)
+
+ // Now window should be closed
+ expect(mockCloseWindow).toHaveBeenCalledWith(100, "onFocusChanged")
+ expect(mockRemoveWindow).toHaveBeenCalledWith(100)
+ })
+
+ it("PAC-03: should cancel timeout when focus returns before delay", async () => {
+ const delay = 1000 // 1 second
+
+ // Mock WindowStackManager - first returns windows to close, then empty array
+ const mockGetWindowsToClose = vi
+ .fn()
+ .mockResolvedValueOnce([{ id: 100, commandId: "test", srcWindowId: 1 }])
+ .mockResolvedValueOnce([]) // No windows to close when focus returns
+ const mockRemoveWindow = vi.fn().mockResolvedValue(undefined)
+
+ vi.doMock("@/services/windowStackManager", () => ({
+ WindowStackManager: {
+ getWindowsToClose: mockGetWindowsToClose,
+ removeWindow: mockRemoveWindow,
+ },
+ }))
+
+ // Mock closeWindow
+ const mockCloseWindow = vi.fn().mockResolvedValue(undefined)
+ vi.doMock("@/services/chrome", () => ({
+ closeWindow: mockCloseWindow,
+ windowExists: vi.fn(),
+ }))
+
+ // Mock enhancedSettings to return delay
+ mockEnhancedSettings.getSection.mockResolvedValue({
+ windowOption: {
+ popupAutoCloseDelay: delay,
+ },
+ } as any)
+
+ // Mock Storage
+ const mockStorageSet = vi.fn().mockResolvedValue(undefined)
+ vi.doMock("@/services/storage", () => ({
+ Storage: {
+ set: mockStorageSet,
+ },
+ SESSION_STORAGE_KEY: {
+ SELECTION_TEXT: "selectionText",
+ },
+ }))
+
+ // Mock updateActiveScreenId
+ vi.doMock("@/services/screen", () => ({
+ updateActiveScreenId: vi.fn().mockResolvedValue(undefined),
+ }))
+
+ // Clear module cache and re-import
+ vi.resetModules()
+ await import("./background_script")
+
+ // Get the registered listener
+ const listenerCalls = (chrome.windows.onFocusChanged.addListener as any)
+ .mock.calls
+ const focusChangedListener = listenerCalls[listenerCalls.length - 1][0]
+
+ // Trigger focus change (popup loses focus)
+ await focusChangedListener(200)
+
+ // Window should not be closed yet
+ expect(mockCloseWindow).not.toHaveBeenCalled()
+
+ // Advance timers only halfway
+ await vi.advanceTimersByTimeAsync(delay / 2)
+
+ // Focus returns to popup (no windows to close)
+ await focusChangedListener(100)
+
+ // Advance remaining time
+ await vi.advanceTimersByTimeAsync(delay / 2 + 100)
+
+ // Window should NOT be closed because timeout was cancelled
+ expect(mockCloseWindow).not.toHaveBeenCalled()
+ expect(mockRemoveWindow).not.toHaveBeenCalled()
+ })
+})
diff --git a/packages/extension/src/background_script.ts b/packages/extension/src/background_script.ts
index 61881762..d6eb43eb 100644
--- a/packages/extension/src/background_script.ts
+++ b/packages/extension/src/background_script.ts
@@ -17,6 +17,7 @@ import { BgData } from "@/services/backgroundData"
import { ContextMenu } from "@/services/contextMenus"
import { closeWindow, windowExists } from "@/services/chrome"
import { WindowStackManager } from "@/services/windowStackManager"
+import { PopupAutoClose } from "@/services/popupAutoClose"
import { isSearchCommand, isPageActionCommand } from "@/lib/utils"
import { execute } from "@/action/background"
import * as ActionHelper from "@/action/helper"
@@ -287,9 +288,11 @@ const commandFuncs = {
return
}
- // Remove the window.
- await closeWindow(windowId, "onHidden")
- await WindowStackManager.removeWindow(windowId)
+ // Schedule popup window to close with configured delay
+ const window = layer.find((w) => w.id === windowId)
+ if (window) {
+ await PopupAutoClose.scheduleClose([window], "onHidden")
+ }
response(false)
}
@@ -379,11 +382,8 @@ chrome.windows.onFocusChanged.addListener(async (windowId: number) => {
// Get windows to close based on focus change
const windowsToClose = await WindowStackManager.getWindowsToClose(windowId)
- // Execute close for all windows that need to be closed
- for (const window of windowsToClose) {
- await closeWindow(window.id, "onFocusChanged")
- await WindowStackManager.removeWindow(window.id)
- }
+ // Schedule popup windows to close with configured delay
+ await PopupAutoClose.scheduleClose(windowsToClose)
})
chrome.windows.onRemoved.addListener((windowId: number) => {
diff --git a/packages/extension/src/components/option/SettingForm.tsx b/packages/extension/src/components/option/SettingForm.tsx
index 81a86adc..8c8d6316 100644
--- a/packages/extension/src/components/option/SettingForm.tsx
+++ b/packages/extension/src/components/option/SettingForm.tsx
@@ -9,6 +9,7 @@ import {
Eye,
BookOpen,
Paintbrush,
+ AppWindow,
} from "lucide-react"
import { Form } from "@/components/ui/form"
@@ -79,6 +80,16 @@ const formSchema = z
})
.strict(),
popupPlacement: popupPlacementSchema,
+ windowOption: z
+ .object({
+ sidePanelAutoHide: z.boolean(),
+ popupAutoCloseDelay: z
+ .number({ message: t("zod_number") })
+ .min(0, { message: t("zod_number_min", ["0"]) })
+ .max(10000, { message: t("zod_number_max", ["10000"]) })
+ .optional(),
+ })
+ .strict(),
style: z.nativeEnum(STYLE),
commands: z.array(commandSchema).min(1),
folders: z.array(folderSchema),
@@ -129,6 +140,7 @@ export function SettingForm({ className }: { className?: string }) {
defaultValues: {
startupMethod: emptySettings.startupMethod,
popupPlacement: emptySettings.popupPlacement,
+ windowOption: emptySettings.windowOption,
style: emptySettings.style,
commands: [], // Empty array to avoid type conflicts
folders: emptySettings.folders,
@@ -549,31 +561,60 @@ export function SettingForm({ className }: { className?: string }) {
)}
{linkCommandMethod ===
LINK_COMMAND_STARTUP_METHOD.LEFT_CLICK_HOLD && (
+
{t("windowSettings_desc")}
+ + {startupMethod !== STARTUP_METHOD.CONTEXT_MENU && (