webview_target.go RawHistoryFolder
1package textinput
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/bnema/dumber/internal/application/port"
9 "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/puregotk-webkit/webkit"
11)
12
13// clipboardRestoreDelay is the delay before restoring the clipboard after paste.
14const clipboardRestoreDelay = 50 * time.Millisecond
15
16// deleteBeforeCursorJS is the JavaScript template to delete n characters before cursor.
17// It handles both input/textarea elements and contenteditable elements.
18const deleteBeforeCursorJS = `
19(function() {
20 const n = %d;
21 const el = document.activeElement;
22 if (!el) return;
23
24 // Handle input and textarea elements
25 if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
26 const start = el.selectionStart;
27 const end = el.selectionEnd;
28 if (start === end && start >= n) {
29 el.value = el.value.slice(0, start - n) + el.value.slice(end);
30 el.selectionStart = el.selectionEnd = start - n;
31 // Trigger input event for frameworks like React
32 el.dispatchEvent(new Event('input', { bubbles: true }));
33 }
34 return;
35 }
36
37 // Handle contenteditable elements
38 if (el.isContentEditable) {
39 const sel = window.getSelection();
40 if (!sel.rangeCount) return;
41 const range = sel.getRangeAt(0);
42 for (let i = 0; i < n; i++) {
43 sel.modify('extend', 'backward', 'character');
44 }
45 sel.deleteFromDocument();
46 }
47})();
48`
49
50// WebViewTarget implements TextInputTarget for WebView text fields.
51// It uses the clipboard-paste pattern to insert text:
52// 1. Save current clipboard
53// 2. Write text to clipboard
54// 3. Execute paste command
55// 4. Restore original clipboard after a short delay
56type WebViewTarget struct {
57 webView *webkit.WebView
58 clipboard port.Clipboard
59}
60
61// Compile-time interface check.
62var _ port.TextInputTarget = (*WebViewTarget)(nil)
63
64// NewWebViewTarget creates a new WebView target.
65func NewWebViewTarget(webView *webkit.WebView, clipboard port.Clipboard) *WebViewTarget {
66 return &WebViewTarget{
67 webView: webView,
68 clipboard: clipboard,
69 }
70}
71
72// InsertText inserts text at the current cursor position in the WebView.
73// Uses clipboard-paste pattern since WebView doesn't expose direct text insertion.
74func (t *WebViewTarget) InsertText(ctx context.Context, text string) error {
75 log := logging.FromContext(ctx)
76
77 if t.webView == nil {
78 log.Warn().Msg("WebViewTarget: webView is nil")
79 return nil
80 }
81
82 // Save current clipboard contents
83 originalClipboard, _ := t.clipboard.ReadText(ctx)
84
85 // Write the accent to clipboard
86 if err := t.clipboard.WriteText(ctx, text); err != nil {
87 log.Error().Err(err).Msg("failed to write text to clipboard")
88 return err
89 }
90
91 // Execute paste command
92 t.webView.ExecuteEditingCommand(webkit.EDITING_COMMAND_PASTE)
93
94 log.Debug().
95 Str("text", text).
96 Msg("inserted text into WebView via clipboard paste")
97
98 // Restore original clipboard after a short delay
99 // This allows the paste operation to complete first
100 // Use context.Background() since this cleanup should complete regardless of ctx cancellation
101 go func() {
102 time.Sleep(clipboardRestoreDelay)
103 if originalClipboard != "" {
104 if err := t.clipboard.WriteText(context.Background(), originalClipboard); err != nil {
105 log.Debug().Err(err).Msg("failed to restore clipboard")
106 }
107 }
108 }()
109
110 return nil
111}
112
113// DeleteBeforeCursor deletes n characters before the cursor in the WebView.
114// Uses JavaScript to manipulate the focused input element.
115func (t *WebViewTarget) DeleteBeforeCursor(ctx context.Context, n int) error {
116 log := logging.FromContext(ctx)
117
118 if t.webView == nil {
119 log.Warn().Msg("WebViewTarget: webView is nil")
120 return nil
121 }
122
123 if n <= 0 {
124 return nil
125 }
126
127 // Execute JavaScript to delete characters
128 script := fmt.Sprintf(deleteBeforeCursorJS, n)
129 t.webView.EvaluateJavascript(script, -1, nil, nil, nil, nil, 0)
130
131 log.Debug().Int("n", n).Msg("deleted characters before cursor in WebView")
132 return nil
133}