1package runtimeprofile
2
3import (
4 "fmt"
5 "path/filepath"
6 "strings"
7)
8
9const (
10 browserLaunchSocketName = "browser-launch.sock"
11 engineWebKit = "webkit"
12 engineCEF = "cef"
13)
14
15// Mode identifies the execution sandbox mode.
16type Mode string
17
18const (
19 ModeProd Mode = "prod"
20 ModeDev Mode = "dev"
21)
22
23// BasePaths holds the already-resolved dumber XDG homes used in prod.
24type BasePaths struct {
25 ConfigHome string
26 DataHome string
27 StateHome string
28 CacheHome string
29}
30
31// ResolveInput contains the external inputs needed to resolve a runtime profile.
32type ResolveInput struct {
33 Env func(string) string
34 CWD func() (string, error)
35 Engine string
36 Base BasePaths
37}
38
39// SharedPaths are shared by the whole runtime sandbox.
40type SharedPaths struct {
41 // RootDir is only populated when the shared sandbox has a single root
42 // directory (for example ENV=dev under .dev/dumber). It is intentionally
43 // empty in prod, where shared paths come from distinct XDG homes.
44 RootDir string
45 ConfigDir string
46 DataDir string
47 StateDir string
48 CacheDir string
49 LogDir string
50}
51
52// EnginePaths are engine-specific technical paths.
53type EnginePaths struct {
54 Name string
55 RootDir string
56 RuntimeDir string
57 LogDir string
58}
59
60// IPCPaths are runtime coordination paths.
61type IPCPaths struct {
62 RuntimeDir string
63 BrowserLaunchSocket string
64}
65
66// Profile is the fully resolved runtime profile for one mode+engine namespace.
67type Profile struct {
68 Mode Mode
69 Engine string
70 Shared SharedPaths
71 EnginePaths EnginePaths
72 IPC IPCPaths
73}
74
75// Resolve builds a runtime profile from pure inputs.
76func Resolve(input ResolveInput) (Profile, error) {
77 engine := normalizeEngine(input.Engine)
78 if envValue(input.Env, "ENV") == string(ModeDev) {
79 cwd, err := cwdValue(input.CWD)
80 if err != nil {
81 return Profile{}, fmt.Errorf("resolve cwd: %w", err)
82 }
83 root := filepath.Join(cwd, ".dev", "dumber")
84 engineRoot := filepath.Join(root, "engines", engine)
85 ipcRoot := filepath.Join(root, "runtime", engine)
86 return Profile{
87 Mode: ModeDev,
88 Engine: engine,
89 Shared: SharedPaths{
90 RootDir: root,
91 ConfigDir: filepath.Join(root, "config"),
92 DataDir: filepath.Join(root, "data"),
93 StateDir: filepath.Join(root, "state"),
94 CacheDir: filepath.Join(root, "cache"),
95 LogDir: filepath.Join(root, "logs"),
96 },
97 EnginePaths: EnginePaths{
98 Name: engine,
99 RootDir: engineRoot,
100 RuntimeDir: filepath.Join(engineRoot, "runtime"),
101 LogDir: filepath.Join(engineRoot, "logs"),
102 },
103 IPC: IPCPaths{
104 RuntimeDir: ipcRoot,
105 BrowserLaunchSocket: filepath.Join(ipcRoot, browserLaunchSocketName),
106 },
107 }, nil
108 }
109
110 shared := SharedPaths{
111 ConfigDir: input.Base.ConfigHome,
112 DataDir: input.Base.DataHome,
113 StateDir: input.Base.StateHome,
114 CacheDir: input.Base.CacheHome,
115 LogDir: filepath.Join(input.Base.StateHome, "logs"),
116 }
117 engineRoot := filepath.Join(shared.StateDir, "engines", engine)
118 ipcRoot := filepath.Join(shared.StateDir, "runtime", engine)
119 return Profile{
120 Mode: ModeProd,
121 Engine: engine,
122 Shared: shared,
123 EnginePaths: EnginePaths{
124 Name: engine,
125 RootDir: engineRoot,
126 RuntimeDir: filepath.Join(engineRoot, "runtime"),
127 LogDir: filepath.Join(engineRoot, "logs"),
128 },
129 IPC: IPCPaths{
130 RuntimeDir: ipcRoot,
131 BrowserLaunchSocket: filepath.Join(ipcRoot, browserLaunchSocketName),
132 },
133 }, nil
134}
135
136// normalizeEngine trims and lowercases the runtime engine name.
137// Supported engines are "webkit" and "cef"; any other value falls back to
138// the default "webkit" runtime namespace.
139func normalizeEngine(engine string) string {
140 switch normalized := strings.ToLower(strings.TrimSpace(engine)); normalized {
141 case "", engineWebKit:
142 return engineWebKit
143 case engineCEF:
144 return engineCEF
145 default:
146 return engineWebKit
147 }
148}
149
150func envValue(get func(string) string, key string) string {
151 if get == nil {
152 return ""
153 }
154 return get(key)
155}
156
157func cwdValue(get func() (string, error)) (string, error) {
158 if get == nil {
159 return "", fmt.Errorf("cwd getter is nil")
160 }
161 return get()
162}
163
164// CEFUserDataDir returns the resolved CEF root cache/user-data directory.
165func (p Profile) CEFUserDataDir() string {
166 if p.Mode == ModeDev {
167 return filepath.Join(p.EnginePaths.RootDir, "data")
168 }
169 return filepath.Join(p.Shared.DataDir, "cef_user_data")
170}
171
172// CEFLogFile returns the resolved default CEF runtime log path.
173func (p Profile) CEFLogFile() string {
174 return filepath.Join(p.EnginePaths.LogDir, "cef_runtime.log")
175}
176
177// WebKitDataDir returns the resolved WebKit website data directory.
178func (p Profile) WebKitDataDir() string {
179 if p.Mode == ModeDev {
180 return filepath.Join(p.EnginePaths.RootDir, "data")
181 }
182 return p.Shared.DataDir
183}
184
185// WebKitCacheDir returns the resolved WebKit cache directory.
186func (p Profile) WebKitCacheDir() string {
187 if p.Mode == ModeDev {
188 return filepath.Join(p.EnginePaths.RootDir, "cache")
189 }
190 // Keep the current prod layout under state for non-user-managed technical
191 // engine cache data; this intentionally preserves existing WebKit behavior.
192 return filepath.Join(p.Shared.StateDir, "webkit-cache")
193}