1package cef
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 purecef "github.com/bnema/purego-cef/cef"
9 cef2gtk "github.com/bnema/purego-cef2gtk"
10 "github.com/stretchr/testify/require"
11)
12
13type resizeOrderHost struct {
14 calls []string
15}
16
17func (h *resizeOrderHost) WasResized() {
18 h.calls = append(h.calls, "WasResized")
19}
20
21func (h *resizeOrderHost) Invalidate(_ purecef.PaintElementType) {
22 h.calls = append(h.calls, "Invalidate")
23}
24
25func TestNotifyBrowserResize_CallsWasResizedThenInvalidate(t *testing.T) {
26 host := &resizeOrderHost{}
27
28 notifyBrowserResize(host)
29
30 require.Equal(t, []string{"WasResized", "Invalidate"}, host.calls)
31}
32
33func TestConfigureWindowInfo_SetsAcceleratedWindowlessAndSharedTexture(t *testing.T) {
34 info := purecef.NewWindowInfo()
35
36 cef2gtk.ConfigureWindowInfo(&info, cef2gtk.WindowInfoOptions{})
37
38 require.Equal(t, int32(1), info.WindowlessRenderingEnabled)
39 require.Equal(t, int32(1), info.SharedTextureEnabled)
40}
41
42func TestShouldStartBrowserCreateFromSizeObserver_FalseAfterInitialResizeHandled(t *testing.T) {
43 wv := &WebView{pendingCreate: &pendingBrowserCreate{}}
44
45 require.True(t, wv.shouldStartBrowserCreateFromSizeObserver())
46 wv.markInitialBrowserCreateResizeHandled()
47 require.False(t, wv.shouldStartBrowserCreateFromSizeObserver())
48}
49
50func TestShouldStartBrowserCreateFromSizeObserver_FalseAfterPendingCreateConsumed(t *testing.T) {
51 wv := &WebView{pendingCreate: &pendingBrowserCreate{}}
52
53 require.True(t, wv.shouldStartBrowserCreateFromSizeObserver())
54 require.NotNil(t, wv.takePendingCreate())
55 require.False(t, wv.shouldStartBrowserCreateFromSizeObserver())
56}
57
58func TestInitialBrowserCreateSizeReadyFromObserver(t *testing.T) {
59 tests := []struct {
60 name string
61 width int32
62 height int32
63 want bool
64 }{
65 {name: "rejects bootstrap one by one", width: 1, height: 1, want: false},
66 {name: "rejects zero width", width: 0, height: 480, want: false},
67 {name: "rejects one pixel height", width: 640, height: 1, want: false},
68 {name: "accepts real positive size", width: 640, height: 480, want: true},
69 }
70
71 for _, tt := range tests {
72 t.Run(tt.name, func(t *testing.T) {
73 require.Equal(t, tt.want, initialBrowserCreateSizeReadyFromObserver(tt.width, tt.height))
74 })
75 }
76}
77
78func TestHandleInitialBrowserCreateSizeObserver_IgnoresBootstrapForNormalWebView(t *testing.T) {
79 factory := &WebViewFactory{}
80 wv := &WebView{pendingCreate: &pendingBrowserCreate{}}
81 prepared := false
82 resized := false
83
84 handled := factory.handleInitialBrowserCreateSizeObserver(
85 context.Background(),
86 wv,
87 func(int32, int32) { resized = true },
88 func() error {
89 prepared = true
90 return nil
91 },
92 1,
93 1,
94 )
95
96 require.True(t, handled)
97 require.False(t, prepared)
98 require.False(t, resized)
99 require.True(t, wv.shouldStartBrowserCreateFromSizeObserver())
100}
101
102func TestHandleInitialBrowserCreateSizeObserver_BootstrapPopupArmsFallbackWithoutPreparing(t *testing.T) {
103 factory := &WebViewFactory{}
104 wv := &WebView{pendingCreate: &pendingBrowserCreate{}, nativePopupCandidate: true}
105 prepared := false
106 var scheduled func()
107 wv.nativePopupFallbackSchedule = func(_ time.Duration, fn func()) stoppableTimer {
108 scheduled = fn
109 return stubStoppableTimer{}
110 }
111
112 handled := factory.handleInitialBrowserCreateSizeObserver(
113 context.Background(),
114 wv,
115 func(w, h int32) {
116 factory.handlePopupShellInitialResize(context.Background(), wv, nil, w, h)
117 },
118 func() error {
119 prepared = true
120 return nil
121 },
122 1,
123 1,
124 )
125
126 require.True(t, handled)
127 require.False(t, prepared)
128 require.NotNil(t, scheduled)
129 require.True(t, wv.shouldStartBrowserCreateFromSizeObserver())
130}
131
132func TestHandleInitialBrowserCreateSizeObserver_ReadySizePreparesAndConsumesInitialResize(t *testing.T) {
133 factory := &WebViewFactory{}
134 wv := &WebView{pendingCreate: &pendingBrowserCreate{}}
135 prepared := 0
136 resizes := 0
137
138 handled := factory.handleInitialBrowserCreateSizeObserver(
139 context.Background(),
140 wv,
141 func(int32, int32) { resizes++ },
142 func() error {
143 prepared++
144 return nil
145 },
146 640,
147 480,
148 )
149
150 require.True(t, handled)
151 require.Equal(t, 1, prepared)
152 require.Equal(t, 1, resizes)
153 require.False(t, wv.shouldStartBrowserCreateFromSizeObserver())
154}
155
156func TestHandleInitialBrowserCreateSizeObserver_ReadySizeAfterPopupFallbackPostsCreate(t *testing.T) {
157 factory := &WebViewFactory{}
158 wv := &WebView{pendingCreate: &pendingBrowserCreate{}, nativePopupCandidate: true}
159 prepared := 0
160 posted := 0
161 var scheduled func()
162 wv.nativePopupFallbackSchedule = func(_ time.Duration, fn func()) stoppableTimer {
163 scheduled = fn
164 return stubStoppableTimer{}
165 }
166 onFirstResize := func(w, h int32) {
167 factory.handlePopupShellInitialResize(context.Background(), wv, func(int32, int32) {
168 posted++
169 }, w, h)
170 }
171
172 handled := factory.handleInitialBrowserCreateSizeObserver(
173 context.Background(),
174 wv,
175 onFirstResize,
176 func() error {
177 prepared++
178 return nil
179 },
180 0,
181 480,
182 )
183 require.True(t, handled)
184 require.Equal(t, 0, prepared)
185 require.Equal(t, 0, posted)
186 require.NotNil(t, scheduled)
187 require.True(t, wv.shouldStartBrowserCreateFromSizeObserver())
188
189 scheduled()
190 require.True(t, wv.awaitsBrowserCreateFromNativePopupFallback())
191
192 handled = factory.handleInitialBrowserCreateSizeObserver(
193 context.Background(),
194 wv,
195 onFirstResize,
196 func() error {
197 prepared++
198 return nil
199 },
200 640,
201 480,
202 )
203 require.True(t, handled)
204 require.Equal(t, 1, prepared)
205 require.Equal(t, 1, posted)
206 require.False(t, wv.shouldStartBrowserCreateFromSizeObserver())
207}
208
209func TestSchedulePopupShellNativeFallback_StartsFallbackOnTimeout(t *testing.T) {
210 factory := &WebViewFactory{}
211 wv := &WebView{pendingCreate: &pendingBrowserCreate{}, nativePopupCandidate: true}
212 var scheduled func()
213 wv.nativePopupFallbackSchedule = func(_ time.Duration, fn func()) stoppableTimer {
214 scheduled = fn
215 return stubStoppableTimer{}
216 }
217
218 factory.schedulePopupShellNativeFallback(context.Background(), wv)
219 require.NotNil(t, scheduled)
220
221 scheduled()
222
223 require.True(t, wv.nativePopupFallbackStarted)
224 require.False(t, wv.nativePopupCandidate)
225}
226
227func TestPostPendingBrowserCreate_RetriesWhenPostingTaskFails(t *testing.T) {
228 oldNewTask := cefNewTask
229 oldPostTask := cefPostTask
230 oldScheduleAfter := cefScheduleAfter
231 oldCreateBrowser := cefBrowserHostCreateBrowser
232 defer func() {
233 cefNewTask = oldNewTask
234 cefPostTask = oldPostTask
235 cefScheduleAfter = oldScheduleAfter
236 cefBrowserHostCreateBrowser = oldCreateBrowser
237 }()
238
239 cefNewTask = func(task purecef.Task) purecef.Task { return task }
240
241 postCalls := 0
242 cefPostTask = func(threadID purecef.ThreadID, task purecef.Task) int32 {
243 require.Equal(t, purecef.ThreadIDTidUi, threadID)
244 require.NotNil(t, task)
245 postCalls++
246 if postCalls == 1 {
247 return 0
248 }
249 task.Execute()
250 return 1
251 }
252
253 retried := false
254 cefScheduleAfter = func(delay time.Duration, fn func()) {
255 require.Equal(t, pendingBrowserCreateRetryDelay, delay)
256 retried = true
257 fn()
258 }
259
260 createCalls := 0
261 cefBrowserHostCreateBrowser = func(
262 windowInfo *purecef.WindowInfo,
263 _ purecef.RawClient,
264 url string,
265 settings *purecef.BrowserSettings,
266 _ purecef.DictionaryValue,
267 _ purecef.RequestContext,
268 ) int32 {
269 createCalls++
270 require.NotNil(t, windowInfo)
271 require.NotNil(t, settings)
272 require.Equal(t, "about:blank", url)
273 require.Equal(t, int32(1), windowInfo.WindowlessRenderingEnabled)
274 require.Equal(t, int32(1), windowInfo.SharedTextureEnabled)
275 return 1
276 }
277
278 windowInfo := purecef.NewWindowInfo()
279 cef2gtk.ConfigureWindowInfo(&windowInfo, cef2gtk.WindowInfoOptions{})
280 settings := purecef.NewBrowserSettings()
281 wv := &WebView{
282 ctx: context.Background(),
283 pendingCreate: &pendingBrowserCreate{
284 windowInfo: &windowInfo,
285 settings: &settings,
286 },
287 }
288
289 factory := &WebViewFactory{}
290 factory.postPendingBrowserCreate(context.Background(), wv, 640, 480)
291
292 require.True(t, retried)
293 require.Equal(t, 2, postCalls)
294 require.Equal(t, 1, createCalls)
295 require.Nil(t, wv.takePendingCreate())
296}