|
6 | 6 | lastMathJaxV3Latex: null, |
7 | 7 | lastCopyGestureTs: 0, |
8 | 8 | lastCopiedTex: null, |
| 9 | + themeMode: 'system', |
| 10 | + themeModeLoaded: false, |
9 | 11 | }; |
10 | 12 |
|
11 | | - function getOverlay() { |
12 | | - return ns.state.overlay; |
13 | | - } |
14 | | - |
15 | | - function setOverlay(el) { |
16 | | - ns.state.overlay = el; |
17 | | - } |
18 | | - |
19 | | - async function setOverlayThemeClass() { |
20 | | - const overlay = getOverlay(); |
21 | | - if (!overlay) return; |
22 | | - // Remove any previous theme class |
23 | | - overlay.classList.remove('theme-light', 'theme-dark'); |
24 | | - let theme = 'system'; |
25 | | - |
| 13 | + async function loadThemeModeOnce() { |
| 14 | + if (ns.state.themeModeLoaded) return; |
26 | 15 | try { |
27 | 16 | const result = await browser.storage.local.get('themeMode'); |
28 | | - // console.log('[Copy LaTeX] setOverlayThemeClass: browser.storage.local themeMode =', result.themeMode); |
29 | | - theme = result.themeMode || 'system'; |
| 17 | + ns.state.themeMode = result.themeMode || 'system'; |
30 | 18 | } catch (e) { |
31 | 19 | // This can happen during navigation/refresh or extension reload: the content-script |
32 | 20 | // context is torn down while an async storage call is in-flight. |
33 | 21 | const message = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e); |
34 | 22 | if (!message.includes('Extension context invalidated')) { |
35 | | - console.warn('[Copy LaTeX] setOverlayThemeClass: error reading browser.storage.local', e); |
| 23 | + console.warn('[Copy LaTeX] loadThemeModeOnce: error reading browser.storage.local', e); |
36 | 24 | } |
| 25 | + // Keep default themeMode='system' |
| 26 | + } finally { |
| 27 | + ns.state.themeModeLoaded = true; |
| 28 | + // If the overlay already exists, apply the loaded theme immediately. |
| 29 | + applyOverlayThemeClass(); |
37 | 30 | } |
| 31 | + } |
| 32 | + |
| 33 | + function applyOverlayThemeClass() { |
| 34 | + const overlay = getOverlay(); |
| 35 | + if (!overlay) return; |
| 36 | + |
| 37 | + const theme = ns.state.themeMode || 'system'; |
| 38 | + const wantsLight = theme === 'light'; |
| 39 | + const wantsDark = theme === 'dark'; |
| 40 | + |
| 41 | + // Avoid class churn: only touch DOM if we need to. |
| 42 | + const hasLight = overlay.classList.contains('theme-light'); |
| 43 | + const hasDark = overlay.classList.contains('theme-dark'); |
| 44 | + if (wantsLight === hasLight && wantsDark === hasDark) return; |
| 45 | + |
| 46 | + overlay.classList.toggle('theme-light', wantsLight); |
| 47 | + overlay.classList.toggle('theme-dark', wantsDark); |
| 48 | + } |
| 49 | + |
| 50 | + // Keep ns.state.themeMode in sync without doing async work on hover. |
| 51 | + try { |
| 52 | + browser.storage.onChanged.addListener((changes, areaName) => { |
| 53 | + if (areaName !== 'local') return; |
| 54 | + if (!changes.themeMode) return; |
| 55 | + ns.state.themeMode = changes.themeMode.newValue || 'system'; |
| 56 | + ns.state.themeModeLoaded = true; |
| 57 | + applyOverlayThemeClass(); |
| 58 | + }); |
| 59 | + } catch (e) { |
| 60 | + // Defensive: some environments may not expose storage listeners in this context. |
| 61 | + } |
| 62 | + |
| 63 | + // Kick off a single async load early. |
| 64 | + loadThemeModeOnce(); |
38 | 65 |
|
39 | | - // console.log('[Copy LaTeX] setOverlayThemeClass: using theme =', theme); |
40 | | - if (theme === 'light') overlay.classList.add('theme-light'); |
41 | | - else if (theme === 'dark') overlay.classList.add('theme-dark'); |
42 | | - // If system, do not add any theme class (prefers-color-scheme CSS media query will apply) |
43 | | - } |
| 66 | + function getOverlay() { |
| 67 | + return ns.state.overlay; |
| 68 | + } |
| 69 | + |
| 70 | + function setOverlay(el) { |
| 71 | + ns.state.overlay = el; |
| 72 | + } |
| 73 | + |
| 74 | + async function setOverlayThemeClass() { |
| 75 | + // Backwards-compatible async signature. |
| 76 | + // We *never* do async theme reads on hover; we apply the cached state. |
| 77 | + if (!ns.state.themeModeLoaded) loadThemeModeOnce(); |
| 78 | + applyOverlayThemeClass(); |
| 79 | + } |
44 | 80 |
|
45 | 81 | function createOverlay() { |
46 | 82 | const overlay = document.createElement('div'); |
47 | 83 | overlay.className = 'hoverlatex-overlay'; |
48 | | - // console.log('[Copy LaTeX] Overlay created. Initial class:', overlay.className); |
49 | 84 |
|
50 | 85 | setOverlay(overlay); |
51 | 86 |
|
52 | | - // Set theme class based on user preference |
53 | | - setOverlayThemeClass().then(() => { |
54 | | - console.log('[Copy LaTeX] Overlay after theme set. Classes:', overlay.className); |
55 | | - const bg = window.getComputedStyle(overlay).backgroundColor; |
56 | | - console.log('[Copy LaTeX] Overlay computed background after theme set:', bg); |
57 | | - }); |
58 | | - |
59 | | - // MutationObserver to log class/style changes (I'll remove it later) |
60 | | - const observer = new MutationObserver((mutationsList) => { |
61 | | - for (const mutation of mutationsList) { |
62 | | - if ( |
63 | | - mutation.type === 'attributes' && |
64 | | - (mutation.attributeName === 'class' || mutation.attributeName === 'style') |
65 | | - ) { |
66 | | - console.log('[Copy LaTeX][MutationObserver] Overlay attribute changed:', mutation.attributeName, { |
67 | | - class: overlay.className, |
68 | | - style: overlay.getAttribute('style'), |
69 | | - computedBg: window.getComputedStyle(overlay).backgroundColor, |
70 | | - }); |
71 | | - } |
72 | | - } |
73 | | - }); |
74 | | - observer.observe(overlay, { attributes: true, attributeFilter: ['class', 'style'] }); |
75 | | - |
76 | | - // Random interval observer: logs computed background color at random intervals (10-100ms) |
77 | | - let lastBg = ''; |
78 | | - function randomBgLogger() { |
79 | | - const currentOverlay = getOverlay(); |
80 | | - if (currentOverlay) { |
81 | | - const bg = window.getComputedStyle(currentOverlay).backgroundColor; |
82 | | - if (bg !== lastBg) { |
83 | | - console.log('[Copy LaTeX][RandomBgObserver] Overlay computed background:', bg); |
84 | | - lastBg = bg; |
85 | | - } |
86 | | - } |
87 | | - |
88 | | - // Schedule next check at a random interval between 10ms and 100ms |
89 | | - const nextDelay = Math.floor(Math.random() * 91) + 10; |
90 | | - setTimeout(randomBgLogger, nextDelay); |
91 | | - } |
92 | | - randomBgLogger(); |
| 87 | + // Apply cached theme immediately (no async waits). |
| 88 | + setOverlayThemeClass(); |
93 | 89 |
|
94 | 90 | // HTML overlay content with inline SVG icon and 'Click to copy' text |
95 | 91 | overlay.appendChild(ns.svg.createSvgFromString(ns.svg.copy_svg)); |
|
110 | 106 | // console.log('[Copy LaTeX] showOverlay: overlay exists, reusing. Classes:', overlay.className); |
111 | 107 | } |
112 | 108 |
|
113 | | - // Only make overlay visible after theme is set |
114 | | - setOverlayThemeClass().then(() => { |
115 | | - // Remove .visible if present before theme is set |
116 | | - overlay.classList.remove('visible'); |
117 | | - // console.log('[Copy LaTeX] showOverlay: after theme set. Classes:', overlay.className); |
118 | | - const bg = window.getComputedStyle(overlay).backgroundColor; |
119 | | - // console.log('[Copy LaTeX] showOverlay: computed background:', bg); |
120 | | - overlay.dataset.tex = tex; |
121 | | - |
122 | | - const rect = target.getBoundingClientRect(); |
123 | | - const overlayWidth = overlay.offsetWidth; |
124 | | - const top = rect.top + window.scrollY - overlay.offsetHeight - 8; |
125 | | - const left = rect.left + window.scrollX + rect.width / 2 - overlayWidth / 2; |
126 | | - overlay.style.top = `${top}px`; |
127 | | - overlay.style.left = `${left}px`; |
128 | | - overlay.classList.add('visible'); |
129 | | - }); |
| 109 | + // Ensure theme is applied synchronously to avoid flicker. |
| 110 | + setOverlayThemeClass(); |
| 111 | + overlay.dataset.tex = tex; |
| 112 | + |
| 113 | + const rect = target.getBoundingClientRect(); |
| 114 | + const overlayWidth = overlay.offsetWidth; |
| 115 | + const top = rect.top + window.scrollY - overlay.offsetHeight - 8; |
| 116 | + const left = rect.left + window.scrollX + rect.width / 2 - overlayWidth / 2; |
| 117 | + overlay.style.top = `${top}px`; |
| 118 | + overlay.style.left = `${left}px`; |
| 119 | + overlay.classList.add('visible'); |
130 | 120 | } |
131 | 121 |
|
132 | 122 | function hideOverlay() { |
|
0 commit comments