detached 521a0b3

refactor(cef): use dumber as its own subprocess

commit
521a0b3e59fe8fb50e0af3204d03441745b126de
author
brice · · 2026-04-19 07:03 UTC
committer
brice · · 2026-04-19 07:06 UTC
parent
2eb5513
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +160 -100
M .gitignore +0 -1
@@ -8,7 +8,6 @@ dist/
88
99 # Binaries for programs and plugins
1010 /dumber
11-/cef-helper
1211 dumber.exe
1312
1413 # Test binary, built with `go test -c`
@@ -8,7 +8,6 @@ dist/
8 8
9 # Binaries for programs and plugins9 # Binaries for programs and plugins
10 /dumber10 /dumber
11-/cef-helper
12 dumber.exe11 dumber.exe
13 12
14 # Test binary, built with `go test -c`13 # Test binary, built with `go test -c`
M Makefile +9 -10
@@ -43,7 +43,7 @@ build: build-frontend ## Build the application (pure Go, no CGO)
4343 @echo "Building $(BINARY_NAME) $(VERSION) using $(NPROCS) cores..."
4444 @mkdir -p $(DIST_DIR)
4545 CGO_ENABLED=0 go build -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)
46- CGO_ENABLED=0 go build -p $(NPROCS) -ldflags "-s -w" -o $(DIST_DIR)/cef-helper ./cmd/cef-helper
46+ @rm -f $(DIST_DIR)/cef-helper
4747 @echo "Build successful! Binary: $(DIST_DIR)/$(BINARY_NAME)"
4848
4949 build-frontend: ## Build homepage and error pages
@@ -55,22 +55,21 @@ build-quick: ## Build without frontend (faster for backend development)
5555 @echo "Building $(BINARY_NAME) $(VERSION) (quick, no frontend)..."
5656 @mkdir -p $(DIST_DIR)
5757 CGO_ENABLED=0 go build -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)
58- CGO_ENABLED=0 go build -p $(NPROCS) -ldflags "-s -w" -o $(DIST_DIR)/cef-helper ./cmd/cef-helper
59- @echo "Build successful! Binaries: $(DIST_DIR)/$(BINARY_NAME) and $(DIST_DIR)/cef-helper"
58+ @rm -f $(DIST_DIR)/cef-helper
59+ @echo "Build successful! Binary: $(DIST_DIR)/$(BINARY_NAME)"
6060
61-install-local: build-quick ## Install dumber + cef-helper to ~/.local/bin atomically
62- @echo "Installing $(BINARY_NAME) and cef-helper to $(LOCAL_BIN_DIR)..."
61+install-local: build-quick ## Install dumber to ~/.local/bin atomically
62+ @echo "Installing $(BINARY_NAME) to $(LOCAL_BIN_DIR)..."
6363 @mkdir -p $(LOCAL_BIN_DIR)
6464 @tmp_dumber="$$(mktemp '$(LOCAL_BIN_DIR)/.dumber.tmp.XXXXXX')"; \
65- tmp_helper="$$(mktemp '$(LOCAL_BIN_DIR)/.cef-helper.tmp.XXXXXX')"; \
66- trap 'rm -f "$$tmp_dumber" "$$tmp_helper"' EXIT INT TERM; \
65+ trap 'rm -f "$$tmp_dumber"' EXIT INT TERM; \
6766 install -m 0755 $(DIST_DIR)/$(BINARY_NAME) "$$tmp_dumber"; \
68- install -m 0755 $(DIST_DIR)/cef-helper "$$tmp_helper"; \
6967 mv -f "$$tmp_dumber" $(LOCAL_BIN_DIR)/$(BINARY_NAME); \
70- mv -f "$$tmp_helper" $(LOCAL_BIN_DIR)/cef-helper; \
68+ removed_stale=0; \
69+ if [ -e $(LOCAL_BIN_DIR)/cef-helper ]; then rm -f $(LOCAL_BIN_DIR)/cef-helper && removed_stale=1; fi; \
7170 trap - EXIT INT TERM; \
7271 echo "Installed: $(LOCAL_BIN_DIR)/$(BINARY_NAME)"; \
73- echo "Installed: $(LOCAL_BIN_DIR)/cef-helper"
72+ if [ "$$removed_stale" -eq 1 ]; then echo "Removed stale: $(LOCAL_BIN_DIR)/cef-helper"; fi
7473
7574 # Development targets
7675 dev: ## Run in development mode
@@ -43,7 +43,7 @@ build: build-frontend ## Build the application (pure Go, no CGO)
43 @echo "Building $(BINARY_NAME) $(VERSION) using $(NPROCS) cores..."43 @echo "Building $(BINARY_NAME) $(VERSION) using $(NPROCS) cores..."
44 @mkdir -p $(DIST_DIR)44 @mkdir -p $(DIST_DIR)
45 CGO_ENABLED=0 go build -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)45 CGO_ENABLED=0 go build -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)
46- CGO_ENABLED=0 go build -p $(NPROCS) -ldflags "-s -w" -o $(DIST_DIR)/cef-helper ./cmd/cef-helper46+ @rm -f $(DIST_DIR)/cef-helper
47 @echo "Build successful! Binary: $(DIST_DIR)/$(BINARY_NAME)"47 @echo "Build successful! Binary: $(DIST_DIR)/$(BINARY_NAME)"
48 48
49 build-frontend: ## Build homepage and error pages49 build-frontend: ## Build homepage and error pages
@@ -55,22 +55,21 @@ build-quick: ## Build without frontend (faster for backend development)
55 @echo "Building $(BINARY_NAME) $(VERSION) (quick, no frontend)..."55 @echo "Building $(BINARY_NAME) $(VERSION) (quick, no frontend)..."
56 @mkdir -p $(DIST_DIR)56 @mkdir -p $(DIST_DIR)
57 CGO_ENABLED=0 go build -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)57 CGO_ENABLED=0 go build -p $(NPROCS) $(GCFLAGS) $(LDFLAGS) -o $(DIST_DIR)/$(BINARY_NAME) $(MAIN_PATH)
58- CGO_ENABLED=0 go build -p $(NPROCS) -ldflags "-s -w" -o $(DIST_DIR)/cef-helper ./cmd/cef-helper58+ @rm -f $(DIST_DIR)/cef-helper
59- @echo "Build successful! Binaries: $(DIST_DIR)/$(BINARY_NAME) and $(DIST_DIR)/cef-helper"59+ @echo "Build successful! Binary: $(DIST_DIR)/$(BINARY_NAME)"
60 60
61-install-local: build-quick ## Install dumber + cef-helper to ~/.local/bin atomically61+install-local: build-quick ## Install dumber to ~/.local/bin atomically
62- @echo "Installing $(BINARY_NAME) and cef-helper to $(LOCAL_BIN_DIR)..."62+ @echo "Installing $(BINARY_NAME) to $(LOCAL_BIN_DIR)..."
63 @mkdir -p $(LOCAL_BIN_DIR)63 @mkdir -p $(LOCAL_BIN_DIR)
64 @tmp_dumber="$$(mktemp '$(LOCAL_BIN_DIR)/.dumber.tmp.XXXXXX')"; \64 @tmp_dumber="$$(mktemp '$(LOCAL_BIN_DIR)/.dumber.tmp.XXXXXX')"; \
65- tmp_helper="$$(mktemp '$(LOCAL_BIN_DIR)/.cef-helper.tmp.XXXXXX')"; \65+ trap 'rm -f "$$tmp_dumber"' EXIT INT TERM; \
66- trap 'rm -f "$$tmp_dumber" "$$tmp_helper"' EXIT INT TERM; \
67 install -m 0755 $(DIST_DIR)/$(BINARY_NAME) "$$tmp_dumber"; \66 install -m 0755 $(DIST_DIR)/$(BINARY_NAME) "$$tmp_dumber"; \
68- install -m 0755 $(DIST_DIR)/cef-helper "$$tmp_helper"; \
69 mv -f "$$tmp_dumber" $(LOCAL_BIN_DIR)/$(BINARY_NAME); \67 mv -f "$$tmp_dumber" $(LOCAL_BIN_DIR)/$(BINARY_NAME); \
70- mv -f "$$tmp_helper" $(LOCAL_BIN_DIR)/cef-helper; \68+ removed_stale=0; \
69+ if [ -e $(LOCAL_BIN_DIR)/cef-helper ]; then rm -f $(LOCAL_BIN_DIR)/cef-helper && removed_stale=1; fi; \
71 trap - EXIT INT TERM; \70 trap - EXIT INT TERM; \
72 echo "Installed: $(LOCAL_BIN_DIR)/$(BINARY_NAME)"; \71 echo "Installed: $(LOCAL_BIN_DIR)/$(BINARY_NAME)"; \
73- echo "Installed: $(LOCAL_BIN_DIR)/cef-helper"72+ if [ "$$removed_stale" -eq 1 ]; then echo "Removed stale: $(LOCAL_BIN_DIR)/cef-helper"; fi
74 73
75 # Development targets74 # Development targets
76 dev: ## Run in development mode75 dev: ## Run in development mode
D cmd/cef-helper/main.go +0 -33
@@ -1,33 +0,0 @@
1-// cef-helper is a minimal subprocess binary for CEF.
2-//
3-// CEF spawns renderer, GPU, and utility subprocesses by re-executing a binary
4-// with --type=renderer/gpu-process/utility. In Go, the main dumber binary has
5-// heavy initialization (config, DB, GTK) that corrupts process state before
6-// CEF gets control. This dedicated helper binary calls cef_execute_process as
7-// the very first thing and exits, avoiding that problem.
8-//
9-// Usage: set Settings.BrowserSubprocessPath to the path of this binary.
10-package main
11-
12-import (
13- "os"
14- "runtime"
15-
16- infracef "github.com/bnema/dumber/internal/infrastructure/cef"
17- cef "github.com/bnema/purego-cef/cef"
18-)
19-
20-func main() {
21- // Pin to the main OS thread — CEF requires it.
22- runtime.LockOSThread()
23-
24- // cef_execute_process will handle the subprocess lifecycle and call
25- // os.Exit internally if this is a valid CEF subprocess invocation.
26- // We pass the lightweight subprocess app, which intentionally returns nil
27- // from GetRenderProcessHandler(), so renderer-side bridges remain disabled
28- // in helper subprocesses while the OSR startup regression is under repair.
29- cef.MaybeExitSubprocessWithApp(infracef.NewSubprocessApp())
30-
31- // If we get here, something went wrong.
32- os.Exit(1)
33-}
@@ -1,33 +0,0 @@
1-// cef-helper is a minimal subprocess binary for CEF.
2-//
3-// CEF spawns renderer, GPU, and utility subprocesses by re-executing a binary
4-// with --type=renderer/gpu-process/utility. In Go, the main dumber binary has
5-// heavy initialization (config, DB, GTK) that corrupts process state before
6-// CEF gets control. This dedicated helper binary calls cef_execute_process as
7-// the very first thing and exits, avoiding that problem.
8-//
9-// Usage: set Settings.BrowserSubprocessPath to the path of this binary.
10-package main
11-
12-import (
13- "os"
14- "runtime"
15-
16- infracef "github.com/bnema/dumber/internal/infrastructure/cef"
17- cef "github.com/bnema/purego-cef/cef"
18-)
19-
20-func main() {
21- // Pin to the main OS thread — CEF requires it.
22- runtime.LockOSThread()
23-
24- // cef_execute_process will handle the subprocess lifecycle and call
25- // os.Exit internally if this is a valid CEF subprocess invocation.
26- // We pass the lightweight subprocess app, which intentionally returns nil
27- // from GetRenderProcessHandler(), so renderer-side bridges remain disabled
28- // in helper subprocesses while the OSR startup regression is under repair.
29- cef.MaybeExitSubprocessWithApp(infracef.NewSubprocessApp())
30-
31- // If we get here, something went wrong.
32- os.Exit(1)
33-}
M cmd/dumber/main.go +18 -10
@@ -18,6 +18,7 @@ import (
1818 "github.com/bnema/dumber/internal/domain/entity"
1919 "github.com/bnema/dumber/internal/domain/repository"
2020 "github.com/bnema/dumber/internal/infrastructure/cache"
21+ infracef "github.com/bnema/dumber/internal/infrastructure/cef"
2122 "github.com/bnema/dumber/internal/infrastructure/clipboard"
2223 "github.com/bnema/dumber/internal/infrastructure/config"
2324 "github.com/bnema/dumber/internal/infrastructure/deps"
@@ -117,19 +118,20 @@ func configureBrowserLaunchRelay(cfg *config.Config) {
117118 }
118119
119120 func main() {
120- enableCrashForensics()
121-
122- // CEF subprocess handling: when CEF re-launches the binary with
121+ // CEF subprocess handling: when CEF re-launches this binary with
123122 // --type=renderer/gpu/etc, we must call ExecuteProcess before anything
124123 // else (Cobra, config, arg stripping). We detect subprocesses by the
125- // presence of --type= in os.Args rather than checking the engine env
126- // var, because subprocesses may not inherit the environment and the
124+ // presence of a --type flag in os.Args rather than checking the engine
125+ // env var, because subprocesses may not inherit the environment and the
127126 // engine can also be selected via config.
128- if isCEFSubprocess() {
129- cef.MaybeExitSubprocess()
127+ if isCEFSubprocess(os.Args) {
128+ runtime.LockOSThread()
129+ cef.MaybeExitSubprocessWithApp(infracef.NewSubprocessApp())
130130 os.Exit(1)
131131 }
132132
133+ enableCrashForensics()
134+
133135 mode, browseURL := launchModeFromArgs(os.Args)
134136 // Run GUI mode for browse command
135137 if mode == launchModeBrowse {
@@ -727,13 +729,19 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases {
727729 }
728730 }
729731
730-// isCEFSubprocess returns true if os.Args contains a --type= flag, indicating
732+// isCEFSubprocess returns true if args contains a --type flag, indicating
731733 // this process was spawned by CEF as a renderer, GPU, or utility subprocess.
732-func isCEFSubprocess() bool {
733- for _, arg := range os.Args {
734+func isCEFSubprocess(args []string) bool {
735+ for i, arg := range args {
734736 if strings.HasPrefix(arg, "--type=") {
735737 return true
736738 }
739+ if arg == "--type" && i+1 < len(args) {
740+ next := args[i+1]
741+ if next != "" && !strings.HasPrefix(next, "-") {
742+ return true
743+ }
744+ }
737745 }
738746 return false
739747 }
@@ -18,6 +18,7 @@ import (
18 "github.com/bnema/dumber/internal/domain/entity"18 "github.com/bnema/dumber/internal/domain/entity"
19 "github.com/bnema/dumber/internal/domain/repository"19 "github.com/bnema/dumber/internal/domain/repository"
20 "github.com/bnema/dumber/internal/infrastructure/cache"20 "github.com/bnema/dumber/internal/infrastructure/cache"
21+ infracef "github.com/bnema/dumber/internal/infrastructure/cef"
21 "github.com/bnema/dumber/internal/infrastructure/clipboard"22 "github.com/bnema/dumber/internal/infrastructure/clipboard"
22 "github.com/bnema/dumber/internal/infrastructure/config"23 "github.com/bnema/dumber/internal/infrastructure/config"
23 "github.com/bnema/dumber/internal/infrastructure/deps"24 "github.com/bnema/dumber/internal/infrastructure/deps"
@@ -117,19 +118,20 @@ func configureBrowserLaunchRelay(cfg *config.Config) {
117 }118 }
118 119
119 func main() {120 func main() {
120- enableCrashForensics()121+ // CEF subprocess handling: when CEF re-launches this binary with
121-
122- // CEF subprocess handling: when CEF re-launches the binary with
123 // --type=renderer/gpu/etc, we must call ExecuteProcess before anything122 // --type=renderer/gpu/etc, we must call ExecuteProcess before anything
124 // else (Cobra, config, arg stripping). We detect subprocesses by the123 // else (Cobra, config, arg stripping). We detect subprocesses by the
125- // presence of --type= in os.Args rather than checking the engine env124+ // presence of a --type flag in os.Args rather than checking the engine
126- // var, because subprocesses may not inherit the environment and the125+ // env var, because subprocesses may not inherit the environment and the
127 // engine can also be selected via config.126 // engine can also be selected via config.
128- if isCEFSubprocess() {127+ if isCEFSubprocess(os.Args) {
129- cef.MaybeExitSubprocess()128+ runtime.LockOSThread()
129+ cef.MaybeExitSubprocessWithApp(infracef.NewSubprocessApp())
130 os.Exit(1)130 os.Exit(1)
131 }131 }
132 132
133+ enableCrashForensics()
134+
133 mode, browseURL := launchModeFromArgs(os.Args)135 mode, browseURL := launchModeFromArgs(os.Args)
134 // Run GUI mode for browse command136 // Run GUI mode for browse command
135 if mode == launchModeBrowse {137 if mode == launchModeBrowse {
@@ -727,13 +729,19 @@ func createUseCases(repos *repositories, cfg *config.Config) *useCases {
727 }729 }
728 }730 }
729 731
730-// isCEFSubprocess returns true if os.Args contains a --type= flag, indicating732+// isCEFSubprocess returns true if args contains a --type flag, indicating
731 // this process was spawned by CEF as a renderer, GPU, or utility subprocess.733 // this process was spawned by CEF as a renderer, GPU, or utility subprocess.
732-func isCEFSubprocess() bool {734+func isCEFSubprocess(args []string) bool {
733- for _, arg := range os.Args {735+ for i, arg := range args {
734 if strings.HasPrefix(arg, "--type=") {736 if strings.HasPrefix(arg, "--type=") {
735 return true737 return true
736 }738 }
739+ if arg == "--type" && i+1 < len(args) {
740+ next := args[i+1]
741+ if next != "" && !strings.HasPrefix(next, "-") {
742+ return true
743+ }
744+ }
737 }745 }
738 return false746 return false
739 }747 }
M cmd/dumber/main_test.go +42 -0
@@ -72,6 +72,48 @@ func TestLaunchModeFromArgs_OmniboxFlagFallsBackToCLI(t *testing.T) {
7272 }
7373 }
7474
75+func TestIsCEFSubprocess(t *testing.T) {
76+ tests := []struct {
77+ name string
78+ args []string
79+ want bool
80+ }{
81+ {
82+ name: "equals form",
83+ args: []string{"dumber", "--type=renderer"},
84+ want: true,
85+ },
86+ {
87+ name: "separate value form",
88+ args: []string{"dumber", "--type", "renderer"},
89+ want: true,
90+ },
91+ {
92+ name: "bare type",
93+ args: []string{"dumber", "--type"},
94+ want: false,
95+ },
96+ {
97+ name: "type followed by flag",
98+ args: []string{"dumber", "--type", "--unexpected"},
99+ want: false,
100+ },
101+ {
102+ name: "no type flag",
103+ args: []string{"dumber", "--unexpected"},
104+ want: false,
105+ },
106+ }
107+
108+ for _, tc := range tests {
109+ t.Run(tc.name, func(t *testing.T) {
110+ if got := isCEFSubprocess(tc.args); got != tc.want {
111+ t.Fatalf("expected %v, got %v", tc.want, got)
112+ }
113+ })
114+ }
115+}
116+
75117 func TestResolveCurrentExecutable_ReturnsExecutablePath(t *testing.T) {
76118 got, err := resolveCurrentExecutable(func() (string, error) {
77119 return "/usr/bin/dumber", nil
@@ -72,6 +72,48 @@ func TestLaunchModeFromArgs_OmniboxFlagFallsBackToCLI(t *testing.T) {
72 }72 }
73 }73 }
74 74
75+func TestIsCEFSubprocess(t *testing.T) {
76+ tests := []struct {
77+ name string
78+ args []string
79+ want bool
80+ }{
81+ {
82+ name: "equals form",
83+ args: []string{"dumber", "--type=renderer"},
84+ want: true,
85+ },
86+ {
87+ name: "separate value form",
88+ args: []string{"dumber", "--type", "renderer"},
89+ want: true,
90+ },
91+ {
92+ name: "bare type",
93+ args: []string{"dumber", "--type"},
94+ want: false,
95+ },
96+ {
97+ name: "type followed by flag",
98+ args: []string{"dumber", "--type", "--unexpected"},
99+ want: false,
100+ },
101+ {
102+ name: "no type flag",
103+ args: []string{"dumber", "--unexpected"},
104+ want: false,
105+ },
106+ }
107+
108+ for _, tc := range tests {
109+ t.Run(tc.name, func(t *testing.T) {
110+ if got := isCEFSubprocess(tc.args); got != tc.want {
111+ t.Fatalf("expected %v, got %v", tc.want, got)
112+ }
113+ })
114+ }
115+}
116+
75 func TestResolveCurrentExecutable_ReturnsExecutablePath(t *testing.T) {117 func TestResolveCurrentExecutable_ReturnsExecutablePath(t *testing.T) {
76 got, err := resolveCurrentExecutable(func() (string, error) {118 got, err := resolveCurrentExecutable(func() (string, error) {
77 return "/usr/bin/dumber", nil119 return "/usr/bin/dumber", nil
M internal/infrastructure/cef/app.go +12 -4
@@ -56,10 +56,10 @@ func newDumberApp(engine *Engine) purecef.App {
5656 return app
5757 }
5858
59-// NewSubprocessApp returns a lightweight raw App implementation for helper
60-// processes so cef_execute_process can wrap it itself. Keep helper processes
61-// free of custom render handlers for now: the renderer bridge introduced a
62-// startup regression where OSR pages stopped producing their first paint.
59+// NewSubprocessApp returns a lightweight raw App implementation for CEF
60+// subprocesses re-executed from the main dumber binary. Keep subprocesses free
61+// of custom render handlers for now: the renderer bridge introduced a startup
62+// regression where OSR pages stopped producing their first paint.
6363 func NewSubprocessApp() purecef.App {
6464 return &subprocessApp{}
6565 }
@@ -122,6 +122,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine)
122122 useAngle := ""
123123 ozonePlatform := ""
124124 if commandLine != nil {
125+ appendSwitchIfMissing(commandLine, "no-zygote")
125126 processType = commandLine.GetSwitchValue("type")
126127 commandLineString = commandLine.GetCommandLineString()
127128 useAngle = commandLine.GetSwitchValue("use-angle")
@@ -130,6 +131,13 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine)
130131 h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLineString)
131132 }
132133
134+func appendSwitchIfMissing(commandLine purecef.CommandLine, name string) {
135+ if commandLine == nil || commandLine.HasSwitch(name) {
136+ return
137+ }
138+ commandLine.AppendSwitch(name)
139+}
140+
133141 func parseRelaunchCommandLineArgs(commandLine string) []string {
134142 args := make([]string, 0, 4)
135143 var current strings.Builder
@@ -56,10 +56,10 @@ func newDumberApp(engine *Engine) purecef.App {
56 return app56 return app
57 }57 }
58 58
59-// NewSubprocessApp returns a lightweight raw App implementation for helper59+// NewSubprocessApp returns a lightweight raw App implementation for CEF
60-// processes so cef_execute_process can wrap it itself. Keep helper processes60+// subprocesses re-executed from the main dumber binary. Keep subprocesses free
61-// free of custom render handlers for now: the renderer bridge introduced a61+// of custom render handlers for now: the renderer bridge introduced a startup
62-// startup regression where OSR pages stopped producing their first paint.62+// regression where OSR pages stopped producing their first paint.
63 func NewSubprocessApp() purecef.App {63 func NewSubprocessApp() purecef.App {
64 return &subprocessApp{}64 return &subprocessApp{}
65 }65 }
@@ -122,6 +122,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine)
122 useAngle := ""122 useAngle := ""
123 ozonePlatform := ""123 ozonePlatform := ""
124 if commandLine != nil {124 if commandLine != nil {
125+ appendSwitchIfMissing(commandLine, "no-zygote")
125 processType = commandLine.GetSwitchValue("type")126 processType = commandLine.GetSwitchValue("type")
126 commandLineString = commandLine.GetCommandLineString()127 commandLineString = commandLine.GetCommandLineString()
127 useAngle = commandLine.GetSwitchValue("use-angle")128 useAngle = commandLine.GetSwitchValue("use-angle")
@@ -130,6 +131,13 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine)
130 h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLineString)131 h.engine.recordChildProcessLaunch(processType, useAngle, ozonePlatform, commandLineString)
131 }132 }
132 133
134+func appendSwitchIfMissing(commandLine purecef.CommandLine, name string) {
135+ if commandLine == nil || commandLine.HasSwitch(name) {
136+ return
137+ }
138+ commandLine.AppendSwitch(name)
139+}
140+
133 func parseRelaunchCommandLineArgs(commandLine string) []string {141 func parseRelaunchCommandLineArgs(commandLine string) []string {
134 args := make([]string, 0, 4)142 args := make([]string, 0, 4)
135 var current strings.Builder143 var current strings.Builder
M internal/infrastructure/cef/app_test.go +78 -21
@@ -1,6 +1,7 @@
11 package cef
22
33 import (
4+ "context"
45 "testing"
56 "unsafe"
67
@@ -24,27 +25,56 @@ type relaunchCommandLineStub struct {
2425 commandLineString string
2526 }
2627
27-func (c relaunchCommandLineStub) IsValid() bool { return true }
28-func (c relaunchCommandLineStub) IsReadOnly() bool { return true }
29-func (c relaunchCommandLineStub) Copy() purecef.CommandLine { return c }
30-func (c relaunchCommandLineStub) InitFromArgv(int32, unsafe.Pointer) {}
31-func (c relaunchCommandLineStub) InitFromString(string) {}
32-func (c relaunchCommandLineStub) Reset() {}
33-func (c relaunchCommandLineStub) GetArgv(uintptr) {}
34-func (c relaunchCommandLineStub) GetCommandLineString() string { return c.commandLineString }
35-func (c relaunchCommandLineStub) GetProgram() string { return "dumber" }
36-func (c relaunchCommandLineStub) SetProgram(string) {}
37-func (c relaunchCommandLineStub) HasSwitches() bool { return false }
38-func (c relaunchCommandLineStub) HasSwitch(string) bool { return false }
39-func (c relaunchCommandLineStub) GetSwitchValue(string) string { return "" }
40-func (c relaunchCommandLineStub) GetSwitches(uintptr) {}
41-func (c relaunchCommandLineStub) AppendSwitch(string) {}
42-func (c relaunchCommandLineStub) AppendSwitchWithValue(string, string) {}
43-func (c relaunchCommandLineStub) HasArguments() bool { return true }
44-func (c relaunchCommandLineStub) GetArguments(uintptr) {}
45-func (c relaunchCommandLineStub) AppendArgument(string) {}
46-func (c relaunchCommandLineStub) PrependWrapper(string) {}
47-func (c relaunchCommandLineStub) RemoveSwitch(string) {}
28+func (c relaunchCommandLineStub) IsValid() bool { return true }
29+func (c relaunchCommandLineStub) IsReadOnly() bool { return true }
30+func (c relaunchCommandLineStub) Copy() purecef.CommandLine { return c }
31+func (c relaunchCommandLineStub) InitFromArgv(int32, unsafe.Pointer) {}
32+func (c relaunchCommandLineStub) InitFromString(string) {}
33+func (c relaunchCommandLineStub) Reset() {}
34+func (c relaunchCommandLineStub) GetArgv(uintptr) {}
35+func (c relaunchCommandLineStub) GetCommandLineString() string { return c.commandLineString }
36+func (c relaunchCommandLineStub) GetProgram() string { return "dumber" }
37+func (c relaunchCommandLineStub) SetProgram(string) {}
38+func (c relaunchCommandLineStub) HasSwitches() bool { return false }
39+func (c relaunchCommandLineStub) HasSwitch(string) bool { return false }
40+func (c relaunchCommandLineStub) GetSwitchValue(string) string { return "" }
41+func (c relaunchCommandLineStub) GetSwitches(uintptr) {}
42+func (c relaunchCommandLineStub) AppendSwitch(string) {}
43+func (c relaunchCommandLineStub) AppendSwitchWithValue(string, string) {
44+}
45+func (c relaunchCommandLineStub) HasArguments() bool { return true }
46+func (c relaunchCommandLineStub) GetArguments(uintptr) {}
47+func (c relaunchCommandLineStub) AppendArgument(string) {
48+}
49+func (c relaunchCommandLineStub) PrependWrapper(string) {}
50+func (c relaunchCommandLineStub) RemoveSwitch(string) {}
51+
52+type mutableCommandLineStub struct {
53+ relaunchCommandLineStub
54+ switches map[string]string
55+}
56+
57+func newMutableCommandLineStub() *mutableCommandLineStub {
58+ return &mutableCommandLineStub{switches: make(map[string]string)}
59+}
60+
61+func (c *mutableCommandLineStub) IsReadOnly() bool { return false }
62+func (c *mutableCommandLineStub) Copy() purecef.CommandLine { return c }
63+func (c *mutableCommandLineStub) Reset() { c.switches = make(map[string]string) }
64+func (c *mutableCommandLineStub) HasSwitches() bool { return len(c.switches) > 0 }
65+func (c *mutableCommandLineStub) HasSwitch(name string) bool {
66+ _, ok := c.switches[name]
67+ return ok
68+}
69+func (c *mutableCommandLineStub) GetSwitchValue(name string) string { return c.switches[name] }
70+func (c *mutableCommandLineStub) AppendSwitch(name string) { c.switches[name] = "" }
71+func (c *mutableCommandLineStub) AppendSwitchWithValue(name, value string) {
72+ c.switches[name] = value
73+}
74+func (c *mutableCommandLineStub) HasArguments() bool { return false }
75+func (c *mutableCommandLineStub) RemoveSwitch(name string) {
76+ delete(c.switches, name)
77+}
4878
4979 func TestRegisterDumbScheme(t *testing.T) {
5080 t.Parallel()
@@ -143,3 +173,30 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testin
143173 t.Fatal("handler should not be called for non-browse relaunch")
144174 }
145175 }
176+
177+func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
178+ t.Parallel()
179+
180+ commandLine := newMutableCommandLineStub()
181+ configureCommandLine(commandLine)
182+
183+ if !commandLine.HasSwitch("enable-smooth-scrolling") {
184+ t.Fatal("expected enable-smooth-scrolling switch to be appended")
185+ }
186+ if got := commandLine.GetSwitchValue("autoplay-policy"); got != "no-user-gesture-required" {
187+ t.Fatalf("autoplay-policy = %q, want %q", got, "no-user-gesture-required")
188+ }
189+}
190+
191+func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
192+ t.Parallel()
193+
194+ commandLine := newMutableCommandLineStub()
195+ commandLine.AppendSwitchWithValue("type", "renderer")
196+
197+ (&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
198+
199+ if !commandLine.HasSwitch("no-zygote") {
200+ t.Fatal("expected no-zygote switch to be appended for child processes")
201+ }
202+}
@@ -1,6 +1,7 @@
1 package cef1 package cef
2 2
3 import (3 import (
4+ "context"
4 "testing"5 "testing"
5 "unsafe"6 "unsafe"
6 7
@@ -24,27 +25,56 @@ type relaunchCommandLineStub struct {
24 commandLineString string25 commandLineString string
25 }26 }
26 27
27-func (c relaunchCommandLineStub) IsValid() bool { return true }28+func (c relaunchCommandLineStub) IsValid() bool { return true }
28-func (c relaunchCommandLineStub) IsReadOnly() bool { return true }29+func (c relaunchCommandLineStub) IsReadOnly() bool { return true }
29-func (c relaunchCommandLineStub) Copy() purecef.CommandLine { return c }30+func (c relaunchCommandLineStub) Copy() purecef.CommandLine { return c }
30-func (c relaunchCommandLineStub) InitFromArgv(int32, unsafe.Pointer) {}31+func (c relaunchCommandLineStub) InitFromArgv(int32, unsafe.Pointer) {}
31-func (c relaunchCommandLineStub) InitFromString(string) {}32+func (c relaunchCommandLineStub) InitFromString(string) {}
32-func (c relaunchCommandLineStub) Reset() {}33+func (c relaunchCommandLineStub) Reset() {}
33-func (c relaunchCommandLineStub) GetArgv(uintptr) {}34+func (c relaunchCommandLineStub) GetArgv(uintptr) {}
34-func (c relaunchCommandLineStub) GetCommandLineString() string { return c.commandLineString }35+func (c relaunchCommandLineStub) GetCommandLineString() string { return c.commandLineString }
35-func (c relaunchCommandLineStub) GetProgram() string { return "dumber" }36+func (c relaunchCommandLineStub) GetProgram() string { return "dumber" }
36-func (c relaunchCommandLineStub) SetProgram(string) {}37+func (c relaunchCommandLineStub) SetProgram(string) {}
37-func (c relaunchCommandLineStub) HasSwitches() bool { return false }38+func (c relaunchCommandLineStub) HasSwitches() bool { return false }
38-func (c relaunchCommandLineStub) HasSwitch(string) bool { return false }39+func (c relaunchCommandLineStub) HasSwitch(string) bool { return false }
39-func (c relaunchCommandLineStub) GetSwitchValue(string) string { return "" }40+func (c relaunchCommandLineStub) GetSwitchValue(string) string { return "" }
40-func (c relaunchCommandLineStub) GetSwitches(uintptr) {}41+func (c relaunchCommandLineStub) GetSwitches(uintptr) {}
41-func (c relaunchCommandLineStub) AppendSwitch(string) {}42+func (c relaunchCommandLineStub) AppendSwitch(string) {}
42-func (c relaunchCommandLineStub) AppendSwitchWithValue(string, string) {}43+func (c relaunchCommandLineStub) AppendSwitchWithValue(string, string) {
43-func (c relaunchCommandLineStub) HasArguments() bool { return true }44+}
44-func (c relaunchCommandLineStub) GetArguments(uintptr) {}45+func (c relaunchCommandLineStub) HasArguments() bool { return true }
45-func (c relaunchCommandLineStub) AppendArgument(string) {}46+func (c relaunchCommandLineStub) GetArguments(uintptr) {}
46-func (c relaunchCommandLineStub) PrependWrapper(string) {}47+func (c relaunchCommandLineStub) AppendArgument(string) {
47-func (c relaunchCommandLineStub) RemoveSwitch(string) {}48+}
49+func (c relaunchCommandLineStub) PrependWrapper(string) {}
50+func (c relaunchCommandLineStub) RemoveSwitch(string) {}
51+
52+type mutableCommandLineStub struct {
53+ relaunchCommandLineStub
54+ switches map[string]string
55+}
56+
57+func newMutableCommandLineStub() *mutableCommandLineStub {
58+ return &mutableCommandLineStub{switches: make(map[string]string)}
59+}
60+
61+func (c *mutableCommandLineStub) IsReadOnly() bool { return false }
62+func (c *mutableCommandLineStub) Copy() purecef.CommandLine { return c }
63+func (c *mutableCommandLineStub) Reset() { c.switches = make(map[string]string) }
64+func (c *mutableCommandLineStub) HasSwitches() bool { return len(c.switches) > 0 }
65+func (c *mutableCommandLineStub) HasSwitch(name string) bool {
66+ _, ok := c.switches[name]
67+ return ok
68+}
69+func (c *mutableCommandLineStub) GetSwitchValue(name string) string { return c.switches[name] }
70+func (c *mutableCommandLineStub) AppendSwitch(name string) { c.switches[name] = "" }
71+func (c *mutableCommandLineStub) AppendSwitchWithValue(name, value string) {
72+ c.switches[name] = value
73+}
74+func (c *mutableCommandLineStub) HasArguments() bool { return false }
75+func (c *mutableCommandLineStub) RemoveSwitch(name string) {
76+ delete(c.switches, name)
77+}
48 78
49 func TestRegisterDumbScheme(t *testing.T) {79 func TestRegisterDumbScheme(t *testing.T) {
50 t.Parallel()80 t.Parallel()
@@ -143,3 +173,30 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testin
143 t.Fatal("handler should not be called for non-browse relaunch")173 t.Fatal("handler should not be called for non-browse relaunch")
144 }174 }
145 }175 }
176+
177+func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
178+ t.Parallel()
179+
180+ commandLine := newMutableCommandLineStub()
181+ configureCommandLine(commandLine)
182+
183+ if !commandLine.HasSwitch("enable-smooth-scrolling") {
184+ t.Fatal("expected enable-smooth-scrolling switch to be appended")
185+ }
186+ if got := commandLine.GetSwitchValue("autoplay-policy"); got != "no-user-gesture-required" {
187+ t.Fatalf("autoplay-policy = %q, want %q", got, "no-user-gesture-required")
188+ }
189+}
190+
191+func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
192+ t.Parallel()
193+
194+ commandLine := newMutableCommandLineStub()
195+ commandLine.AppendSwitchWithValue("type", "renderer")
196+
197+ (&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
198+
199+ if !commandLine.HasSwitch("no-zygote") {
200+ t.Fatal("expected no-zygote switch to be appended for child processes")
201+ }
202+}
M internal/infrastructure/cef/engine_init.go +0 -21
@@ -153,12 +153,6 @@ func prepareCEFSettings(
153153 if cfg.LogSeverity != 0 {
154154 settings.LogSeverity = cfg.LogSeverity
155155 }
156- if helperPath := findHelperBinary(); helperPath != "" {
157- settings.BrowserSubprocessPath = helperPath
158- logger.Debug().Str("path", helperPath).Msg("cef: using subprocess helper")
159- } else {
160- logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary")
161- }
162156 return settings, nil
163157 }
164158
@@ -413,21 +407,6 @@ func cleanStaleSingletonLocks(logger *zerolog.Logger, dir string) {
413407 }
414408 }
415409
416-// findHelperBinary looks for the cef-helper binary next to the running
417-// executable. Returns empty string if not found.
418-func findHelperBinary() string {
419- exe, err := os.Executable()
420- if err != nil {
421- return ""
422- }
423- dir := filepath.Dir(exe)
424- helper := filepath.Join(dir, "cef-helper")
425- if _, err := os.Stat(helper); err == nil {
426- return helper
427- }
428- return ""
429-}
430-
431410 const (
432411 dirPerm = 0o700
433412 filePerm = 0o600
@@ -153,12 +153,6 @@ func prepareCEFSettings(
153 if cfg.LogSeverity != 0 {153 if cfg.LogSeverity != 0 {
154 settings.LogSeverity = cfg.LogSeverity154 settings.LogSeverity = cfg.LogSeverity
155 }155 }
156- if helperPath := findHelperBinary(); helperPath != "" {
157- settings.BrowserSubprocessPath = helperPath
158- logger.Debug().Str("path", helperPath).Msg("cef: using subprocess helper")
159- } else {
160- logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary")
161- }
162 return settings, nil156 return settings, nil
163 }157 }
164 158
@@ -413,21 +407,6 @@ func cleanStaleSingletonLocks(logger *zerolog.Logger, dir string) {
413 }407 }
414 }408 }
415 409
416-// findHelperBinary looks for the cef-helper binary next to the running
417-// executable. Returns empty string if not found.
418-func findHelperBinary() string {
419- exe, err := os.Executable()
420- if err != nil {
421- return ""
422- }
423- dir := filepath.Dir(exe)
424- helper := filepath.Join(dir, "cef-helper")
425- if _, err := os.Stat(helper); err == nil {
426- return helper
427- }
428- return ""
429-}
430-
431 const (410 const (
432 dirPerm = 0o700411 dirPerm = 0o700
433 filePerm = 0o600412 filePerm = 0o600
M internal/infrastructure/cef/engine_init_test.go +1 -0
@@ -42,6 +42,7 @@ func TestPrepareCEFSettings_UsesResolvedProfilePaths(t *testing.T) {
4242 require.NoError(t, err)
4343 require.Equal(t, profile.CEFUserDataDir(), settings.RootCachePath)
4444 require.Equal(t, profile.CEFLogFile(), settings.LogFile)
45+ require.Empty(t, settings.BrowserSubprocessPath)
4546 }
4647
4748 func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) {
@@ -42,6 +42,7 @@ func TestPrepareCEFSettings_UsesResolvedProfilePaths(t *testing.T) {
42 require.NoError(t, err)42 require.NoError(t, err)
43 require.Equal(t, profile.CEFUserDataDir(), settings.RootCachePath)43 require.Equal(t, profile.CEFUserDataDir(), settings.RootCachePath)
44 require.Equal(t, profile.CEFLogFile(), settings.LogFile)44 require.Equal(t, profile.CEFLogFile(), settings.LogFile)
45+ require.Empty(t, settings.BrowserSubprocessPath)
45 }46 }
46 47
47 func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) {48 func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) {

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