1package cef
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "sync/atomic"
8 "testing"
9 "time"
10
11 purecef "github.com/bnema/purego-cef/cef"
12 "github.com/rs/zerolog"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
15
16 "github.com/bnema/dumber/internal/application/dto"
17 "github.com/bnema/dumber/internal/application/port"
18 portmocks "github.com/bnema/dumber/internal/application/port/mocks"
19 "github.com/bnema/dumber/internal/logging"
20)
21
22func TestMapCEFPopupFeatures(t *testing.T) {
23 t.Parallel()
24
25 tests := []struct {
26 name string
27 in *purecef.PopupFeatures
28 want dto.PopupFeatures
29 }{
30 {
31 name: "nil is known featureless",
32 want: dto.PopupFeatures{State: dto.PopupFeaturesNone},
33 },
34 {
35 name: "positive width is popup-like",
36 in: &purecef.PopupFeatures{Width: 640, Widthset: 1},
37 want: dto.PopupFeatures{
38 State: dto.PopupFeaturesSpecified, Width: 640, WidthSet: true, IsPopupSet: true,
39 },
40 },
41 {
42 name: "geometry and flags are normalized",
43 in: &purecef.PopupFeatures{
44 X: 10, Xset: 1, Y: 20, Yset: 1,
45 Width: 800, Widthset: 1, Height: 600, Heightset: 1,
46 },
47 want: dto.PopupFeatures{
48 State: dto.PopupFeaturesSpecified,
49 X: 10, XSet: true, Y: 20, YSet: true,
50 Width: 800, WidthSet: true, Height: 600, HeightSet: true,
51 IsPopupSet: true,
52 },
53 },
54 {
55 name: "is popup flag survives without inventing geometry",
56 in: &purecef.PopupFeatures{Ispopup: 1},
57 want: dto.PopupFeatures{
58 State: dto.PopupFeaturesSpecified, IsPopup: true, IsPopupSet: true,
59 },
60 },
61 {
62 name: "allocated defaults are known featureless",
63 in: &purecef.PopupFeatures{},
64 want: dto.PopupFeatures{State: dto.PopupFeaturesNone, IsPopupSet: true},
65 },
66 }
67
68 for _, tt := range tests {
69 t.Run(tt.name, func(t *testing.T) {
70 got := mapCEFPopupFeatures(tt.in)
71 assert.Equal(t, tt.want, got)
72 })
73 }
74}
75
76func TestOnBeforePopup_PropagatesCEFEngineFeaturesGestureAndNoJavaScriptAccess(t *testing.T) {
77 parentWV := &WebView{ctx: context.Background(), id: 16}
78 popupWV := &WebView{ctx: context.Background(), id: 24, pendingCreate: &pendingBrowserCreate{}}
79 popupWV.markNativePopupCandidate(parentWV)
80 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateBrowserWindow})
81 noJavaScriptAccess := true
82 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(req port.PopupRequest) port.WebView {
83 assert.Equal(t, dto.BrowserEngineCEF, req.Engine)
84 assert.Equal(t, dto.PopupFeatures{
85 State: dto.PopupFeaturesSpecified, X: 10, XSet: true, Width: 640, WidthSet: true,
86 IsPopup: true, IsPopupSet: true,
87 }, req.PopupFeatures)
88 assert.Equal(t, dto.WindowDispositionNewPopup, req.TargetDisposition)
89 assert.True(t, req.IsUserGesture)
90 assert.True(t, req.NoJavaScriptAccess)
91 return popupWV
92 }})
93
94 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
95 nil, nil, 90, "https://example.com/popup", "named-popup",
96 purecef.WindowOpenDispositionWodNewPopup, 1,
97 &purecef.PopupFeatures{X: 10, Xset: 1, Width: 640, Widthset: 1, Ispopup: 1},
98 nil, nil, nil, nil, &noJavaScriptAccess,
99 )
100
101 require.True(t, blocked)
102 require.True(t, popupWV.nativePopupFallbackStarted)
103 require.Nil(t, popupWV.popupOpenerBridgeParent, "noopener must not install the synthetic opener bridge")
104}
105
106func TestOnBeforePopup_FeaturelessBlankNavigateSourceBlocksNativePopup(t *testing.T) {
107 const targetURI = "https://example.com/source-navigation"
108 parentWV := &WebView{ctx: context.Background(), id: 17}
109 candidate := portmocks.NewMockWebView(t)
110 candidate.EXPECT().Destroy().Once()
111
112 var callbackResult port.WebView = candidate
113 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(req port.PopupRequest) port.WebView {
114 require.Equal(t, dto.BrowserEngineCEF, req.Engine)
115 require.Equal(t, "_blank", req.FrameName)
116 require.Equal(t, dto.WindowDispositionNewTab, req.TargetDisposition)
117 require.Equal(t, dto.PopupFeaturesNone, req.PopupFeatures.State)
118
119 // NavigateSource ownership belongs to the coordinator callback: navigate
120 // the source, destroy its unused candidate exactly once, then return nil.
121 require.NoError(t, parentWV.LoadURI(context.Background(), req.TargetURI))
122 candidate.Destroy()
123 callbackResult = nil
124 return callbackResult
125 }})
126
127 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
128 nil, nil, 91, targetURI, "_blank",
129 purecef.WindowOpenDispositionWodNewForegroundTab, 1, nil, nil, nil, nil, nil, nil,
130 )
131
132 require.True(t, blocked, "CEF native popup creation must remain blocked")
133 require.Nil(t, callbackResult, "NavigateSource callback must return nil")
134 require.Equal(t, targetURI, parentWV.pendingNavigationURI())
135}
136
137func TestOnBeforePopup_BrowserWindowDecisionPreservesSyntheticOpenerBridge(t *testing.T) {
138 parentWV := &WebView{ctx: context.Background(), id: 17}
139 parentWV.updateURI("https://example.com/opener")
140 popupWV := &WebView{ctx: context.Background(), id: 25, pendingCreate: &pendingBrowserCreate{}}
141 popupWV.markNativePopupCandidate(parentWV)
142 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateBrowserWindow})
143 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
144
145 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
146 nil, nil, 91, "https://example.com/child", "child",
147 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
148 )
149
150 require.True(t, blocked)
151 require.True(t, popupWV.nativePopupFallbackStarted)
152 require.Same(t, parentWV, popupWV.popupOpenerBridgeParent)
153 require.Equal(t, "https://example.com/opener", popupWV.popupOpenerBridgeParentURI)
154}
155
156func TestOnBeforePopup_NativeArmFailurePreparesEligibleFallbackBeforeAbort(t *testing.T) {
157 parentWV := &WebView{ctx: context.Background(), id: 18}
158 parentWV.updateURI("https://example.com/opener")
159 popupWV := &WebView{ctx: context.Background(), id: 26, pendingCreate: &pendingBrowserCreate{}}
160 popupWV.markNativePopupCandidate(parentWV)
161 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateNativePopup})
162 abortCalls := 0
163 popupWV.SetNativePopupHostAbort(func() {
164 abortCalls++
165 assert.True(t, popupWV.nativePopupFallbackStarted)
166 assert.Same(t, parentWV, popupWV.popupOpenerBridgeParent)
167 })
168 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
169
170 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
171 nil, nil, 92, "https://example.com/visual", "visual",
172 purecef.WindowOpenDispositionWodNewPopup, 1,
173 &purecef.PopupFeatures{Width: 640, Widthset: 1}, nil, nil, nil, nil, nil,
174 )
175
176 require.True(t, blocked)
177 require.Equal(t, 1, abortCalls)
178 require.False(t, popupWV.IsDestroyed())
179}
180
181func TestOnBeforePopup_OpenerRequiredNativeArmFailureDeniesWithoutFallback(t *testing.T) {
182 var output bytes.Buffer
183 logger := zerolog.New(&output).Level(zerolog.WarnLevel)
184 ctx := logging.WithContext(context.Background(), logger)
185 parentWV := &WebView{ctx: ctx, id: 19}
186 popupWV := &WebView{ctx: context.Background(), id: 27, pendingCreate: &pendingBrowserCreate{}}
187 popupWV.markNativePopupCandidate(parentWV)
188 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{
189 Kind: dto.HostDecisionCreateNativePopup, SourceHost: dto.SourceHostFloating, RequiresNativeOpener: true,
190 })
191 abortCalls := 0
192 popupWV.SetNativePopupHostAbort(func() {
193 abortCalls++
194 popupWV.Destroy()
195 })
196 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
197
198 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
199 nil, nil, 93, "https://example.com/opener-required", "required",
200 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
201 )
202
203 require.True(t, blocked)
204 require.Equal(t, 1, abortCalls)
205 require.False(t, popupWV.nativePopupFallbackStarted)
206 require.Nil(t, popupWV.popupOpenerBridgeParent)
207 require.True(t, popupWV.IsDestroyed())
208 var record map[string]any
209 require.NoError(t, json.Unmarshal(output.Bytes(), &record))
210 assert.Equal(t, "cef", record["engine"])
211 assert.Equal(t, "floating", record["source_host"])
212 assert.Equal(t, "create-native-popup", record["decision"])
213 assert.Equal(t, "new-popup", record["target_disposition"])
214 assert.Equal(t, "native-arm-failed", record["reason_code"])
215 assert.Equal(t, true, record["host_abort_invoked"])
216 assert.Equal(t, "cef: native popup arming failed", record["message"])
217}
218
219func TestOnBeforePopup_NativeArmFailureWithoutHostAbortLogsDirectCleanup(t *testing.T) {
220 var output bytes.Buffer
221 logger := zerolog.New(&output).Level(zerolog.WarnLevel)
222 ctx := logging.WithContext(context.Background(), logger)
223 parentWV := &WebView{ctx: ctx, id: 20}
224 popupWV := &WebView{ctx: context.Background(), id: 28, pendingCreate: &pendingBrowserCreate{}}
225 popupWV.markNativePopupCandidate(parentWV)
226 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{
227 Kind: dto.HostDecisionCreateNativePopup, RequiresNativeOpener: true,
228 })
229 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
230
231 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
232 nil, nil, 94, "https://example.com/opener-required", "required",
233 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
234 )
235
236 require.True(t, blocked)
237 require.True(t, popupWV.IsDestroyed())
238 var record map[string]any
239 require.NoError(t, json.Unmarshal(output.Bytes(), &record))
240 assert.Equal(t, false, record["host_abort_invoked"])
241 assert.Equal(t, "cef: native popup arming failed", record["message"])
242}
243
244func TestOnBeforePopup_AuthNativeArmFailureDeniesWithoutFallback(t *testing.T) {
245 parentWV := &WebView{ctx: context.Background(), id: 20}
246 popupWV := &WebView{ctx: context.Background(), id: 28, pendingCreate: &pendingBrowserCreate{}}
247 popupWV.markNativePopupCandidate(parentWV)
248 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{
249 Kind: dto.HostDecisionCreateNativePopup, ReasonCode: dto.HostDecisionReasonAuthNativePopup,
250 })
251 popupWV.SetNativePopupHostAbort(func() { popupWV.Destroy() })
252 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
253
254 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
255 nil, nil, 94, "https://accounts.example.test/oauth", "oauth",
256 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
257 )
258
259 require.True(t, blocked)
260 require.False(t, popupWV.nativePopupFallbackStarted)
261 require.True(t, popupWV.IsDestroyed())
262}
263
264func TestOnBeforePopup_IsPopupFlagCanSelectNativePopup(t *testing.T) {
265 parentWV := &WebView{ctx: context.Background(), id: 22}
266 popupWV := &WebView{ctx: context.Background(), id: 30, pendingCreate: &pendingBrowserCreate{}}
267 popupWV.markNativePopupCandidate(parentWV)
268 abortCalls := 0
269 popupWV.SetNativePopupHostAbort(func() { abortCalls++ })
270 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(req port.PopupRequest) port.WebView {
271 require.Equal(t, dto.PopupFeaturesSpecified, req.PopupFeatures.State)
272 require.True(t, req.PopupFeatures.IsPopup)
273 require.True(t, req.PopupFeatures.IsPopupSet)
274 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateNativePopup})
275 return popupWV
276 }})
277
278 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
279 nil, nil, 96, "https://example.com/chrome-restricted", "restricted",
280 purecef.WindowOpenDispositionWodNewPopup, 1,
281 &purecef.PopupFeatures{Ispopup: 1}, nil, nil, nil, nil, nil,
282 )
283
284 require.True(t, blocked)
285 require.Equal(t, 1, abortCalls)
286}
287
288func TestOnBeforePopup_ReuseNamedPaneBlocksNativePopup(t *testing.T) {
289 parentWV := &WebView{ctx: context.Background(), id: 23}
290 popupWV := &WebView{ctx: context.Background(), id: 31, pendingCreate: &pendingBrowserCreate{}}
291 popupWV.markNativePopupCandidate(parentWV)
292 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionReuseNamedPane})
293 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
294
295 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
296 nil, nil, 97, "https://example.com/named", "shared",
297 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
298 )
299
300 require.True(t, blocked)
301 require.True(t, popupWV.nativePopupFallbackStarted)
302}
303
304func TestOnBeforePopup_HostedDecisionDiscardsUnpreparedNativeCandidate(t *testing.T) {
305 parentWV := &WebView{ctx: context.Background(), id: 24}
306 popupWV := &WebView{ctx: context.Background(), id: 32}
307 popupWV.markNativePopupCandidate(parentWV)
308 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateBrowserWindow})
309 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
310
311 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
312 nil, nil, 98, "https://example.com/window", "window",
313 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
314 )
315
316 require.True(t, blocked)
317 require.False(t, popupWV.isNativePopupCandidate())
318 require.False(t, popupWV.nativePopupFallbackStarted)
319}
320
321func TestOnBeforePopup_HostedDecisionPreservesAlreadyPreparedFallbackOpener(t *testing.T) {
322 parentWV := &WebView{ctx: context.Background(), id: 25}
323 parentWV.updateURI("https://example.com/opener")
324 popupWV := &WebView{ctx: context.Background(), id: 33, pendingCreate: &pendingBrowserCreate{}}
325 popupWV.markNativePopupCandidate(parentWV)
326 require.True(t, popupWV.preparePopupShellDirectBrowserCreation())
327 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateBrowserWindow})
328 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
329
330 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
331 nil, nil, 99, "https://example.com/window", "window",
332 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
333 )
334
335 require.True(t, blocked)
336 require.True(t, popupWV.nativePopupFallbackStarted)
337 require.Same(t, parentWV, popupWV.popupOpenerBridgeParent)
338 require.Equal(t, "https://example.com/opener", popupWV.popupOpenerBridgeParentURI)
339}
340
341func TestOnBeforePopup_DenyCleansUp(t *testing.T) {
342 parentWV := &WebView{ctx: context.Background(), id: 24}
343 popupWV := &WebView{ctx: context.Background(), id: 32}
344 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionDeny})
345 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
346
347 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
348 nil, nil, 98, "https://example.com/denied", "denied",
349 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
350 )
351
352 require.True(t, blocked)
353 require.True(t, popupWV.IsDestroyed())
354}
355
356func TestOnBeforePopup_AwaitFeaturesIsImpossibleAndCleansUp(t *testing.T) {
357 parentWV := &WebView{ctx: context.Background(), id: 21}
358 popupWV := &WebView{ctx: context.Background(), id: 29}
359 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionAwaitPopupFeatures})
360 parentWV.SetCallbacks(&port.WebViewCallbacks{OnCreate: func(port.PopupRequest) port.WebView { return popupWV }})
361
362 blocked := (&handlerSet{wv: parentWV}).OnBeforePopup(
363 nil, nil, 95, "https://example.com/impossible", "impossible",
364 purecef.WindowOpenDispositionWodNewPopup, 1, nil, nil, nil, nil, nil, nil,
365 )
366
367 require.True(t, blocked)
368 require.True(t, popupWV.IsDestroyed())
369}
370
371func TestOnBeforePopup_TimesOutGTKDispatchAndBlocksPopup(t *testing.T) {
372 delayed := make(chan func(), 1)
373 parentWV := &WebView{
374 ctx: context.Background(),
375 id: 16,
376 gtkSyncTimeout: 5 * time.Millisecond,
377 gtkSyncIsOwner: func() bool { return false },
378 gtkSyncDispatch: func(fn func()) {
379 delayed <- fn
380 },
381 }
382 var createCalls atomic.Int32
383 parentWV.SetCallbacks(&port.WebViewCallbacks{
384 OnCreate: func(_ port.PopupRequest) port.WebView {
385 createCalls.Add(1)
386 return &WebView{ctx: context.Background(), id: 24}
387 },
388 })
389
390 h := &handlerSet{wv: parentWV}
391 blocked := h.OnBeforePopup(nil, nil, 90, "https://example.com/slow-popup", "slow-popup", 0, 1, nil, nil, nil, nil, nil, nil)
392
393 require.True(t, blocked)
394 require.Zero(t, createCalls.Load())
395
396 select {
397 case fn := <-delayed:
398 fn()
399 case <-time.After(time.Second):
400 t.Fatal("timed out waiting for delayed popup dispatch")
401 }
402 require.Zero(t, createCalls.Load())
403}
404
405func TestOnBeforePopup_PrimesPopupNavigationWhenCEFPopupBlocksNativeCreation(t *testing.T) {
406 parentWV := &WebView{ctx: context.Background(), id: 17}
407 popupWV := &WebView{ctx: context.Background(), id: 23}
408 parentWV.SetCallbacks(&port.WebViewCallbacks{
409 OnCreate: func(req port.PopupRequest) port.WebView {
410 require.Equal(t, "https://example.com/popup", req.TargetURI)
411 require.Equal(t, "auth-popup", req.FrameName)
412 require.True(t, req.IsUserGesture)
413 popupWV.PrimePopupNavigation(req.TargetURI)
414 return popupWV
415 },
416 })
417
418 h := &handlerSet{wv: parentWV}
419 blocked := h.OnBeforePopup(nil, nil, 91, "https://example.com/popup", "auth-popup", 0, 1, nil, nil, nil, nil, nil, nil)
420
421 require.True(t, blocked)
422 require.Equal(t, "https://example.com/popup", popupWV.pendingNavigationURI())
423}
424
425func TestOnBeforePopup_PrimesPopupShellWhenNativePopupCannotBeArmed(t *testing.T) {
426 parentWV := &WebView{ctx: context.Background(), id: 31}
427 popupWV := &WebView{ctx: context.Background(), id: 32}
428 popupWV.markNativePopupCandidate(parentWV)
429 parentWV.SetCallbacks(&port.WebViewCallbacks{
430 OnCreate: func(req port.PopupRequest) port.WebView {
431 require.Equal(t, "https://example.com/login", req.TargetURI)
432 popupWV.PrimePopupNavigation(req.TargetURI)
433 return popupWV
434 },
435 })
436
437 h := &handlerSet{wv: parentWV}
438 blocked := h.OnBeforePopup(nil, nil, 77, "https://example.com/login", "Google login", 0, 1, nil, nil, nil, nil, nil, nil)
439
440 require.True(t, blocked)
441 require.False(t, popupWV.isNativePopupCandidate())
442 require.Equal(t, "https://example.com/login", popupWV.pendingNavigationURI())
443}
444
445func TestOnBeforePopup_PaneDecisionBlocksNativePopupAndKeepsPanePath(t *testing.T) {
446 parentWV := &WebView{ctx: context.Background(), id: 41}
447 popupWV := &WebView{ctx: context.Background(), id: 42}
448 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreatePane})
449 parentWV.SetCallbacks(&port.WebViewCallbacks{
450 OnCreate: func(req port.PopupRequest) port.WebView {
451 popupWV.PreparePaneHostedBrowsingContext()
452 popupWV.PrimePopupNavigation(req.TargetURI)
453 return popupWV
454 },
455 })
456
457 h := &handlerSet{wv: parentWV}
458 blocked := h.OnBeforePopup(nil, nil, 81, "https://example.com/pane", "_blank", 0, 1, nil, nil, nil, nil, nil, nil)
459
460 require.True(t, blocked)
461 require.Equal(t, "https://example.com/pane", popupWV.pendingNavigationURI())
462}
463
464func TestOnBeforePopup_NativeDecisionAbortsHostWhenArmingFails(t *testing.T) {
465 parentWV := &WebView{ctx: context.Background(), id: 51}
466 popupWV := &WebView{ctx: context.Background(), id: 52}
467 popupWV.markNativePopupCandidate(parentWV)
468 popupWV.SetBrowsingContextHostDecision(dto.HostDecision{Kind: dto.HostDecisionCreateNativePopup})
469 aborted := false
470 popupWV.SetNativePopupHostAbort(func() { aborted = true })
471 parentWV.SetCallbacks(&port.WebViewCallbacks{
472 OnCreate: func(req port.PopupRequest) port.WebView {
473 popupWV.PrimePopupNavigation(req.TargetURI)
474 return popupWV
475 },
476 })
477
478 h := &handlerSet{wv: parentWV}
479 blocked := h.OnBeforePopup(nil, nil, 82, "https://accounts.google.com/o/oauth2/v2/auth", "oauth", 0, 1, nil, nil, nil, nil, nil, nil)
480
481 require.True(t, blocked)
482 require.True(t, aborted)
483}