1package webkit
2
3import (
4 "context"
5 "encoding/json"
6 "sync"
7 "testing"
8
9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/stretchr/testify/require"
11)
12
13type messageRouterContextKey struct{}
14
15func TestMessageRouterBaseContextConcurrentUpdate(t *testing.T) {
16 t.Parallel()
17
18 router := NewMessageRouter(context.Background())
19 var wg sync.WaitGroup
20 wg.Add(2)
21
22 go func() {
23 defer wg.Done()
24 for i := range 1000 {
25 router.SetBaseContext(context.WithValue(context.Background(), messageRouterContextKey{}, i))
26 }
27 }()
28 go func() {
29 defer wg.Done()
30 for range 1000 {
31 if router.baseContext() == nil {
32 t.Error("base context must never be nil")
33 }
34 }
35 }()
36
37 wg.Wait()
38}
39
40func TestHandleAllowlistedEditableFocusMessage_AllowsUntrustedPage(t *testing.T) {
41 router := NewMessageRouter(context.Background())
42 wv := &WebView{uri: "https://example.com", editableFocusBridgeToken: "secret-token"}
43
44 var states []bool
45 wv.SetCallbacks(&port.WebViewCallbacks{
46 OnEditableFocusChanged: func(editable bool) {
47 states = append(states, editable)
48 },
49 })
50
51 handled := router.handleAllowlistedBridgeMessage(wv, Message{
52 Type: "editable_focus_changed",
53 Payload: json.RawMessage(`{"editable":true,"token":"secret-token"}`),
54 })
55
56 require.True(t, handled)
57 require.Equal(t, []bool{true}, states)
58}
59
60func TestHandleAllowlistedEditableFocusMessage_IsNoopAfterPoolReuse(t *testing.T) {
61 router := NewMessageRouter(context.Background())
62 wv := &WebView{uri: "https://example.com", editableFocusBridgeToken: "secret-token"}
63 wv.ResetForPoolReuse()
64
65 // Re-register after reset so a stale token is the only reason handling is a noop.
66 called := false
67 wv.SetCallbacks(&port.WebViewCallbacks{
68 OnEditableFocusChanged: func(bool) {
69 called = true
70 },
71 })
72
73 handled := router.handleAllowlistedBridgeMessage(wv, Message{
74 Type: "editable_focus_changed",
75 Payload: json.RawMessage(`{"editable":true,"token":"secret-token"}`),
76 })
77
78 require.True(t, handled)
79 require.False(t, called)
80}
81
82func TestResetForPoolReuse_RotatesEditableFocusBridgeToken(t *testing.T) {
83 wv := &WebView{editableFocusBridgeToken: "secret-token"}
84
85 wv.ResetForPoolReuse()
86
87 require.NotEmpty(t, wv.EditableFocusBridgeToken())
88 require.NotEqual(t, "secret-token", wv.EditableFocusBridgeToken())
89}
90
91func TestIsTrustedBridgeURI(t *testing.T) {
92 t.Parallel()
93
94 trusted := []string{
95 "dumb://history",
96 "dumb://favorites",
97 "dumb://config",
98 "dumb://error",
99 "dumb://crash",
100 "dumb://history/path?cursor=1",
101 }
102 for _, raw := range trusted {
103 t.Run(raw, func(t *testing.T) {
104 t.Parallel()
105 require.True(t, isTrustedBridgeURI(raw))
106 })
107 }
108
109 untrusted := []string{
110 "",
111 "https://example.com",
112 "http://localhost",
113 "file:///tmp/history.html",
114 "dumb://evil/history",
115 "dumb://example.com",
116 "javascript:alert(1)",
117 }
118 for _, raw := range untrusted {
119 t.Run(raw, func(t *testing.T) {
120 t.Parallel()
121 require.False(t, isTrustedBridgeURI(raw))
122 })
123 }
124}