From 4d916207a49e549765dce42fb2c2b4d1fe3ae9ff Mon Sep 17 00:00:00 2001 From: AlastorFox MAC Date: Thu, 1 Oct 2026 22:36:17 +0300 Subject: [PATCH] Changed how the built-in extensions work and fixed tab loading --- Core/SessionStore.cs | 6 ++++ Core/TabManager.cs | 79 +++++++++++++++++++++++++++++++++++++++----- MainWindow.xaml.cs | 17 ++++++++-- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/Core/SessionStore.cs b/Core/SessionStore.cs index c93a0d3..8c73c19 100644 --- a/Core/SessionStore.cs +++ b/Core/SessionStore.cs @@ -38,6 +38,7 @@ public sealed class Session public static class SessionStore { private static readonly string SessionPath = Path.Combine(AppContext.BaseDirectory, "session.json"); + private static readonly SemaphoreSlim SaveGate = new(1, 1); private static readonly JsonSerializerOptions JsonOptions = new() { @@ -47,6 +48,7 @@ public static class SessionStore public static async Task SaveAsync(IEnumerable tabs, int activeIndex) { + await SaveGate.WaitAsync(); try { var snapshots = tabs.Select(t => new TabSnapshot @@ -83,6 +85,10 @@ public static class SessionStore { System.Diagnostics.Debug.WriteLine($"[SessionStore] Save failed: {ex.Message}"); } + finally + { + SaveGate.Release(); + } } public static async Task LoadAsync() diff --git a/Core/TabManager.cs b/Core/TabManager.cs index 0824fc7..9a289be 100644 --- a/Core/TabManager.cs +++ b/Core/TabManager.cs @@ -26,6 +26,7 @@ public sealed class TabManager : IAsyncDisposable private bool _minimizedMode; private bool _navigationInProgress; private bool _switchInProgress; + private bool _restoringSession; private readonly string _blockedElementsPath = Path.Combine(AppContext.BaseDirectory, "blocked-elements.json"); private readonly Dictionary> _blockedElements; @@ -43,6 +44,7 @@ public sealed class TabManager : IAsyncDisposable public BrowserTab? ActiveTab { get; private set; } public bool IsMinimizedMode => _minimizedMode; public bool ExtensionsAvailable { get; private set; } + public bool SessionReady { get; private set; } public CoreWebView2Environment Environment => _environment; public WebView2 View => _view; public ExtensionManager? ExtensionManager { get; private set; } @@ -68,7 +70,17 @@ public sealed class TabManager : IAsyncDisposable _view.CoreWebView2.IsDocumentPlayingAudioChanged += OnAudioStateChanged; _view.CoreWebView2.HistoryChanged += OnHistoryChanged; _view.CoreWebView2.WebMessageReceived += OnWebMessageReceived; - await RestoreSessionOrDefaultAsync(); + _restoringSession = true; + try + { + await RestoreSessionOrDefaultAsync(); + } + finally + { + _restoringSession = false; + SessionReady = true; + } + await SaveSessionAsync(); } public async Task SaveSessionAsync() @@ -129,7 +141,9 @@ public sealed class TabManager : IAsyncDisposable _audioOnly = tab.IsAudioOnly; ActiveTabStateChanged?.Invoke(this, EventArgs.Empty); _navigationInProgress = true; + await UpdateDocumentCreatedScriptAsync(); core.Navigate(tab.Address); + if (!_restoringSession) await SaveSessionAsync(); await Task.Yield(); } finally { _switchInProgress = false; } @@ -143,6 +157,7 @@ public sealed class TabManager : IAsyncDisposable tab.Address = NormalizeAddress(address); if (ActiveTab != tab) { await SwitchToTabAsync(tab); return; } _navigationInProgress = true; + await UpdateDocumentCreatedScriptAsync(); core.Navigate(tab.Address); } @@ -276,13 +291,20 @@ public sealed class TabManager : IAsyncDisposable public void GoForward() { var core = _view.CoreWebView2; if (core?.CanGoForward == true) core.GoForward(); } public void Reload() { var core = _view.CoreWebView2; if (core is null) return; _navigationInProgress = true; core.Reload(); } - public void CloseTab(BrowserTab tab) + public async Task CloseTabAsync(BrowserTab tab) { var index = Tabs.IndexOf(tab); + if (index < 0) return; Tabs.Remove(tab); - if (Tabs.Count == 0) { _ = AddTabAsync(); return; } - if (ActiveTab == tab) _ = SwitchToTabAsync(Tabs[Math.Min(index, Tabs.Count - 1)]); - _ = SaveSessionAsync(); + if (Tabs.Count == 0) + { + await AddTabAsync(); + return; + } + if (ActiveTab == tab) + await SwitchToTabAsync(Tabs[Math.Min(index, Tabs.Count - 1)]); + else + await SaveSessionAsync(); } public async ValueTask DisposeAsync() @@ -367,6 +389,47 @@ public sealed class TabManager : IAsyncDisposable try { await _view.CoreWebView2.ExecuteScriptAsync(script); } catch { } } + private async Task UpdateDocumentCreatedScriptAsync() + { + var core = _view.CoreWebView2; + if (core is null) return; + + var host = GetCurrentHost(); + var selectors = host is not null && _blockedElements.TryGetValue(host, out var savedSelectors) + ? savedSelectors + : new List(); + var darkEnabled = ActiveTab?.IsDarkMode == true && + Uri.TryCreate(ActiveTab.Address, UriKind.Absolute, out var address) && + (address.Scheme == Uri.UriSchemeHttp || address.Scheme == Uri.UriSchemeHttps); + var brightness = (ActiveTab?.DarkBrightness ?? 0.72).ToString(System.Globalization.CultureInfo.InvariantCulture); + var contrast = (ActiveTab?.DarkContrast ?? 1.08).ToString(System.Globalization.CultureInfo.InvariantCulture); + var saturation = (ActiveTab?.DarkSaturation ?? 0.88).ToString(System.Globalization.CultureInfo.InvariantCulture); + var script = """ + (()=>{ + const selectors=__SELECTORS__, dark=__DARK__, brightness=__BRIGHTNESS__, contrast=__CONTRAST__, saturation=__SATURATION__; + const apply=()=>{ + const root=document.documentElement; + if(!root){setTimeout(apply,0);return;} + let darkStyle=document.getElementById('fox-dark-mode'); + if(!darkStyle){darkStyle=document.createElement('style');darkStyle.id='fox-dark-mode';root.appendChild(darkStyle);} + let blockedStyle=document.getElementById('fox-blocked-elements'); + if(!blockedStyle){blockedStyle=document.createElement('style');blockedStyle.id='fox-blocked-elements';root.appendChild(blockedStyle);} + const darkCss=dark?'html{background:#111!important;color-scheme:dark!important}html.fox-dark-mode{background:#111!important;color-scheme:dark!important}html.fox-dark-mode body{background-color:#15171b!important;filter:brightness('+brightness+') contrast('+contrast+') saturate('+saturation+')!important}html.fox-dark-mode input,html.fox-dark-mode textarea,html.fox-dark-mode select,html.fox-dark-mode button{color-scheme:dark!important}':''; + darkStyle.textContent=darkCss; + blockedStyle.textContent=selectors.map(x=>x+'{display:none!important;}').join(''); + if(dark) root.classList.add('fox-dark-mode'); else root.classList.remove('fox-dark-mode'); + }; + apply(); + })(); + """ + .Replace("__SELECTORS__", JsonSerializer.Serialize(selectors), StringComparison.Ordinal) + .Replace("__DARK__", darkEnabled ? "true" : "false", StringComparison.Ordinal) + .Replace("__BRIGHTNESS__", brightness, StringComparison.Ordinal) + .Replace("__CONTRAST__", contrast, StringComparison.Ordinal) + .Replace("__SATURATION__", saturation, StringComparison.Ordinal); + try { await core.AddScriptToExecuteOnDocumentCreatedAsync(script); } catch { } + } + private string? GetCurrentHost() { return Uri.TryCreate(ActiveTab?.Address, UriKind.Absolute, out var uri) && @@ -470,11 +533,9 @@ public sealed class TabManager : IAsyncDisposable const root=document.documentElement; if(!root)return; root.classList.add('fox-dark-mode'); - document.getElementById('fox-dark-mode')?.remove(); - const s=document.createElement('style'); - s.id='fox-dark-mode'; + let s=document.getElementById('fox-dark-mode'); + if(!s){s=document.createElement('style');s.id='fox-dark-mode';(document.head||root).appendChild(s);} s.textContent=`html.fox-dark-mode{background:#111!important;color-scheme:dark!important}html.fox-dark-mode body{background-color:#15171b!important;filter:brightness(__BRIGHTNESS__) contrast(__CONTRAST__) saturate(__SATURATION__)!important}html.fox-dark-mode input,html.fox-dark-mode textarea,html.fox-dark-mode select,html.fox-dark-mode button{color-scheme:dark!important}`; - (document.head||root).appendChild(s); })(); """ : """ diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 00800da..43d9777 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -19,6 +19,7 @@ public partial class MainWindow : Window private TabManager? _tabManager; private TrayIconManager? _tray; private bool _allowClose; + private bool _closingAfterSessionSave; private readonly DispatcherTimer _watchdogTimer = new() { Interval = TimeSpan.FromSeconds(30) }; private AudioSessionLabeler? _audioLabeler; private bool _updatingDarkModeMenu; @@ -149,6 +150,7 @@ public partial class MainWindow : Window _tabManager.ActiveTabStateChanged += (_, _) => Dispatcher.Invoke(RefreshAddressBar); DataContext = _tabManager; await _tabManager.InitializeAsync(); + TabStrip.SelectedItem = _tabManager.ActiveTab; var iconPath = ResolveAppIconPath(_appSettings); try { @@ -330,15 +332,16 @@ public partial class MainWindow : Window private async void TabStrip_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (e.Source != TabStrip || TabStrip.SelectedItem is not BrowserTab tab || _tabManager is null) return; + if (e.Source != TabStrip || TabStrip.SelectedItem is not BrowserTab tab || _tabManager is null || !_tabManager.SessionReady) return; AddressBox.Text = tab.Address; await _tabManager.SwitchToTabAsync(tab); RefreshAddressBar(); } - private void CloseTab_Click(object sender, RoutedEventArgs e) + private async void CloseTab_Click(object sender, RoutedEventArgs e) { - if ((sender as System.Windows.Controls.Button)?.Tag is BrowserTab tab) _tabManager?.CloseTab(tab); + if ((sender as System.Windows.Controls.Button)?.Tag is BrowserTab tab && _tabManager is not null) + await _tabManager.CloseTabAsync(tab); RefreshAddressBar(); } @@ -360,6 +363,14 @@ public partial class MainWindow : Window } else { + if (!_closingAfterSessionSave) + { + e.Cancel = true; + _closingAfterSessionSave = true; + if (_tabManager is not null) await _tabManager.SaveSessionAsync(); + Close(); + return; + } _watchdogTimer.Stop(); _tray?.Dispose(); _audioLabeler?.Dispose();