main 5041353

app_test.go RawHistoryFolder

1package cef
2
3import (
4	"context"
5	"strings"
6	"testing"
7	"unsafe"
8
9	purecef "github.com/bnema/purego-cef/cef"
10)
11
12type schemeRegistrarStub struct {
13	scheme  string
14	options int32
15	calls   int
16}
17
18func (s *schemeRegistrarStub) AddCustomScheme(schemeName string, options int32) int32 {
19	s.calls++
20	s.scheme = schemeName
21	s.options = options
22	return 1
23}
24
25type relaunchCommandLineStub struct {
26	commandLineString string
27}
28
29func (c relaunchCommandLineStub) IsValid() bool                      { return true }
30func (c relaunchCommandLineStub) IsReadOnly() bool                   { return true }
31func (c relaunchCommandLineStub) Copy() purecef.CommandLine          { return c }
32func (c relaunchCommandLineStub) InitFromArgv(int32, unsafe.Pointer) {}
33func (c relaunchCommandLineStub) InitFromString(string)              {}
34func (c relaunchCommandLineStub) Reset()                             {}
35func (c relaunchCommandLineStub) GetArgv(purecef.StringList)         {}
36func (c relaunchCommandLineStub) GetCommandLineString() string       { return c.commandLineString }
37func (c relaunchCommandLineStub) GetProgram() string                 { return "dumber" }
38func (c relaunchCommandLineStub) SetProgram(string)                  {}
39func (c relaunchCommandLineStub) HasSwitches() bool                  { return false }
40func (c relaunchCommandLineStub) HasSwitch(string) bool              { return false }
41func (c relaunchCommandLineStub) GetSwitchValue(string) string       { return "" }
42func (c relaunchCommandLineStub) GetSwitches(purecef.StringMap)      {}
43func (c relaunchCommandLineStub) AppendSwitch(string)                {}
44func (c relaunchCommandLineStub) AppendSwitchWithValue(string, string) {
45}
46func (c relaunchCommandLineStub) HasArguments() bool              { return true }
47func (c relaunchCommandLineStub) GetArguments(purecef.StringList) {}
48func (c relaunchCommandLineStub) AppendArgument(string) {
49}
50func (c relaunchCommandLineStub) PrependWrapper(string) {}
51func (c relaunchCommandLineStub) RemoveSwitch(string)   {}
52
53type mutableCommandLineStub struct {
54	relaunchCommandLineStub
55	switches map[string]string
56}
57
58func newMutableCommandLineStub() *mutableCommandLineStub {
59	return &mutableCommandLineStub{switches: make(map[string]string)}
60}
61
62func (c *mutableCommandLineStub) IsReadOnly() bool          { return false }
63func (c *mutableCommandLineStub) Copy() purecef.CommandLine { return c }
64func (c *mutableCommandLineStub) Reset()                    { c.switches = make(map[string]string) }
65func (c *mutableCommandLineStub) HasSwitches() bool         { return len(c.switches) > 0 }
66func (c *mutableCommandLineStub) HasSwitch(name string) bool {
67	_, ok := c.switches[name]
68	return ok
69}
70func (c *mutableCommandLineStub) GetSwitchValue(name string) string { return c.switches[name] }
71func (c *mutableCommandLineStub) AppendSwitch(name string)          { c.switches[name] = "" }
72func (c *mutableCommandLineStub) AppendSwitchWithValue(name, value string) {
73	c.switches[name] = value
74}
75func (c *mutableCommandLineStub) HasArguments() bool { return false }
76func (c *mutableCommandLineStub) RemoveSwitch(name string) {
77	delete(c.switches, name)
78}
79
80func TestRegisterDumbScheme(t *testing.T) {
81	t.Parallel()
82
83	stub := &schemeRegistrarStub{}
84	registerDumbScheme(stub)
85
86	wantOptions := purecef.SchemeOptionsSchemeOptionStandard |
87		purecef.SchemeOptionsSchemeOptionSecure |
88		purecef.SchemeOptionsSchemeOptionCorsEnabled |
89		purecef.SchemeOptionsSchemeOptionCspBypassing |
90		purecef.SchemeOptionsSchemeOptionFetchEnabled
91
92	if stub.calls != 1 {
93		t.Fatalf("AddCustomScheme call count = %d, want 1", stub.calls)
94	}
95	if stub.scheme != dumbSchemeName {
96		t.Fatalf("scheme = %q, want %q", stub.scheme, dumbSchemeName)
97	}
98	if stub.options != wantOptions {
99		t.Fatalf("options = %d, want %d", stub.options, wantOptions)
100	}
101}
102
103func TestParseBrowseURLFromRelaunchCommandLine(t *testing.T) {
104	t.Parallel()
105
106	got := parseBrowseURLFromRelaunchCommandLine(relaunchCommandLineStub{
107		commandLineString: "dumber browse https://example.com",
108	})
109
110	if got != "https://example.com" {
111		t.Fatalf("parsed browse url = %q, want %q", got, "https://example.com")
112	}
113}
114
115func TestParseBrowseURLFromRelaunchCommandLineWithInstanceFlag(t *testing.T) {
116	t.Parallel()
117
118	for _, commandLine := range []string{
119		"dumber browse --instance work https://example.com",
120		"dumber browse https://example.com --instance=work",
121	} {
122		got := parseBrowseURLFromRelaunchCommandLine(relaunchCommandLineStub{commandLineString: commandLine})
123		if got != "https://example.com" {
124			t.Fatalf("parsed browse url from %q = %q", commandLine, got)
125		}
126	}
127}
128
129func TestParseBrowseRelaunchCommandLineRejectsCrossProfileLaunches(t *testing.T) {
130	t.Parallel()
131
132	for _, commandLine := range []string{
133		"dumber browse --profile work https://example.com",
134		"dumber browse --profile=work https://example.com",
135		"dumber browse --ephemeral https://example.com",
136	} {
137		if got, ok := parseBrowseRelaunchCommandLine(relaunchCommandLineStub{commandLineString: commandLine}); ok || got != "" {
138			t.Fatalf("parseBrowseRelaunchCommandLine(%q) = (%q, %v), want rejected", commandLine, got, ok)
139		}
140	}
141}
142
143func TestDumberBPH_OnAlreadyRunningAppRelaunch_ForwardsBrowseURLAndReturns1(t *testing.T) {
144	t.Parallel()
145
146	gotURL := ""
147	eng := &Engine{}
148	eng.SetAlreadyRunningAppRelaunchHandler(func(url string) {
149		gotURL = url
150	})
151
152	ret := (&dumberBPH{engine: eng}).OnAlreadyRunningAppRelaunch(relaunchCommandLineStub{
153		commandLineString: "dumber browse https://example.com",
154	}, "")
155
156	if ret != 1 {
157		t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 1", ret)
158	}
159	if gotURL != "https://example.com" {
160		t.Fatalf("forwarded browse url = %q, want %q", gotURL, "https://example.com")
161	}
162}
163
164func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardEmptyBrowseURLAndReturns1(t *testing.T) {
165	t.Parallel()
166
167	gotURL := "sentinel"
168	eng := &Engine{}
169	eng.SetAlreadyRunningAppRelaunchHandler(func(url string) {
170		gotURL = url
171	})
172
173	ret := (&dumberBPH{engine: eng}).OnAlreadyRunningAppRelaunch(relaunchCommandLineStub{
174		commandLineString: "dumber browse",
175	}, "")
176
177	if ret != 1 {
178		t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 1", ret)
179	}
180	if gotURL != "sentinel" {
181		t.Fatalf("forwarded browse url = %q, want %q", gotURL, "sentinel")
182	}
183}
184
185func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testing.T) {
186	t.Parallel()
187
188	called := false
189	eng := &Engine{}
190	eng.SetAlreadyRunningAppRelaunchHandler(func(string) {
191		called = true
192	})
193
194	ret := (&dumberBPH{engine: eng}).OnAlreadyRunningAppRelaunch(relaunchCommandLineStub{
195		commandLineString: "dumber",
196	}, "")
197
198	if ret != 0 {
199		t.Fatalf("OnAlreadyRunningAppRelaunch returned %d, want 0", ret)
200	}
201	if called {
202		t.Fatal("handler should not be called for non-browse relaunch")
203	}
204}
205
206func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
207	commandLine := newMutableCommandLineStub()
208	configureCommandLine(commandLine)
209
210	if !commandLine.HasSwitch("enable-smooth-scrolling") {
211		t.Fatal("expected enable-smooth-scrolling switch to be appended")
212	}
213	if got := commandLine.GetSwitchValue("autoplay-policy"); got != "no-user-gesture-required" {
214		t.Fatalf("autoplay-policy = %q, want %q", got, "no-user-gesture-required")
215	}
216}
217
218func TestConfigureCommandLine_DisablesWebAuthnByDefault(t *testing.T) {
219	t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "")
220
221	commandLine := newMutableCommandLineStub()
222	configureCommandLine(commandLine)
223
224	want := "WebAuth"
225	if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != want {
226		t.Fatalf("disable-features = %q, want %q", got, want)
227	}
228	if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != want {
229		t.Fatalf("disable-blink-features = %q, want %q", got, want)
230	}
231}
232
233func TestConfigureCommandLine_WebAuthnUnsafeOptInDoesNotDisableWebAuth(t *testing.T) {
234	t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "1")
235
236	commandLine := newMutableCommandLineStub()
237	configureCommandLine(commandLine)
238
239	if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); strings.Contains(got, "WebAuth") {
240		t.Fatalf("disable-features = %q, should not contain WebAuth", got)
241	}
242	if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); strings.Contains(got, "WebAuth") {
243		t.Fatalf("disable-blink-features = %q, should not contain WebAuth", got)
244	}
245}
246
247func TestConfigureCommandLine_WebAuthnDisablePreservesExistingDisableFeatures(t *testing.T) {
248	commandLine := newMutableCommandLineStub()
249	commandLine.AppendSwitchWithValue(chromiumDisableFeaturesSwitch, "ExistingFeature,WebAuth")
250	commandLine.AppendSwitchWithValue(chromiumDisableBlinkFeaturesSwitch, "ExistingBlinkFeature,WebAuth")
251
252	configureCommandLine(commandLine)
253
254	if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != "ExistingFeature,WebAuth" {
255		t.Fatalf("disable-features = %q, want %q", got, "ExistingFeature,WebAuth")
256	}
257	if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != "ExistingBlinkFeature,WebAuth" {
258		t.Fatalf("disable-blink-features = %q, want %q", got, "ExistingBlinkFeature,WebAuth")
259	}
260}
261
262func TestConfigureCommandLine_WebAuthnUnsafeOverrideRemovesExistingWebAuthTokens(t *testing.T) {
263	t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "1")
264
265	commandLine := newMutableCommandLineStub()
266	commandLine.AppendSwitchWithValue(chromiumDisableFeaturesSwitch, "ExistingFeature,WebAuth,AnotherFeature")
267	commandLine.AppendSwitchWithValue(chromiumDisableBlinkFeaturesSwitch, "WebAuth,ExistingBlinkFeature")
268
269	configureCommandLine(commandLine)
270
271	if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != "ExistingFeature,AnotherFeature" {
272		t.Fatalf("disable-features = %q, want %q", got, "ExistingFeature,AnotherFeature")
273	}
274	if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != "ExistingBlinkFeature" {
275		t.Fatalf("disable-blink-features = %q, want %q", got, "ExistingBlinkFeature")
276	}
277}
278
279func TestConfigureCommandLine_WebAuthnUnsafeOverrideRemovesSwitchWhenOnlyWebAuth(t *testing.T) {
280	t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "1")
281
282	commandLine := newMutableCommandLineStub()
283	commandLine.AppendSwitchWithValue(chromiumDisableFeaturesSwitch, "WebAuth")
284
285	configureCommandLine(commandLine)
286
287	if commandLine.HasSwitch(chromiumDisableFeaturesSwitch) {
288		t.Fatalf("disable-features switch should have been removed entirely, but has value %q", commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch))
289	}
290}
291
292func TestConfigureCommandLine_SetsOzonePlatformToWayland(t *testing.T) {
293	commandLine := newMutableCommandLineStub()
294	configureCommandLine(commandLine)
295
296	if got := commandLine.GetSwitchValue("ozone-platform"); got != "wayland" {
297		t.Fatalf("ozone-platform = %q, want %q", got, "wayland")
298	}
299
300	// Dumber-specific switches must remain present.
301	if !commandLine.HasSwitch("enable-smooth-scrolling") {
302		t.Fatal("expected enable-smooth-scrolling switch to be present")
303	}
304	if got := commandLine.GetSwitchValue("autoplay-policy"); got != "no-user-gesture-required" {
305		t.Fatalf("autoplay-policy = %q, want %q", got, "no-user-gesture-required")
306	}
307}
308
309func TestConfigureCommandLine_PreservesExistingOzonePlatform(t *testing.T) {
310	commandLine := newMutableCommandLineStub()
311	commandLine.AppendSwitchWithValue("ozone-platform", "x11")
312
313	configureCommandLine(commandLine)
314
315	if got := commandLine.GetSwitchValue("ozone-platform"); got != "x11" {
316		t.Fatalf("ozone-platform = %q, want %q (bridge preserves existing value)", got, "x11")
317	}
318
319	// Dumber-specific switches must remain present.
320	if !commandLine.HasSwitch("enable-smooth-scrolling") {
321		t.Fatal("expected enable-smooth-scrolling switch to be present")
322	}
323	if got := commandLine.GetSwitchValue("autoplay-policy"); got != "no-user-gesture-required" {
324		t.Fatalf("autoplay-policy = %q, want %q", got, "no-user-gesture-required")
325	}
326}
327
328func TestConfigureCommandLine_PreservesInheritedANGLEBackend(t *testing.T) {
329	commandLine := newMutableCommandLineStub()
330	commandLine.AppendSwitchWithValue("use-angle", "gl-egl")
331
332	configureCommandLine(commandLine)
333
334	if got := commandLine.GetSwitchValue("use-angle"); got != "gl-egl" {
335		t.Fatalf("use-angle = %q, want inherited gl-egl", got)
336	}
337}
338
339func TestConfigureCommandLine_EnableVAAPIEnvAppendsHardwareDecodeFlags(t *testing.T) {
340	t.Setenv(cefEnableVAAPIEnvVar, "1")
341
342	commandLine := newMutableCommandLineStub()
343	commandLine.AppendSwitchWithValue(chromiumEnableFeaturesSwitch, "ExistingFeature")
344
345	configureCommandLine(commandLine)
346
347	gotFeatures := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch)
348	for _, feature := range []string{
349		"ExistingFeature",
350		"AcceleratedVideoDecoder",
351		"AcceleratedVideoEncoder",
352		"AcceleratedVideoDecodeLinuxGL",
353		"AcceleratedVideoDecodeLinuxZeroCopyGL",
354		"VaapiIgnoreDriverChecks",
355	} {
356		if !strings.Contains(gotFeatures, feature) {
357			t.Fatalf("enable-features = %q, missing %q", gotFeatures, feature)
358		}
359	}
360	if !commandLine.HasSwitch("ignore-gpu-blocklist") {
361		t.Fatal("expected ignore-gpu-blocklist switch")
362	}
363	if !commandLine.HasSwitch("enable-zero-copy") {
364		t.Fatal("expected enable-zero-copy switch")
365	}
366	if !commandLine.HasSwitch("disable-gpu-driver-bug-workaround") {
367		t.Fatal("expected disable-gpu-driver-bug-workaround switch")
368	}
369}
370
371func TestConfigureCommandLine_ChromiumFlagsEnvAppendsSwitches(t *testing.T) {
372	t.Setenv(cefChromiumFlagsEnvVar, `--enable-features=VaapiVideoDecoder,VaapiIgnoreDriverChecks --ignore-gpu-blocklist --autoplay-policy=document-user-activation-required ignored-arg`)
373
374	commandLine := newMutableCommandLineStub()
375	commandLine.AppendSwitchWithValue(chromiumEnableFeaturesSwitch, "ExistingFeature")
376
377	configureCommandLine(commandLine)
378
379	if got := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch); got != "ExistingFeature,Vulkan,DefaultANGLEVulkan,VulkanFromANGLE,VaapiVideoDecoder,VaapiIgnoreDriverChecks" {
380		t.Fatalf("enable-features = %q, want render stack and env features appended", got)
381	}
382	if !commandLine.HasSwitch("ignore-gpu-blocklist") {
383		t.Fatal("expected ignore-gpu-blocklist switch")
384	}
385	if got := commandLine.GetSwitchValue("autoplay-policy"); got != "document-user-activation-required" {
386		t.Fatalf("autoplay-policy = %q, want env override", got)
387	}
388	if commandLine.HasSwitch("ignored-arg") {
389		t.Fatal("non-switch env token should be ignored")
390	}
391}
392
393func TestParseChromiumSwitchToken(t *testing.T) {
394	t.Parallel()
395
396	tests := []struct {
397		name      string
398		token     string
399		wantName  string
400		wantValue string
401		wantOK    bool
402	}{
403		{name: "long switch with value", token: "--enable-features=VaapiVideoDecoder", wantName: "enable-features", wantValue: "VaapiVideoDecoder", wantOK: true},
404		{name: "long switch without value", token: "--ignore-gpu-blocklist", wantName: "ignore-gpu-blocklist", wantOK: true},
405		{name: "single dash switch", token: "-enable-zero-copy", wantName: "enable-zero-copy", wantOK: true},
406		{name: "empty value", token: "--flag=", wantName: "flag", wantOK: true},
407		{name: "embedded equals", token: "--flag=a=b", wantName: "flag", wantValue: "a=b", wantOK: true},
408		{name: "non switch", token: "youtube.com", wantOK: false},
409		{name: "sentinel", token: "--", wantOK: false},
410		{name: "empty", token: " ", wantOK: false},
411	}
412
413	for _, tt := range tests {
414		t.Run(tt.name, func(t *testing.T) {
415			gotName, gotValue, gotOK := parseChromiumSwitchToken(tt.token)
416			if gotName != tt.wantName || gotValue != tt.wantValue || gotOK != tt.wantOK {
417				t.Fatalf("parseChromiumSwitchToken(%q) = (%q, %q, %v), want (%q, %q, %v)", tt.token, gotName, gotValue, gotOK, tt.wantName, tt.wantValue, tt.wantOK)
418			}
419		})
420	}
421}
422
423func TestAppendUniqueCommaSeparatedSwitchValues(t *testing.T) {
424	t.Run("trims whitespace and skips empty existing values", func(t *testing.T) {
425		commandLine := newMutableCommandLineStub()
426		commandLine.AppendSwitchWithValue("disable-features", " , ExistingFeature , ")
427
428		appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features", "FeatureA")
429
430		want := "ExistingFeature,FeatureA"
431		if got := commandLine.GetSwitchValue("disable-features"); got != want {
432			t.Fatalf("disable-features = %q, want %q", got, want)
433		}
434	})
435
436	t.Run("deduplicates existing and appended values", func(t *testing.T) {
437		commandLine := newMutableCommandLineStub()
438		commandLine.AppendSwitchWithValue("disable-features", "FeatureA,FeatureA")
439
440		appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features", "FeatureA", "FeatureB")
441
442		want := "FeatureA,FeatureB"
443		if got := commandLine.GetSwitchValue("disable-features"); got != want {
444			t.Fatalf("disable-features = %q, want %q", got, want)
445		}
446	})
447
448	t.Run("empty appended values are no-op", func(t *testing.T) {
449		commandLine := newMutableCommandLineStub()
450		commandLine.AppendSwitchWithValue("disable-features", "ExistingFeature")
451
452		appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features")
453
454		if got := commandLine.GetSwitchValue("disable-features"); got != "ExistingFeature" {
455			t.Fatalf("disable-features = %q, want ExistingFeature", got)
456		}
457	})
458
459	t.Run("nil command line is no-op", func(_ *testing.T) {
460		appendUniqueCommaSeparatedSwitchValues(nil, "disable-features", "FeatureA")
461	})
462}
463
464func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
465	commandLine := newMutableCommandLineStub()
466	commandLine.AppendSwitchWithValue("type", "renderer")
467
468	(&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
469
470	if !commandLine.HasSwitch("no-zygote") {
471		t.Fatal("expected no-zygote switch to be appended for child processes")
472	}
473}
474
475func TestConfigureCommandLine_CEFRenderNodeEnvOverridesExistingRenderNode(t *testing.T) {
476	t.Setenv(cefRenderNodeEnvVar, "/dev/dri/renderD129")
477
478	commandLine := newMutableCommandLineStub()
479	commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, "/dev/dri/renderD128")
480
481	configureCommandLine(commandLine)
482
483	if got := commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch); got != "/dev/dri/renderD129" {
484		t.Fatalf("render node override = %q, want /dev/dri/renderD129", got)
485	}
486}
487
488func TestConfigureCommandLine_CEFRenderNodeEnvSentinelsRemoveExistingRenderNode(t *testing.T) {
489	for _, value := range []string{"auto", "default", "none", "off", "disable", "disabled"} {
490		t.Run(value, func(t *testing.T) {
491			t.Setenv(cefRenderNodeEnvVar, value)
492
493			commandLine := newMutableCommandLineStub()
494			commandLine.AppendSwitchWithValue(chromiumRenderNodeOverrideSwitch, "/dev/dri/renderD128")
495
496			configureCommandLine(commandLine)
497
498			if commandLine.HasSwitch(chromiumRenderNodeOverrideSwitch) {
499				t.Fatalf("render node override still present: %q", commandLine.GetSwitchValue(chromiumRenderNodeOverrideSwitch))
500			}
501		})
502	}
503}
504
505func TestDumberBPH_OnBeforeChildProcessLaunch_AppliesRenderingAndHardwareDecodeFlags(t *testing.T) {
506	t.Setenv(cef2gtkAngleBackendVar, "vulkan")
507	t.Setenv(cefEnableVAAPIEnvVar, "1")
508
509	commandLine := newMutableCommandLineStub()
510	commandLine.AppendSwitchWithValue("type", "gpu-process")
511	commandLine.AppendSwitchWithValue("use-angle", "gl-egl")
512
513	(&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
514
515	if got := commandLine.GetSwitchValue("use-angle"); got != "vulkan" {
516		t.Fatalf("use-angle = %q, want vulkan", got)
517	}
518	if got := commandLine.GetSwitchValue(chromiumEnableFeaturesSwitch); !strings.Contains(got, "AcceleratedVideoDecoder") || !strings.Contains(got, "VulkanFromANGLE") {
519		t.Fatalf("enable-features = %q, want hardware decode and Vulkan ANGLE features", got)
520	}
521	if !commandLine.HasSwitch("ignore-gpu-blocklist") {
522		t.Fatal("expected ignore-gpu-blocklist switch")
523	}
524	if !commandLine.HasSwitch("enable-zero-copy") {
525		t.Fatal("expected enable-zero-copy switch")
526	}
527	if !commandLine.HasSwitch("no-zygote") {
528		t.Fatal("expected no-zygote switch")
529	}
530}
531
532func TestDumberBPH_OnBeforeChildProcessLaunch_DisablesWebAuthnByDefault(t *testing.T) {
533	t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "")
534
535	commandLine := newMutableCommandLineStub()
536	commandLine.AppendSwitchWithValue("type", "renderer")
537
538	(&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
539
540	want := "WebAuth"
541	if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != want {
542		t.Fatalf("disable-features = %q, want %q", got, want)
543	}
544	if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != want {
545		t.Fatalf("disable-blink-features = %q, want %q", got, want)
546	}
547	if !commandLine.HasSwitch("no-zygote") {
548		t.Fatal("expected no-zygote switch to be appended")
549	}
550}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close