fix: filter accent detection JS to only accent-capable keys
Generate JS allowlist from entity.AccentMap so only a,c,e,i,n,o,s,u,y trigger bridge messages. Reduces unnecessary JS-to-Go traffic for the 17 non-accent letters.
- commit
- c97e5fd0abedf7c2376a6357c8d0a01aedd66cf5
- author
- brice · · 2026-03-13 06:32 UTC
- parent
- b0dae66
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +20 -19
M internal/infrastructure/webkit/injector.go +20 -19
| @@ -3,9 +3,11 @@ package webkit | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "sort" | |
| 6 | 7 | "strings" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| 10 | + | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | 11 | "github.com/bnema/dumber/internal/logging" | |
| 10 | 12 | "github.com/bnema/puregotk-webkit/webkit" | |
| 11 | 13 | ) | |
| @@ -141,16 +143,23 @@ const autoCopySelectionScript = `(function() { | |||
| 141 | 143 | }); | |
| 142 | 144 | })();` | |
| 143 | 145 | ||
| 144 | - | // accentDetectionScript detects keydown/keyup of a-z keys in WebView inputs and | |
| 145 | - | // forwards them to the Go-side InsertAccentUseCase via the message bridge. | |
| 146 | - | // Go handles timing, accent lookup, and picker display — JS only reports events. | |
| 147 | - | const accentDetectionScript = `(function() { | |
| 148 | - | 'use strict'; | |
| 146 | + | // accentDetectionScript is built at init from entity.AccentMap so the JS | |
| 147 | + | // filter stays in sync with the Go-side accent table. | |
| 148 | + | var accentDetectionScript string | |
| 149 | + | ||
| 150 | + | func init() { | |
| 151 | + | // Build JS Set literal from AccentMap keys: "new Set(['a','c','e',...])" | |
| 152 | + | keys := make([]string, 0, len(entity.AccentMap)) | |
| 153 | + | for k := range entity.AccentMap { | |
| 154 | + | keys = append(keys, fmt.Sprintf("'%c'", k)) | |
| 155 | + | } | |
| 156 | + | sort.Strings(keys) | |
| 149 | 157 | ||
| 150 | - | // Track pressed key for release matching | |
| 158 | + | accentDetectionScript = fmt.Sprintf(`(function() { | |
| 159 | + | 'use strict'; | |
| 160 | + | const accentKeys = new Set([%s]); | |
| 151 | 161 | let pressedKey = null; | |
| 152 | 162 | ||
| 153 | - | // Check if the event target is an editable element (input, textarea, contenteditable) | |
| 154 | 163 | function isEditableTarget(target) { | |
| 155 | 164 | const el = target instanceof Element ? target : null; | |
| 156 | 165 | if (!el) return false; | |
| @@ -158,29 +167,20 @@ const accentDetectionScript = `(function() { | |||
| 158 | 167 | if (el.closest('textarea')) return true; | |
| 159 | 168 | const input = el.closest('input'); | |
| 160 | 169 | if (!input) return false; | |
| 161 | - | // Exclude non-text input types | |
| 162 | 170 | return !/^(button|checkbox|color|file|hidden|image|radio|range|reset|submit)$/i.test(input.type); | |
| 163 | 171 | } | |
| 164 | 172 | ||
| 165 | 173 | document.addEventListener('keydown', function(e) { | |
| 166 | 174 | if (!isEditableTarget(e.target)) return; | |
| 167 | - | ||
| 168 | - | // Only handle a-z keys, with optional Shift | |
| 169 | - | // Skip if Ctrl or Alt are held (those are shortcuts) | |
| 170 | 175 | if (e.ctrlKey || e.altKey || e.metaKey) return; | |
| 171 | - | ||
| 172 | - | // Only single character keys a-z | |
| 173 | 176 | const key = e.key.toLowerCase(); | |
| 174 | - | if (key.length !== 1 || key < 'a' || key > 'z') return; | |
| 175 | - | ||
| 177 | + | if (key.length !== 1 || !accentKeys.has(key)) return; | |
| 176 | 178 | pressedKey = key; | |
| 177 | - | ||
| 178 | - | // Send to Go for accent detection | |
| 179 | 179 | window.webkit.messageHandlers.dumber.postMessage({ | |
| 180 | 180 | type: 'accent_key_press', | |
| 181 | 181 | payload: { char: key, shift: e.shiftKey } | |
| 182 | 182 | }); | |
| 183 | - | }, true); // capture phase to see events first | |
| 183 | + | }, true); | |
| 184 | 184 | ||
| 185 | 185 | document.addEventListener('keyup', function(e) { | |
| 186 | 186 | if (!isEditableTarget(e.target)) return; | |
| @@ -193,7 +193,8 @@ const accentDetectionScript = `(function() { | |||
| 193 | 193 | }); | |
| 194 | 194 | } | |
| 195 | 195 | }, true); | |
| 196 | - | })();` | |
| 196 | + | })();`, strings.Join(keys, ",")) | |
| 197 | + | } | |
| 197 | 198 | ||
| 198 | 199 | // accentDetectionInjectionMode controls which frames receive the accent detection script. | |
| 199 | 200 | // Set to AllFrames so the script runs in iframes as well as the top-level document. | |
| @@ -3,9 +3,11 @@ package webkit | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "sort" | |||
| 6 | "strings" | 7 | "strings" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| 10 | + | "github.com/bnema/dumber/internal/domain/entity" | |||
| 9 | "github.com/bnema/dumber/internal/logging" | 11 | "github.com/bnema/dumber/internal/logging" | ||
| 10 | "github.com/bnema/puregotk-webkit/webkit" | 12 | "github.com/bnema/puregotk-webkit/webkit" | ||
| 11 | ) | 13 | ) | ||
| @@ -141,16 +143,23 @@ const autoCopySelectionScript = `(function() { | |||||
| 141 | }); | 143 | }); | ||
| 142 | })();` | 144 | })();` | ||
| 143 | 145 | ||||
| 144 | - | // accentDetectionScript detects keydown/keyup of a-z keys in WebView inputs and | 146 | + | // accentDetectionScript is built at init from entity.AccentMap so the JS |
| 145 | - | // forwards them to the Go-side InsertAccentUseCase via the message bridge. | 147 | + | // filter stays in sync with the Go-side accent table. |
| 146 | - | // Go handles timing, accent lookup, and picker display — JS only reports events. | 148 | + | var accentDetectionScript string |
| 147 | - | const accentDetectionScript = `(function() { | 149 | + | |
| 148 | - | 'use strict'; | 150 | + | func init() { |
| 151 | + | // Build JS Set literal from AccentMap keys: "new Set(['a','c','e',...])" | |||
| 152 | + | keys := make([]string, 0, len(entity.AccentMap)) | |||
| 153 | + | for k := range entity.AccentMap { | |||
| 154 | + | keys = append(keys, fmt.Sprintf("'%c'", k)) | |||
| 155 | + | } | |||
| 156 | + | sort.Strings(keys) | |||
| 149 | 157 | ||||
| 150 | - | // Track pressed key for release matching | 158 | + | accentDetectionScript = fmt.Sprintf(`(function() { |
| 159 | + | 'use strict'; | |||
| 160 | + | const accentKeys = new Set([%s]); | |||
| 151 | let pressedKey = null; | 161 | let pressedKey = null; | ||
| 152 | 162 | ||||
| 153 | - | // Check if the event target is an editable element (input, textarea, contenteditable) | |||
| 154 | function isEditableTarget(target) { | 163 | function isEditableTarget(target) { | ||
| 155 | const el = target instanceof Element ? target : null; | 164 | const el = target instanceof Element ? target : null; | ||
| 156 | if (!el) return false; | 165 | if (!el) return false; | ||
| @@ -158,29 +167,20 @@ const accentDetectionScript = `(function() { | |||||
| 158 | if (el.closest('textarea')) return true; | 167 | if (el.closest('textarea')) return true; | ||
| 159 | const input = el.closest('input'); | 168 | const input = el.closest('input'); | ||
| 160 | if (!input) return false; | 169 | if (!input) return false; | ||
| 161 | - | // Exclude non-text input types | |||
| 162 | return !/^(button|checkbox|color|file|hidden|image|radio|range|reset|submit)$/i.test(input.type); | 170 | return !/^(button|checkbox|color|file|hidden|image|radio|range|reset|submit)$/i.test(input.type); | ||
| 163 | } | 171 | } | ||
| 164 | 172 | ||||
| 165 | document.addEventListener('keydown', function(e) { | 173 | document.addEventListener('keydown', function(e) { | ||
| 166 | if (!isEditableTarget(e.target)) return; | 174 | if (!isEditableTarget(e.target)) return; | ||
| 167 | - | ||||
| 168 | - | // Only handle a-z keys, with optional Shift | |||
| 169 | - | // Skip if Ctrl or Alt are held (those are shortcuts) | |||
| 170 | if (e.ctrlKey || e.altKey || e.metaKey) return; | 175 | if (e.ctrlKey || e.altKey || e.metaKey) return; | ||
| 171 | - | ||||
| 172 | - | // Only single character keys a-z | |||
| 173 | const key = e.key.toLowerCase(); | 176 | const key = e.key.toLowerCase(); | ||
| 174 | - | if (key.length !== 1 || key < 'a' || key > 'z') return; | 177 | + | if (key.length !== 1 || !accentKeys.has(key)) return; |
| 175 | - | ||||
| 176 | pressedKey = key; | 178 | pressedKey = key; | ||
| 177 | - | ||||
| 178 | - | // Send to Go for accent detection | |||
| 179 | window.webkit.messageHandlers.dumber.postMessage({ | 179 | window.webkit.messageHandlers.dumber.postMessage({ | ||
| 180 | type: 'accent_key_press', | 180 | type: 'accent_key_press', | ||
| 181 | payload: { char: key, shift: e.shiftKey } | 181 | payload: { char: key, shift: e.shiftKey } | ||
| 182 | }); | 182 | }); | ||
| 183 | - | }, true); // capture phase to see events first | 183 | + | }, true); |
| 184 | 184 | ||||
| 185 | document.addEventListener('keyup', function(e) { | 185 | document.addEventListener('keyup', function(e) { | ||
| 186 | if (!isEditableTarget(e.target)) return; | 186 | if (!isEditableTarget(e.target)) return; | ||
| @@ -193,7 +193,8 @@ const accentDetectionScript = `(function() { | |||||
| 193 | }); | 193 | }); | ||
| 194 | } | 194 | } | ||
| 195 | }, true); | 195 | }, true); | ||
| 196 | - | })();` | 196 | + | })();`, strings.Join(keys, ",")) |
| 197 | + | } | |||
| 197 | 198 | ||||
| 198 | // accentDetectionInjectionMode controls which frames receive the accent detection script. | 199 | // accentDetectionInjectionMode controls which frames receive the accent detection script. | ||
| 199 | // Set to AllFrames so the script runs in iframes as well as the top-level document. | 200 | // Set to AllFrames so the script runs in iframes as well as the top-level document. | ||