fix(webkit): keep runtime settings updates typed and atomic
- commit
- e7434fb89f0c1f78e5211e581f8efdaf5a365f3b
- author
- brice · · 2026-06-23 16:10 UTC
- committer
- brice · · 2026-06-23 20:01 UTC
- parent
- 9634a01
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +161 -5
M internal/application/port/engine.go +1 -0
| @@ -162,6 +162,7 @@ type EngineWebContentSettingsPayload struct { | |||
| 162 | 162 | CaptureConsole bool | |
| 163 | 163 | DrawCompositingIndicators bool | |
| 164 | 164 | HardwareDecoding EngineHardwareDecodingMode | |
| 165 | + | AutoCopyOnSelection bool | |
| 165 | 166 | } | |
| 166 | 167 | ||
| 167 | 168 | // EngineSettingsPayload is the engine-facing boundary view of runtime config. | |
| @@ -162,6 +162,7 @@ type EngineWebContentSettingsPayload struct { | |||||
| 162 | CaptureConsole bool | 162 | CaptureConsole bool | ||
| 163 | DrawCompositingIndicators bool | 163 | DrawCompositingIndicators bool | ||
| 164 | HardwareDecoding EngineHardwareDecodingMode | 164 | HardwareDecoding EngineHardwareDecodingMode | ||
| 165 | + | AutoCopyOnSelection bool | |||
| 165 | } | 166 | } | ||
| 166 | 167 | ||||
| 167 | // EngineSettingsPayload is the engine-facing boundary view of runtime config. | 168 | // EngineSettingsPayload is the engine-facing boundary view of runtime config. | ||
M internal/application/port/engine_settings_test.go +1 -0
| @@ -22,6 +22,7 @@ func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | |||
| 22 | 22 | "CaptureConsole", | |
| 23 | 23 | "DrawCompositingIndicators", | |
| 24 | 24 | "HardwareDecoding", | |
| 25 | + | "AutoCopyOnSelection", | |
| 25 | 26 | } { | |
| 26 | 27 | if _, ok := webContentType.FieldByName(field); !ok { | |
| 27 | 28 | t.Fatalf("EngineWebContentSettingsPayload missing %s", field) | |
| @@ -22,6 +22,7 @@ func TestEngineSettingsPayloadContainsRuntimeWebContentFields(t *testing.T) { | |||||
| 22 | "CaptureConsole", | 22 | "CaptureConsole", | ||
| 23 | "DrawCompositingIndicators", | 23 | "DrawCompositingIndicators", | ||
| 24 | "HardwareDecoding", | 24 | "HardwareDecoding", | ||
| 25 | + | "AutoCopyOnSelection", | |||
| 25 | } { | 26 | } { | ||
| 26 | if _, ok := webContentType.FieldByName(field); !ok { | 27 | if _, ok := webContentType.FieldByName(field); !ok { | ||
| 27 | t.Fatalf("EngineWebContentSettingsPayload missing %s", field) | 28 | t.Fatalf("EngineWebContentSettingsPayload missing %s", field) | ||
M internal/bootstrap/runtime_config.go +1 -0
| @@ -65,6 +65,7 @@ func EngineSettingsPayloadFromConfig(cfg *config.Config) port.EngineSettingsPayl | |||
| 65 | 65 | CaptureConsole: cfg.Logging.CaptureConsole, | |
| 66 | 66 | DrawCompositingIndicators: cfg.Engine.WebKit.DrawCompositingIndicators, | |
| 67 | 67 | HardwareDecoding: engineHardwareDecodingModeFromConfig(cfg.Media.HardwareDecodingMode), | |
| 68 | + | AutoCopyOnSelection: cfg.Clipboard.AutoCopyOnSelection, | |
| 68 | 69 | }, | |
| 69 | 70 | } | |
| 70 | 71 | } | |
| @@ -65,6 +65,7 @@ func EngineSettingsPayloadFromConfig(cfg *config.Config) port.EngineSettingsPayl | |||||
| 65 | CaptureConsole: cfg.Logging.CaptureConsole, | 65 | CaptureConsole: cfg.Logging.CaptureConsole, | ||
| 66 | DrawCompositingIndicators: cfg.Engine.WebKit.DrawCompositingIndicators, | 66 | DrawCompositingIndicators: cfg.Engine.WebKit.DrawCompositingIndicators, | ||
| 67 | HardwareDecoding: engineHardwareDecodingModeFromConfig(cfg.Media.HardwareDecodingMode), | 67 | HardwareDecoding: engineHardwareDecodingModeFromConfig(cfg.Media.HardwareDecodingMode), | ||
| 68 | + | AutoCopyOnSelection: cfg.Clipboard.AutoCopyOnSelection, | |||
| 68 | }, | 69 | }, | ||
| 69 | } | 70 | } | ||
| 70 | } | 71 | } | ||
M internal/bootstrap/runtime_config_test.go +5 -0
| @@ -58,6 +58,7 @@ func TestEngineSettingsPayloadFromConfigMapsRuntimeFields(t *testing.T) { | |||
| 58 | 58 | cfg.Logging.CaptureConsole = true | |
| 59 | 59 | cfg.Engine.WebKit.DrawCompositingIndicators = true | |
| 60 | 60 | cfg.Media.HardwareDecodingMode = config.HardwareDecodingForce | |
| 61 | + | cfg.Clipboard.AutoCopyOnSelection = true | |
| 61 | 62 | ||
| 62 | 63 | got := EngineSettingsPayloadFromConfig(cfg) | |
| 63 | 64 | ||
| @@ -78,6 +79,10 @@ func TestEngineSettingsPayloadFromConfigMapsRuntimeFields(t *testing.T) { | |||
| 78 | 79 | if got.WebContent.HardwareDecoding != port.EngineHardwareDecodingForce { | |
| 79 | 80 | t.Fatalf("HardwareDecoding=%q, want %q", got.WebContent.HardwareDecoding, port.EngineHardwareDecodingForce) | |
| 80 | 81 | } | |
| 82 | + | autoCopyField := reflect.ValueOf(got.WebContent).FieldByName("AutoCopyOnSelection") | |
| 83 | + | if !autoCopyField.IsValid() || !autoCopyField.Bool() { | |
| 84 | + | t.Fatalf("AutoCopyOnSelection not mapped in web content payload: %#v", got.WebContent) | |
| 85 | + | } | |
| 81 | 86 | } | |
| 82 | 87 | ||
| 83 | 88 | func TestRuntimeConfigProviderUpdatesSnapshotBeforeCallback(t *testing.T) { | |
| @@ -58,6 +58,7 @@ func TestEngineSettingsPayloadFromConfigMapsRuntimeFields(t *testing.T) { | |||||
| 58 | cfg.Logging.CaptureConsole = true | 58 | cfg.Logging.CaptureConsole = true | ||
| 59 | cfg.Engine.WebKit.DrawCompositingIndicators = true | 59 | cfg.Engine.WebKit.DrawCompositingIndicators = true | ||
| 60 | cfg.Media.HardwareDecodingMode = config.HardwareDecodingForce | 60 | cfg.Media.HardwareDecodingMode = config.HardwareDecodingForce | ||
| 61 | + | cfg.Clipboard.AutoCopyOnSelection = true | |||
| 61 | 62 | ||||
| 62 | got := EngineSettingsPayloadFromConfig(cfg) | 63 | got := EngineSettingsPayloadFromConfig(cfg) | ||
| 63 | 64 | ||||
| @@ -78,6 +79,10 @@ func TestEngineSettingsPayloadFromConfigMapsRuntimeFields(t *testing.T) { | |||||
| 78 | if got.WebContent.HardwareDecoding != port.EngineHardwareDecodingForce { | 79 | if got.WebContent.HardwareDecoding != port.EngineHardwareDecodingForce { | ||
| 79 | t.Fatalf("HardwareDecoding=%q, want %q", got.WebContent.HardwareDecoding, port.EngineHardwareDecodingForce) | 80 | t.Fatalf("HardwareDecoding=%q, want %q", got.WebContent.HardwareDecoding, port.EngineHardwareDecodingForce) | ||
| 80 | } | 81 | } | ||
| 82 | + | autoCopyField := reflect.ValueOf(got.WebContent).FieldByName("AutoCopyOnSelection") | |||
| 83 | + | if !autoCopyField.IsValid() || !autoCopyField.Bool() { | |||
| 84 | + | t.Fatalf("AutoCopyOnSelection not mapped in web content payload: %#v", got.WebContent) | |||
| 85 | + | } | |||
| 81 | } | 86 | } | ||
| 82 | 87 | ||||
| 83 | func TestRuntimeConfigProviderUpdatesSnapshotBeforeCallback(t *testing.T) { | 88 | func TestRuntimeConfigProviderUpdatesSnapshotBeforeCallback(t *testing.T) { | ||
M internal/infrastructure/webkit/engine_init.go +13 -3
| @@ -67,9 +67,7 @@ func NewEngine( | |||
| 67 | 67 | settings := NewSettingsManager(ctx, initialSettings) | |
| 68 | 68 | injector := NewContentInjector(colorResolver) | |
| 69 | 69 | ||
| 70 | - | injector.SetAutoCopyConfigGetter(func() bool { | |
| 71 | - | return config.Get().Clipboard.AutoCopyOnSelection | |
| 72 | - | }) | |
| 70 | + | engineConfigureContentInjectorRuntimeSettings(injector, settings) | |
| 73 | 71 | ||
| 74 | 72 | prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector) | |
| 75 | 73 | themeCSSText := themeManager.GetWebUIThemeCSS() | |
| @@ -130,6 +128,18 @@ func NewEngine( | |||
| 130 | 128 | return engine, nil | |
| 131 | 129 | } | |
| 132 | 130 | ||
| 131 | + | func engineConfigureContentInjectorRuntimeSettings(injector *ContentInjector, settings *SettingsManager) { | |
| 132 | + | if injector == nil { | |
| 133 | + | return | |
| 134 | + | } | |
| 135 | + | injector.SetAutoCopyConfigGetter(func() bool { | |
| 136 | + | if settings == nil { | |
| 137 | + | return false | |
| 138 | + | } | |
| 139 | + | return settings.current().WebContent.AutoCopyOnSelection | |
| 140 | + | }) | |
| 141 | + | } | |
| 142 | + | ||
| 133 | 143 | // engineSurveyHardwareAndResolveProfile surveys hardware and resolves the performance profile. | |
| 134 | 144 | func engineSurveyHardwareAndResolveProfile( | |
| 135 | 145 | ctx context.Context, cfg *config.Config, logger zerolog.Logger, | |
| @@ -67,9 +67,7 @@ func NewEngine( | |||||
| 67 | settings := NewSettingsManager(ctx, initialSettings) | 67 | settings := NewSettingsManager(ctx, initialSettings) | ||
| 68 | injector := NewContentInjector(colorResolver) | 68 | injector := NewContentInjector(colorResolver) | ||
| 69 | 69 | ||||
| 70 | - | injector.SetAutoCopyConfigGetter(func() bool { | 70 | + | engineConfigureContentInjectorRuntimeSettings(injector, settings) |
| 71 | - | return config.Get().Clipboard.AutoCopyOnSelection | |||
| 72 | - | }) | |||
| 73 | 71 | ||||
| 74 | prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector) | 72 | prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector) | ||
| 75 | themeCSSText := themeManager.GetWebUIThemeCSS() | 73 | themeCSSText := themeManager.GetWebUIThemeCSS() | ||
| @@ -130,6 +128,18 @@ func NewEngine( | |||||
| 130 | return engine, nil | 128 | return engine, nil | ||
| 131 | } | 129 | } | ||
| 132 | 130 | ||||
| 131 | + | func engineConfigureContentInjectorRuntimeSettings(injector *ContentInjector, settings *SettingsManager) { | |||
| 132 | + | if injector == nil { | |||
| 133 | + | return | |||
| 134 | + | } | |||
| 135 | + | injector.SetAutoCopyConfigGetter(func() bool { | |||
| 136 | + | if settings == nil { | |||
| 137 | + | return false | |||
| 138 | + | } | |||
| 139 | + | return settings.current().WebContent.AutoCopyOnSelection | |||
| 140 | + | }) | |||
| 141 | + | } | |||
| 142 | + | ||||
| 133 | // engineSurveyHardwareAndResolveProfile surveys hardware and resolves the performance profile. | 143 | // engineSurveyHardwareAndResolveProfile surveys hardware and resolves the performance profile. | ||
| 134 | func engineSurveyHardwareAndResolveProfile( | 144 | func engineSurveyHardwareAndResolveProfile( | ||
| 135 | ctx context.Context, cfg *config.Config, logger zerolog.Logger, | 145 | ctx context.Context, cfg *config.Config, logger zerolog.Logger, | ||
M internal/infrastructure/webkit/engine_runtime_test.go +27 -0
| @@ -2,6 +2,7 @@ package webkit | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | + | "os" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -72,3 +73,29 @@ func TestEngine_Close_NilPool(t *testing.T) { | |||
| 72 | 73 | err := e.Close() | |
| 73 | 74 | require.NoError(t, err) | |
| 74 | 75 | } | |
| 76 | + | ||
| 77 | + | func TestEngineConfigureContentInjectorAutoCopyGetterReadsCurrentPayload(t *testing.T) { | |
| 78 | + | settings := NewSettingsManager(context.Background(), port.EngineSettingsPayload{}) | |
| 79 | + | injector := NewContentInjector(nil) | |
| 80 | + | ||
| 81 | + | engineConfigureContentInjectorRuntimeSettings(injector, settings) | |
| 82 | + | ||
| 83 | + | require.NotNil(t, injector.autoCopyConfigGetter) | |
| 84 | + | require.False(t, injector.autoCopyConfigGetter()) | |
| 85 | + | ||
| 86 | + | settings.UpdateFromPayload(context.Background(), port.EngineSettingsPayload{ | |
| 87 | + | WebContent: port.EngineWebContentSettingsPayload{ | |
| 88 | + | AutoCopyOnSelection: true, | |
| 89 | + | }, | |
| 90 | + | }) | |
| 91 | + | ||
| 92 | + | require.True(t, injector.autoCopyConfigGetter()) | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func TestNewEngineAutoCopyGetterUsesRuntimeSettingsPayload(t *testing.T) { | |
| 96 | + | source, err := os.ReadFile("engine_init.go") | |
| 97 | + | require.NoError(t, err) | |
| 98 | + | globalGetter := "config." + "Get()" + ".Clipboard.AutoCopyOnSelection" | |
| 99 | + | require.NotContains(t, string(source), globalGetter) | |
| 100 | + | require.Contains(t, string(source), "settings.current().WebContent.AutoCopyOnSelection") | |
| 101 | + | } | |
| @@ -2,6 +2,7 @@ package webkit | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | + | "os" | |||
| 5 | "testing" | 6 | "testing" | ||
| 6 | 7 | ||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -72,3 +73,29 @@ func TestEngine_Close_NilPool(t *testing.T) { | |||||
| 72 | err := e.Close() | 73 | err := e.Close() | ||
| 73 | require.NoError(t, err) | 74 | require.NoError(t, err) | ||
| 74 | } | 75 | } | ||
| 76 | + | ||||
| 77 | + | func TestEngineConfigureContentInjectorAutoCopyGetterReadsCurrentPayload(t *testing.T) { | |||
| 78 | + | settings := NewSettingsManager(context.Background(), port.EngineSettingsPayload{}) | |||
| 79 | + | injector := NewContentInjector(nil) | |||
| 80 | + | ||||
| 81 | + | engineConfigureContentInjectorRuntimeSettings(injector, settings) | |||
| 82 | + | ||||
| 83 | + | require.NotNil(t, injector.autoCopyConfigGetter) | |||
| 84 | + | require.False(t, injector.autoCopyConfigGetter()) | |||
| 85 | + | ||||
| 86 | + | settings.UpdateFromPayload(context.Background(), port.EngineSettingsPayload{ | |||
| 87 | + | WebContent: port.EngineWebContentSettingsPayload{ | |||
| 88 | + | AutoCopyOnSelection: true, | |||
| 89 | + | }, | |||
| 90 | + | }) | |||
| 91 | + | ||||
| 92 | + | require.True(t, injector.autoCopyConfigGetter()) | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func TestNewEngineAutoCopyGetterUsesRuntimeSettingsPayload(t *testing.T) { | |||
| 96 | + | source, err := os.ReadFile("engine_init.go") | |||
| 97 | + | require.NoError(t, err) | |||
| 98 | + | globalGetter := "config." + "Get()" + ".Clipboard.AutoCopyOnSelection" | |||
| 99 | + | require.NotContains(t, string(source), globalGetter) | |||
| 100 | + | require.Contains(t, string(source), "settings.current().WebContent.AutoCopyOnSelection") | |||
| 101 | + | } | |||
M internal/infrastructure/webkit/settings.go +21 -2
| @@ -10,6 +10,21 @@ import ( | |||
| 10 | 10 | "github.com/rs/zerolog" | |
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | + | const hardwareRequiredContentTypes = "video/av01;video/mp4;video/webm;video/x-h264;video/x-h265" | |
| 14 | + | ||
| 15 | + | type mediaSettings interface { | |
| 16 | + | SetEnableWebaudio(bool) | |
| 17 | + | SetEnableWebgl(bool) | |
| 18 | + | SetEnableMedia(bool) | |
| 19 | + | SetEnableMediasource(bool) | |
| 20 | + | SetEnableMediaCapabilities(bool) | |
| 21 | + | SetEnableEncryptedMedia(bool) | |
| 22 | + | SetMediaPlaybackRequiresUserGesture(bool) | |
| 23 | + | SetMediaPlaybackAllowsInline(bool) | |
| 24 | + | SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicy) | |
| 25 | + | SetMediaContentTypesRequiringHardwareSupport(*string) | |
| 26 | + | } | |
| 27 | + | ||
| 13 | 28 | // SettingsManager creates and manages WebKit Settings instances from payloads. | |
| 14 | 29 | type SettingsManager struct { | |
| 15 | 30 | settings port.EngineSettingsPayload | |
| @@ -104,7 +119,7 @@ func applyBrowsingSettings(settings *webkit.Settings) { | |||
| 104 | 119 | settings.SetEnableSiteSpecificQuirks(true) | |
| 105 | 120 | } | |
| 106 | 121 | ||
| 107 | - | func applyMediaSettings(settings *webkit.Settings, mode port.EngineHardwareDecodingMode, log *zerolog.Logger) { | |
| 122 | + | func applyMediaSettings(settings mediaSettings, mode port.EngineHardwareDecodingMode, log *zerolog.Logger) { | |
| 108 | 123 | settings.SetEnableWebaudio(true) | |
| 109 | 124 | settings.SetEnableWebgl(true) | |
| 110 | 125 | settings.SetEnableMedia(true) | |
| @@ -116,14 +131,18 @@ func applyMediaSettings(settings *webkit.Settings, mode port.EngineHardwareDecod | |||
| 116 | 131 | ||
| 117 | 132 | switch mode { | |
| 118 | 133 | case port.EngineHardwareDecodingForce: | |
| 119 | - | hwTypes := "video/av01;video/mp4;video/webm;video/x-h264;video/x-h265" | |
| 134 | + | hwTypes := hardwareRequiredContentTypes | |
| 135 | + | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue) | |
| 120 | 136 | settings.SetMediaContentTypesRequiringHardwareSupport(&hwTypes) | |
| 121 | 137 | log.Debug().Msg("hardware decoding: forced (may fail without hw support)") | |
| 122 | 138 | case port.EngineHardwareDecodingDisable: | |
| 139 | + | emptyTypes := "" | |
| 123 | 140 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyNeverValue) | |
| 141 | + | settings.SetMediaContentTypesRequiringHardwareSupport(&emptyTypes) | |
| 124 | 142 | log.Debug().Msg("hardware decoding: disabled (software only)") | |
| 125 | 143 | default: | |
| 126 | 144 | emptyTypes := "" | |
| 145 | + | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue) | |
| 127 | 146 | settings.SetMediaContentTypesRequiringHardwareSupport(&emptyTypes) | |
| 128 | 147 | log.Debug().Msg("hardware decoding: auto (hw preferred, software fallback)") | |
| 129 | 148 | } | |
| @@ -10,6 +10,21 @@ import ( | |||||
| 10 | "github.com/rs/zerolog" | 10 | "github.com/rs/zerolog" | ||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | + | const hardwareRequiredContentTypes = "video/av01;video/mp4;video/webm;video/x-h264;video/x-h265" | |||
| 14 | + | ||||
| 15 | + | type mediaSettings interface { | |||
| 16 | + | SetEnableWebaudio(bool) | |||
| 17 | + | SetEnableWebgl(bool) | |||
| 18 | + | SetEnableMedia(bool) | |||
| 19 | + | SetEnableMediasource(bool) | |||
| 20 | + | SetEnableMediaCapabilities(bool) | |||
| 21 | + | SetEnableEncryptedMedia(bool) | |||
| 22 | + | SetMediaPlaybackRequiresUserGesture(bool) | |||
| 23 | + | SetMediaPlaybackAllowsInline(bool) | |||
| 24 | + | SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicy) | |||
| 25 | + | SetMediaContentTypesRequiringHardwareSupport(*string) | |||
| 26 | + | } | |||
| 27 | + | ||||
| 13 | // SettingsManager creates and manages WebKit Settings instances from payloads. | 28 | // SettingsManager creates and manages WebKit Settings instances from payloads. | ||
| 14 | type SettingsManager struct { | 29 | type SettingsManager struct { | ||
| 15 | settings port.EngineSettingsPayload | 30 | settings port.EngineSettingsPayload | ||
| @@ -104,7 +119,7 @@ func applyBrowsingSettings(settings *webkit.Settings) { | |||||
| 104 | settings.SetEnableSiteSpecificQuirks(true) | 119 | settings.SetEnableSiteSpecificQuirks(true) | ||
| 105 | } | 120 | } | ||
| 106 | 121 | ||||
| 107 | - | func applyMediaSettings(settings *webkit.Settings, mode port.EngineHardwareDecodingMode, log *zerolog.Logger) { | 122 | + | func applyMediaSettings(settings mediaSettings, mode port.EngineHardwareDecodingMode, log *zerolog.Logger) { |
| 108 | settings.SetEnableWebaudio(true) | 123 | settings.SetEnableWebaudio(true) | ||
| 109 | settings.SetEnableWebgl(true) | 124 | settings.SetEnableWebgl(true) | ||
| 110 | settings.SetEnableMedia(true) | 125 | settings.SetEnableMedia(true) | ||
| @@ -116,14 +131,18 @@ func applyMediaSettings(settings *webkit.Settings, mode port.EngineHardwareDecod | |||||
| 116 | 131 | ||||
| 117 | switch mode { | 132 | switch mode { | ||
| 118 | case port.EngineHardwareDecodingForce: | 133 | case port.EngineHardwareDecodingForce: | ||
| 119 | - | hwTypes := "video/av01;video/mp4;video/webm;video/x-h264;video/x-h265" | 134 | + | hwTypes := hardwareRequiredContentTypes |
| 135 | + | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue) | |||
| 120 | settings.SetMediaContentTypesRequiringHardwareSupport(&hwTypes) | 136 | settings.SetMediaContentTypesRequiringHardwareSupport(&hwTypes) | ||
| 121 | log.Debug().Msg("hardware decoding: forced (may fail without hw support)") | 137 | log.Debug().Msg("hardware decoding: forced (may fail without hw support)") | ||
| 122 | case port.EngineHardwareDecodingDisable: | 138 | case port.EngineHardwareDecodingDisable: | ||
| 139 | + | emptyTypes := "" | |||
| 123 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyNeverValue) | 140 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyNeverValue) | ||
| 141 | + | settings.SetMediaContentTypesRequiringHardwareSupport(&emptyTypes) | |||
| 124 | log.Debug().Msg("hardware decoding: disabled (software only)") | 142 | log.Debug().Msg("hardware decoding: disabled (software only)") | ||
| 125 | default: | 143 | default: | ||
| 126 | emptyTypes := "" | 144 | emptyTypes := "" | ||
| 145 | + | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue) | |||
| 127 | settings.SetMediaContentTypesRequiringHardwareSupport(&emptyTypes) | 146 | settings.SetMediaContentTypesRequiringHardwareSupport(&emptyTypes) | ||
| 128 | log.Debug().Msg("hardware decoding: auto (hw preferred, software fallback)") | 147 | log.Debug().Msg("hardware decoding: auto (hw preferred, software fallback)") | ||
| 129 | } | 148 | } | ||
M internal/infrastructure/webkit/settings_runtime_test.go +92 -0
| @@ -5,6 +5,8 @@ import ( | |||
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 8 | + | "github.com/bnema/puregotk/v4/webkit" | |
| 9 | + | "github.com/rs/zerolog" | |
| 8 | 10 | ) | |
| 9 | 11 | ||
| 10 | 12 | func TestSettingsManagerUpdateFromPayloadStoresTypedRuntimeSettings(t *testing.T) { | |
| @@ -30,3 +32,93 @@ func TestSettingsManagerUpdateFromPayloadStoresTypedRuntimeSettings(t *testing.T | |||
| 30 | 32 | t.Fatalf("stored payload=%#v", got) | |
| 31 | 33 | } | |
| 32 | 34 | } | |
| 35 | + | ||
| 36 | + | func TestApplyMediaSettingsUpdatesHardwareDecodingKnobsOnSameSettingsObject(t *testing.T) { | |
| 37 | + | tests := []struct { | |
| 38 | + | name string | |
| 39 | + | first port.EngineHardwareDecodingMode | |
| 40 | + | next port.EngineHardwareDecodingMode | |
| 41 | + | wantPolicy webkit.HardwareAccelerationPolicy | |
| 42 | + | wantHWTypes string | |
| 43 | + | wantEmptyHWT bool | |
| 44 | + | }{ | |
| 45 | + | { | |
| 46 | + | name: "disable to force resets acceleration policy", | |
| 47 | + | first: port.EngineHardwareDecodingDisable, | |
| 48 | + | next: port.EngineHardwareDecodingForce, | |
| 49 | + | wantPolicy: webkit.HardwareAccelerationPolicyAlwaysValue, | |
| 50 | + | wantHWTypes: "video/av01;video/mp4;video/webm;video/x-h264;video/x-h265", | |
| 51 | + | }, | |
| 52 | + | { | |
| 53 | + | name: "force to disable clears required content types", | |
| 54 | + | first: port.EngineHardwareDecodingForce, | |
| 55 | + | next: port.EngineHardwareDecodingDisable, | |
| 56 | + | wantPolicy: webkit.HardwareAccelerationPolicyNeverValue, | |
| 57 | + | wantEmptyHWT: true, | |
| 58 | + | }, | |
| 59 | + | { | |
| 60 | + | name: "disable to auto resets acceleration policy", | |
| 61 | + | first: port.EngineHardwareDecodingDisable, | |
| 62 | + | next: port.EngineHardwareDecodingAuto, | |
| 63 | + | wantPolicy: webkit.HardwareAccelerationPolicyAlwaysValue, | |
| 64 | + | wantEmptyHWT: true, | |
| 65 | + | }, | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | logger := zerolog.Nop() | |
| 69 | + | for _, tt := range tests { | |
| 70 | + | t.Run(tt.name, func(t *testing.T) { | |
| 71 | + | settings := &recordingMediaSettings{} | |
| 72 | + | ||
| 73 | + | applyMediaSettings(settings, tt.first, &logger) | |
| 74 | + | applyMediaSettings(settings, tt.next, &logger) | |
| 75 | + | ||
| 76 | + | if got := settings.hardwareAccelerationPolicy; got != tt.wantPolicy { | |
| 77 | + | t.Fatalf("HardwareAccelerationPolicy=%v, want %v", got, tt.wantPolicy) | |
| 78 | + | } | |
| 79 | + | gotHWTypes := settings.mediaContentTypesRequiringHardwareSupport | |
| 80 | + | if tt.wantEmptyHWT { | |
| 81 | + | if gotHWTypes != "" { | |
| 82 | + | t.Fatalf("MediaContentTypesRequiringHardwareSupport=%q, want empty", gotHWTypes) | |
| 83 | + | } | |
| 84 | + | return | |
| 85 | + | } | |
| 86 | + | if gotHWTypes != tt.wantHWTypes { | |
| 87 | + | t.Fatalf("MediaContentTypesRequiringHardwareSupport=%q, want %q", gotHWTypes, tt.wantHWTypes) | |
| 88 | + | } | |
| 89 | + | }) | |
| 90 | + | } | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | type recordingMediaSettings struct { | |
| 94 | + | hardwareAccelerationPolicy webkit.HardwareAccelerationPolicy | |
| 95 | + | mediaContentTypesRequiringHardwareSupport string | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | func (*recordingMediaSettings) SetEnableWebaudio(bool) {} | |
| 99 | + | ||
| 100 | + | func (*recordingMediaSettings) SetEnableWebgl(bool) {} | |
| 101 | + | ||
| 102 | + | func (*recordingMediaSettings) SetEnableMedia(bool) {} | |
| 103 | + | ||
| 104 | + | func (*recordingMediaSettings) SetEnableMediasource(bool) {} | |
| 105 | + | ||
| 106 | + | func (*recordingMediaSettings) SetEnableMediaCapabilities(bool) {} | |
| 107 | + | ||
| 108 | + | func (*recordingMediaSettings) SetEnableEncryptedMedia(bool) {} | |
| 109 | + | ||
| 110 | + | func (*recordingMediaSettings) SetMediaPlaybackRequiresUserGesture(bool) {} | |
| 111 | + | ||
| 112 | + | func (*recordingMediaSettings) SetMediaPlaybackAllowsInline(bool) {} | |
| 113 | + | ||
| 114 | + | func (s *recordingMediaSettings) SetHardwareAccelerationPolicy(policy webkit.HardwareAccelerationPolicy) { | |
| 115 | + | s.hardwareAccelerationPolicy = policy | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | func (s *recordingMediaSettings) SetMediaContentTypesRequiringHardwareSupport(contentTypes *string) { | |
| 119 | + | if contentTypes == nil { | |
| 120 | + | s.mediaContentTypesRequiringHardwareSupport = "" | |
| 121 | + | return | |
| 122 | + | } | |
| 123 | + | s.mediaContentTypesRequiringHardwareSupport = *contentTypes | |
| 124 | + | } | |
| @@ -5,6 +5,8 @@ import ( | |||||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 8 | + | "github.com/bnema/puregotk/v4/webkit" | |||
| 9 | + | "github.com/rs/zerolog" | |||
| 8 | ) | 10 | ) | ||
| 9 | 11 | ||||
| 10 | func TestSettingsManagerUpdateFromPayloadStoresTypedRuntimeSettings(t *testing.T) { | 12 | func TestSettingsManagerUpdateFromPayloadStoresTypedRuntimeSettings(t *testing.T) { | ||
| @@ -30,3 +32,93 @@ func TestSettingsManagerUpdateFromPayloadStoresTypedRuntimeSettings(t *testing.T | |||||
| 30 | t.Fatalf("stored payload=%#v", got) | 32 | t.Fatalf("stored payload=%#v", got) | ||
| 31 | } | 33 | } | ||
| 32 | } | 34 | } | ||
| 35 | + | ||||
| 36 | + | func TestApplyMediaSettingsUpdatesHardwareDecodingKnobsOnSameSettingsObject(t *testing.T) { | |||
| 37 | + | tests := []struct { | |||
| 38 | + | name string | |||
| 39 | + | first port.EngineHardwareDecodingMode | |||
| 40 | + | next port.EngineHardwareDecodingMode | |||
| 41 | + | wantPolicy webkit.HardwareAccelerationPolicy | |||
| 42 | + | wantHWTypes string | |||
| 43 | + | wantEmptyHWT bool | |||
| 44 | + | }{ | |||
| 45 | + | { | |||
| 46 | + | name: "disable to force resets acceleration policy", | |||
| 47 | + | first: port.EngineHardwareDecodingDisable, | |||
| 48 | + | next: port.EngineHardwareDecodingForce, | |||
| 49 | + | wantPolicy: webkit.HardwareAccelerationPolicyAlwaysValue, | |||
| 50 | + | wantHWTypes: "video/av01;video/mp4;video/webm;video/x-h264;video/x-h265", | |||
| 51 | + | }, | |||
| 52 | + | { | |||
| 53 | + | name: "force to disable clears required content types", | |||
| 54 | + | first: port.EngineHardwareDecodingForce, | |||
| 55 | + | next: port.EngineHardwareDecodingDisable, | |||
| 56 | + | wantPolicy: webkit.HardwareAccelerationPolicyNeverValue, | |||
| 57 | + | wantEmptyHWT: true, | |||
| 58 | + | }, | |||
| 59 | + | { | |||
| 60 | + | name: "disable to auto resets acceleration policy", | |||
| 61 | + | first: port.EngineHardwareDecodingDisable, | |||
| 62 | + | next: port.EngineHardwareDecodingAuto, | |||
| 63 | + | wantPolicy: webkit.HardwareAccelerationPolicyAlwaysValue, | |||
| 64 | + | wantEmptyHWT: true, | |||
| 65 | + | }, | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | logger := zerolog.Nop() | |||
| 69 | + | for _, tt := range tests { | |||
| 70 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 71 | + | settings := &recordingMediaSettings{} | |||
| 72 | + | ||||
| 73 | + | applyMediaSettings(settings, tt.first, &logger) | |||
| 74 | + | applyMediaSettings(settings, tt.next, &logger) | |||
| 75 | + | ||||
| 76 | + | if got := settings.hardwareAccelerationPolicy; got != tt.wantPolicy { | |||
| 77 | + | t.Fatalf("HardwareAccelerationPolicy=%v, want %v", got, tt.wantPolicy) | |||
| 78 | + | } | |||
| 79 | + | gotHWTypes := settings.mediaContentTypesRequiringHardwareSupport | |||
| 80 | + | if tt.wantEmptyHWT { | |||
| 81 | + | if gotHWTypes != "" { | |||
| 82 | + | t.Fatalf("MediaContentTypesRequiringHardwareSupport=%q, want empty", gotHWTypes) | |||
| 83 | + | } | |||
| 84 | + | return | |||
| 85 | + | } | |||
| 86 | + | if gotHWTypes != tt.wantHWTypes { | |||
| 87 | + | t.Fatalf("MediaContentTypesRequiringHardwareSupport=%q, want %q", gotHWTypes, tt.wantHWTypes) | |||
| 88 | + | } | |||
| 89 | + | }) | |||
| 90 | + | } | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | type recordingMediaSettings struct { | |||
| 94 | + | hardwareAccelerationPolicy webkit.HardwareAccelerationPolicy | |||
| 95 | + | mediaContentTypesRequiringHardwareSupport string | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | func (*recordingMediaSettings) SetEnableWebaudio(bool) {} | |||
| 99 | + | ||||
| 100 | + | func (*recordingMediaSettings) SetEnableWebgl(bool) {} | |||
| 101 | + | ||||
| 102 | + | func (*recordingMediaSettings) SetEnableMedia(bool) {} | |||
| 103 | + | ||||
| 104 | + | func (*recordingMediaSettings) SetEnableMediasource(bool) {} | |||
| 105 | + | ||||
| 106 | + | func (*recordingMediaSettings) SetEnableMediaCapabilities(bool) {} | |||
| 107 | + | ||||
| 108 | + | func (*recordingMediaSettings) SetEnableEncryptedMedia(bool) {} | |||
| 109 | + | ||||
| 110 | + | func (*recordingMediaSettings) SetMediaPlaybackRequiresUserGesture(bool) {} | |||
| 111 | + | ||||
| 112 | + | func (*recordingMediaSettings) SetMediaPlaybackAllowsInline(bool) {} | |||
| 113 | + | ||||
| 114 | + | func (s *recordingMediaSettings) SetHardwareAccelerationPolicy(policy webkit.HardwareAccelerationPolicy) { | |||
| 115 | + | s.hardwareAccelerationPolicy = policy | |||
| 116 | + | } | |||
| 117 | + | ||||
| 118 | + | func (s *recordingMediaSettings) SetMediaContentTypesRequiringHardwareSupport(contentTypes *string) { | |||
| 119 | + | if contentTypes == nil { | |||
| 120 | + | s.mediaContentTypesRequiringHardwareSupport = "" | |||
| 121 | + | return | |||
| 122 | + | } | |||
| 123 | + | s.mediaContentTypesRequiringHardwareSupport = *contentTypes | |||
| 124 | + | } | |||