diff --git a/src/@/components/SavedLinkCard.tsx b/src/@/components/SavedLinkCard.tsx index a8c984e..d4e1411 100644 --- a/src/@/components/SavedLinkCard.tsx +++ b/src/@/components/SavedLinkCard.tsx @@ -72,6 +72,7 @@ function useSavedLinkPolling({ useEffect(() => { let isMounted = true; let pollInterval: NodeJS.Timeout | null = null; + let timeoutId: NodeJS.Timeout | null = null; const hasSharedData = sharedImgSrc?.startsWith('data:'); const fetchData = async () => { @@ -91,7 +92,7 @@ function useSavedLinkPolling({ } } catch { } }, 2000); - setTimeout(() => { if (pollInterval) clearInterval(pollInterval); if (isMounted && !link.preview && !imgSrc) setIsLoading(false); }, 30000); + timeoutId = setTimeout(() => { if (pollInterval) clearInterval(pollInterval); if (isMounted && !link.preview && !imgSrc) setIsLoading(false); }, 30000); return; } @@ -110,7 +111,7 @@ function useSavedLinkPolling({ } }; fetchData(); - return () => { isMounted = false; if (pollInterval) clearInterval(pollInterval); }; + return () => { isMounted = false; if (pollInterval) clearInterval(pollInterval); if (timeoutId) clearTimeout(timeoutId); }; }, [link.preview, link.url, link.id, baseUrl, sharedImgSrc, onImgSrcChange]); return { imgSrc, isLoading, isPollingTags, setIsPollingTags }; diff --git a/src/@/hooks/useThumbnail.ts b/src/@/hooks/useThumbnail.ts index 7f4b26c..ea4668b 100644 --- a/src/@/hooks/useThumbnail.ts +++ b/src/@/hooks/useThumbnail.ts @@ -135,16 +135,21 @@ export function useThumbnail({ useEffect(() => { if (!isLoading || imgSrc || !linkId || !baseUrl) return; + let cancelled = false; + const pollInterval = setInterval(async () => { const apiImage = await fetchFromApi(); - if (apiImage) { + if (!cancelled && apiImage) { setImgSrc(apiImage); setIsLoading(false); clearInterval(pollInterval); } }, 3000); - return () => clearInterval(pollInterval); + return () => { + cancelled = true; + clearInterval(pollInterval); + }; }, [isLoading, imgSrc, linkId, baseUrl, fetchFromApi]); return { diff --git a/src/pages/ContentScript/SmartCapture/SmartCaptureMode.ts b/src/pages/ContentScript/SmartCapture/SmartCaptureMode.ts index b9d6fed..4c4b799 100644 --- a/src/pages/ContentScript/SmartCapture/SmartCaptureMode.ts +++ b/src/pages/ContentScript/SmartCapture/SmartCaptureMode.ts @@ -33,6 +33,8 @@ export class SmartCaptureMode { private shortcutConfig: ShortcutConfig = DEFAULT_PREFERENCES.smartCaptureShortcut; private enableSmartCapture = DEFAULT_PREFERENCES.enableSmartCapture; + private storageChangeListener: (changes: { [key: string]: chrome.storage.StorageChange }, area: string) => void; + private boundHandlers: { mousemove: (e: MouseEvent) => void; mousedown: (e: MouseEvent) => void; @@ -74,11 +76,12 @@ export class SmartCaptureMode { document.addEventListener('keyup', this.globalKeyupHandler); const hostname = getHostname(window.location.href); getEffectivePreferences(hostname).then(prefs => { if (prefs) this.updateSettings(prefs); }); - chrome.storage.onChanged.addListener((changes, area) => { + this.storageChangeListener = (changes, area) => { if (area === 'local' && (changes.grabshark_preferences || changes.grabshark_site_overrides)) { getEffectivePreferences(getHostname(window.location.href)).then(prefs => this.updateSettings(prefs)); } - }); + }; + chrome.storage.onChanged.addListener(this.storageChangeListener); } public updateSettings(prefs: ExtensionPreferences) { if (prefs.smartCaptureShortcut) this.shortcutConfig = prefs.smartCaptureShortcut; @@ -293,5 +296,7 @@ export class SmartCaptureMode { try { this.deactivate(); } catch { } try { this.actionBar?.destroy(); } catch { } try { document.removeEventListener('keydown', this.globalKeydownHandler); document.removeEventListener('keyup', this.globalKeyupHandler); } catch { } + try { chrome.storage.onChanged.removeListener(this.storageChangeListener); } catch { } + if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; } } } diff --git a/src/pages/ContentScript/highlightObserver.ts b/src/pages/ContentScript/highlightObserver.ts index 0185090..9478dba 100644 --- a/src/pages/ContentScript/highlightObserver.ts +++ b/src/pages/ContentScript/highlightObserver.ts @@ -42,6 +42,11 @@ class HighlightObserver { }; start(highlights: Highlight[], initialResults: Map): void { + // If already observing, ensure we stop previous timers/observers before restarting or merging + if (this.isObserving) { + this.stop(); + } + highlights.forEach(h => this.state.allHighlights.set(h.id, h)); this.state.lastDocHeight = document.body.scrollHeight; diff --git a/yarn.lock b/yarn.lock index 71c8eb1..e8d0f03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -175,10 +175,10 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.28.5" -"@esbuild/darwin-arm64@0.18.20": +"@esbuild/linux-x64@0.18.20": version "0.18.20" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz" - integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -1818,11 +1818,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"