detached cd437e3

feat(webkit): add low-level performance tuning infrastructure

commit
cd437e3cc74fc2213e06d77a13fae5253f79afeb
author
brice · · 2025-12-28 21:21 UTC
committer
brice · · 2025-12-29 17:28 UTC
parent
a85da87
signature
unsigned
9 files · +1068 -9
M .mockery.yaml +2 -0
@@ -18,6 +18,8 @@ packages:
1818 WebView: {}
1919 WebViewFactory: {}
2020 WebViewPool: {}
21+ MemoryPressureApplier: {}
22+ WebKitContextProvider: {}
2123 github.com/bnema/dumber/internal/domain/repository:
2224 config:
2325 dir: '{{.InterfaceDir}}/mocks'
@@ -18,6 +18,8 @@ packages:
18 WebView: {}18 WebView: {}
19 WebViewFactory: {}19 WebViewFactory: {}
20 WebViewPool: {}20 WebViewPool: {}
21+ MemoryPressureApplier: {}
22+ WebKitContextProvider: {}
21 github.com/bnema/dumber/internal/domain/repository:23 github.com/bnema/dumber/internal/domain/repository:
22 config:24 config:
23 dir: '{{.InterfaceDir}}/mocks'25 dir: '{{.InterfaceDir}}/mocks'
M internal/application/port/mocks/mocks.go +408 -0
@@ -940,6 +940,414 @@ func (_c *MockSessionSpawner_SpawnWithSession_Call) RunAndReturn(run func(sessio
940940 return _c
941941 }
942942
943+// NewMockMemoryPressureApplier creates a new instance of MockMemoryPressureApplier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
944+// The first argument is typically a *testing.T value.
945+func NewMockMemoryPressureApplier(t interface {
946+ mock.TestingT
947+ Cleanup(func())
948+}) *MockMemoryPressureApplier {
949+ mock := &MockMemoryPressureApplier{}
950+ mock.Mock.Test(t)
951+
952+ t.Cleanup(func() { mock.AssertExpectations(t) })
953+
954+ return mock
955+}
956+
957+// MockMemoryPressureApplier is an autogenerated mock type for the MemoryPressureApplier type
958+type MockMemoryPressureApplier struct {
959+ mock.Mock
960+}
961+
962+type MockMemoryPressureApplier_Expecter struct {
963+ mock *mock.Mock
964+}
965+
966+func (_m *MockMemoryPressureApplier) EXPECT() *MockMemoryPressureApplier_Expecter {
967+ return &MockMemoryPressureApplier_Expecter{mock: &_m.Mock}
968+}
969+
970+// ApplyNetworkProcessSettings provides a mock function for the type MockMemoryPressureApplier
971+func (_mock *MockMemoryPressureApplier) ApplyNetworkProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) error {
972+ ret := _mock.Called(ctx, cfg)
973+
974+ if len(ret) == 0 {
975+ panic("no return value specified for ApplyNetworkProcessSettings")
976+ }
977+
978+ var r0 error
979+ if returnFunc, ok := ret.Get(0).(func(context.Context, *port.MemoryPressureConfig) error); ok {
980+ r0 = returnFunc(ctx, cfg)
981+ } else {
982+ r0 = ret.Error(0)
983+ }
984+ return r0
985+}
986+
987+// MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyNetworkProcessSettings'
988+type MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call struct {
989+ *mock.Call
990+}
991+
992+// ApplyNetworkProcessSettings is a helper method to define mock.On call
993+// - ctx context.Context
994+// - cfg *port.MemoryPressureConfig
995+func (_e *MockMemoryPressureApplier_Expecter) ApplyNetworkProcessSettings(ctx interface{}, cfg interface{}) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
996+ return &MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call{Call: _e.mock.On("ApplyNetworkProcessSettings", ctx, cfg)}
997+}
998+
999+func (_c *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call) Run(run func(ctx context.Context, cfg *port.MemoryPressureConfig)) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
1000+ _c.Call.Run(func(args mock.Arguments) {
1001+ var arg0 context.Context
1002+ if args[0] != nil {
1003+ arg0 = args[0].(context.Context)
1004+ }
1005+ var arg1 *port.MemoryPressureConfig
1006+ if args[1] != nil {
1007+ arg1 = args[1].(*port.MemoryPressureConfig)
1008+ }
1009+ run(
1010+ arg0,
1011+ arg1,
1012+ )
1013+ })
1014+ return _c
1015+}
1016+
1017+func (_c *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call) Return(err error) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
1018+ _c.Call.Return(err)
1019+ return _c
1020+}
1021+
1022+func (_c *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call) RunAndReturn(run func(ctx context.Context, cfg *port.MemoryPressureConfig) error) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
1023+ _c.Call.Return(run)
1024+ return _c
1025+}
1026+
1027+// ApplyWebProcessSettings provides a mock function for the type MockMemoryPressureApplier
1028+func (_mock *MockMemoryPressureApplier) ApplyWebProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) (any, error) {
1029+ ret := _mock.Called(ctx, cfg)
1030+
1031+ if len(ret) == 0 {
1032+ panic("no return value specified for ApplyWebProcessSettings")
1033+ }
1034+
1035+ var r0 any
1036+ var r1 error
1037+ if returnFunc, ok := ret.Get(0).(func(context.Context, *port.MemoryPressureConfig) (any, error)); ok {
1038+ return returnFunc(ctx, cfg)
1039+ }
1040+ if returnFunc, ok := ret.Get(0).(func(context.Context, *port.MemoryPressureConfig) any); ok {
1041+ r0 = returnFunc(ctx, cfg)
1042+ } else {
1043+ if ret.Get(0) != nil {
1044+ r0 = ret.Get(0).(any)
1045+ }
1046+ }
1047+ if returnFunc, ok := ret.Get(1).(func(context.Context, *port.MemoryPressureConfig) error); ok {
1048+ r1 = returnFunc(ctx, cfg)
1049+ } else {
1050+ r1 = ret.Error(1)
1051+ }
1052+ return r0, r1
1053+}
1054+
1055+// MockMemoryPressureApplier_ApplyWebProcessSettings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyWebProcessSettings'
1056+type MockMemoryPressureApplier_ApplyWebProcessSettings_Call struct {
1057+ *mock.Call
1058+}
1059+
1060+// ApplyWebProcessSettings is a helper method to define mock.On call
1061+// - ctx context.Context
1062+// - cfg *port.MemoryPressureConfig
1063+func (_e *MockMemoryPressureApplier_Expecter) ApplyWebProcessSettings(ctx interface{}, cfg interface{}) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1064+ return &MockMemoryPressureApplier_ApplyWebProcessSettings_Call{Call: _e.mock.On("ApplyWebProcessSettings", ctx, cfg)}
1065+}
1066+
1067+func (_c *MockMemoryPressureApplier_ApplyWebProcessSettings_Call) Run(run func(ctx context.Context, cfg *port.MemoryPressureConfig)) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1068+ _c.Call.Run(func(args mock.Arguments) {
1069+ var arg0 context.Context
1070+ if args[0] != nil {
1071+ arg0 = args[0].(context.Context)
1072+ }
1073+ var arg1 *port.MemoryPressureConfig
1074+ if args[1] != nil {
1075+ arg1 = args[1].(*port.MemoryPressureConfig)
1076+ }
1077+ run(
1078+ arg0,
1079+ arg1,
1080+ )
1081+ })
1082+ return _c
1083+}
1084+
1085+func (_c *MockMemoryPressureApplier_ApplyWebProcessSettings_Call) Return(v any, err error) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1086+ _c.Call.Return(v, err)
1087+ return _c
1088+}
1089+
1090+func (_c *MockMemoryPressureApplier_ApplyWebProcessSettings_Call) RunAndReturn(run func(ctx context.Context, cfg *port.MemoryPressureConfig) (any, error)) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1091+ _c.Call.Return(run)
1092+ return _c
1093+}
1094+
1095+// NewMockWebKitContextProvider creates a new instance of MockWebKitContextProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
1096+// The first argument is typically a *testing.T value.
1097+func NewMockWebKitContextProvider(t interface {
1098+ mock.TestingT
1099+ Cleanup(func())
1100+}) *MockWebKitContextProvider {
1101+ mock := &MockWebKitContextProvider{}
1102+ mock.Mock.Test(t)
1103+
1104+ t.Cleanup(func() { mock.AssertExpectations(t) })
1105+
1106+ return mock
1107+}
1108+
1109+// MockWebKitContextProvider is an autogenerated mock type for the WebKitContextProvider type
1110+type MockWebKitContextProvider struct {
1111+ mock.Mock
1112+}
1113+
1114+type MockWebKitContextProvider_Expecter struct {
1115+ mock *mock.Mock
1116+}
1117+
1118+func (_m *MockWebKitContextProvider) EXPECT() *MockWebKitContextProvider_Expecter {
1119+ return &MockWebKitContextProvider_Expecter{mock: &_m.Mock}
1120+}
1121+
1122+// CacheDir provides a mock function for the type MockWebKitContextProvider
1123+func (_mock *MockWebKitContextProvider) CacheDir() string {
1124+ ret := _mock.Called()
1125+
1126+ if len(ret) == 0 {
1127+ panic("no return value specified for CacheDir")
1128+ }
1129+
1130+ var r0 string
1131+ if returnFunc, ok := ret.Get(0).(func() string); ok {
1132+ r0 = returnFunc()
1133+ } else {
1134+ r0 = ret.Get(0).(string)
1135+ }
1136+ return r0
1137+}
1138+
1139+// MockWebKitContextProvider_CacheDir_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheDir'
1140+type MockWebKitContextProvider_CacheDir_Call struct {
1141+ *mock.Call
1142+}
1143+
1144+// CacheDir is a helper method to define mock.On call
1145+func (_e *MockWebKitContextProvider_Expecter) CacheDir() *MockWebKitContextProvider_CacheDir_Call {
1146+ return &MockWebKitContextProvider_CacheDir_Call{Call: _e.mock.On("CacheDir")}
1147+}
1148+
1149+func (_c *MockWebKitContextProvider_CacheDir_Call) Run(run func()) *MockWebKitContextProvider_CacheDir_Call {
1150+ _c.Call.Run(func(args mock.Arguments) {
1151+ run()
1152+ })
1153+ return _c
1154+}
1155+
1156+func (_c *MockWebKitContextProvider_CacheDir_Call) Return(s string) *MockWebKitContextProvider_CacheDir_Call {
1157+ _c.Call.Return(s)
1158+ return _c
1159+}
1160+
1161+func (_c *MockWebKitContextProvider_CacheDir_Call) RunAndReturn(run func() string) *MockWebKitContextProvider_CacheDir_Call {
1162+ _c.Call.Return(run)
1163+ return _c
1164+}
1165+
1166+// Close provides a mock function for the type MockWebKitContextProvider
1167+func (_mock *MockWebKitContextProvider) Close() error {
1168+ ret := _mock.Called()
1169+
1170+ if len(ret) == 0 {
1171+ panic("no return value specified for Close")
1172+ }
1173+
1174+ var r0 error
1175+ if returnFunc, ok := ret.Get(0).(func() error); ok {
1176+ r0 = returnFunc()
1177+ } else {
1178+ r0 = ret.Error(0)
1179+ }
1180+ return r0
1181+}
1182+
1183+// MockWebKitContextProvider_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
1184+type MockWebKitContextProvider_Close_Call struct {
1185+ *mock.Call
1186+}
1187+
1188+// Close is a helper method to define mock.On call
1189+func (_e *MockWebKitContextProvider_Expecter) Close() *MockWebKitContextProvider_Close_Call {
1190+ return &MockWebKitContextProvider_Close_Call{Call: _e.mock.On("Close")}
1191+}
1192+
1193+func (_c *MockWebKitContextProvider_Close_Call) Run(run func()) *MockWebKitContextProvider_Close_Call {
1194+ _c.Call.Run(func(args mock.Arguments) {
1195+ run()
1196+ })
1197+ return _c
1198+}
1199+
1200+func (_c *MockWebKitContextProvider_Close_Call) Return(err error) *MockWebKitContextProvider_Close_Call {
1201+ _c.Call.Return(err)
1202+ return _c
1203+}
1204+
1205+func (_c *MockWebKitContextProvider_Close_Call) RunAndReturn(run func() error) *MockWebKitContextProvider_Close_Call {
1206+ _c.Call.Return(run)
1207+ return _c
1208+}
1209+
1210+// DataDir provides a mock function for the type MockWebKitContextProvider
1211+func (_mock *MockWebKitContextProvider) DataDir() string {
1212+ ret := _mock.Called()
1213+
1214+ if len(ret) == 0 {
1215+ panic("no return value specified for DataDir")
1216+ }
1217+
1218+ var r0 string
1219+ if returnFunc, ok := ret.Get(0).(func() string); ok {
1220+ r0 = returnFunc()
1221+ } else {
1222+ r0 = ret.Get(0).(string)
1223+ }
1224+ return r0
1225+}
1226+
1227+// MockWebKitContextProvider_DataDir_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DataDir'
1228+type MockWebKitContextProvider_DataDir_Call struct {
1229+ *mock.Call
1230+}
1231+
1232+// DataDir is a helper method to define mock.On call
1233+func (_e *MockWebKitContextProvider_Expecter) DataDir() *MockWebKitContextProvider_DataDir_Call {
1234+ return &MockWebKitContextProvider_DataDir_Call{Call: _e.mock.On("DataDir")}
1235+}
1236+
1237+func (_c *MockWebKitContextProvider_DataDir_Call) Run(run func()) *MockWebKitContextProvider_DataDir_Call {
1238+ _c.Call.Run(func(args mock.Arguments) {
1239+ run()
1240+ })
1241+ return _c
1242+}
1243+
1244+func (_c *MockWebKitContextProvider_DataDir_Call) Return(s string) *MockWebKitContextProvider_DataDir_Call {
1245+ _c.Call.Return(s)
1246+ return _c
1247+}
1248+
1249+func (_c *MockWebKitContextProvider_DataDir_Call) RunAndReturn(run func() string) *MockWebKitContextProvider_DataDir_Call {
1250+ _c.Call.Return(run)
1251+ return _c
1252+}
1253+
1254+// Initialize provides a mock function for the type MockWebKitContextProvider
1255+func (_mock *MockWebKitContextProvider) Initialize(ctx context.Context, opts port.WebKitContextOptions) error {
1256+ ret := _mock.Called(ctx, opts)
1257+
1258+ if len(ret) == 0 {
1259+ panic("no return value specified for Initialize")
1260+ }
1261+
1262+ var r0 error
1263+ if returnFunc, ok := ret.Get(0).(func(context.Context, port.WebKitContextOptions) error); ok {
1264+ r0 = returnFunc(ctx, opts)
1265+ } else {
1266+ r0 = ret.Error(0)
1267+ }
1268+ return r0
1269+}
1270+
1271+// MockWebKitContextProvider_Initialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Initialize'
1272+type MockWebKitContextProvider_Initialize_Call struct {
1273+ *mock.Call
1274+}
1275+
1276+// Initialize is a helper method to define mock.On call
1277+// - ctx context.Context
1278+// - opts port.WebKitContextOptions
1279+func (_e *MockWebKitContextProvider_Expecter) Initialize(ctx interface{}, opts interface{}) *MockWebKitContextProvider_Initialize_Call {
1280+ return &MockWebKitContextProvider_Initialize_Call{Call: _e.mock.On("Initialize", ctx, opts)}
1281+}
1282+
1283+func (_c *MockWebKitContextProvider_Initialize_Call) Run(run func(ctx context.Context, opts port.WebKitContextOptions)) *MockWebKitContextProvider_Initialize_Call {
1284+ _c.Call.Run(func(args mock.Arguments) {
1285+ var arg0 context.Context
1286+ if args[0] != nil {
1287+ arg0 = args[0].(context.Context)
1288+ }
1289+ var arg1 port.WebKitContextOptions
1290+ if args[1] != nil {
1291+ arg1 = args[1].(port.WebKitContextOptions)
1292+ }
1293+ run(
1294+ arg0,
1295+ arg1,
1296+ )
1297+ })
1298+ return _c
1299+}
1300+
1301+func (_c *MockWebKitContextProvider_Initialize_Call) Return(err error) *MockWebKitContextProvider_Initialize_Call {
1302+ _c.Call.Return(err)
1303+ return _c
1304+}
1305+
1306+func (_c *MockWebKitContextProvider_Initialize_Call) RunAndReturn(run func(ctx context.Context, opts port.WebKitContextOptions) error) *MockWebKitContextProvider_Initialize_Call {
1307+ _c.Call.Return(run)
1308+ return _c
1309+}
1310+
1311+// PrefetchDNS provides a mock function for the type MockWebKitContextProvider
1312+func (_mock *MockWebKitContextProvider) PrefetchDNS(hostname string) {
1313+ _mock.Called(hostname)
1314+ return
1315+}
1316+
1317+// MockWebKitContextProvider_PrefetchDNS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrefetchDNS'
1318+type MockWebKitContextProvider_PrefetchDNS_Call struct {
1319+ *mock.Call
1320+}
1321+
1322+// PrefetchDNS is a helper method to define mock.On call
1323+// - hostname string
1324+func (_e *MockWebKitContextProvider_Expecter) PrefetchDNS(hostname interface{}) *MockWebKitContextProvider_PrefetchDNS_Call {
1325+ return &MockWebKitContextProvider_PrefetchDNS_Call{Call: _e.mock.On("PrefetchDNS", hostname)}
1326+}
1327+
1328+func (_c *MockWebKitContextProvider_PrefetchDNS_Call) Run(run func(hostname string)) *MockWebKitContextProvider_PrefetchDNS_Call {
1329+ _c.Call.Run(func(args mock.Arguments) {
1330+ var arg0 string
1331+ if args[0] != nil {
1332+ arg0 = args[0].(string)
1333+ }
1334+ run(
1335+ arg0,
1336+ )
1337+ })
1338+ return _c
1339+}
1340+
1341+func (_c *MockWebKitContextProvider_PrefetchDNS_Call) Return() *MockWebKitContextProvider_PrefetchDNS_Call {
1342+ _c.Call.Return()
1343+ return _c
1344+}
1345+
1346+func (_c *MockWebKitContextProvider_PrefetchDNS_Call) RunAndReturn(run func(hostname string)) *MockWebKitContextProvider_PrefetchDNS_Call {
1347+ _c.Run(run)
1348+ return _c
1349+}
1350+
9431351 // NewMockWebView creates a new instance of MockWebView. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
9441352 // The first argument is typically a *testing.T value.
9451353 func NewMockWebView(t interface {
@@ -940,6 +940,414 @@ func (_c *MockSessionSpawner_SpawnWithSession_Call) RunAndReturn(run func(sessio
940 return _c940 return _c
941 }941 }
942 942
943+// NewMockMemoryPressureApplier creates a new instance of MockMemoryPressureApplier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
944+// The first argument is typically a *testing.T value.
945+func NewMockMemoryPressureApplier(t interface {
946+ mock.TestingT
947+ Cleanup(func())
948+}) *MockMemoryPressureApplier {
949+ mock := &MockMemoryPressureApplier{}
950+ mock.Mock.Test(t)
951+
952+ t.Cleanup(func() { mock.AssertExpectations(t) })
953+
954+ return mock
955+}
956+
957+// MockMemoryPressureApplier is an autogenerated mock type for the MemoryPressureApplier type
958+type MockMemoryPressureApplier struct {
959+ mock.Mock
960+}
961+
962+type MockMemoryPressureApplier_Expecter struct {
963+ mock *mock.Mock
964+}
965+
966+func (_m *MockMemoryPressureApplier) EXPECT() *MockMemoryPressureApplier_Expecter {
967+ return &MockMemoryPressureApplier_Expecter{mock: &_m.Mock}
968+}
969+
970+// ApplyNetworkProcessSettings provides a mock function for the type MockMemoryPressureApplier
971+func (_mock *MockMemoryPressureApplier) ApplyNetworkProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) error {
972+ ret := _mock.Called(ctx, cfg)
973+
974+ if len(ret) == 0 {
975+ panic("no return value specified for ApplyNetworkProcessSettings")
976+ }
977+
978+ var r0 error
979+ if returnFunc, ok := ret.Get(0).(func(context.Context, *port.MemoryPressureConfig) error); ok {
980+ r0 = returnFunc(ctx, cfg)
981+ } else {
982+ r0 = ret.Error(0)
983+ }
984+ return r0
985+}
986+
987+// MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyNetworkProcessSettings'
988+type MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call struct {
989+ *mock.Call
990+}
991+
992+// ApplyNetworkProcessSettings is a helper method to define mock.On call
993+// - ctx context.Context
994+// - cfg *port.MemoryPressureConfig
995+func (_e *MockMemoryPressureApplier_Expecter) ApplyNetworkProcessSettings(ctx interface{}, cfg interface{}) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
996+ return &MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call{Call: _e.mock.On("ApplyNetworkProcessSettings", ctx, cfg)}
997+}
998+
999+func (_c *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call) Run(run func(ctx context.Context, cfg *port.MemoryPressureConfig)) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
1000+ _c.Call.Run(func(args mock.Arguments) {
1001+ var arg0 context.Context
1002+ if args[0] != nil {
1003+ arg0 = args[0].(context.Context)
1004+ }
1005+ var arg1 *port.MemoryPressureConfig
1006+ if args[1] != nil {
1007+ arg1 = args[1].(*port.MemoryPressureConfig)
1008+ }
1009+ run(
1010+ arg0,
1011+ arg1,
1012+ )
1013+ })
1014+ return _c
1015+}
1016+
1017+func (_c *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call) Return(err error) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
1018+ _c.Call.Return(err)
1019+ return _c
1020+}
1021+
1022+func (_c *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call) RunAndReturn(run func(ctx context.Context, cfg *port.MemoryPressureConfig) error) *MockMemoryPressureApplier_ApplyNetworkProcessSettings_Call {
1023+ _c.Call.Return(run)
1024+ return _c
1025+}
1026+
1027+// ApplyWebProcessSettings provides a mock function for the type MockMemoryPressureApplier
1028+func (_mock *MockMemoryPressureApplier) ApplyWebProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) (any, error) {
1029+ ret := _mock.Called(ctx, cfg)
1030+
1031+ if len(ret) == 0 {
1032+ panic("no return value specified for ApplyWebProcessSettings")
1033+ }
1034+
1035+ var r0 any
1036+ var r1 error
1037+ if returnFunc, ok := ret.Get(0).(func(context.Context, *port.MemoryPressureConfig) (any, error)); ok {
1038+ return returnFunc(ctx, cfg)
1039+ }
1040+ if returnFunc, ok := ret.Get(0).(func(context.Context, *port.MemoryPressureConfig) any); ok {
1041+ r0 = returnFunc(ctx, cfg)
1042+ } else {
1043+ if ret.Get(0) != nil {
1044+ r0 = ret.Get(0).(any)
1045+ }
1046+ }
1047+ if returnFunc, ok := ret.Get(1).(func(context.Context, *port.MemoryPressureConfig) error); ok {
1048+ r1 = returnFunc(ctx, cfg)
1049+ } else {
1050+ r1 = ret.Error(1)
1051+ }
1052+ return r0, r1
1053+}
1054+
1055+// MockMemoryPressureApplier_ApplyWebProcessSettings_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyWebProcessSettings'
1056+type MockMemoryPressureApplier_ApplyWebProcessSettings_Call struct {
1057+ *mock.Call
1058+}
1059+
1060+// ApplyWebProcessSettings is a helper method to define mock.On call
1061+// - ctx context.Context
1062+// - cfg *port.MemoryPressureConfig
1063+func (_e *MockMemoryPressureApplier_Expecter) ApplyWebProcessSettings(ctx interface{}, cfg interface{}) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1064+ return &MockMemoryPressureApplier_ApplyWebProcessSettings_Call{Call: _e.mock.On("ApplyWebProcessSettings", ctx, cfg)}
1065+}
1066+
1067+func (_c *MockMemoryPressureApplier_ApplyWebProcessSettings_Call) Run(run func(ctx context.Context, cfg *port.MemoryPressureConfig)) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1068+ _c.Call.Run(func(args mock.Arguments) {
1069+ var arg0 context.Context
1070+ if args[0] != nil {
1071+ arg0 = args[0].(context.Context)
1072+ }
1073+ var arg1 *port.MemoryPressureConfig
1074+ if args[1] != nil {
1075+ arg1 = args[1].(*port.MemoryPressureConfig)
1076+ }
1077+ run(
1078+ arg0,
1079+ arg1,
1080+ )
1081+ })
1082+ return _c
1083+}
1084+
1085+func (_c *MockMemoryPressureApplier_ApplyWebProcessSettings_Call) Return(v any, err error) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1086+ _c.Call.Return(v, err)
1087+ return _c
1088+}
1089+
1090+func (_c *MockMemoryPressureApplier_ApplyWebProcessSettings_Call) RunAndReturn(run func(ctx context.Context, cfg *port.MemoryPressureConfig) (any, error)) *MockMemoryPressureApplier_ApplyWebProcessSettings_Call {
1091+ _c.Call.Return(run)
1092+ return _c
1093+}
1094+
1095+// NewMockWebKitContextProvider creates a new instance of MockWebKitContextProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
1096+// The first argument is typically a *testing.T value.
1097+func NewMockWebKitContextProvider(t interface {
1098+ mock.TestingT
1099+ Cleanup(func())
1100+}) *MockWebKitContextProvider {
1101+ mock := &MockWebKitContextProvider{}
1102+ mock.Mock.Test(t)
1103+
1104+ t.Cleanup(func() { mock.AssertExpectations(t) })
1105+
1106+ return mock
1107+}
1108+
1109+// MockWebKitContextProvider is an autogenerated mock type for the WebKitContextProvider type
1110+type MockWebKitContextProvider struct {
1111+ mock.Mock
1112+}
1113+
1114+type MockWebKitContextProvider_Expecter struct {
1115+ mock *mock.Mock
1116+}
1117+
1118+func (_m *MockWebKitContextProvider) EXPECT() *MockWebKitContextProvider_Expecter {
1119+ return &MockWebKitContextProvider_Expecter{mock: &_m.Mock}
1120+}
1121+
1122+// CacheDir provides a mock function for the type MockWebKitContextProvider
1123+func (_mock *MockWebKitContextProvider) CacheDir() string {
1124+ ret := _mock.Called()
1125+
1126+ if len(ret) == 0 {
1127+ panic("no return value specified for CacheDir")
1128+ }
1129+
1130+ var r0 string
1131+ if returnFunc, ok := ret.Get(0).(func() string); ok {
1132+ r0 = returnFunc()
1133+ } else {
1134+ r0 = ret.Get(0).(string)
1135+ }
1136+ return r0
1137+}
1138+
1139+// MockWebKitContextProvider_CacheDir_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheDir'
1140+type MockWebKitContextProvider_CacheDir_Call struct {
1141+ *mock.Call
1142+}
1143+
1144+// CacheDir is a helper method to define mock.On call
1145+func (_e *MockWebKitContextProvider_Expecter) CacheDir() *MockWebKitContextProvider_CacheDir_Call {
1146+ return &MockWebKitContextProvider_CacheDir_Call{Call: _e.mock.On("CacheDir")}
1147+}
1148+
1149+func (_c *MockWebKitContextProvider_CacheDir_Call) Run(run func()) *MockWebKitContextProvider_CacheDir_Call {
1150+ _c.Call.Run(func(args mock.Arguments) {
1151+ run()
1152+ })
1153+ return _c
1154+}
1155+
1156+func (_c *MockWebKitContextProvider_CacheDir_Call) Return(s string) *MockWebKitContextProvider_CacheDir_Call {
1157+ _c.Call.Return(s)
1158+ return _c
1159+}
1160+
1161+func (_c *MockWebKitContextProvider_CacheDir_Call) RunAndReturn(run func() string) *MockWebKitContextProvider_CacheDir_Call {
1162+ _c.Call.Return(run)
1163+ return _c
1164+}
1165+
1166+// Close provides a mock function for the type MockWebKitContextProvider
1167+func (_mock *MockWebKitContextProvider) Close() error {
1168+ ret := _mock.Called()
1169+
1170+ if len(ret) == 0 {
1171+ panic("no return value specified for Close")
1172+ }
1173+
1174+ var r0 error
1175+ if returnFunc, ok := ret.Get(0).(func() error); ok {
1176+ r0 = returnFunc()
1177+ } else {
1178+ r0 = ret.Error(0)
1179+ }
1180+ return r0
1181+}
1182+
1183+// MockWebKitContextProvider_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
1184+type MockWebKitContextProvider_Close_Call struct {
1185+ *mock.Call
1186+}
1187+
1188+// Close is a helper method to define mock.On call
1189+func (_e *MockWebKitContextProvider_Expecter) Close() *MockWebKitContextProvider_Close_Call {
1190+ return &MockWebKitContextProvider_Close_Call{Call: _e.mock.On("Close")}
1191+}
1192+
1193+func (_c *MockWebKitContextProvider_Close_Call) Run(run func()) *MockWebKitContextProvider_Close_Call {
1194+ _c.Call.Run(func(args mock.Arguments) {
1195+ run()
1196+ })
1197+ return _c
1198+}
1199+
1200+func (_c *MockWebKitContextProvider_Close_Call) Return(err error) *MockWebKitContextProvider_Close_Call {
1201+ _c.Call.Return(err)
1202+ return _c
1203+}
1204+
1205+func (_c *MockWebKitContextProvider_Close_Call) RunAndReturn(run func() error) *MockWebKitContextProvider_Close_Call {
1206+ _c.Call.Return(run)
1207+ return _c
1208+}
1209+
1210+// DataDir provides a mock function for the type MockWebKitContextProvider
1211+func (_mock *MockWebKitContextProvider) DataDir() string {
1212+ ret := _mock.Called()
1213+
1214+ if len(ret) == 0 {
1215+ panic("no return value specified for DataDir")
1216+ }
1217+
1218+ var r0 string
1219+ if returnFunc, ok := ret.Get(0).(func() string); ok {
1220+ r0 = returnFunc()
1221+ } else {
1222+ r0 = ret.Get(0).(string)
1223+ }
1224+ return r0
1225+}
1226+
1227+// MockWebKitContextProvider_DataDir_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DataDir'
1228+type MockWebKitContextProvider_DataDir_Call struct {
1229+ *mock.Call
1230+}
1231+
1232+// DataDir is a helper method to define mock.On call
1233+func (_e *MockWebKitContextProvider_Expecter) DataDir() *MockWebKitContextProvider_DataDir_Call {
1234+ return &MockWebKitContextProvider_DataDir_Call{Call: _e.mock.On("DataDir")}
1235+}
1236+
1237+func (_c *MockWebKitContextProvider_DataDir_Call) Run(run func()) *MockWebKitContextProvider_DataDir_Call {
1238+ _c.Call.Run(func(args mock.Arguments) {
1239+ run()
1240+ })
1241+ return _c
1242+}
1243+
1244+func (_c *MockWebKitContextProvider_DataDir_Call) Return(s string) *MockWebKitContextProvider_DataDir_Call {
1245+ _c.Call.Return(s)
1246+ return _c
1247+}
1248+
1249+func (_c *MockWebKitContextProvider_DataDir_Call) RunAndReturn(run func() string) *MockWebKitContextProvider_DataDir_Call {
1250+ _c.Call.Return(run)
1251+ return _c
1252+}
1253+
1254+// Initialize provides a mock function for the type MockWebKitContextProvider
1255+func (_mock *MockWebKitContextProvider) Initialize(ctx context.Context, opts port.WebKitContextOptions) error {
1256+ ret := _mock.Called(ctx, opts)
1257+
1258+ if len(ret) == 0 {
1259+ panic("no return value specified for Initialize")
1260+ }
1261+
1262+ var r0 error
1263+ if returnFunc, ok := ret.Get(0).(func(context.Context, port.WebKitContextOptions) error); ok {
1264+ r0 = returnFunc(ctx, opts)
1265+ } else {
1266+ r0 = ret.Error(0)
1267+ }
1268+ return r0
1269+}
1270+
1271+// MockWebKitContextProvider_Initialize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Initialize'
1272+type MockWebKitContextProvider_Initialize_Call struct {
1273+ *mock.Call
1274+}
1275+
1276+// Initialize is a helper method to define mock.On call
1277+// - ctx context.Context
1278+// - opts port.WebKitContextOptions
1279+func (_e *MockWebKitContextProvider_Expecter) Initialize(ctx interface{}, opts interface{}) *MockWebKitContextProvider_Initialize_Call {
1280+ return &MockWebKitContextProvider_Initialize_Call{Call: _e.mock.On("Initialize", ctx, opts)}
1281+}
1282+
1283+func (_c *MockWebKitContextProvider_Initialize_Call) Run(run func(ctx context.Context, opts port.WebKitContextOptions)) *MockWebKitContextProvider_Initialize_Call {
1284+ _c.Call.Run(func(args mock.Arguments) {
1285+ var arg0 context.Context
1286+ if args[0] != nil {
1287+ arg0 = args[0].(context.Context)
1288+ }
1289+ var arg1 port.WebKitContextOptions
1290+ if args[1] != nil {
1291+ arg1 = args[1].(port.WebKitContextOptions)
1292+ }
1293+ run(
1294+ arg0,
1295+ arg1,
1296+ )
1297+ })
1298+ return _c
1299+}
1300+
1301+func (_c *MockWebKitContextProvider_Initialize_Call) Return(err error) *MockWebKitContextProvider_Initialize_Call {
1302+ _c.Call.Return(err)
1303+ return _c
1304+}
1305+
1306+func (_c *MockWebKitContextProvider_Initialize_Call) RunAndReturn(run func(ctx context.Context, opts port.WebKitContextOptions) error) *MockWebKitContextProvider_Initialize_Call {
1307+ _c.Call.Return(run)
1308+ return _c
1309+}
1310+
1311+// PrefetchDNS provides a mock function for the type MockWebKitContextProvider
1312+func (_mock *MockWebKitContextProvider) PrefetchDNS(hostname string) {
1313+ _mock.Called(hostname)
1314+ return
1315+}
1316+
1317+// MockWebKitContextProvider_PrefetchDNS_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrefetchDNS'
1318+type MockWebKitContextProvider_PrefetchDNS_Call struct {
1319+ *mock.Call
1320+}
1321+
1322+// PrefetchDNS is a helper method to define mock.On call
1323+// - hostname string
1324+func (_e *MockWebKitContextProvider_Expecter) PrefetchDNS(hostname interface{}) *MockWebKitContextProvider_PrefetchDNS_Call {
1325+ return &MockWebKitContextProvider_PrefetchDNS_Call{Call: _e.mock.On("PrefetchDNS", hostname)}
1326+}
1327+
1328+func (_c *MockWebKitContextProvider_PrefetchDNS_Call) Run(run func(hostname string)) *MockWebKitContextProvider_PrefetchDNS_Call {
1329+ _c.Call.Run(func(args mock.Arguments) {
1330+ var arg0 string
1331+ if args[0] != nil {
1332+ arg0 = args[0].(string)
1333+ }
1334+ run(
1335+ arg0,
1336+ )
1337+ })
1338+ return _c
1339+}
1340+
1341+func (_c *MockWebKitContextProvider_PrefetchDNS_Call) Return() *MockWebKitContextProvider_PrefetchDNS_Call {
1342+ _c.Call.Return()
1343+ return _c
1344+}
1345+
1346+func (_c *MockWebKitContextProvider_PrefetchDNS_Call) RunAndReturn(run func(hostname string)) *MockWebKitContextProvider_PrefetchDNS_Call {
1347+ _c.Run(run)
1348+ return _c
1349+}
1350+
943 // NewMockWebView creates a new instance of MockWebView. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.1351 // NewMockWebView creates a new instance of MockWebView. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
944 // The first argument is typically a *testing.T value.1352 // The first argument is typically a *testing.T value.
945 func NewMockWebView(t interface {1353 func NewMockWebView(t interface {
M internal/application/port/rendering_env.go +10 -0
@@ -46,6 +46,16 @@ type RenderingEnvSettings struct {
4646 ShowFPS bool
4747 SampleMemory bool
4848 DebugFrames bool
49+
50+ // --- Skia rendering thread settings (from PerformanceConfig) ---
51+ // SkiaCPUPaintingThreads sets WEBKIT_SKIA_CPU_PAINTING_THREADS.
52+ // 0 means unset (use WebKit default).
53+ SkiaCPUPaintingThreads int
54+ // SkiaGPUPaintingThreads sets WEBKIT_SKIA_GPU_PAINTING_THREADS.
55+ // -1 means unset; 0 disables GPU tile painting.
56+ SkiaGPUPaintingThreads int
57+ // SkiaEnableCPURendering forces CPU rendering via WEBKIT_SKIA_ENABLE_CPU_RENDERING=1.
58+ SkiaEnableCPURendering bool
4959 }
5060
5161 // RenderingEnvManager configures rendering environment variables
@@ -46,6 +46,16 @@ type RenderingEnvSettings struct {
46 ShowFPS bool46 ShowFPS bool
47 SampleMemory bool47 SampleMemory bool
48 DebugFrames bool48 DebugFrames bool
49+
50+ // --- Skia rendering thread settings (from PerformanceConfig) ---
51+ // SkiaCPUPaintingThreads sets WEBKIT_SKIA_CPU_PAINTING_THREADS.
52+ // 0 means unset (use WebKit default).
53+ SkiaCPUPaintingThreads int
54+ // SkiaGPUPaintingThreads sets WEBKIT_SKIA_GPU_PAINTING_THREADS.
55+ // -1 means unset; 0 disables GPU tile painting.
56+ SkiaGPUPaintingThreads int
57+ // SkiaEnableCPURendering forces CPU rendering via WEBKIT_SKIA_ENABLE_CPU_RENDERING=1.
58+ SkiaEnableCPURendering bool
49 }59 }
50 60
51 // RenderingEnvManager configures rendering environment variables61 // RenderingEnvManager configures rendering environment variables
A internal/application/port/webkit_context.go +101 -0
@@ -0,0 +1,101 @@
1+// Package port defines interfaces for external dependencies.
2+package port
3+
4+import "context"
5+
6+// MemoryPressureConfig holds memory pressure settings for a WebKit process.
7+// Zero values mean "use WebKit defaults" except for KillThreshold where -1 means unset.
8+type MemoryPressureConfig struct {
9+ // MemoryLimitMB sets the memory limit in megabytes.
10+ // 0 means unset (uses WebKit default: system RAM capped at 3GB).
11+ MemoryLimitMB int
12+
13+ // PollIntervalSec sets the interval for memory usage checks.
14+ // 0 means unset (uses WebKit default: 30 seconds).
15+ PollIntervalSec float64
16+
17+ // ConservativeThreshold sets threshold for conservative memory release.
18+ // Must be in (0, 1). 0 means unset (uses WebKit default: 0.33).
19+ ConservativeThreshold float64
20+
21+ // StrictThreshold sets threshold for strict memory release.
22+ // Must be in (0, 1). 0 means unset (uses WebKit default: 0.5).
23+ StrictThreshold float64
24+
25+ // KillThreshold sets threshold for killing the process.
26+ // -1 means unset; 0 means never kill; values > 1 are allowed by WebKit.
27+ KillThreshold float64
28+}
29+
30+// IsConfigured returns true if any memory pressure setting is configured.
31+func (c *MemoryPressureConfig) IsConfigured() bool {
32+ if c == nil {
33+ return false
34+ }
35+ return c.MemoryLimitMB > 0 ||
36+ c.PollIntervalSec > 0 ||
37+ c.ConservativeThreshold > 0 ||
38+ c.StrictThreshold > 0 ||
39+ c.KillThreshold >= 0
40+}
41+
42+// WebKitContextOptions configures WebKitContext creation.
43+type WebKitContextOptions struct {
44+ // DataDir is the directory for persistent data (cookies, storage, etc.).
45+ DataDir string
46+
47+ // CacheDir is the directory for cache data.
48+ CacheDir string
49+
50+ // WebProcessMemory configures memory pressure for web processes.
51+ // nil means use WebKit defaults.
52+ WebProcessMemory *MemoryPressureConfig
53+
54+ // NetworkProcessMemory configures memory pressure for the network process.
55+ // nil means use WebKit defaults.
56+ NetworkProcessMemory *MemoryPressureConfig
57+}
58+
59+// IsWebProcessMemoryConfigured returns true if web process memory settings are configured.
60+func (o *WebKitContextOptions) IsWebProcessMemoryConfigured() bool {
61+ return o.WebProcessMemory.IsConfigured()
62+}
63+
64+// IsNetworkProcessMemoryConfigured returns true if network process memory settings are configured.
65+func (o *WebKitContextOptions) IsNetworkProcessMemoryConfigured() bool {
66+ return o.NetworkProcessMemory.IsConfigured()
67+}
68+
69+// MemoryPressureApplier applies memory pressure settings to WebKit processes.
70+// This interface abstracts the WebKit-specific memory pressure configuration
71+// to allow testing without WebKit dependencies.
72+type MemoryPressureApplier interface {
73+ // ApplyNetworkProcessSettings applies memory pressure settings to the network process.
74+ // Must be called BEFORE creating any NetworkSession.
75+ ApplyNetworkProcessSettings(ctx context.Context, cfg *MemoryPressureConfig) error
76+
77+ // ApplyWebProcessSettings applies memory pressure settings to web processes.
78+ // Returns an opaque settings object that should be passed to WebContext creation.
79+ // Returns nil if no settings are configured.
80+ ApplyWebProcessSettings(ctx context.Context, cfg *MemoryPressureConfig) (any, error)
81+}
82+
83+// WebKitContextProvider creates and manages WebKit contexts.
84+// This interface abstracts WebKit context creation for testability.
85+type WebKitContextProvider interface {
86+ // Initialize sets up the WebKit context with the given options.
87+ // Must be called before any other methods.
88+ Initialize(ctx context.Context, opts WebKitContextOptions) error
89+
90+ // DataDir returns the data directory path.
91+ DataDir() string
92+
93+ // CacheDir returns the cache directory path.
94+ CacheDir() string
95+
96+ // PrefetchDNS prefetches DNS for the given hostname.
97+ PrefetchDNS(hostname string)
98+
99+ // Close releases resources held by the context.
100+ Close() error
101+}
@@ -0,0 +1,101 @@
1+// Package port defines interfaces for external dependencies.
2+package port
3+
4+import "context"
5+
6+// MemoryPressureConfig holds memory pressure settings for a WebKit process.
7+// Zero values mean "use WebKit defaults" except for KillThreshold where -1 means unset.
8+type MemoryPressureConfig struct {
9+ // MemoryLimitMB sets the memory limit in megabytes.
10+ // 0 means unset (uses WebKit default: system RAM capped at 3GB).
11+ MemoryLimitMB int
12+
13+ // PollIntervalSec sets the interval for memory usage checks.
14+ // 0 means unset (uses WebKit default: 30 seconds).
15+ PollIntervalSec float64
16+
17+ // ConservativeThreshold sets threshold for conservative memory release.
18+ // Must be in (0, 1). 0 means unset (uses WebKit default: 0.33).
19+ ConservativeThreshold float64
20+
21+ // StrictThreshold sets threshold for strict memory release.
22+ // Must be in (0, 1). 0 means unset (uses WebKit default: 0.5).
23+ StrictThreshold float64
24+
25+ // KillThreshold sets threshold for killing the process.
26+ // -1 means unset; 0 means never kill; values > 1 are allowed by WebKit.
27+ KillThreshold float64
28+}
29+
30+// IsConfigured returns true if any memory pressure setting is configured.
31+func (c *MemoryPressureConfig) IsConfigured() bool {
32+ if c == nil {
33+ return false
34+ }
35+ return c.MemoryLimitMB > 0 ||
36+ c.PollIntervalSec > 0 ||
37+ c.ConservativeThreshold > 0 ||
38+ c.StrictThreshold > 0 ||
39+ c.KillThreshold >= 0
40+}
41+
42+// WebKitContextOptions configures WebKitContext creation.
43+type WebKitContextOptions struct {
44+ // DataDir is the directory for persistent data (cookies, storage, etc.).
45+ DataDir string
46+
47+ // CacheDir is the directory for cache data.
48+ CacheDir string
49+
50+ // WebProcessMemory configures memory pressure for web processes.
51+ // nil means use WebKit defaults.
52+ WebProcessMemory *MemoryPressureConfig
53+
54+ // NetworkProcessMemory configures memory pressure for the network process.
55+ // nil means use WebKit defaults.
56+ NetworkProcessMemory *MemoryPressureConfig
57+}
58+
59+// IsWebProcessMemoryConfigured returns true if web process memory settings are configured.
60+func (o *WebKitContextOptions) IsWebProcessMemoryConfigured() bool {
61+ return o.WebProcessMemory.IsConfigured()
62+}
63+
64+// IsNetworkProcessMemoryConfigured returns true if network process memory settings are configured.
65+func (o *WebKitContextOptions) IsNetworkProcessMemoryConfigured() bool {
66+ return o.NetworkProcessMemory.IsConfigured()
67+}
68+
69+// MemoryPressureApplier applies memory pressure settings to WebKit processes.
70+// This interface abstracts the WebKit-specific memory pressure configuration
71+// to allow testing without WebKit dependencies.
72+type MemoryPressureApplier interface {
73+ // ApplyNetworkProcessSettings applies memory pressure settings to the network process.
74+ // Must be called BEFORE creating any NetworkSession.
75+ ApplyNetworkProcessSettings(ctx context.Context, cfg *MemoryPressureConfig) error
76+
77+ // ApplyWebProcessSettings applies memory pressure settings to web processes.
78+ // Returns an opaque settings object that should be passed to WebContext creation.
79+ // Returns nil if no settings are configured.
80+ ApplyWebProcessSettings(ctx context.Context, cfg *MemoryPressureConfig) (any, error)
81+}
82+
83+// WebKitContextProvider creates and manages WebKit contexts.
84+// This interface abstracts WebKit context creation for testability.
85+type WebKitContextProvider interface {
86+ // Initialize sets up the WebKit context with the given options.
87+ // Must be called before any other methods.
88+ Initialize(ctx context.Context, opts WebKitContextOptions) error
89+
90+ // DataDir returns the data directory path.
91+ DataDir() string
92+
93+ // CacheDir returns the cache directory path.
94+ CacheDir() string
95+
96+ // PrefetchDNS prefetches DNS for the given hostname.
97+ PrefetchDNS(hostname string)
98+
99+ // Close releases resources held by the context.
100+ Close() error
101+}
A internal/application/port/webkit_context_test.go +185 -0
@@ -0,0 +1,185 @@
1+package port_test
2+
3+import (
4+ "testing"
5+
6+ "github.com/bnema/dumber/internal/application/port"
7+ "github.com/stretchr/testify/assert"
8+)
9+
10+func TestMemoryPressureConfig_IsConfigured(t *testing.T) {
11+ tests := []struct {
12+ name string
13+ config *port.MemoryPressureConfig
14+ expected bool
15+ }{
16+ {
17+ name: "nil config",
18+ config: nil,
19+ expected: false,
20+ },
21+ {
22+ name: "empty config with default kill threshold",
23+ config: &port.MemoryPressureConfig{
24+ KillThreshold: -1, // explicit unset
25+ },
26+ expected: false,
27+ },
28+ {
29+ name: "only MemoryLimitMB set",
30+ config: &port.MemoryPressureConfig{
31+ MemoryLimitMB: 1024,
32+ },
33+ expected: true,
34+ },
35+ {
36+ name: "only PollIntervalSec set",
37+ config: &port.MemoryPressureConfig{
38+ PollIntervalSec: 30.0,
39+ },
40+ expected: true,
41+ },
42+ {
43+ name: "only ConservativeThreshold set",
44+ config: &port.MemoryPressureConfig{
45+ ConservativeThreshold: 0.33,
46+ },
47+ expected: true,
48+ },
49+ {
50+ name: "only StrictThreshold set",
51+ config: &port.MemoryPressureConfig{
52+ StrictThreshold: 0.5,
53+ },
54+ expected: true,
55+ },
56+ {
57+ name: "KillThreshold set to 0 (never kill)",
58+ config: &port.MemoryPressureConfig{
59+ KillThreshold: 0,
60+ },
61+ expected: true,
62+ },
63+ {
64+ name: "KillThreshold set to positive value",
65+ config: &port.MemoryPressureConfig{
66+ KillThreshold: 1.5,
67+ },
68+ expected: true,
69+ },
70+ {
71+ name: "KillThreshold unset (-1)",
72+ config: &port.MemoryPressureConfig{
73+ KillThreshold: -1,
74+ },
75+ expected: false,
76+ },
77+ {
78+ name: "all defaults (unset)",
79+ config: &port.MemoryPressureConfig{
80+ MemoryLimitMB: 0,
81+ PollIntervalSec: 0,
82+ ConservativeThreshold: 0,
83+ StrictThreshold: 0,
84+ KillThreshold: -1,
85+ },
86+ expected: false,
87+ },
88+ {
89+ name: "fully configured",
90+ config: &port.MemoryPressureConfig{
91+ MemoryLimitMB: 2048,
92+ PollIntervalSec: 15.0,
93+ ConservativeThreshold: 0.25,
94+ StrictThreshold: 0.6,
95+ KillThreshold: 0.9,
96+ },
97+ expected: true,
98+ },
99+ }
100+
101+ for _, tt := range tests {
102+ t.Run(tt.name, func(t *testing.T) {
103+ result := tt.config.IsConfigured()
104+ assert.Equal(t, tt.expected, result)
105+ })
106+ }
107+}
108+
109+func TestWebKitContextOptions_IsWebProcessMemoryConfigured(t *testing.T) {
110+ tests := []struct {
111+ name string
112+ opts port.WebKitContextOptions
113+ expected bool
114+ }{
115+ {
116+ name: "nil WebProcessMemory",
117+ opts: port.WebKitContextOptions{},
118+ expected: false,
119+ },
120+ {
121+ name: "empty WebProcessMemory with default kill threshold",
122+ opts: port.WebKitContextOptions{
123+ WebProcessMemory: &port.MemoryPressureConfig{
124+ KillThreshold: -1, // explicit unset
125+ },
126+ },
127+ expected: false,
128+ },
129+ {
130+ name: "configured WebProcessMemory",
131+ opts: port.WebKitContextOptions{
132+ WebProcessMemory: &port.MemoryPressureConfig{
133+ MemoryLimitMB: 1024,
134+ },
135+ },
136+ expected: true,
137+ },
138+ }
139+
140+ for _, tt := range tests {
141+ t.Run(tt.name, func(t *testing.T) {
142+ result := tt.opts.IsWebProcessMemoryConfigured()
143+ assert.Equal(t, tt.expected, result)
144+ })
145+ }
146+}
147+
148+func TestWebKitContextOptions_IsNetworkProcessMemoryConfigured(t *testing.T) {
149+ tests := []struct {
150+ name string
151+ opts port.WebKitContextOptions
152+ expected bool
153+ }{
154+ {
155+ name: "nil NetworkProcessMemory",
156+ opts: port.WebKitContextOptions{},
157+ expected: false,
158+ },
159+ {
160+ name: "empty NetworkProcessMemory with default kill threshold",
161+ opts: port.WebKitContextOptions{
162+ NetworkProcessMemory: &port.MemoryPressureConfig{
163+ KillThreshold: -1, // explicit unset
164+ },
165+ },
166+ expected: false,
167+ },
168+ {
169+ name: "configured NetworkProcessMemory",
170+ opts: port.WebKitContextOptions{
171+ NetworkProcessMemory: &port.MemoryPressureConfig{
172+ PollIntervalSec: 20.0,
173+ },
174+ },
175+ expected: true,
176+ },
177+ }
178+
179+ for _, tt := range tests {
180+ t.Run(tt.name, func(t *testing.T) {
181+ result := tt.opts.IsNetworkProcessMemoryConfigured()
182+ assert.Equal(t, tt.expected, result)
183+ })
184+ }
185+}
@@ -0,0 +1,185 @@
1+package port_test
2+
3+import (
4+ "testing"
5+
6+ "github.com/bnema/dumber/internal/application/port"
7+ "github.com/stretchr/testify/assert"
8+)
9+
10+func TestMemoryPressureConfig_IsConfigured(t *testing.T) {
11+ tests := []struct {
12+ name string
13+ config *port.MemoryPressureConfig
14+ expected bool
15+ }{
16+ {
17+ name: "nil config",
18+ config: nil,
19+ expected: false,
20+ },
21+ {
22+ name: "empty config with default kill threshold",
23+ config: &port.MemoryPressureConfig{
24+ KillThreshold: -1, // explicit unset
25+ },
26+ expected: false,
27+ },
28+ {
29+ name: "only MemoryLimitMB set",
30+ config: &port.MemoryPressureConfig{
31+ MemoryLimitMB: 1024,
32+ },
33+ expected: true,
34+ },
35+ {
36+ name: "only PollIntervalSec set",
37+ config: &port.MemoryPressureConfig{
38+ PollIntervalSec: 30.0,
39+ },
40+ expected: true,
41+ },
42+ {
43+ name: "only ConservativeThreshold set",
44+ config: &port.MemoryPressureConfig{
45+ ConservativeThreshold: 0.33,
46+ },
47+ expected: true,
48+ },
49+ {
50+ name: "only StrictThreshold set",
51+ config: &port.MemoryPressureConfig{
52+ StrictThreshold: 0.5,
53+ },
54+ expected: true,
55+ },
56+ {
57+ name: "KillThreshold set to 0 (never kill)",
58+ config: &port.MemoryPressureConfig{
59+ KillThreshold: 0,
60+ },
61+ expected: true,
62+ },
63+ {
64+ name: "KillThreshold set to positive value",
65+ config: &port.MemoryPressureConfig{
66+ KillThreshold: 1.5,
67+ },
68+ expected: true,
69+ },
70+ {
71+ name: "KillThreshold unset (-1)",
72+ config: &port.MemoryPressureConfig{
73+ KillThreshold: -1,
74+ },
75+ expected: false,
76+ },
77+ {
78+ name: "all defaults (unset)",
79+ config: &port.MemoryPressureConfig{
80+ MemoryLimitMB: 0,
81+ PollIntervalSec: 0,
82+ ConservativeThreshold: 0,
83+ StrictThreshold: 0,
84+ KillThreshold: -1,
85+ },
86+ expected: false,
87+ },
88+ {
89+ name: "fully configured",
90+ config: &port.MemoryPressureConfig{
91+ MemoryLimitMB: 2048,
92+ PollIntervalSec: 15.0,
93+ ConservativeThreshold: 0.25,
94+ StrictThreshold: 0.6,
95+ KillThreshold: 0.9,
96+ },
97+ expected: true,
98+ },
99+ }
100+
101+ for _, tt := range tests {
102+ t.Run(tt.name, func(t *testing.T) {
103+ result := tt.config.IsConfigured()
104+ assert.Equal(t, tt.expected, result)
105+ })
106+ }
107+}
108+
109+func TestWebKitContextOptions_IsWebProcessMemoryConfigured(t *testing.T) {
110+ tests := []struct {
111+ name string
112+ opts port.WebKitContextOptions
113+ expected bool
114+ }{
115+ {
116+ name: "nil WebProcessMemory",
117+ opts: port.WebKitContextOptions{},
118+ expected: false,
119+ },
120+ {
121+ name: "empty WebProcessMemory with default kill threshold",
122+ opts: port.WebKitContextOptions{
123+ WebProcessMemory: &port.MemoryPressureConfig{
124+ KillThreshold: -1, // explicit unset
125+ },
126+ },
127+ expected: false,
128+ },
129+ {
130+ name: "configured WebProcessMemory",
131+ opts: port.WebKitContextOptions{
132+ WebProcessMemory: &port.MemoryPressureConfig{
133+ MemoryLimitMB: 1024,
134+ },
135+ },
136+ expected: true,
137+ },
138+ }
139+
140+ for _, tt := range tests {
141+ t.Run(tt.name, func(t *testing.T) {
142+ result := tt.opts.IsWebProcessMemoryConfigured()
143+ assert.Equal(t, tt.expected, result)
144+ })
145+ }
146+}
147+
148+func TestWebKitContextOptions_IsNetworkProcessMemoryConfigured(t *testing.T) {
149+ tests := []struct {
150+ name string
151+ opts port.WebKitContextOptions
152+ expected bool
153+ }{
154+ {
155+ name: "nil NetworkProcessMemory",
156+ opts: port.WebKitContextOptions{},
157+ expected: false,
158+ },
159+ {
160+ name: "empty NetworkProcessMemory with default kill threshold",
161+ opts: port.WebKitContextOptions{
162+ NetworkProcessMemory: &port.MemoryPressureConfig{
163+ KillThreshold: -1, // explicit unset
164+ },
165+ },
166+ expected: false,
167+ },
168+ {
169+ name: "configured NetworkProcessMemory",
170+ opts: port.WebKitContextOptions{
171+ NetworkProcessMemory: &port.MemoryPressureConfig{
172+ PollIntervalSec: 20.0,
173+ },
174+ },
175+ expected: true,
176+ },
177+ }
178+
179+ for _, tt := range tests {
180+ t.Run(tt.name, func(t *testing.T) {
181+ result := tt.opts.IsNetworkProcessMemoryConfigured()
182+ assert.Equal(t, tt.expected, result)
183+ })
184+ }
185+}
M internal/infrastructure/env/manager.go +18 -0
@@ -54,6 +54,7 @@ func (m *Manager) ApplyEnvironment(ctx context.Context, settings port.RenderingE
5454 m.applyWebKitEnv(settings)
5555 m.applyGTKEnv(settings)
5656 m.applyDebugEnv(settings)
57+ m.applySkiaEnv(settings)
5758
5859 log.Debug().
5960 Interface("vars", m.appliedVars).
@@ -162,6 +163,23 @@ func (m *Manager) applyDebugEnv(settings port.RenderingEnvSettings) {
162163 }
163164 }
164165
166+func (m *Manager) applySkiaEnv(settings port.RenderingEnvSettings) {
167+ // CPU painting threads: only set if > 0 and env var not already set
168+ if settings.SkiaCPUPaintingThreads > 0 && os.Getenv("WEBKIT_SKIA_CPU_PAINTING_THREADS") == "" {
169+ m.setEnv("WEBKIT_SKIA_CPU_PAINTING_THREADS", fmt.Sprintf("%d", settings.SkiaCPUPaintingThreads))
170+ }
171+
172+ // GPU painting threads: only set if >= 0 (0 is valid: disables GPU tile painting)
173+ if settings.SkiaGPUPaintingThreads >= 0 && os.Getenv("WEBKIT_SKIA_GPU_PAINTING_THREADS") == "" {
174+ m.setEnv("WEBKIT_SKIA_GPU_PAINTING_THREADS", fmt.Sprintf("%d", settings.SkiaGPUPaintingThreads))
175+ }
176+
177+ // CPU rendering: only set if true and env var not already set
178+ if settings.SkiaEnableCPURendering && os.Getenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING") == "" {
179+ m.setEnv("WEBKIT_SKIA_ENABLE_CPU_RENDERING", "1")
180+ }
181+}
182+
165183 func (m *Manager) setEnv(key, value string) {
166184 _ = os.Setenv(key, value)
167185 m.appliedVars[key] = value
@@ -54,6 +54,7 @@ func (m *Manager) ApplyEnvironment(ctx context.Context, settings port.RenderingE
54 m.applyWebKitEnv(settings)54 m.applyWebKitEnv(settings)
55 m.applyGTKEnv(settings)55 m.applyGTKEnv(settings)
56 m.applyDebugEnv(settings)56 m.applyDebugEnv(settings)
57+ m.applySkiaEnv(settings)
57 58
58 log.Debug().59 log.Debug().
59 Interface("vars", m.appliedVars).60 Interface("vars", m.appliedVars).
@@ -162,6 +163,23 @@ func (m *Manager) applyDebugEnv(settings port.RenderingEnvSettings) {
162 }163 }
163 }164 }
164 165
166+func (m *Manager) applySkiaEnv(settings port.RenderingEnvSettings) {
167+ // CPU painting threads: only set if > 0 and env var not already set
168+ if settings.SkiaCPUPaintingThreads > 0 && os.Getenv("WEBKIT_SKIA_CPU_PAINTING_THREADS") == "" {
169+ m.setEnv("WEBKIT_SKIA_CPU_PAINTING_THREADS", fmt.Sprintf("%d", settings.SkiaCPUPaintingThreads))
170+ }
171+
172+ // GPU painting threads: only set if >= 0 (0 is valid: disables GPU tile painting)
173+ if settings.SkiaGPUPaintingThreads >= 0 && os.Getenv("WEBKIT_SKIA_GPU_PAINTING_THREADS") == "" {
174+ m.setEnv("WEBKIT_SKIA_GPU_PAINTING_THREADS", fmt.Sprintf("%d", settings.SkiaGPUPaintingThreads))
175+ }
176+
177+ // CPU rendering: only set if true and env var not already set
178+ if settings.SkiaEnableCPURendering && os.Getenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING") == "" {
179+ m.setEnv("WEBKIT_SKIA_ENABLE_CPU_RENDERING", "1")
180+ }
181+}
182+
165 func (m *Manager) setEnv(key, value string) {183 func (m *Manager) setEnv(key, value string) {
166 _ = os.Setenv(key, value)184 _ = os.Setenv(key, value)
167 m.appliedVars[key] = value185 m.appliedVars[key] = value
A internal/infrastructure/env/manager_test.go +172 -0
@@ -0,0 +1,172 @@
1+package env_test
2+
3+import (
4+ "context"
5+ "os"
6+ "testing"
7+
8+ "github.com/bnema/dumber/internal/application/port"
9+ "github.com/bnema/dumber/internal/infrastructure/env"
10+ "github.com/stretchr/testify/assert"
11+ "github.com/stretchr/testify/require"
12+)
13+
14+func TestManager_ApplyEnvironment_SkiaEnv(t *testing.T) {
15+ // Save original env vars and restore after test
16+ origCPU := os.Getenv("WEBKIT_SKIA_CPU_PAINTING_THREADS")
17+ origGPU := os.Getenv("WEBKIT_SKIA_GPU_PAINTING_THREADS")
18+ origCPURender := os.Getenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING")
19+ t.Cleanup(func() {
20+ restoreEnv("WEBKIT_SKIA_CPU_PAINTING_THREADS", origCPU)
21+ restoreEnv("WEBKIT_SKIA_GPU_PAINTING_THREADS", origGPU)
22+ restoreEnv("WEBKIT_SKIA_ENABLE_CPU_RENDERING", origCPURender)
23+ })
24+
25+ tests := []struct {
26+ name string
27+ settings port.RenderingEnvSettings
28+ presetEnv map[string]string
29+ expectedVars map[string]string
30+ unexpectedVars []string
31+ }{
32+ {
33+ name: "CPU threads set when > 0",
34+ settings: port.RenderingEnvSettings{
35+ SkiaCPUPaintingThreads: 4,
36+ SkiaGPUPaintingThreads: -1, // unset
37+ },
38+ expectedVars: map[string]string{
39+ "WEBKIT_SKIA_CPU_PAINTING_THREADS": "4",
40+ },
41+ unexpectedVars: []string{"WEBKIT_SKIA_GPU_PAINTING_THREADS"},
42+ },
43+ {
44+ name: "GPU threads set when >= 0",
45+ settings: port.RenderingEnvSettings{
46+ SkiaCPUPaintingThreads: 0, // unset
47+ SkiaGPUPaintingThreads: 2,
48+ },
49+ expectedVars: map[string]string{
50+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "2",
51+ },
52+ unexpectedVars: []string{"WEBKIT_SKIA_CPU_PAINTING_THREADS"},
53+ },
54+ {
55+ name: "GPU threads 0 disables GPU tile painting",
56+ settings: port.RenderingEnvSettings{
57+ SkiaGPUPaintingThreads: 0,
58+ },
59+ expectedVars: map[string]string{
60+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "0",
61+ },
62+ },
63+ {
64+ name: "CPU rendering enabled",
65+ settings: port.RenderingEnvSettings{
66+ SkiaEnableCPURendering: true,
67+ },
68+ expectedVars: map[string]string{
69+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING": "1",
70+ },
71+ },
72+ {
73+ name: "CPU rendering disabled (default)",
74+ settings: port.RenderingEnvSettings{
75+ SkiaEnableCPURendering: false,
76+ },
77+ unexpectedVars: []string{"WEBKIT_SKIA_ENABLE_CPU_RENDERING"},
78+ },
79+ {
80+ name: "does not override existing CPU threads env var",
81+ settings: port.RenderingEnvSettings{
82+ SkiaCPUPaintingThreads: 8,
83+ },
84+ presetEnv: map[string]string{
85+ "WEBKIT_SKIA_CPU_PAINTING_THREADS": "2",
86+ },
87+ expectedVars: map[string]string{
88+ "WEBKIT_SKIA_CPU_PAINTING_THREADS": "2", // keeps original
89+ },
90+ },
91+ {
92+ name: "does not override existing GPU threads env var",
93+ settings: port.RenderingEnvSettings{
94+ SkiaGPUPaintingThreads: 4,
95+ },
96+ presetEnv: map[string]string{
97+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "1",
98+ },
99+ expectedVars: map[string]string{
100+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "1", // keeps original
101+ },
102+ },
103+ {
104+ name: "does not override existing CPU rendering env var",
105+ settings: port.RenderingEnvSettings{
106+ SkiaEnableCPURendering: true,
107+ },
108+ presetEnv: map[string]string{
109+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING": "0",
110+ },
111+ expectedVars: map[string]string{
112+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING": "0", // keeps original
113+ },
114+ },
115+ {
116+ name: "all unset by default",
117+ settings: port.RenderingEnvSettings{
118+ SkiaCPUPaintingThreads: 0, // unset
119+ SkiaGPUPaintingThreads: -1, // unset
120+ SkiaEnableCPURendering: false,
121+ },
122+ unexpectedVars: []string{
123+ "WEBKIT_SKIA_CPU_PAINTING_THREADS",
124+ "WEBKIT_SKIA_GPU_PAINTING_THREADS",
125+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING",
126+ },
127+ },
128+ }
129+
130+ for _, tt := range tests {
131+ t.Run(tt.name, func(t *testing.T) {
132+ // Clear env vars before each test
133+ os.Unsetenv("WEBKIT_SKIA_CPU_PAINTING_THREADS")
134+ os.Unsetenv("WEBKIT_SKIA_GPU_PAINTING_THREADS")
135+ os.Unsetenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING")
136+
137+ // Set any preset env vars
138+ for k, v := range tt.presetEnv {
139+ os.Setenv(k, v)
140+ }
141+
142+ manager := env.NewManager()
143+ ctx := context.Background()
144+
145+ err := manager.ApplyEnvironment(ctx, tt.settings)
146+ require.NoError(t, err)
147+
148+ // Check expected vars are set correctly
149+ for k, v := range tt.expectedVars {
150+ actual := os.Getenv(k)
151+ assert.Equal(t, v, actual, "env var %s", k)
152+ }
153+
154+ // Check unexpected vars are not set (or were not set by us)
155+ appliedVars := manager.GetAppliedVars()
156+ for _, k := range tt.unexpectedVars {
157+ _, wasApplied := appliedVars[k]
158+ if tt.presetEnv[k] == "" {
159+ assert.False(t, wasApplied, "env var %s should not be applied", k)
160+ }
161+ }
162+ })
163+ }
164+}
165+
166+func restoreEnv(key, value string) {
167+ if value == "" {
168+ os.Unsetenv(key)
169+ } else {
170+ os.Setenv(key, value)
171+ }
172+}
@@ -0,0 +1,172 @@
1+package env_test
2+
3+import (
4+ "context"
5+ "os"
6+ "testing"
7+
8+ "github.com/bnema/dumber/internal/application/port"
9+ "github.com/bnema/dumber/internal/infrastructure/env"
10+ "github.com/stretchr/testify/assert"
11+ "github.com/stretchr/testify/require"
12+)
13+
14+func TestManager_ApplyEnvironment_SkiaEnv(t *testing.T) {
15+ // Save original env vars and restore after test
16+ origCPU := os.Getenv("WEBKIT_SKIA_CPU_PAINTING_THREADS")
17+ origGPU := os.Getenv("WEBKIT_SKIA_GPU_PAINTING_THREADS")
18+ origCPURender := os.Getenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING")
19+ t.Cleanup(func() {
20+ restoreEnv("WEBKIT_SKIA_CPU_PAINTING_THREADS", origCPU)
21+ restoreEnv("WEBKIT_SKIA_GPU_PAINTING_THREADS", origGPU)
22+ restoreEnv("WEBKIT_SKIA_ENABLE_CPU_RENDERING", origCPURender)
23+ })
24+
25+ tests := []struct {
26+ name string
27+ settings port.RenderingEnvSettings
28+ presetEnv map[string]string
29+ expectedVars map[string]string
30+ unexpectedVars []string
31+ }{
32+ {
33+ name: "CPU threads set when > 0",
34+ settings: port.RenderingEnvSettings{
35+ SkiaCPUPaintingThreads: 4,
36+ SkiaGPUPaintingThreads: -1, // unset
37+ },
38+ expectedVars: map[string]string{
39+ "WEBKIT_SKIA_CPU_PAINTING_THREADS": "4",
40+ },
41+ unexpectedVars: []string{"WEBKIT_SKIA_GPU_PAINTING_THREADS"},
42+ },
43+ {
44+ name: "GPU threads set when >= 0",
45+ settings: port.RenderingEnvSettings{
46+ SkiaCPUPaintingThreads: 0, // unset
47+ SkiaGPUPaintingThreads: 2,
48+ },
49+ expectedVars: map[string]string{
50+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "2",
51+ },
52+ unexpectedVars: []string{"WEBKIT_SKIA_CPU_PAINTING_THREADS"},
53+ },
54+ {
55+ name: "GPU threads 0 disables GPU tile painting",
56+ settings: port.RenderingEnvSettings{
57+ SkiaGPUPaintingThreads: 0,
58+ },
59+ expectedVars: map[string]string{
60+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "0",
61+ },
62+ },
63+ {
64+ name: "CPU rendering enabled",
65+ settings: port.RenderingEnvSettings{
66+ SkiaEnableCPURendering: true,
67+ },
68+ expectedVars: map[string]string{
69+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING": "1",
70+ },
71+ },
72+ {
73+ name: "CPU rendering disabled (default)",
74+ settings: port.RenderingEnvSettings{
75+ SkiaEnableCPURendering: false,
76+ },
77+ unexpectedVars: []string{"WEBKIT_SKIA_ENABLE_CPU_RENDERING"},
78+ },
79+ {
80+ name: "does not override existing CPU threads env var",
81+ settings: port.RenderingEnvSettings{
82+ SkiaCPUPaintingThreads: 8,
83+ },
84+ presetEnv: map[string]string{
85+ "WEBKIT_SKIA_CPU_PAINTING_THREADS": "2",
86+ },
87+ expectedVars: map[string]string{
88+ "WEBKIT_SKIA_CPU_PAINTING_THREADS": "2", // keeps original
89+ },
90+ },
91+ {
92+ name: "does not override existing GPU threads env var",
93+ settings: port.RenderingEnvSettings{
94+ SkiaGPUPaintingThreads: 4,
95+ },
96+ presetEnv: map[string]string{
97+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "1",
98+ },
99+ expectedVars: map[string]string{
100+ "WEBKIT_SKIA_GPU_PAINTING_THREADS": "1", // keeps original
101+ },
102+ },
103+ {
104+ name: "does not override existing CPU rendering env var",
105+ settings: port.RenderingEnvSettings{
106+ SkiaEnableCPURendering: true,
107+ },
108+ presetEnv: map[string]string{
109+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING": "0",
110+ },
111+ expectedVars: map[string]string{
112+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING": "0", // keeps original
113+ },
114+ },
115+ {
116+ name: "all unset by default",
117+ settings: port.RenderingEnvSettings{
118+ SkiaCPUPaintingThreads: 0, // unset
119+ SkiaGPUPaintingThreads: -1, // unset
120+ SkiaEnableCPURendering: false,
121+ },
122+ unexpectedVars: []string{
123+ "WEBKIT_SKIA_CPU_PAINTING_THREADS",
124+ "WEBKIT_SKIA_GPU_PAINTING_THREADS",
125+ "WEBKIT_SKIA_ENABLE_CPU_RENDERING",
126+ },
127+ },
128+ }
129+
130+ for _, tt := range tests {
131+ t.Run(tt.name, func(t *testing.T) {
132+ // Clear env vars before each test
133+ os.Unsetenv("WEBKIT_SKIA_CPU_PAINTING_THREADS")
134+ os.Unsetenv("WEBKIT_SKIA_GPU_PAINTING_THREADS")
135+ os.Unsetenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING")
136+
137+ // Set any preset env vars
138+ for k, v := range tt.presetEnv {
139+ os.Setenv(k, v)
140+ }
141+
142+ manager := env.NewManager()
143+ ctx := context.Background()
144+
145+ err := manager.ApplyEnvironment(ctx, tt.settings)
146+ require.NoError(t, err)
147+
148+ // Check expected vars are set correctly
149+ for k, v := range tt.expectedVars {
150+ actual := os.Getenv(k)
151+ assert.Equal(t, v, actual, "env var %s", k)
152+ }
153+
154+ // Check unexpected vars are not set (or were not set by us)
155+ appliedVars := manager.GetAppliedVars()
156+ for _, k := range tt.unexpectedVars {
157+ _, wasApplied := appliedVars[k]
158+ if tt.presetEnv[k] == "" {
159+ assert.False(t, wasApplied, "env var %s should not be applied", k)
160+ }
161+ }
162+ })
163+ }
164+}
165+
166+func restoreEnv(key, value string) {
167+ if value == "" {
168+ os.Unsetenv(key)
169+ } else {
170+ os.Setenv(key, value)
171+ }
172+}
M internal/infrastructure/webkit/context.go +64 -9
@@ -6,6 +6,7 @@ import (
66 "path/filepath"
77 "sync"
88
9+ "github.com/bnema/dumber/internal/application/port"
910 "github.com/bnema/dumber/internal/logging"
1011 "github.com/bnema/puregotk-webkit/webkit"
1112 "github.com/rs/zerolog"
@@ -34,31 +35,66 @@ type WebKitContext struct {
3435 // The dataDir and cacheDir are used for cookie storage, cache, and other persistent data.
3536 // This MUST be called before creating any WebViews to ensure they use persistent storage.
3637 func NewWebKitContext(ctx context.Context, dataDir, cacheDir string) (*WebKitContext, error) {
38+ return NewWebKitContextWithOptions(ctx, port.WebKitContextOptions{
39+ DataDir: dataDir,
40+ CacheDir: cacheDir,
41+ })
42+}
43+
44+// NewWebKitContextWithOptions creates and initializes a WebKitContext with the given options.
45+// This allows configuring memory pressure settings for web and network processes.
46+func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOptions) (*WebKitContext, error) {
3747 log := logging.FromContext(ctx).With().Str("component", "webkit-context").Logger()
3848
39- if dataDir == "" {
49+ if opts.DataDir == "" {
4050 return nil, fmt.Errorf("data directory cannot be empty")
4151 }
42- if cacheDir == "" {
52+ if opts.CacheDir == "" {
4353 return nil, fmt.Errorf("cache directory cannot be empty")
4454 }
4555
4656 wkCtx := &WebKitContext{
47- dataDir: dataDir,
48- cacheDir: cacheDir,
57+ dataDir: opts.DataDir,
58+ cacheDir: opts.CacheDir,
4959 logger: log,
5060 }
5161
62+ // Apply network process memory pressure settings BEFORE creating session
63+ // Per WebKitGTK docs: must be called before creating any NetworkSession
64+ if opts.IsNetworkProcessMemoryConfigured() {
65+ applier := NewMemoryPressureApplier()
66+ if err := applier.ApplyNetworkProcessSettings(ctx, opts.NetworkProcessMemory); err != nil {
67+ log.Warn().Err(err).Msg("failed to apply network process memory pressure settings")
68+ }
69+ }
70+
5271 // Create persistent network session FIRST
5372 // Per WebKitGTK 6.0 docs: "The first WebKitNetworkSession created becomes the default"
5473 if err := wkCtx.initNetworkSession(); err != nil {
5574 return nil, fmt.Errorf("failed to init network session: %w", err)
5675 }
5776
58- // Get or create WebContext
59- wkCtx.webContext = webkit.WebContextGetDefault()
77+ // Create WebContext - with memory pressure settings if configured
78+ if opts.IsWebProcessMemoryConfigured() {
79+ applier := NewMemoryPressureApplier()
80+ settings, err := applier.ApplyWebProcessSettings(ctx, opts.WebProcessMemory)
81+ if err != nil {
82+ log.Warn().Err(err).Msg("failed to prepare web process memory pressure settings")
83+ }
84+ if settings != nil {
85+ wkCtx.webContext = createWebContextWithMemorySettings(settings.(*webkit.MemoryPressureSettings))
86+ if wkCtx.webContext != nil {
87+ log.Debug().Msg("created WebContext with memory pressure settings")
88+ }
89+ }
90+ }
91+
92+ // Fall back to default WebContext if not created above
6093 if wkCtx.webContext == nil {
61- wkCtx.webContext = webkit.NewWebContext()
94+ wkCtx.webContext = webkit.WebContextGetDefault()
95+ if wkCtx.webContext == nil {
96+ wkCtx.webContext = webkit.NewWebContext()
97+ }
6298 }
6399 if wkCtx.webContext == nil {
64100 return nil, fmt.Errorf("failed to get or create WebContext")
@@ -69,13 +105,32 @@ func NewWebKitContext(ctx context.Context, dataDir, cacheDir string) (*WebKitCon
69105
70106 wkCtx.initialized = true
71107 log.Info().
72- Str("data_dir", dataDir).
73- Str("cache_dir", cacheDir).
108+ Str("data_dir", opts.DataDir).
109+ Str("cache_dir", opts.CacheDir).
74110 Msg("webkit context initialized")
75111
76112 return wkCtx, nil
77113 }
78114
115+// createWebContextWithMemorySettings creates a WebContext with memory-pressure-settings.
116+// The memory-pressure-settings property is construct-only, so we need to set it at creation time.
117+func createWebContextWithMemorySettings(settings *webkit.MemoryPressureSettings) *webkit.WebContext {
118+ if settings == nil {
119+ return nil
120+ }
121+
122+ // Create a new WebContext and set memory pressure settings
123+ // Note: memory-pressure-settings is construct-only, but we try setting it via property
124+ // as an alternative to g_object_new_with_properties which may not work reliably
125+ ctx := webkit.NewWebContext()
126+ if ctx != nil {
127+ // Try to set the property - this may not work for construct-only properties
128+ // but it's the best we can do without lower-level GObject access
129+ ctx.SetPropertyMemoryPressureSettings(settings.GoPointer())
130+ }
131+ return ctx
132+}
133+
79134 // initNetworkSession creates and configures the persistent network session.
80135 func (c *WebKitContext) initNetworkSession() error {
81136 // Create persistent network session
@@ -6,6 +6,7 @@ import (
6 "path/filepath"6 "path/filepath"
7 "sync"7 "sync"
8 8
9+ "github.com/bnema/dumber/internal/application/port"
9 "github.com/bnema/dumber/internal/logging"10 "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/puregotk-webkit/webkit"11 "github.com/bnema/puregotk-webkit/webkit"
11 "github.com/rs/zerolog"12 "github.com/rs/zerolog"
@@ -34,31 +35,66 @@ type WebKitContext struct {
34 // The dataDir and cacheDir are used for cookie storage, cache, and other persistent data.35 // The dataDir and cacheDir are used for cookie storage, cache, and other persistent data.
35 // This MUST be called before creating any WebViews to ensure they use persistent storage.36 // This MUST be called before creating any WebViews to ensure they use persistent storage.
36 func NewWebKitContext(ctx context.Context, dataDir, cacheDir string) (*WebKitContext, error) {37 func NewWebKitContext(ctx context.Context, dataDir, cacheDir string) (*WebKitContext, error) {
38+ return NewWebKitContextWithOptions(ctx, port.WebKitContextOptions{
39+ DataDir: dataDir,
40+ CacheDir: cacheDir,
41+ })
42+}
43+
44+// NewWebKitContextWithOptions creates and initializes a WebKitContext with the given options.
45+// This allows configuring memory pressure settings for web and network processes.
46+func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOptions) (*WebKitContext, error) {
37 log := logging.FromContext(ctx).With().Str("component", "webkit-context").Logger()47 log := logging.FromContext(ctx).With().Str("component", "webkit-context").Logger()
38 48
39- if dataDir == "" {49+ if opts.DataDir == "" {
40 return nil, fmt.Errorf("data directory cannot be empty")50 return nil, fmt.Errorf("data directory cannot be empty")
41 }51 }
42- if cacheDir == "" {52+ if opts.CacheDir == "" {
43 return nil, fmt.Errorf("cache directory cannot be empty")53 return nil, fmt.Errorf("cache directory cannot be empty")
44 }54 }
45 55
46 wkCtx := &WebKitContext{56 wkCtx := &WebKitContext{
47- dataDir: dataDir,57+ dataDir: opts.DataDir,
48- cacheDir: cacheDir,58+ cacheDir: opts.CacheDir,
49 logger: log,59 logger: log,
50 }60 }
51 61
62+ // Apply network process memory pressure settings BEFORE creating session
63+ // Per WebKitGTK docs: must be called before creating any NetworkSession
64+ if opts.IsNetworkProcessMemoryConfigured() {
65+ applier := NewMemoryPressureApplier()
66+ if err := applier.ApplyNetworkProcessSettings(ctx, opts.NetworkProcessMemory); err != nil {
67+ log.Warn().Err(err).Msg("failed to apply network process memory pressure settings")
68+ }
69+ }
70+
52 // Create persistent network session FIRST71 // Create persistent network session FIRST
53 // Per WebKitGTK 6.0 docs: "The first WebKitNetworkSession created becomes the default"72 // Per WebKitGTK 6.0 docs: "The first WebKitNetworkSession created becomes the default"
54 if err := wkCtx.initNetworkSession(); err != nil {73 if err := wkCtx.initNetworkSession(); err != nil {
55 return nil, fmt.Errorf("failed to init network session: %w", err)74 return nil, fmt.Errorf("failed to init network session: %w", err)
56 }75 }
57 76
58- // Get or create WebContext77+ // Create WebContext - with memory pressure settings if configured
59- wkCtx.webContext = webkit.WebContextGetDefault()78+ if opts.IsWebProcessMemoryConfigured() {
79+ applier := NewMemoryPressureApplier()
80+ settings, err := applier.ApplyWebProcessSettings(ctx, opts.WebProcessMemory)
81+ if err != nil {
82+ log.Warn().Err(err).Msg("failed to prepare web process memory pressure settings")
83+ }
84+ if settings != nil {
85+ wkCtx.webContext = createWebContextWithMemorySettings(settings.(*webkit.MemoryPressureSettings))
86+ if wkCtx.webContext != nil {
87+ log.Debug().Msg("created WebContext with memory pressure settings")
88+ }
89+ }
90+ }
91+
92+ // Fall back to default WebContext if not created above
60 if wkCtx.webContext == nil {93 if wkCtx.webContext == nil {
61- wkCtx.webContext = webkit.NewWebContext()94+ wkCtx.webContext = webkit.WebContextGetDefault()
95+ if wkCtx.webContext == nil {
96+ wkCtx.webContext = webkit.NewWebContext()
97+ }
62 }98 }
63 if wkCtx.webContext == nil {99 if wkCtx.webContext == nil {
64 return nil, fmt.Errorf("failed to get or create WebContext")100 return nil, fmt.Errorf("failed to get or create WebContext")
@@ -69,13 +105,32 @@ func NewWebKitContext(ctx context.Context, dataDir, cacheDir string) (*WebKitCon
69 105
70 wkCtx.initialized = true106 wkCtx.initialized = true
71 log.Info().107 log.Info().
72- Str("data_dir", dataDir).108+ Str("data_dir", opts.DataDir).
73- Str("cache_dir", cacheDir).109+ Str("cache_dir", opts.CacheDir).
74 Msg("webkit context initialized")110 Msg("webkit context initialized")
75 111
76 return wkCtx, nil112 return wkCtx, nil
77 }113 }
78 114
115+// createWebContextWithMemorySettings creates a WebContext with memory-pressure-settings.
116+// The memory-pressure-settings property is construct-only, so we need to set it at creation time.
117+func createWebContextWithMemorySettings(settings *webkit.MemoryPressureSettings) *webkit.WebContext {
118+ if settings == nil {
119+ return nil
120+ }
121+
122+ // Create a new WebContext and set memory pressure settings
123+ // Note: memory-pressure-settings is construct-only, but we try setting it via property
124+ // as an alternative to g_object_new_with_properties which may not work reliably
125+ ctx := webkit.NewWebContext()
126+ if ctx != nil {
127+ // Try to set the property - this may not work for construct-only properties
128+ // but it's the best we can do without lower-level GObject access
129+ ctx.SetPropertyMemoryPressureSettings(settings.GoPointer())
130+ }
131+ return ctx
132+}
133+
79 // initNetworkSession creates and configures the persistent network session.134 // initNetworkSession creates and configures the persistent network session.
80 func (c *WebKitContext) initNetworkSession() error {135 func (c *WebKitContext) initNetworkSession() error {
81 // Create persistent network session136 // Create persistent network session
A internal/infrastructure/webkit/memory_pressure.go +108 -0
@@ -0,0 +1,108 @@
1+package webkit
2+
3+import (
4+ "context"
5+
6+ "github.com/bnema/dumber/internal/application/port"
7+ "github.com/bnema/dumber/internal/logging"
8+ "github.com/bnema/puregotk-webkit/webkit"
9+)
10+
11+// MemoryPressureApplier implements port.MemoryPressureApplier for WebKitGTK.
12+type MemoryPressureApplier struct{}
13+
14+// NewMemoryPressureApplier creates a new MemoryPressureApplier.
15+func NewMemoryPressureApplier() *MemoryPressureApplier {
16+ return &MemoryPressureApplier{}
17+}
18+
19+// ApplyNetworkProcessSettings applies memory pressure settings to the network process.
20+// Must be called BEFORE creating any NetworkSession.
21+func (*MemoryPressureApplier) ApplyNetworkProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) error {
22+ log := logging.FromContext(ctx)
23+
24+ if cfg == nil || !cfg.IsConfigured() {
25+ log.Debug().Msg("no network process memory pressure settings configured")
26+ return nil
27+ }
28+
29+ settings := buildMemoryPressureSettings(cfg)
30+ if settings == nil {
31+ return nil
32+ }
33+
34+ webkit.NetworkSessionSetMemoryPressureSettings(settings)
35+ log.Info().
36+ Int("limit_mb", cfg.MemoryLimitMB).
37+ Float64("poll_sec", cfg.PollIntervalSec).
38+ Float64("conservative", cfg.ConservativeThreshold).
39+ Float64("strict", cfg.StrictThreshold).
40+ Float64("kill", cfg.KillThreshold).
41+ Msg("applied network process memory pressure settings")
42+
43+ return nil
44+}
45+
46+// ApplyWebProcessSettings applies memory pressure settings to web processes.
47+// Returns an opaque settings object that should be passed to WebContext creation.
48+// Returns nil if no settings are configured.
49+func (*MemoryPressureApplier) ApplyWebProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) (any, error) {
50+ log := logging.FromContext(ctx)
51+
52+ if cfg == nil || !cfg.IsConfigured() {
53+ log.Debug().Msg("no web process memory pressure settings configured")
54+ return nil, nil
55+ }
56+
57+ settings := buildMemoryPressureSettings(cfg)
58+ if settings == nil {
59+ return nil, nil
60+ }
61+
62+ log.Info().
63+ Int("limit_mb", cfg.MemoryLimitMB).
64+ Float64("poll_sec", cfg.PollIntervalSec).
65+ Float64("conservative", cfg.ConservativeThreshold).
66+ Float64("strict", cfg.StrictThreshold).
67+ Float64("kill", cfg.KillThreshold).
68+ Msg("prepared web process memory pressure settings")
69+
70+ return settings, nil
71+}
72+
73+// buildMemoryPressureSettings creates WebKitMemoryPressureSettings from config.
74+// Returns nil if no settings are configured.
75+func buildMemoryPressureSettings(cfg *port.MemoryPressureConfig) *webkit.MemoryPressureSettings {
76+ if cfg == nil || !cfg.IsConfigured() {
77+ return nil
78+ }
79+
80+ settings := webkit.NewMemoryPressureSettings()
81+
82+ if cfg.MemoryLimitMB > 0 {
83+ settings.SetMemoryLimit(uint(cfg.MemoryLimitMB))
84+ }
85+
86+ if cfg.PollIntervalSec > 0 {
87+ settings.SetPollInterval(cfg.PollIntervalSec)
88+ }
89+
90+ // Thresholds must be in (0, 1)
91+ if cfg.ConservativeThreshold > 0 && cfg.ConservativeThreshold < 1 {
92+ settings.SetConservativeThreshold(cfg.ConservativeThreshold)
93+ }
94+
95+ if cfg.StrictThreshold > 0 && cfg.StrictThreshold < 1 {
96+ settings.SetStrictThreshold(cfg.StrictThreshold)
97+ }
98+
99+ // Kill threshold: -1 = unset, 0 = never kill, >0 = threshold
100+ if cfg.KillThreshold >= 0 {
101+ settings.SetKillThreshold(cfg.KillThreshold)
102+ }
103+
104+ return settings
105+}
106+
107+// Ensure MemoryPressureApplier implements the interface.
108+var _ port.MemoryPressureApplier = (*MemoryPressureApplier)(nil)
@@ -0,0 +1,108 @@
1+package webkit
2+
3+import (
4+ "context"
5+
6+ "github.com/bnema/dumber/internal/application/port"
7+ "github.com/bnema/dumber/internal/logging"
8+ "github.com/bnema/puregotk-webkit/webkit"
9+)
10+
11+// MemoryPressureApplier implements port.MemoryPressureApplier for WebKitGTK.
12+type MemoryPressureApplier struct{}
13+
14+// NewMemoryPressureApplier creates a new MemoryPressureApplier.
15+func NewMemoryPressureApplier() *MemoryPressureApplier {
16+ return &MemoryPressureApplier{}
17+}
18+
19+// ApplyNetworkProcessSettings applies memory pressure settings to the network process.
20+// Must be called BEFORE creating any NetworkSession.
21+func (*MemoryPressureApplier) ApplyNetworkProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) error {
22+ log := logging.FromContext(ctx)
23+
24+ if cfg == nil || !cfg.IsConfigured() {
25+ log.Debug().Msg("no network process memory pressure settings configured")
26+ return nil
27+ }
28+
29+ settings := buildMemoryPressureSettings(cfg)
30+ if settings == nil {
31+ return nil
32+ }
33+
34+ webkit.NetworkSessionSetMemoryPressureSettings(settings)
35+ log.Info().
36+ Int("limit_mb", cfg.MemoryLimitMB).
37+ Float64("poll_sec", cfg.PollIntervalSec).
38+ Float64("conservative", cfg.ConservativeThreshold).
39+ Float64("strict", cfg.StrictThreshold).
40+ Float64("kill", cfg.KillThreshold).
41+ Msg("applied network process memory pressure settings")
42+
43+ return nil
44+}
45+
46+// ApplyWebProcessSettings applies memory pressure settings to web processes.
47+// Returns an opaque settings object that should be passed to WebContext creation.
48+// Returns nil if no settings are configured.
49+func (*MemoryPressureApplier) ApplyWebProcessSettings(ctx context.Context, cfg *port.MemoryPressureConfig) (any, error) {
50+ log := logging.FromContext(ctx)
51+
52+ if cfg == nil || !cfg.IsConfigured() {
53+ log.Debug().Msg("no web process memory pressure settings configured")
54+ return nil, nil
55+ }
56+
57+ settings := buildMemoryPressureSettings(cfg)
58+ if settings == nil {
59+ return nil, nil
60+ }
61+
62+ log.Info().
63+ Int("limit_mb", cfg.MemoryLimitMB).
64+ Float64("poll_sec", cfg.PollIntervalSec).
65+ Float64("conservative", cfg.ConservativeThreshold).
66+ Float64("strict", cfg.StrictThreshold).
67+ Float64("kill", cfg.KillThreshold).
68+ Msg("prepared web process memory pressure settings")
69+
70+ return settings, nil
71+}
72+
73+// buildMemoryPressureSettings creates WebKitMemoryPressureSettings from config.
74+// Returns nil if no settings are configured.
75+func buildMemoryPressureSettings(cfg *port.MemoryPressureConfig) *webkit.MemoryPressureSettings {
76+ if cfg == nil || !cfg.IsConfigured() {
77+ return nil
78+ }
79+
80+ settings := webkit.NewMemoryPressureSettings()
81+
82+ if cfg.MemoryLimitMB > 0 {
83+ settings.SetMemoryLimit(uint(cfg.MemoryLimitMB))
84+ }
85+
86+ if cfg.PollIntervalSec > 0 {
87+ settings.SetPollInterval(cfg.PollIntervalSec)
88+ }
89+
90+ // Thresholds must be in (0, 1)
91+ if cfg.ConservativeThreshold > 0 && cfg.ConservativeThreshold < 1 {
92+ settings.SetConservativeThreshold(cfg.ConservativeThreshold)
93+ }
94+
95+ if cfg.StrictThreshold > 0 && cfg.StrictThreshold < 1 {
96+ settings.SetStrictThreshold(cfg.StrictThreshold)
97+ }
98+
99+ // Kill threshold: -1 = unset, 0 = never kill, >0 = threshold
100+ if cfg.KillThreshold >= 0 {
101+ settings.SetKillThreshold(cfg.KillThreshold)
102+ }
103+
104+ return settings
105+}
106+
107+// Ensure MemoryPressureApplier implements the interface.
108+var _ port.MemoryPressureApplier = (*MemoryPressureApplier)(nil)

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