Fix omnibox initialization flash
- commit
- 72a6840c31a30a79c770957073e7136a0b6aa6a6
- author
- bnema · · 2025-11-18 22:20 UTC
- parent
- f83a7ce
- signature
- unsigned
5 files · +68 -53
M CHANGELOG.md +3 -0
| @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. | |||
| 7 | 7 | ### Added | |
| 8 | 8 | - **Omnibox initial history display**: Configurable behavior when opening empty omnibox via `omnibox.initial_behavior` ("recent", "most_visited", "none"). Shows initial history on open and when input is cleared. Default: recent visits. | |
| 9 | 9 | ||
| 10 | + | ### Fixed | |
| 11 | + | - **Omnibox flash on load**: Load the injected GUI bundle at document-start and initialize the omnibox as soon as DOM is ready, ensuring it is ready without briefly flashing during page startup or new pane creation. | |
| 12 | + | ||
| 10 | 13 | ## [0.14.1] - 2025-11-18 | |
| 11 | 14 | ||
| 12 | 15 | ### Added | |
| @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. | |||||
| 7 | ### Added | 7 | ### Added | ||
| 8 | - **Omnibox initial history display**: Configurable behavior when opening empty omnibox via `omnibox.initial_behavior` ("recent", "most_visited", "none"). Shows initial history on open and when input is cleared. Default: recent visits. | 8 | - **Omnibox initial history display**: Configurable behavior when opening empty omnibox via `omnibox.initial_behavior` ("recent", "most_visited", "none"). Shows initial history on open and when input is cleared. Default: recent visits. | ||
| 9 | 9 | ||||
| 10 | + | ### Fixed | |||
| 11 | + | - **Omnibox flash on load**: Load the injected GUI bundle at document-start and initialize the omnibox as soon as DOM is ready, ensuring it is ready without briefly flashing during page startup or new pane creation. | |||
| 12 | + | ||||
| 10 | ## [0.14.1] - 2025-11-18 | 13 | ## [0.14.1] - 2025-11-18 | ||
| 11 | 14 | ||||
| 12 | ### Added | 15 | ### Added | ||
M gui/src/components/omnibox/Omnibox.svelte +25 -26
| @@ -16,6 +16,9 @@ | |||
| 16 | 16 | let mode = $derived(omniboxStore.mode); | |
| 17 | 17 | let faded = $derived(omniboxStore.faded); | |
| 18 | 18 | ||
| 19 | + | // Ready state - prevents flash before initialization | |
| 20 | + | let isReady = $state(false); | |
| 21 | + | ||
| 19 | 22 | // Component refs | |
| 20 | 23 | let boxElement: HTMLDivElement; | |
| 21 | 24 | let blurLayerElement: HTMLDivElement; | |
| @@ -136,29 +139,27 @@ | |||
| 136 | 139 | // Functions to control main-world event blocking | |
| 137 | 140 | function enablePageEventBlocking() { | |
| 138 | 141 | try { | |
| 139 | - | // Send message to Go backend to enable keyboard blocking | |
| 140 | - | if (window.webkit?.messageHandlers?.dumber) { | |
| 141 | - | window.webkit.messageHandlers.dumber.postMessage(JSON.stringify({ | |
| 142 | - | type: 'keyboard_blocking', | |
| 143 | - | action: 'enable' | |
| 144 | - | })); | |
| 145 | - | } | |
| 142 | + | // Send message to Go backend via bridge (isolated world → main world → Go) | |
| 143 | + | omniboxBridge.postMessage({ | |
| 144 | + | type: 'keyboard_blocking', | |
| 145 | + | action: 'enable' | |
| 146 | + | }); | |
| 147 | + | console.log('[omnibox] Sent enable keyboard blocking message'); | |
| 146 | 148 | } catch (error) { | |
| 147 | - | console.warn('[omnibox] Failed to enable page event blocking:', error); | |
| 149 | + | console.error('[omnibox] Failed to enable page event blocking:', error); | |
| 148 | 150 | } | |
| 149 | 151 | } | |
| 150 | 152 | ||
| 151 | 153 | function disablePageEventBlocking() { | |
| 152 | 154 | try { | |
| 153 | - | // Send message to Go backend to disable keyboard blocking | |
| 154 | - | if (window.webkit?.messageHandlers?.dumber) { | |
| 155 | - | window.webkit.messageHandlers.dumber.postMessage(JSON.stringify({ | |
| 156 | - | type: 'keyboard_blocking', | |
| 157 | - | action: 'disable' | |
| 158 | - | })); | |
| 159 | - | } | |
| 155 | + | // Send message to Go backend via bridge (isolated world → main world → Go) | |
| 156 | + | omniboxBridge.postMessage({ | |
| 157 | + | type: 'keyboard_blocking', | |
| 158 | + | action: 'disable' | |
| 159 | + | }); | |
| 160 | + | console.log('[omnibox] Sent disable keyboard blocking message'); | |
| 160 | 161 | } catch (error) { | |
| 161 | - | console.warn('[omnibox] Failed to disable page event blocking:', error); | |
| 162 | + | console.error('[omnibox] Failed to disable page event blocking:', error); | |
| 162 | 163 | } | |
| 163 | 164 | } | |
| 164 | 165 | ||
| @@ -369,17 +370,13 @@ | |||
| 369 | 370 | omniboxBridge.setSuggestions(pending); | |
| 370 | 371 | delete (window as any).__dumber_omnibox_pending_suggestions; | |
| 371 | 372 | } | |
| 372 | - | ||
| 373 | - | // Auto-open omnibox on about:blank pages | |
| 374 | - | if (window.location.href === 'about:blank') { | |
| 375 | - | console.log('Auto-opening omnibox for about:blank'); | |
| 376 | - | omniboxStore.open('omnibox'); | |
| 377 | - | // Fetch initial history for empty omnibox | |
| 378 | - | omniboxBridge.fetchInitialHistory(); | |
| 379 | - | } | |
| 380 | 373 | } catch (error) { | |
| 381 | 374 | console.error('❌ Failed to set up omnibox global API:', error); | |
| 382 | 375 | } | |
| 376 | + | ||
| 377 | + | // Mark as ready - removes initial hiding styles | |
| 378 | + | isReady = true; | |
| 379 | + | console.log('✅ Omnibox fully initialized and ready'); | |
| 383 | 380 | }); | |
| 384 | 381 | ||
| 385 | 382 | onDestroy(() => { | |
| @@ -413,8 +410,10 @@ | |||
| 413 | 410 | left: 0 !important; | |
| 414 | 411 | right: 0 !important; | |
| 415 | 412 | bottom: 0 !important; | |
| 416 | - | z-index: 2147483647 !important; | |
| 417 | - | pointer-events: none !important; | |
| 413 | + | z-index: {isReady ? '2147483647' : '-9999'} !important; | |
| 414 | + | visibility: {isReady ? 'visible' : 'hidden'} !important; | |
| 415 | + | opacity: {isReady ? '1' : '0'} !important; | |
| 416 | + | pointer-events: {isReady ? 'none' : 'none'} !important; | |
| 418 | 417 | margin: 0 !important; | |
| 419 | 418 | padding: 0 !important; | |
| 420 | 419 | isolation: isolate;" | |
| @@ -16,6 +16,9 @@ | |||||
| 16 | let mode = $derived(omniboxStore.mode); | 16 | let mode = $derived(omniboxStore.mode); | ||
| 17 | let faded = $derived(omniboxStore.faded); | 17 | let faded = $derived(omniboxStore.faded); | ||
| 18 | 18 | ||||
| 19 | + | // Ready state - prevents flash before initialization | |||
| 20 | + | let isReady = $state(false); | |||
| 21 | + | ||||
| 19 | // Component refs | 22 | // Component refs | ||
| 20 | let boxElement: HTMLDivElement; | 23 | let boxElement: HTMLDivElement; | ||
| 21 | let blurLayerElement: HTMLDivElement; | 24 | let blurLayerElement: HTMLDivElement; | ||
| @@ -136,29 +139,27 @@ | |||||
| 136 | // Functions to control main-world event blocking | 139 | // Functions to control main-world event blocking | ||
| 137 | function enablePageEventBlocking() { | 140 | function enablePageEventBlocking() { | ||
| 138 | try { | 141 | try { | ||
| 139 | - | // Send message to Go backend to enable keyboard blocking | 142 | + | // Send message to Go backend via bridge (isolated world → main world → Go) |
| 140 | - | if (window.webkit?.messageHandlers?.dumber) { | 143 | + | omniboxBridge.postMessage({ |
| 141 | - | window.webkit.messageHandlers.dumber.postMessage(JSON.stringify({ | 144 | + | type: 'keyboard_blocking', |
| 142 | - | type: 'keyboard_blocking', | 145 | + | action: 'enable' |
| 143 | - | action: 'enable' | 146 | + | }); |
| 144 | - | })); | 147 | + | console.log('[omnibox] Sent enable keyboard blocking message'); |
| 145 | - | } | |||
| 146 | } catch (error) { | 148 | } catch (error) { | ||
| 147 | - | console.warn('[omnibox] Failed to enable page event blocking:', error); | 149 | + | console.error('[omnibox] Failed to enable page event blocking:', error); |
| 148 | } | 150 | } | ||
| 149 | } | 151 | } | ||
| 150 | 152 | ||||
| 151 | function disablePageEventBlocking() { | 153 | function disablePageEventBlocking() { | ||
| 152 | try { | 154 | try { | ||
| 153 | - | // Send message to Go backend to disable keyboard blocking | 155 | + | // Send message to Go backend via bridge (isolated world → main world → Go) |
| 154 | - | if (window.webkit?.messageHandlers?.dumber) { | 156 | + | omniboxBridge.postMessage({ |
| 155 | - | window.webkit.messageHandlers.dumber.postMessage(JSON.stringify({ | 157 | + | type: 'keyboard_blocking', |
| 156 | - | type: 'keyboard_blocking', | 158 | + | action: 'disable' |
| 157 | - | action: 'disable' | 159 | + | }); |
| 158 | - | })); | 160 | + | console.log('[omnibox] Sent disable keyboard blocking message'); |
| 159 | - | } | |||
| 160 | } catch (error) { | 161 | } catch (error) { | ||
| 161 | - | console.warn('[omnibox] Failed to disable page event blocking:', error); | 162 | + | console.error('[omnibox] Failed to disable page event blocking:', error); |
| 162 | } | 163 | } | ||
| 163 | } | 164 | } | ||
| 164 | 165 | ||||
| @@ -369,17 +370,13 @@ | |||||
| 369 | omniboxBridge.setSuggestions(pending); | 370 | omniboxBridge.setSuggestions(pending); | ||
| 370 | delete (window as any).__dumber_omnibox_pending_suggestions; | 371 | delete (window as any).__dumber_omnibox_pending_suggestions; | ||
| 371 | } | 372 | } | ||
| 372 | - | ||||
| 373 | - | // Auto-open omnibox on about:blank pages | |||
| 374 | - | if (window.location.href === 'about:blank') { | |||
| 375 | - | console.log('Auto-opening omnibox for about:blank'); | |||
| 376 | - | omniboxStore.open('omnibox'); | |||
| 377 | - | // Fetch initial history for empty omnibox | |||
| 378 | - | omniboxBridge.fetchInitialHistory(); | |||
| 379 | - | } | |||
| 380 | } catch (error) { | 373 | } catch (error) { | ||
| 381 | console.error('❌ Failed to set up omnibox global API:', error); | 374 | console.error('❌ Failed to set up omnibox global API:', error); | ||
| 382 | } | 375 | } | ||
| 376 | + | ||||
| 377 | + | // Mark as ready - removes initial hiding styles | |||
| 378 | + | isReady = true; | |||
| 379 | + | console.log('✅ Omnibox fully initialized and ready'); | |||
| 383 | }); | 380 | }); | ||
| 384 | 381 | ||||
| 385 | onDestroy(() => { | 382 | onDestroy(() => { | ||
| @@ -413,8 +410,10 @@ | |||||
| 413 | left: 0 !important; | 410 | left: 0 !important; | ||
| 414 | right: 0 !important; | 411 | right: 0 !important; | ||
| 415 | bottom: 0 !important; | 412 | bottom: 0 !important; | ||
| 416 | - | z-index: 2147483647 !important; | 413 | + | z-index: {isReady ? '2147483647' : '-9999'} !important; |
| 417 | - | pointer-events: none !important; | 414 | + | visibility: {isReady ? 'visible' : 'hidden'} !important; |
| 415 | + | opacity: {isReady ? '1' : '0'} !important; | |||
| 416 | + | pointer-events: {isReady ? 'none' : 'none'} !important; | |||
| 418 | margin: 0 !important; | 417 | margin: 0 !important; | ||
| 419 | padding: 0 !important; | 418 | padding: 0 !important; | ||
| 420 | isolation: isolate;" | 419 | isolation: isolate;" | ||
M gui/src/injected/bootstrap.ts +15 -20
| @@ -60,6 +60,14 @@ function whenDOMReady(callback: () => void) { | |||
| 60 | 60 | } | |
| 61 | 61 | } | |
| 62 | 62 | ||
| 63 | + | function whenPageLoaded(callback: () => void) { | |
| 64 | + | if (document.readyState === "complete") { | |
| 65 | + | callback(); | |
| 66 | + | } else { | |
| 67 | + | window.addEventListener("load", callback, { once: true }); | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | ||
| 63 | 71 | function normalizeWebviewId(value: unknown): number | null { | |
| 64 | 72 | if (typeof value === "number" && Number.isFinite(value)) { | |
| 65 | 73 | return value; | |
| @@ -177,39 +185,26 @@ export function bootstrapGUI(): void { | |||
| 177 | 185 | } | |
| 178 | 186 | }); | |
| 179 | 187 | ||
| 188 | + | // Initialize toast early (DOM ready is enough) | |
| 180 | 189 | whenDOMReady(async () => { | |
| 181 | - | console.log("[GUI] DOM ready, starting initialization"); | |
| 182 | - | let toastInitialized = false; | |
| 183 | - | ||
| 190 | + | console.log("[GUI] DOM ready, initializing toast"); | |
| 184 | 191 | try { | |
| 185 | - | console.log("[GUI] Attempting toast initialization"); | |
| 186 | 192 | await initializeToast(); | |
| 187 | - | toastInitialized = true; | |
| 188 | 193 | console.log("[GUI] Toast initialized successfully"); | |
| 189 | 194 | } catch (e) { | |
| 190 | - | console.error("❌ Failed to initialize Svelte toast system:", e); | |
| 191 | - | throw e; | |
| 195 | + | console.error("❌ Failed to initialize toast system:", e); | |
| 192 | 196 | } | |
| 197 | + | }); | |
| 193 | 198 | ||
| 199 | + | // Initialize omnibox as soon as the DOM is ready (no need to wait for full load) | |
| 200 | + | whenDOMReady(async () => { | |
| 201 | + | console.log("[GUI] DOM ready, initializing omnibox"); | |
| 194 | 202 | try { | |
| 195 | 203 | await initializeOmnibox(); | |
| 196 | 204 | console.log("✅ Omnibox system initialized successfully"); | |
| 197 | - | ||
| 198 | - | // Auto-open omnibox on about:blank pages | |
| 199 | - | if (window.location.href === "about:blank" && window.__dumber_omnibox) { | |
| 200 | - | console.log("[about:blank] Auto-opening omnibox"); | |
| 201 | - | setTimeout(() => { | |
| 202 | - | window.__dumber_omnibox?.open?.("omnibox", ""); | |
| 203 | - | }, 100); | |
| 204 | - | } | |
| 205 | 205 | } catch (omniboxError) { | |
| 206 | 206 | console.error("❌ Failed to initialize omnibox system:", omniboxError); | |
| 207 | 207 | } | |
| 208 | - | ||
| 209 | - | console.log("✅ GUI systems initialization complete", { | |
| 210 | - | toast: toastInitialized ? "Svelte" : "Fallback", | |
| 211 | - | omnibox: !!window.__dumber_omnibox, | |
| 212 | - | }); | |
| 213 | 208 | }); | |
| 214 | 209 | ||
| 215 | 210 | window.__dumber_keyboard = keyboardService; | |
| @@ -60,6 +60,14 @@ function whenDOMReady(callback: () => void) { | |||||
| 60 | } | 60 | } | ||
| 61 | } | 61 | } | ||
| 62 | 62 | ||||
| 63 | + | function whenPageLoaded(callback: () => void) { | |||
| 64 | + | if (document.readyState === "complete") { | |||
| 65 | + | callback(); | |||
| 66 | + | } else { | |||
| 67 | + | window.addEventListener("load", callback, { once: true }); | |||
| 68 | + | } | |||
| 69 | + | } | |||
| 70 | + | ||||
| 63 | function normalizeWebviewId(value: unknown): number | null { | 71 | function normalizeWebviewId(value: unknown): number | null { | ||
| 64 | if (typeof value === "number" && Number.isFinite(value)) { | 72 | if (typeof value === "number" && Number.isFinite(value)) { | ||
| 65 | return value; | 73 | return value; | ||
| @@ -177,39 +185,26 @@ export function bootstrapGUI(): void { | |||||
| 177 | } | 185 | } | ||
| 178 | }); | 186 | }); | ||
| 179 | 187 | ||||
| 188 | + | // Initialize toast early (DOM ready is enough) | |||
| 180 | whenDOMReady(async () => { | 189 | whenDOMReady(async () => { | ||
| 181 | - | console.log("[GUI] DOM ready, starting initialization"); | 190 | + | console.log("[GUI] DOM ready, initializing toast"); |
| 182 | - | let toastInitialized = false; | |||
| 183 | - | ||||
| 184 | try { | 191 | try { | ||
| 185 | - | console.log("[GUI] Attempting toast initialization"); | |||
| 186 | await initializeToast(); | 192 | await initializeToast(); | ||
| 187 | - | toastInitialized = true; | |||
| 188 | console.log("[GUI] Toast initialized successfully"); | 193 | console.log("[GUI] Toast initialized successfully"); | ||
| 189 | } catch (e) { | 194 | } catch (e) { | ||
| 190 | - | console.error("❌ Failed to initialize Svelte toast system:", e); | 195 | + | console.error("❌ Failed to initialize toast system:", e); |
| 191 | - | throw e; | |||
| 192 | } | 196 | } | ||
| 197 | + | }); | |||
| 193 | 198 | ||||
| 199 | + | // Initialize omnibox as soon as the DOM is ready (no need to wait for full load) | |||
| 200 | + | whenDOMReady(async () => { | |||
| 201 | + | console.log("[GUI] DOM ready, initializing omnibox"); | |||
| 194 | try { | 202 | try { | ||
| 195 | await initializeOmnibox(); | 203 | await initializeOmnibox(); | ||
| 196 | console.log("✅ Omnibox system initialized successfully"); | 204 | console.log("✅ Omnibox system initialized successfully"); | ||
| 197 | - | ||||
| 198 | - | // Auto-open omnibox on about:blank pages | |||
| 199 | - | if (window.location.href === "about:blank" && window.__dumber_omnibox) { | |||
| 200 | - | console.log("[about:blank] Auto-opening omnibox"); | |||
| 201 | - | setTimeout(() => { | |||
| 202 | - | window.__dumber_omnibox?.open?.("omnibox", ""); | |||
| 203 | - | }, 100); | |||
| 204 | - | } | |||
| 205 | } catch (omniboxError) { | 205 | } catch (omniboxError) { | ||
| 206 | console.error("❌ Failed to initialize omnibox system:", omniboxError); | 206 | console.error("❌ Failed to initialize omnibox system:", omniboxError); | ||
| 207 | } | 207 | } | ||
| 208 | - | ||||
| 209 | - | console.log("✅ GUI systems initialization complete", { | |||
| 210 | - | toast: toastInitialized ? "Svelte" : "Fallback", | |||
| 211 | - | omnibox: !!window.__dumber_omnibox, | |||
| 212 | - | }); | |||
| 213 | }); | 208 | }); | ||
| 214 | 209 | ||||
| 215 | window.__dumber_keyboard = keyboardService; | 210 | window.__dumber_keyboard = keyboardService; | ||
M gui/src/injected/modules/omnibox.ts +19 -3
| @@ -36,10 +36,26 @@ export async function initializeOmnibox( | |||
| 36 | 36 | return; | |
| 37 | 37 | } | |
| 38 | 38 | ||
| 39 | - | // Check if DOM is ready | |
| 39 | + | // Ensure a <body> exists before mounting (document-start injection can run before it's available) | |
| 40 | 40 | if (!document.body) { | |
| 41 | - | console.warn("DOM body not ready for omnibox initialization"); | |
| 42 | - | return; | |
| 41 | + | await new Promise<void>((resolve) => { | |
| 42 | + | let resolved = false; | |
| 43 | + | const finish = () => { | |
| 44 | + | if (!resolved) { | |
| 45 | + | resolved = true; | |
| 46 | + | resolve(); | |
| 47 | + | } | |
| 48 | + | }; | |
| 49 | + | const check = () => { | |
| 50 | + | if (document.body) { | |
| 51 | + | finish(); | |
| 52 | + | } else { | |
| 53 | + | setTimeout(check, 10); | |
| 54 | + | } | |
| 55 | + | }; | |
| 56 | + | document.addEventListener("DOMContentLoaded", finish, { once: true }); | |
| 57 | + | check(); | |
| 58 | + | }); | |
| 43 | 59 | } | |
| 44 | 60 | ||
| 45 | 61 | // Mount omnibox into the shared global Shadow DOM host | |
| @@ -36,10 +36,26 @@ export async function initializeOmnibox( | |||||
| 36 | return; | 36 | return; | ||
| 37 | } | 37 | } | ||
| 38 | 38 | ||||
| 39 | - | // Check if DOM is ready | 39 | + | // Ensure a <body> exists before mounting (document-start injection can run before it's available) |
| 40 | if (!document.body) { | 40 | if (!document.body) { | ||
| 41 | - | console.warn("DOM body not ready for omnibox initialization"); | 41 | + | await new Promise<void>((resolve) => { |
| 42 | - | return; | 42 | + | let resolved = false; |
| 43 | + | const finish = () => { | |||
| 44 | + | if (!resolved) { | |||
| 45 | + | resolved = true; | |||
| 46 | + | resolve(); | |||
| 47 | + | } | |||
| 48 | + | }; | |||
| 49 | + | const check = () => { | |||
| 50 | + | if (document.body) { | |||
| 51 | + | finish(); | |||
| 52 | + | } else { | |||
| 53 | + | setTimeout(check, 10); | |||
| 54 | + | } | |||
| 55 | + | }; | |||
| 56 | + | document.addEventListener("DOMContentLoaded", finish, { once: true }); | |||
| 57 | + | check(); | |||
| 58 | + | }); | |||
| 43 | } | 59 | } | ||
| 44 | 60 | ||||
| 45 | // Mount omnibox into the shared global Shadow DOM host | 61 | // Mount omnibox into the shared global Shadow DOM host | ||
M pkg/webkit/user_content.go +6 -4
| @@ -163,8 +163,9 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string, | |||
| 163 | 163 | log.Printf("[webkit] Injected main-world script (%d bytes, main world)", len(assets.MainWorldScript)) | |
| 164 | 164 | } | |
| 165 | 165 | ||
| 166 | - | // Inject GUI controls script in ISOLATED world only | |
| 166 | + | // Inject GUI controls script in ISOLATED world at document-start | |
| 167 | 167 | // This contains Svelte components and must be protected from page interference | |
| 168 | + | // Loading at document-start ensures the omnibox is available as early as possible | |
| 168 | 169 | if assets.GUIScript != "" { | |
| 169 | 170 | ucm.AddScript(webkit.NewUserScriptForWorld( | |
| 170 | 171 | assets.GUIScript, | |
| @@ -174,12 +175,13 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string, | |||
| 174 | 175 | nil, | |
| 175 | 176 | nil, | |
| 176 | 177 | )) | |
| 177 | - | log.Printf("[webkit] Injected GUI controls script (%d bytes, isolated world)", len(assets.GUIScript)) | |
| 178 | + | log.Printf("[webkit] Injected GUI controls script (%d bytes, isolated world, document-start)", len(assets.GUIScript)) | |
| 178 | 179 | } | |
| 179 | 180 | ||
| 180 | - | // Inject component CSS styles as a JavaScript variable in ISOLATED world | |
| 181 | + | // Inject component CSS styles as a JavaScript variable in ISOLATED world at document-start | |
| 181 | 182 | // These styles need to be injected into the shadow root by shadowHost.ts | |
| 182 | 183 | // because Shadow DOM has style encapsulation - external stylesheets don't penetrate it | |
| 184 | + | // Keep at document-start to prevent flash of unstyled content | |
| 183 | 185 | if assets.ComponentStyles != "" { | |
| 184 | 186 | // Use fmt.Sprintf with %q to properly escape the CSS string for JavaScript | |
| 185 | 187 | componentStylesScript := fmt.Sprintf(`window.__dumber_component_styles = %q;`, assets.ComponentStyles) | |
| @@ -192,7 +194,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string, | |||
| 192 | 194 | nil, | |
| 193 | 195 | nil, | |
| 194 | 196 | )) | |
| 195 | - | log.Printf("[webkit] Injected component styles string (%d bytes, isolated world)", len(assets.ComponentStyles)) | |
| 197 | + | log.Printf("[webkit] Injected component styles string (%d bytes, isolated world, document-start)", len(assets.ComponentStyles)) | |
| 196 | 198 | } | |
| 197 | 199 | ||
| 198 | 200 | // Register script message handler "dumber" in the MAIN world | |
| @@ -163,8 +163,9 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string, | |||||
| 163 | log.Printf("[webkit] Injected main-world script (%d bytes, main world)", len(assets.MainWorldScript)) | 163 | log.Printf("[webkit] Injected main-world script (%d bytes, main world)", len(assets.MainWorldScript)) | ||
| 164 | } | 164 | } | ||
| 165 | 165 | ||||
| 166 | - | // Inject GUI controls script in ISOLATED world only | 166 | + | // Inject GUI controls script in ISOLATED world at document-start |
| 167 | // This contains Svelte components and must be protected from page interference | 167 | // This contains Svelte components and must be protected from page interference | ||
| 168 | + | // Loading at document-start ensures the omnibox is available as early as possible | |||
| 168 | if assets.GUIScript != "" { | 169 | if assets.GUIScript != "" { | ||
| 169 | ucm.AddScript(webkit.NewUserScriptForWorld( | 170 | ucm.AddScript(webkit.NewUserScriptForWorld( | ||
| 170 | assets.GUIScript, | 171 | assets.GUIScript, | ||
| @@ -174,12 +175,13 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string, | |||||
| 174 | nil, | 175 | nil, | ||
| 175 | nil, | 176 | nil, | ||
| 176 | )) | 177 | )) | ||
| 177 | - | log.Printf("[webkit] Injected GUI controls script (%d bytes, isolated world)", len(assets.GUIScript)) | 178 | + | log.Printf("[webkit] Injected GUI controls script (%d bytes, isolated world, document-start)", len(assets.GUIScript)) |
| 178 | } | 179 | } | ||
| 179 | 180 | ||||
| 180 | - | // Inject component CSS styles as a JavaScript variable in ISOLATED world | 181 | + | // Inject component CSS styles as a JavaScript variable in ISOLATED world at document-start |
| 181 | // These styles need to be injected into the shadow root by shadowHost.ts | 182 | // These styles need to be injected into the shadow root by shadowHost.ts | ||
| 182 | // because Shadow DOM has style encapsulation - external stylesheets don't penetrate it | 183 | // because Shadow DOM has style encapsulation - external stylesheets don't penetrate it | ||
| 184 | + | // Keep at document-start to prevent flash of unstyled content | |||
| 183 | if assets.ComponentStyles != "" { | 185 | if assets.ComponentStyles != "" { | ||
| 184 | // Use fmt.Sprintf with %q to properly escape the CSS string for JavaScript | 186 | // Use fmt.Sprintf with %q to properly escape the CSS string for JavaScript | ||
| 185 | componentStylesScript := fmt.Sprintf(`window.__dumber_component_styles = %q;`, assets.ComponentStyles) | 187 | componentStylesScript := fmt.Sprintf(`window.__dumber_component_styles = %q;`, assets.ComponentStyles) | ||
| @@ -192,7 +194,7 @@ func SetupUserContentManager(view *webkit.WebView, appearanceConfigJSON string, | |||||
| 192 | nil, | 194 | nil, | ||
| 193 | nil, | 195 | nil, | ||
| 194 | )) | 196 | )) | ||
| 195 | - | log.Printf("[webkit] Injected component styles string (%d bytes, isolated world)", len(assets.ComponentStyles)) | 197 | + | log.Printf("[webkit] Injected component styles string (%d bytes, isolated world, document-start)", len(assets.ComponentStyles)) |
| 196 | } | 198 | } | ||
| 197 | 199 | ||||
| 198 | // Register script message handler "dumber" in the MAIN world | 200 | // Register script message handler "dumber" in the MAIN world | ||