v0.30.0 eb3068f

migrate_engine.go RawHistoryFolder

1package config
2
3import (
4	"fmt"
5	"io/fs"
6	"os"
7	"strings"
8
9	"github.com/pelletier/go-toml/v2"
10)
11
12// configFilePermissions is the file permission used when writing migrated config files.
13const configFilePermissions fs.FileMode = 0o644
14
15// legacyEngineAlias defines the single metadata source for old engine config keys.
16// Dropped aliases are detected as legacy input but are not written to a new target.
17type legacyEngineAlias struct {
18	legacySection string
19	legacyField   string
20	targetSection string // "engine" or "engine.webkit"; empty when dropped
21	targetField   string // empty when dropped
22	dropped       bool
23}
24
25func (a legacyEngineAlias) legacyKey() string {
26	return a.legacySection + "." + a.legacyField
27}
28
29const (
30	engineTargetSection       = "engine"
31	engineWebKitTargetSection = "engine.webkit"
32)
33
34func legacyEngineTarget(legacySection, legacyField, targetSection, targetField string) legacyEngineAlias {
35	return legacyEngineAlias{
36		legacySection: legacySection,
37		legacyField:   legacyField,
38		targetSection: targetSection,
39		targetField:   targetField,
40	}
41}
42
43func legacyEngineSameTarget(legacySection, legacyField, targetSection string) legacyEngineAlias {
44	return legacyEngineTarget(legacySection, legacyField, targetSection, legacyField)
45}
46
47func droppedLegacyEngineAlias(legacySection, legacyField string) legacyEngineAlias {
48	return legacyEngineAlias{legacySection: legacySection, legacyField: legacyField, dropped: true}
49}
50
51// legacyEngineAliases is the canonical list of old-to-new engine config aliases.
52var legacyEngineAliases = []legacyEngineAlias{
53	// [performance] -> [engine]
54	legacyEngineSameTarget("performance", "profile", engineTargetSection),
55	legacyEngineSameTarget("performance", "zoom_cache_size", engineTargetSection),
56	legacyEngineTarget("performance", "webview_pool_prewarm_count", engineTargetSection, "pool_prewarm_count"),
57	// [privacy] -> [engine]
58	legacyEngineSameTarget("privacy", "cookie_policy", engineTargetSection),
59
60	// [privacy] -> [engine.webkit]
61	legacyEngineSameTarget("privacy", "itp_enabled", engineWebKitTargetSection),
62
63	// [rendering] -> [engine.webkit] (rendering.mode is dropped)
64	droppedLegacyEngineAlias("rendering", "mode"),
65	legacyEngineSameTarget("rendering", "disable_dmabuf_renderer", engineWebKitTargetSection),
66	legacyEngineSameTarget("rendering", "force_compositing_mode", engineWebKitTargetSection),
67	legacyEngineSameTarget("rendering", "disable_compositing_mode", engineWebKitTargetSection),
68	legacyEngineSameTarget("rendering", "gsk_renderer", engineWebKitTargetSection),
69	legacyEngineSameTarget("rendering", "disable_mipmaps", engineWebKitTargetSection),
70	legacyEngineSameTarget("rendering", "prefer_gl", engineWebKitTargetSection),
71	legacyEngineSameTarget("rendering", "draw_compositing_indicators", engineWebKitTargetSection),
72	legacyEngineSameTarget("rendering", "show_fps", engineWebKitTargetSection),
73	legacyEngineSameTarget("rendering", "sample_memory", engineWebKitTargetSection),
74	legacyEngineSameTarget("rendering", "debug_frames", engineWebKitTargetSection),
75
76	// [performance] -> [engine.webkit] (webkit-specific) — field names are identical
77	legacyEngineSameTarget("performance", "skia_cpu_painting_threads", engineWebKitTargetSection),
78	legacyEngineSameTarget("performance", "skia_gpu_painting_threads", engineWebKitTargetSection),
79	legacyEngineSameTarget("performance", "skia_enable_cpu_rendering", engineWebKitTargetSection),
80	legacyEngineSameTarget("performance", "web_process_memory_limit_mb", engineWebKitTargetSection),
81	legacyEngineSameTarget("performance", "web_process_memory_poll_interval_sec", engineWebKitTargetSection),
82	legacyEngineSameTarget("performance", "web_process_memory_conservative_threshold", engineWebKitTargetSection),
83	legacyEngineSameTarget("performance", "web_process_memory_strict_threshold", engineWebKitTargetSection),
84	legacyEngineSameTarget("performance", "network_process_memory_limit_mb", engineWebKitTargetSection),
85	legacyEngineSameTarget("performance", "network_process_memory_poll_interval_sec", engineWebKitTargetSection),
86	legacyEngineSameTarget("performance", "network_process_memory_conservative_threshold", engineWebKitTargetSection),
87	legacyEngineSameTarget("performance", "network_process_memory_strict_threshold", engineWebKitTargetSection),
88
89	// [media] GStreamer fields -> [engine.webkit]
90	legacyEngineSameTarget("media", "force_vsync", engineWebKitTargetSection),
91	legacyEngineSameTarget("media", "gl_rendering_mode", engineWebKitTargetSection),
92	legacyEngineSameTarget("media", "gstreamer_debug_level", engineWebKitTargetSection),
93
94	// [runtime] -> [engine.webkit]
95	legacyEngineSameTarget("runtime", "prefix", engineWebKitTargetSection),
96}
97
98func legacyEngineAliasesForSection(section string) []legacyEngineAlias {
99	aliases := make([]legacyEngineAlias, 0)
100	for _, alias := range legacyEngineAliases {
101		if alias.legacySection == section {
102			aliases = append(aliases, alias)
103		}
104	}
105	return aliases
106}
107
108func legacyEngineSectionsToRemove() []string {
109	sections := make([]string, 0)
110	seen := make(map[string]struct{})
111	for _, alias := range legacyEngineAliases {
112		if alias.legacySection == "media" {
113			continue
114		}
115		if _, ok := seen[alias.legacySection]; ok {
116			continue
117		}
118		seen[alias.legacySection] = struct{}{}
119		sections = append(sections, alias.legacySection)
120	}
121	return sections
122}
123
124func legacyEngineInputSections() []string {
125	sections := make([]string, 0)
126	seen := make(map[string]struct{})
127	for _, alias := range legacyEngineAliases {
128		if _, ok := seen[alias.legacySection]; ok {
129			continue
130		}
131		seen[alias.legacySection] = struct{}{}
132		sections = append(sections, alias.legacySection)
133	}
134	return sections
135}
136
137func legacyEngineInputSectionsMessage() string {
138	sections := legacyEngineInputSections()
139	formatted := make([]string, 0, len(sections))
140	for _, section := range sections {
141		formatted = append(formatted, "["+section+"]")
142	}
143	return strings.Join(formatted, ", ")
144}
145
146func hasLegacyEngineAlias(raw map[string]any, alias legacyEngineAlias) bool {
147	sectionAny, exists := raw[alias.legacySection]
148	if !exists {
149		return false
150	}
151	sectionMap, ok := sectionAny.(map[string]any)
152	if !ok {
153		return false
154	}
155	_, exists = sectionMap[alias.legacyField]
156	return exists
157}
158
159func canonicalLegacyEngineValue(alias legacyEngineAlias, val any) any {
160	if alias.targetSection == engineTargetSection && alias.targetField == "cookie_policy" {
161		if cookiePolicy, ok := val.(string); ok && cookiePolicy == "accept_all" {
162			return string(CookiePolicyAlways)
163		}
164	}
165	return val
166}
167
168// MigrateToEngineConfig restructures old top-level config sections into [engine]/[engine.webkit].
169// Returns true if migration was performed, false if already migrated or no old sections found.
170func MigrateToEngineConfig(configFile string) (bool, error) {
171	raw, err := readRawTOML(configFile)
172	if err != nil {
173		return false, err
174	}
175
176	if _, exists := raw["engine"]; exists {
177		for _, section := range legacyEngineSectionsToRemove() {
178			if _, hasLegacy := raw[section]; hasLegacy {
179				return false, fmt.Errorf(
180					"mixed old/new config: [engine] coexists with legacy [%s]; "+
181						"remove [rendering], [performance], [privacy], [runtime]",
182					section,
183				)
184			}
185		}
186		for _, alias := range legacyEngineAliasesForSection("media") {
187			if hasLegacyEngineAlias(raw, alias) {
188				return false, fmt.Errorf(
189					"mixed old/new config: [engine] coexists with deprecated "+
190						"media.%s; remove it from [media] (now in [engine.webkit])",
191					alias.legacyField,
192				)
193			}
194		}
195		return false, nil // already migrated
196	}
197
198	if !hasOldEngineSections(raw) {
199		return false, nil
200	}
201
202	applyEngineMappings(raw)
203
204	out, marshalErr := toml.Marshal(raw)
205	if marshalErr != nil {
206		return false, marshalErr
207	}
208
209	if err := atomicWriteFile(configFile, string(out), configFilePermissions); err != nil {
210		return false, err
211	}
212
213	return true, nil
214}
215
216// readRawTOML reads a TOML file into a generic map.
217func readRawTOML(path string) (map[string]any, error) {
218	data, err := os.ReadFile(path)
219	if err != nil {
220		return nil, err
221	}
222	var raw map[string]any
223	if err := toml.Unmarshal(data, &raw); err != nil {
224		return nil, err
225	}
226	return raw, nil
227}
228
229// hasOldEngineSections checks if the raw config has old sections that need migration.
230func hasOldEngineSections(raw map[string]any) bool {
231	for _, section := range legacyEngineSectionsToRemove() {
232		if _, exists := raw[section]; exists {
233			return true
234		}
235	}
236	for _, alias := range legacyEngineAliasesForSection("media") {
237		if hasLegacyEngineAlias(raw, alias) {
238			return true
239		}
240	}
241	return false
242}
243
244// applyEngineMappings builds [engine]/[engine.webkit] from old sections and cleans up.
245func applyEngineMappings(raw map[string]any) {
246	engineMap := make(map[string]any)
247	webkitMap := make(map[string]any)
248
249	for _, alias := range legacyEngineAliases {
250		if alias.dropped {
251			continue
252		}
253		val := getSectionField(raw, alias.legacySection, alias.legacyField)
254		if val == nil {
255			continue
256		}
257		val = canonicalLegacyEngineValue(alias, val)
258		switch alias.targetSection {
259		case engineTargetSection:
260			engineMap[alias.targetField] = val
261		case engineWebKitTargetSection:
262			webkitMap[alias.targetField] = val
263		}
264	}
265
266	engineMap["type"] = "webkit"
267	if len(webkitMap) > 0 {
268		engineMap["webkit"] = webkitMap
269	}
270	raw["engine"] = engineMap
271
272	for _, section := range legacyEngineSectionsToRemove() {
273		delete(raw, section)
274	}
275
276	stripMigratedMediaFields(raw)
277}
278
279// stripMigratedMediaFields removes migrated fields from [media] while keeping the rest.
280func stripMigratedMediaFields(raw map[string]any) {
281	mediaAny, exists := raw["media"]
282	if !exists {
283		return
284	}
285	media, ok := mediaAny.(map[string]any)
286	if !ok {
287		return
288	}
289	for _, alias := range legacyEngineAliasesForSection("media") {
290		delete(media, alias.legacyField)
291	}
292	if len(media) == 0 {
293		delete(raw, "media")
294	}
295}
296
297// getSectionField retrieves a field value from a named section in the raw config map.
298func getSectionField(raw map[string]any, section, field string) any {
299	sectionAny, exists := raw[section]
300	if !exists {
301		return nil
302	}
303	sectionMap, ok := sectionAny.(map[string]any)
304	if !ok {
305		return nil
306	}
307	val, exists := sectionMap[field]
308	if !exists {
309		return nil
310	}
311	return val
312}

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