Repository: wtcpython/WinUIEdge
Pull request #44: 完善Favicon、支持安装浏览器扩展、增加系统和性能设置
Author: login: XiaofengdiZhu
Created: 2025-05-02T06:40:33Z | Merged: 2025-05-04T00:16:51Z



Diff:
diff --git a/.gitignore b/.gitignore
index daea4d1..8d64221 100644
--- a/.gitignore
+++ b/.gitignore
@@ -396,3 +396,4 @@ FodyWeavers.xsd
 
 # JetBrains Rider
 *.sln.iml
+.idea/
\ No newline at end of file
diff --git a/App.xaml.cs b/App.xaml.cs
index 624fb34..5fc6aed 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -8,7 +8,9 @@
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.IO;
+using System.Text;
 using System.Text.Json;
+using Microsoft.Web.WebView2.Core;
 
 
 namespace Edge
@@ -17,21 +19,56 @@ public partial class App : Application
     {
         public static List<MainWindow> mainWindows = [];
         public static Settings settings;
-        public static WebView2 WebView2 = new();
+        public static WebView2 WebView2;
+        public static CoreWebView2 CoreWebView2;
+        public static CoreWebView2Environment CoreWebView2Environment;
+        public static CoreWebView2Profile CoreWebView2Profile;
         public static ObservableCollection<WebViewHistory> Histories = [];
         public static ObservableCollection<DownloadObject> DownloadList = [];
         public static WordSearchEngine searchEngine;
+        public static bool NeedRestartEnvironment;
 
         public App()
         {
+            Console.OutputEncoding = Encoding.UTF8;
             this.InitializeComponent();
             searchEngine = new("./Assets/words.txt");
-            EnsureWebView2Async();
         }
 
         public async void EnsureWebView2Async()
         {
-            await WebView2.EnsureCoreWebView2Async();
+            List<string> additionalBrowserArguments = [];
+            if (settings.DisableGpu)
+            {
+                additionalBrowserArguments.Add("--disable-gpu");
+            }
+            if (settings.DisableBackgroundTimerThrottling)
+            {
+                additionalBrowserArguments.Add("--disable-background-timer-throttling");
+            }
+            CoreWebView2Environment = await CoreWebView2Environment.CreateWithOptionsAsync(
+                null,
+                null,
+                new CoreWebView2EnvironmentOptions()
+                {
+                    AreBrowserExtensionsEnabled = true,
+                    AdditionalBrowserArguments = string.Join(" ", additionalBrowserArguments)
+                });
+            CoreWebView2Environment.BrowserProcessExited += BrowserProcessExited;
+            WebView2 = new WebView2();
+            await WebView2.EnsureCoreWebView2Async(CoreWebView2Environment);
+            CoreWebView2 = WebView2.CoreWebView2;
+            CoreWebView2Profile = CoreWebView2.Profile;
+        }
+
+        private void BrowserProcessExited(CoreWebView2Environment sender, CoreWebView2BrowserProcessExitedEventArgs args)
+        {
+            Console.WriteLine($"Browser process exited with exit code {args.BrowserProcessExitKind}");
+            if (NeedRestartEnvironment)
+            {
+                NeedRestartEnvironment = false;
+                EnsureWebView2Async();
+            }
         }
 
         public static MainWindow CreateNewWindow()
@@ -40,7 +77,15 @@ public static MainWindow CreateNewWindow()
             window.Closed += (sender, e) =>
             {
                 mainWindows.Remove(window);
-                File.WriteAllText("./settings.json", JsonSerializer.Serialize(settings, JsonContext.Default.Settings));
+                if (mainWindows.Count == 0)
+                {
+                    NeedRestartEnvironment = false;
+                    File.WriteAllText("./settings.json", JsonSerializer.Serialize(settings, JsonContext.Default.Settings));
+                }
+                else if (NeedRestartEnvironment && !AnyWebviewPageExists())
+                {
+                    WebView2.Close();
+                }
             };
             mainWindows.Add(window);
             return window;
@@ -71,6 +116,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
         {
             settings = Info.LoadSettings();
 
+            EnsureWebView2Async();
+
             window = CreateNewWindow();
 
             string[] arguments = Environment.GetCommandLineArgs()[1..];
@@ -128,5 +175,20 @@ private void NotificationManager_NotificationInvoked(AppNotificationManager send
         }
 
         private static MainWindow window;
+
+        public static bool AnyWebviewPageExists()
+        {
+            foreach (MainWindow mainWindow in App.mainWindows)
+            {
+                foreach (object tabItem in mainWindow.TabView.TabItems)
+                {
+                    if (tabItem is TabViewItem { Content: WebViewPage webViewPage } tabViewItem)
+                    {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
     }
 }
diff --git a/Controls/BrowserMenuItems.xaml.cs b/Controls/BrowserMenuItems.xaml.cs
index 9d975a1..f5da499 100644
--- a/Controls/BrowserMenuItems.xaml.cs
+++ b/Controls/BrowserMenuItems.xaml.cs
@@ -32,7 +32,7 @@ private void TryOpenSettingPage(object sender, RoutedEventArgs e)
 
             object item = tabView.TabItems.FirstOrDefault(x => ((TabViewItem)x).Content is SettingsPage);
             if (item != null) tabView.SelectedItem = item;
-            else mainWindow.AddNewTab(new SettingsPage(), "设置");
+            else mainWindow.AddNewTab(new SettingsPage(), "设置", new FontIconSource() { Glyph = "\ue713" });
         }
 
         private void ShowFlyout(object sender, RoutedEventArgs e)
diff --git a/Controls/FavoriteList.xaml b/Controls/FavoriteList.xaml
index fbfc814..fbb7d8e 100644
--- a/Controls/FavoriteList.xaml
+++ b/Controls/FavoriteList.xaml
@@ -26,16 +26,24 @@
 
             <ListView.ItemTemplate>
                 <DataTemplate x:DataType="local:WebsiteInfo">
-                    <StackPanel Orientation="Horizontal" Spacing="5">
-                        <ImageIcon Source="{x:Bind Icon}" Width="16" Height="16" />
-                        <TextBlock Text="{x:Bind Name}" VerticalAlignment="Center"/>
+                    <Grid ColumnSpacing="5">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                        <ImageIcon Grid.Column="0" Width="16" Height="16">
+                            <ImageIcon.Source>
+                                <BitmapImage UriSource="{x:Bind Icon}"/>
+                            </ImageIcon.Source>
+                        </ImageIcon>
+                        <TextBlock Grid.Column="1" Text="{x:Bind Name}" VerticalAlignment="Center"/>
                         <ToolTipService.ToolTip>
                             <StackPanel>
                                 <TextBlock Text="{x:Bind Name}" />
                                 <TextBlock Text="{x:Bind Uri}" />
                             </StackPanel>
                         </ToolTipService.ToolTip>
-                        <StackPanel.ContextFlyout>
+                        <Grid.ContextFlyout>
                             <MenuFlyout>
                                 <MenuFlyoutItem x:Uid="OpenInNewTab" Click="OpenFavoriteWebsiteInNewTab">
                                     <MenuFlyoutItem.Icon>
@@ -65,8 +73,8 @@
                                     </MenuFlyoutItem.Icon>
                                 </MenuFlyoutItem>
                             </MenuFlyout>
-                        </StackPanel.ContextFlyout>
-                    </StackPanel>
+                        </Grid.ContextFlyout>
+                    </Grid>
                 </DataTemplate>
             </ListView.ItemTemplate>
         </ListView>
diff --git a/Controls/ToolBar.xaml b/Controls/ToolBar.xaml
index 92214e5..4a444a1 100644
--- a/Controls/ToolBar.xaml
+++ b/Controls/ToolBar.xaml
@@ -6,16 +6,61 @@
     xmlns:local="using:Edge"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:converters="using:CommunityToolkit.WinUI.Converters"
     mc:Ignorable="d">
 
+    <UserControl.Resources>
+        <converters:StringVisibilityConverter x:Key="StringVisibilityConverter" />
+    </UserControl.Resources>
+
     <StackPanel Orientation="Horizontal">
-        <Button
+
+        <local:ToolButton
+            x:Name="ExtensionsButton"
+            Title="扩展" PlaceholderText="搜索扩展"
+            IconGlyph="&#xea86;"
+            TextChanged="SearchExtension">
+            <StackPanel Orientation="Vertical" Spacing="4">
+                <ListView x:Name="extensionsList" SelectionMode="Single">
+                    <ListView.ItemTemplate>
+                        <DataTemplate x:DataType="local:ExtensionInfo">
+                            <Grid ColumnSpacing="5">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="*" />
+                                    <ColumnDefinition Width="Auto" />
+                                </Grid.ColumnDefinitions>
+                                <TextBlock
+                                    Grid.Column="0" VerticalAlignment="Center"
+                                    Text="{x:Bind Name}" TextTrimming="CharacterEllipsis"/>
+                                <StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center" HorizontalAlignment="Right">
+                                    <HyperlinkButton Click="OpenExtensionOption" Visibility="{x:Bind OptionUri, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" DataContext="{x:Bind}">
+                                        <FontIcon Glyph="&#xe713;"/>
+                                    </HyperlinkButton>
+                                    <Grid Width="40">
+                                        <ToggleSwitch Toggled="ToggleExtension" IsOn="{x:Bind IsEnabled, Mode=OneWay}" DataContext="{x:Bind}" OffContent="" OnContent=""/>
+                                    </Grid>
+                                </StackPanel>
+                            </Grid>
+                        </DataTemplate>
+                    </ListView.ItemTemplate>
+                </ListView>
+                <MenuFlyoutSeparator/>
+                <HyperlinkButton HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" Click="OpenExtensionsPage">
+                    <StackPanel Orientation="Horizontal" Spacing="12">
+                        <FontIcon Glyph="&#xea86;"/>
+                        <TextBlock>管理扩展</TextBlock>
+                    </StackPanel>
+                </HyperlinkButton>
+            </StackPanel>
+        </local:ToolButton>
+
+        <AppBarSeparator x:Name="ToolBarSeparator"/>
+
+        <local:IconButton
             x:Name="SplitButton"
-            ToolTipService.ToolTip="分屏"
-            Style="{StaticResource AlternateCloseButtonStyle}"
-            Click="SplitWindow">
-            <FontIcon Glyph="&#xe106;" />
-        </Button>
+            TipText="分屏"
+            IconGlyph="&#xe736;"
+            ButtonClicked="SplitWindow" /><!--ReadingMode Icon-->
 
         <local:ToolButton
             x:Name="FavoriteButton"
diff --git a/Controls/ToolBar.xaml.cs b/Controls/ToolBar.xaml.cs
index 7fa3124..406c794 100644
--- a/Controls/ToolBar.xaml.cs
+++ b/Controls/ToolBar.xaml.cs
@@ -16,6 +16,7 @@ public class WebViewHistory
         public string Source { get; set; }
         public Uri FaviconUri { get; set; }
         public string Time { get; set; }
+        public ulong NavigationId { get; set; }
     }
 
     public partial class DownloadObject : INotifyPropertyChanged
@@ -60,7 +61,7 @@ private void Operation_BytesReceivedChanged(CoreWebView2DownloadOperation sender
             string received = Converters.ToFileSizeString(sender.BytesReceived);
             string total = Converters.ToFileSizeString(sender.TotalBytesToReceive);
             string speed = receivedDelta + "/s";
-            string information = $"{speed} - {received}/{total}��ʣ��ʱ�䣺{DateTime.Parse(sender.EstimatedEndTime) - DateTime.Now:hh\\:mm\\:ss}";
+            string information = $"{speed} - {received}/{total}，剩余时间：{DateTime.Parse(sender.EstimatedEndTime) - DateTime.Now:hh\\:mm\\:ss}";
             BytesReceived = sender.BytesReceived;
             DateTime = DateTime.Now;
             Information = information;
@@ -72,14 +73,31 @@ public sealed partial class ToolBar : UserControl
         public ToolBar()
         {
             this.InitializeComponent();
+            ExtensionsItem.InitializeExtensionsCollection();
+            extensionsList.ItemsSource = ExtensionsItem.Extensions;
             historyList.ItemsSource = App.Histories;
             downloadList.ItemsSource = App.DownloadList;
             FavoriteList.SetItemsPanel(FavoriteList.VerticalTemplate);
 
+            ExtensionsButton.Visibility = App.settings.ToolBar!["ExtensionsButton"] ? Visibility.Visible : Visibility.Collapsed;
+            ToolBarSeparator.Visibility = ExtensionsButton.Visibility;
             HistoryButton.Visibility = App.settings.ToolBar!["HistoryButton"] ? Visibility.Visible : Visibility.Collapsed;
             DownloadButton.Visibility = App.settings.ToolBar!["DownloadButton"] ? Visibility.Visible : Visibility.Collapsed;
         }
 
+        private void SearchExtension(object sender, RoutedEventArgs e)
+        {
+            string text = (sender as TextBox)?.Text ?? String.Empty;
+            if (text.Length == 0)
+            {
+                extensionsList.ItemsSource = ExtensionsItem.Extensions;
+            }
+            else
+            {
+                extensionsList.ItemsSource = ExtensionsItem.Extensions.Where(x => x.Name.Contains(text, StringComparison.OrdinalIgnoreCase)).ToList();
+            }
+        }
+
         private void SearchHistory(object sender, TextChangedEventArgs e)
         {
             string text = (sender as TextBox).Text;
@@ -110,13 +128,13 @@ public void ShowFlyout(string name)
         {
             switch (name)
             {
-                case "����":
+                case "下载":
                     DownloadButton.ShowFlyout();
                     break;
-                case "��ʷ��¼":
+                case "历史记录":
                     HistoryButton.ShowFlyout();
                     break;
-                case "�ղؼ�":
+                case "收藏夹":
                     FavoriteButton.ShowFlyout();
                     break;
             }
@@ -127,5 +145,36 @@ private void SplitWindow(object sender, RoutedEventArgs e)
             WebViewPage page = App.GetWindowForElement(this).SelectedItem as WebViewPage;
             page.CreateSplitWindow();
         }
+
+        private void OpenExtensionsPage(object sender, RoutedEventArgs e)
+        {
+            MainWindow mainWindow = App.GetWindowForElement(this);
+            TabView tabView = mainWindow.Content as TabView;
+
+            TabViewItem item = tabView?.TabItems.FirstOrDefault(x => ((TabViewItem)x).Content is SettingsPage settingsPage) as TabViewItem;
+            if (item != null)
+            {
+                tabView.SelectedItem = item;
+                (item.Content as SettingsPage)?.Navigate("ExtensionsItem");
+            }
+            else {
+                SettingsPage settingsPage = new ();
+                settingsPage.Navigate("ExtensionsItem");
+                mainWindow.AddNewTab(settingsPage, "设置", new FontIconSource() { Glyph = "\ue713" });
+            }
+        }
+
+        void ToggleExtension(object sender, RoutedEventArgs e) {
+            if (sender is ToggleSwitch { DataContext: ExtensionInfo extensionInfo }) {
+                ExtensionsItem.ExtensionsToggleEnabledAsync(extensionInfo, this.XamlRoot);
+            }
+        }
+
+        void OpenExtensionOption(object sender, RoutedEventArgs e) {
+            if (sender is Button { DataContext: ExtensionInfo extensionInfo }) {
+                MainWindow mainWindow = App.GetWindowForElement(this);
+                mainWindow.AddNewTab(new  WebViewPage(new Uri(extensionInfo.OptionUri)));
+            }
+        }
     }
 }
diff --git a/Data/DefaultSettings.json b/Data/DefaultSettings.json
index e404dd3..e2997fa 100644
--- a/Data/DefaultSettings.json
+++ b/Data/DefaultSettings.json
@@ -10,9 +10,13 @@
     "ShowSuggestUri": true,
     "SpecificUri": "",
     "StartBehavior": 0,
+    "DisableGpu": false,
+    "DisableBackgroundTimerThrottling": false,
+    "InjectExtensionsStore": true,
     "ToolBar": {
         "HomeButton": false,
         "ForwardButton": true,
+        "ExtensionsButton": true,
         "HistoryButton": true,
         "DownloadButton": true
     }
diff --git a/Data/InjectExtensionsStoreScript.js b/Data/InjectExtensionsStoreScript.js
new file mode 100644
index 0000000..3705f24
--- /dev/null
+++ b/Data/InjectExtensionsStoreScript.js
@@ -0,0 +1,103 @@
+﻿console.log("WinUIEdge is injecting");
+let styleAdded = false;
+const observer = new MutationObserver(function(mutationsList) {
+    for (let mutation of mutationsList) {
+        if (mutation.type === "childList") {
+            const addedNodes = mutation.addedNodes;
+            for (let node of addedNodes) {
+                findAndReplaceInstallButtons(node);
+            }
+        }
+    }
+});
+
+const rootElement = document.getElementById("root");
+if(rootElement) {
+    findAndReplaceInstallButtons(rootElement);
+    observer.observe(document.getElementById("root"), {childList: true, subtree: true, attributes: true, characterData: true});
+}
+
+function findAndReplaceInstallButtons(element) {
+    const originalInstallButtons = findInstallButtons(element);
+    if (originalInstallButtons.length === 0) {
+        return;
+    }
+    for (let originalInstallButton of originalInstallButtons) {
+        if (!styleAdded) {
+            addStyle();
+            styleAdded = true;
+        }
+        replaceButton(originalInstallButton);
+    }
+}
+
+function findInstallButtons(element) {
+    const result = [];
+    if (element.children && element.children.length > 0) {
+        for (let child of element.children) {
+            if (child.id && child.id.startsWith("installButton-") && !child.classList.contains("replacedInstallButton")) {
+                result.push(child);
+                continue;
+            }
+            result.push(...findInstallButtons(child));
+        }
+    }
+    return result;
+}
+
+function replaceButton(originalInstallButton){
+    const installAnchorElement = document.createElement("a");
+    installAnchorElement.href = `https://edge.microsoft.com/extensionwebstorebase/v1/crx?response=redirect&x=id%3D${originalInstallButton.id.slice(14)}%26installsource%3Dondemand`;
+    installAnchorElement.innerText = "获取";
+    installAnchorElement.download;
+    originalInstallButton.classList.add("replacedInstallButton");
+    originalInstallButton.removeChild(originalInstallButton.firstElementChild);
+    originalInstallButton.appendChild(installAnchorElement);
+}
+
+function addStyle(){
+    const styleElement = document.createElement("style");
+    styleElement.innerText = `.injectInstallButton {
+    background-color: var(--colorBrandBackground);
+    border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
+    border-radius: 4px;
+    box-sizing: border-box;
+    cursor: pointer;
+    font-family: var(--fontFamilyBase);
+    font-size: var(--fontSizeBase300);
+    font-weight: 600;
+    height: fit-content;
+    padding: 0;
+    outline-style: none;
+    transition: background var(--durationFaster) var(--curveEasyEase);
+    &>a {
+        color: var(--colorNeutralForegroundOnBrand);
+        display: block;
+        line-height: var(--lineHeightBase300);
+        padding: 5px var(--spacingHorizontalM);
+        text-decoration: none;
+    }
+    &:hover {
+        background-color: var(--colorBrandBackgroundHover);
+    }
+}
+.replacedInstallButton {
+    background-color: var(--colorBrandBackground);
+    cursor: pointer;
+    padding: 0;
+    &>a {
+        color: var(--colorNeutralForegroundOnBrand);
+        display: block;
+        padding: 5px var(--spacingHorizontalM);
+        text-decoration: none;
+    }
+    &:hover {
+        background-color: var(--colorBrandBackgroundHover);
+        cursor: pointer;
+    }
+}
+.incompatible {
+    display: none;
+}`;
+    document.head.appendChild(styleElement);
+}
\ No newline at end of file
diff --git a/Edge.csproj b/Edge.csproj
index ba29cd1..c23e4be 100644
--- a/Edge.csproj
+++ b/Edge.csproj
@@ -3,6 +3,7 @@
     <OutputType>WinExe</OutputType>
     <TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
     <TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
+    <WindowsSdkPackageVersion>10.0.26100.57</WindowsSdkPackageVersion>
     <RootNamespace>Edge</RootNamespace>
     <ApplicationManifest>app.manifest</ApplicationManifest>
     <Platforms>x64</Platforms>
@@ -12,18 +13,18 @@
     <EnableMsixTooling>true</EnableMsixTooling>
     <WindowsPackageType>None</WindowsPackageType>
     <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
-	  <PublishTrimmed>true</PublishTrimmed>
+    <PublishTrimmed>true</PublishTrimmed>
   </PropertyGroup>
 
   <ItemGroup>
     <Content Include="Assets\icon.ico" />
-    <Content Include="Data\*.json" />
+    <Content Include="Data\**" />
   </ItemGroup>
   <ItemGroup>
       <None Update="Assets\**">
           <CopyToOutputDirectory>Always</CopyToOutputDirectory>
       </None>
-      <None Update="Data\*.json">
+      <None Update="Data\**">
           <CopyToOutputDirectory>Always</CopyToOutputDirectory>
       </None>
   </ItemGroup>
@@ -70,7 +71,7 @@
     <ApplicationIcon>Assets\icon.ico</ApplicationIcon>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <DebugType>none</DebugType>
+    <DebugType>full</DebugType>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <DebugType>none</DebugType>
diff --git a/JsonLoad.cs b/JsonLoad.cs
index 76fa05b..e6ded38 100644
--- a/JsonLoad.cs
+++ b/JsonLoad.cs
@@ -27,13 +27,17 @@ public class Settings
         public bool ShowFlyoutWhenStartDownloading { get; set; }
         public string SpecificUri { get; set; }
         public int StartBehavior { get; set; }
+        public bool DisableGpu { get; set; }
+        public bool DisableBackgroundTimerThrottling { get; set; }
+        public bool InjectExtensionsStore { get; set; }
         public Dictionary<string, bool> ToolBar { get; set; }
     }
 
     public class WebsiteInfo
     {
         public string Name { get; set; }
-        public string Icon { get; set; }
+        public bool CustomIcon  { get; set; }
+        public Uri Icon { get; set; }
         public Uri Uri { get; set; }
     }
 
diff --git a/MainWindow.xaml b/MainWindow.xaml
index eca4e18..a20716d 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -145,6 +145,7 @@
                 <MenuFlyoutSeparator />
 
                 <MenuFlyoutItem
+                    Click="CreateNewTabOnRight"
                     Text="新建标签页">
                     <MenuFlyoutItem.KeyboardAccelerators>
                         <KeyboardAccelerator Key="T" Modifiers="Control" />
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 7d8cc5b..8e39c15 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -8,6 +8,8 @@
 using Windows.Win32;
 using Windows.Win32.Foundation;
 using Windows.Win32.UI.Shell;
+using Microsoft.UI;
+using Microsoft.UI.Xaml.Media;
 
 
 namespace Edge
@@ -62,22 +64,33 @@ public void AddHomePage()
             }
             else
             {
-                AddNewTab(new HomePage());
+                AddNewTab(new HomePage(), "主页", new FontIconSource() { Glyph = "\ue80f" });
             }
         }
 
-        public void AddNewTab(object content, string header = "主页", int index = -1)
+        public void AddNewTab(object content, string header = "正在加载…", IconSource icon = null, int index = -1)
         {
             TabViewItem newTab = new()
             {
-                IconSource = new FontIconSource() { Glyph = "\ue80f" },
-                Header = header,
                 Content = content
             };
             if (content is WebViewPage web)
             {
+                StackPanel stackPanel = new () { Orientation = Orientation.Horizontal };
+                ProgressRing progressRing = new() { Width = 16, Height = 16, Margin = new Thickness(0,0,16,0) };
+                TextBlock textBlock = new() { Text = header };
+                stackPanel.Children.Add(progressRing);
+                stackPanel.Children.Add(textBlock);
+                newTab.Header = stackPanel;
                 newTab.ContextFlyout = TabFlyout;
                 web.tabViewItem = newTab;
+                web.HeaderProgressRing = progressRing;
+                web.HeaderTextBlock = textBlock;
+            }
+            else
+            {
+                newTab.IconSource = icon ?? new FontIconSource() { Glyph = "\ue774" };
+                newTab.Header = header;
             }
 
             int insertIndex = index >= 0 ? index : tabView.TabItems.Count;
@@ -229,6 +242,10 @@ private void CloseTab(object sender, object e)
             {
                 Close();
             }
+            else if (App.NeedRestartEnvironment && !App.AnyWebviewPageExists())
+            {
+                App.WebView2.Close();
+            }
         }
 
         private void CloseOtherTab(object sender, RoutedEventArgs e)
diff --git a/Pages/TaskManager.xaml.cs b/Pages/TaskManager.xaml.cs
index eccf3a7..a4944de 100644
--- a/Pages/TaskManager.xaml.cs
+++ b/Pages/TaskManager.xaml.cs
@@ -37,7 +37,7 @@ public TaskManager()
 
         private async void InitialData()
         {
-            IReadOnlyList<CoreWebView2ProcessExtendedInfo> eInfos = await App.WebView2.CoreWebView2.Environment.GetProcessExtendedInfosAsync();
+            IReadOnlyList<CoreWebView2ProcessExtendedInfo> eInfos = await App.CoreWebView2Environment.GetProcessExtendedInfosAsync();
             foreach (CoreWebView2ProcessExtendedInfo item in eInfos)
             {
                 ProcessInfo info = new()
diff --git a/Pages/WebViewPage.xaml b/Pages/WebViewPage.xaml
index da940eb..c90946b 100644
--- a/Pages/WebViewPage.xaml
+++ b/Pages/WebViewPage.xaml
@@ -72,11 +72,10 @@
                 <ColumnDefinition x:Name="rightColumn" Width="0" />
             </Grid.ColumnDefinitions>
 
-            <WebView2 x:Name="WebViewEngine" CoreWebView2Initialized="WebView2Initialized" />
+            <WebView2 x:Name="WebViewEngine" />
             <WebView2
                 x:Name="RightWebView"
                 Visibility="Collapsed"
-                CoreWebView2Initialized="WebView2Initialized"
                 Grid.Column="1" />
             <controls:GridSplitter
                 Grid.Column="1"
diff --git a/Pages/WebViewPage.xaml.cs b/Pages/WebViewPage.xaml.cs
index 4db02b6..903674a 100644
--- a/Pages/WebViewPage.xaml.cs
+++ b/Pages/WebViewPage.xaml.cs
@@ -3,8 +3,12 @@
 using Microsoft.UI.Xaml.Media.Imaging;
 using Microsoft.Web.WebView2.Core;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
 using Windows.Foundation;
 
 namespace Edge
@@ -12,13 +16,21 @@ namespace Edge
     public sealed partial class WebViewPage : Page
     {
         public TabViewItem tabViewItem { get; set; }
+        public ProgressRing HeaderProgressRing { get; set; }
+        public TextBlock HeaderTextBlock { get; set; }
         private bool InFavoriteList = false;
+        private bool NavigationCompleted  = false;
+        private ulong NowNavigationId = 0;
+        private static readonly FontIconSource DefaultIconSource = new () { Glyph = "\ue774" };
+        private static string InjectExtensionsStoreScript = null;
+
+        private readonly ConcurrentDictionary<string, ImageIconSource> CachedIconSource = new ();
 
         public WebViewPage(Uri WebUri)
         {
             InitializeComponent();
             InitializeToolbarVisibility();
-            WebViewEngine.Source = WebUri;
+            Loaded += (sender, args) => InitializeWebView2Async(WebViewEngine, WebUri);
 
             favoriteList.Visibility = App.settings.MenuStatus == "Always" ? Visibility.Visible : Visibility.Collapsed;
         }
@@ -33,33 +45,46 @@ private void InitializeToolbarVisibility()
             homeButton.Visibility = App.settings.ToolBar!["HomeButton"] ? Visibility.Visible : Visibility.Collapsed;
         }
 
-        private void WebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
+        private async void InitializeWebView2Async(WebView2 WebView, Uri WebUri)
         {
-            sender.CoreWebView2.ContextMenuRequested += (s, args) => CoreWebView2_ContextMenuRequested(sender, s, args);
-            sender.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged;
-            sender.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
-            sender.CoreWebView2.DownloadStarting += CoreWebView2_DownloadStarting;
-            sender.CoreWebView2.FaviconChanged += CoreWebView2_FaviconChanged;
-            sender.CoreWebView2.NavigationStarting += (s, e) => Search.Text = e.Uri;
-            sender.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
-            sender.CoreWebView2.ScriptDialogOpening += CoreWebView2_ScriptDialogOpening;
-            sender.CoreWebView2.StatusBarTextChanged += (s, e) => uriPreview.Text = s.StatusBarText;
-            sender.CoreWebView2.WindowCloseRequested += CoreWebView2_WindowCloseRequested;
-
-            sender.CoreWebView2.Settings.IsStatusBarEnabled = false;
-            sender.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
+            InjectExtensionsStoreScript ??= await File.ReadAllTextAsync("./Data/InjectExtensionsStoreScript.js", Encoding.UTF8);
+            if (WebView.CoreWebView2 == null)
+            {
+                await WebView.EnsureCoreWebView2Async(App.CoreWebView2Environment);
+                CoreWebView2 coreWebView2 = WebView.CoreWebView2;
+                coreWebView2!.ContextMenuRequested += (s, args) => CoreWebView2_ContextMenuRequested(WebView, s, args);
+                coreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged;
+                coreWebView2.NavigationStarting += CoreWebView2_NavigationStarting;
+                coreWebView2.SourceChanged += CoreWebView2_SourceChanged;
+                coreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
+                coreWebView2.NavigationCompleted += CoreWebView2_NavigationCompleted;
+                coreWebView2.DownloadStarting += CoreWebView2_DownloadStarting;
+                coreWebView2.FaviconChanged += CoreWebView2_FaviconChanged;
+                coreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
+                coreWebView2.ScriptDialogOpening += CoreWebView2_ScriptDialogOpening;
+                coreWebView2.StatusBarTextChanged += (s, e) => uriPreview.Text = s.StatusBarText;
+                coreWebView2.WindowCloseRequested += CoreWebView2_WindowCloseRequested;
+                coreWebView2.Settings.IsStatusBarEnabled = false;
+                coreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
+            }
+            if (WebUri != null && WebUri != WebView.Source)
+            {
+                WebView.Source = WebUri;
+            }
+            WebView.Visibility = Visibility.Visible;
         }
 
-        private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
+        private void CoreWebView2_NavigationStarting(CoreWebView2 sender, CoreWebView2NavigationStartingEventArgs args)
         {
-            App.Histories.Add(new WebViewHistory()
-            {
-                DocumentTitle = sender.DocumentTitle,
-                Source = sender.Source,
-                FaviconUri = new Uri(sender.FaviconUri),
-                Time = DateTime.Now.ToString()
-            });
+            NavigationCompleted = false;
+            NowNavigationId = args.NavigationId;
+            Search.Text = args.Uri;
+            HeaderProgressRing.Visibility = Visibility.Visible;
+            tabViewItem.IconSource = null;
+        }
 
+        private void CoreWebView2_SourceChanged(CoreWebView2 sender, CoreWebView2SourceChangedEventArgs args)
+        {
             if (Info.Favorites.Where(x => x.Uri.Equals(sender.Source)).Any())
             {
                 InFavoriteList = true;
@@ -71,12 +96,69 @@ private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMC
             SetFavoriteIcon();
         }
 
+        private async void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
+        {
+            if (App.settings.InjectExtensionsStore && InjectExtensionsStoreScript != null && sender.Source.StartsWith("https://microsoftedge.microsoft.com/addons/"))
+            {
+               await sender.ExecuteScriptAsync(InjectExtensionsStoreScript);
+            }
+        }
+
+        private void CoreWebView2_NavigationCompleted(CoreWebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
+        {
+            NavigationCompleted = true;
+            App.Histories.Add(new WebViewHistory()
+            {
+                DocumentTitle = sender.DocumentTitle,
+                Source = sender.Source,
+                FaviconUri = sender.FaviconUri.Length > 0 ? new Uri(sender.FaviconUri) : null,
+                Time = DateTime.Now.ToString(),
+                NavigationId = args.NavigationId
+            });
+            if (sender.FaviconUri.Length > 0) {
+                if (!CachedIconSource.TryGetValue(sender.FaviconUri, out ImageIconSource iconSource))
+                {
+                    iconSource = new ImageIconSource()
+                    {
+                        ImageSource = new BitmapImage(new Uri(sender.FaviconUri))
+                    };
+                    CachedIconSource.TryAdd(sender.FaviconUri, iconSource);
+                }
+                tabViewItem.IconSource = iconSource;
+            }
+            else
+            {
+                tabViewItem.IconSource = DefaultIconSource;
+            }
+            HeaderProgressRing.Visibility = Visibility.Collapsed;
+        }
+
         private void CoreWebView2_FaviconChanged(CoreWebView2 sender, object args)
         {
-            tabViewItem.IconSource = new ImageIconSource()
+            if (!NavigationCompleted || sender.FaviconUri.Length == 0)
             {
-                ImageSource = new BitmapImage(new Uri(sender.FaviconUri))
-            };
+                return;
+            }
+            Uri faviconUri = new Uri(sender.FaviconUri);
+            if (!CachedIconSource.TryGetValue(sender.FaviconUri, out ImageIconSource iconSource))
+            {
+                iconSource = new ImageIconSource()
+                {
+                    ImageSource = new BitmapImage(faviconUri)
+                };
+                CachedIconSource.TryAdd(sender.FaviconUri, iconSource);
+            }
+            tabViewItem.IconSource = iconSource;
+            WebViewHistory history = App.Histories.FirstOrDefault(x => x.NavigationId == NowNavigationId, null);
+            if (history != null)
+            {
+                history.FaviconUri = faviconUri;
+            }
+            WebsiteInfo favorite = Info.Favorites.FirstOrDefault(x => !x.CustomIcon && x.Uri.Equals(faviconUri), null);
+            if (favorite != null)
+            {
+                favorite.Icon = faviconUri;
+            }
         }
 
         private void CoreWebView2_DocumentTitleChanged(CoreWebView2 sender, object args)
@@ -84,7 +166,7 @@ private void CoreWebView2_DocumentTitleChanged(CoreWebView2 sender, object args)
             string title = sender.DocumentTitle;
             MainWindow mainWindow = App.GetWindowForElement(this);
             mainWindow.Title = title;
-            tabViewItem.Header = title;
+            HeaderTextBlock.Text = title;
         }
 
         private void CoreWebView2_WindowCloseRequested(CoreWebView2 sender, object args)
@@ -329,7 +411,7 @@ private void FavoriteStateChanged(object sender, RoutedEventArgs e)
                 WebsiteInfo newInfo = new()
                 {
                     Name = WebViewEngine.CoreWebView2.DocumentTitle,
-                    Icon = WebViewEngine.CoreWebView2.FaviconUri,
+                    Icon = WebViewEngine.CoreWebView2.FaviconUri.Length > 0 ? new Uri(WebViewEngine.CoreWebView2.FaviconUri) : null,
                     Uri = WebViewEngine.Source
                 };
                 Info.Favorites.Add(newInfo);
@@ -356,8 +438,7 @@ public void CreateSplitWindow()
             if (RightWebView.Visibility == Visibility.Collapsed)
             {
                 rightColumn.Width = new GridLength(1, GridUnitType.Star);
-                RightWebView.Source = new("https://www.bing.com/");
-                RightWebView.Visibility = Visibility.Visible;
+                InitializeWebView2Async(RightWebView, WebViewEngine.Source);
             }
             else
             {
diff --git a/Settings/Appearance/AppearanceMainPage.xaml.cs b/Settings/Appearance/AppearanceMainPage.xaml.cs
index 7d3dc9e..12e1d44 100644
--- a/Settings/Appearance/AppearanceMainPage.xaml.cs
+++ b/Settings/Appearance/AppearanceMainPage.xaml.cs
@@ -11,12 +11,6 @@ public sealed partial class AppearanceMainPage : Page
 
         public List<string> themeList = [.. Enum.GetNames<ElementTheme>()];
         public List<string> effects = [.. Enum.GetNames<Effect>()];
-        public Dictionary<string, string> ToolBarName = new(){
-            {"HomeButton", "“开始”按钮"},
-            {"ForwardButton", "“前进”按钮"},
-            {"HistoryButton", "历史按钮"},
-            {"DownloadButton", "下载按钮"}
-        };
 
         public AppearanceMainPage()
         {
diff --git a/Settings/Appearance/ToolBarItem.xaml b/Settings/Appearance/ToolBarItem.xaml
index 42ab920..4c99904 100644
--- a/Settings/Appearance/ToolBarItem.xaml
+++ b/Settings/Appearance/ToolBarItem.xaml
@@ -27,7 +27,7 @@
                     <DataTemplate x:DataType="local:ToolBarVisual">
                         <controls:SettingsCard
                             Header="{x:Bind Description}">
-                            <ToggleSwitch IsOn="{x:Bind Visual}"
+                            <ToggleSwitch IsOn="{x:Bind Visual, Mode=OneWay}"
                                     Toggled="VisualChanged"/>
                         </controls:SettingsCard>
                     </DataTemplate>
diff --git a/Settings/Appearance/ToolBarItem.xaml.cs b/Settings/Appearance/ToolBarItem.xaml.cs
index cb76c0f..1e651aa 100644
--- a/Settings/Appearance/ToolBarItem.xaml.cs
+++ b/Settings/Appearance/ToolBarItem.xaml.cs
@@ -22,6 +22,7 @@ public sealed partial class ToolBarItem : Page
         public Dictionary<string, string> ToolBarName = new(){
             {"HomeButton", "“开始”按钮"},
             {"ForwardButton", "“前进”按钮"},
+            {"ExtensionsButton", "扩展按钮"},
             {"HistoryButton", "历史按钮"},
             {"DownloadButton", "下载按钮"}
         };
@@ -48,8 +49,13 @@ private void MenuStatusChanged(object sender, SelectionChangedEventArgs e)
 
         private void VisualChanged(object sender, RoutedEventArgs e)
         {
-            ToolBarVisual visual = (sender as ToggleSwitch).DataContext as ToolBarVisual;
-            App.settings.ToolBar[visual.Text] = (sender as ToggleSwitch).IsOn;
+            if (sender is ToggleSwitch { DataContext: ToolBarVisual visual } toggle) {
+                Dictionary<string, bool> toolbar = App.settings.ToolBar;
+                if (toolbar.TryGetValue(visual.Text, out bool oldValue) && oldValue != toggle.IsOn) {
+                    toolbar[visual.Text] = !oldValue;
+                    visual.Visual = !oldValue;
+                }
+            }
         }
     }
 }
diff --git a/Settings/DownloadItem.xaml.cs b/Settings/DownloadItem.xaml.cs
index 3f90aeb..0da75fa 100644
--- a/Settings/DownloadItem.xaml.cs
+++ b/Settings/DownloadItem.xaml.cs
@@ -10,7 +10,7 @@ public DownloadItem()
         {
             this.InitializeComponent();
 
-            DownloadFolderCard.Description = App.WebView2.CoreWebView2.Profile.DefaultDownloadFolderPath;
+            DownloadFolderCard.Description = App.CoreWebView2Profile.DefaultDownloadFolderPath;
 
             setDownloadBehavior.IsOn = App.settings.AskDownloadBehavior;
             setDownloadFlyout.IsOn = App.settings.ShowFlyoutWhenStartDownloading;
@@ -41,7 +41,7 @@ private async void ChangeDownloadFolder(object sender, Microsoft.UI.Xaml.RoutedE
 
             if (folder != null)
             {
-                DownloadFolderCard.Description = App.WebView2.CoreWebView2.Profile.DefaultDownloadFolderPath = folder.Name;
+                DownloadFolderCard.Description = App.CoreWebView2Profile.DefaultDownloadFolderPath = folder.Name;
             }
         }
     }
diff --git a/Settings/ExtensionsItem.xaml b/Settings/ExtensionsItem.xaml
new file mode 100644
index 0000000..94173dc
--- /dev/null
+++ b/Settings/ExtensionsItem.xaml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Page
+    x:Class="Edge.ExtensionsItem"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:Edge"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:controls="using:CommunityToolkit.WinUI.Controls"
+    xmlns:converters="using:CommunityToolkit.WinUI.Converters"
+    mc:Ignorable="d">
+    <Page.Resources>
+        <converters:StringVisibilityConverter x:Key="StringVisibilityConverter" />
+    </Page.Resources>
+    <ScrollView>
+        <StackPanel
+            Spacing="{StaticResource NavigationViewFrameSpacing}"
+            Margin="{StaticResource NavigationViewFrameMargin}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"></ColumnDefinition>
+                    <ColumnDefinition Width="Auto"></ColumnDefinition>
+                </Grid.ColumnDefinitions>
+                <TextBlock Text="已安装的扩展"
+                    Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}"
+                    Margin="0,10,0,0"/>
+                <StackPanel Grid.Column="1" Orientation="Horizontal">
+                    <Button Click="AddExtension">
+                        <StackPanel Orientation="Horizontal" Spacing="12">
+                            <FontIcon Glyph="&#xE7B8;"/>
+                            <TextBlock>加载解压缩的扩展</TextBlock>
+                        </StackPanel>
+                    </Button>
+                </StackPanel>
+            </Grid>
+            <ItemsView x:Name="extensionsList">
+                <ItemsView.ItemTemplate>
+                    <DataTemplate x:DataType="local:ExtensionInfo">
+                        <ItemContainer>
+                            <controls:SettingsCard Header="{x:Bind Name, Mode=OneWay}" Margin="0,0,0,5">
+                                <controls:SettingsCard.Description>
+                                    <StackPanel Orientation="Horizontal" Spacing="4">
+                                        <TextBlock FontWeight="Bold" Text="ID"/>
+                                        <TextBlock IsTextSelectionEnabled="true" Text="{x:Bind Id, Mode=OneWay}"/>
+                                    </StackPanel>
+                                </controls:SettingsCard.Description>
+                                <StackPanel Orientation="Horizontal" Spacing="16">
+                                    <ToggleSwitch Toggled="ToggleExtension" IsOn="{x:Bind IsEnabled, Mode=OneWay}" DataContext="{x:Bind}"/>
+                                    <Button Click="OpenExtensionOption" Visibility="{x:Bind OptionUri, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" DataContext="{x:Bind}">
+                                        <StackPanel Orientation="Horizontal" Spacing="8">
+                                            <FontIcon Glyph="&#xe713;"/>
+                                            <TextBlock>选项</TextBlock>
+                                        </StackPanel>
+                                    </Button>
+                                    <Button Click="RemoveExtension" DataContext="{x:Bind}">
+                                        <StackPanel Orientation="Horizontal" Spacing="8">
+                                            <FontIcon Glyph="&#xe74d;"/>
+                                            <TextBlock>删除</TextBlock>
+                                        </StackPanel>
+                                    </Button>
+                                </StackPanel>
+                            </controls:SettingsCard>
+                        </ItemContainer>
+                    </DataTemplate>
+                </ItemsView.ItemTemplate>
+            </ItemsView>
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"></ColumnDefinition>
+                    <ColumnDefinition Width="Auto"></ColumnDefinition>
+                </Grid.ColumnDefinitions>
+                <TextBlock Text="查找新扩展"
+                    Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}"
+                    Margin="0,10,0,0"/>
+                <ToggleSwitch Grid.Column="1" x:Name="setToggleInjectExtensionsStore"
+                    OnContent="此功能已开启"
+                    OffContent="此功能已关闭"
+                    Toggled="ToggleInjectExtensionsStore"/>
+            </Grid>
+            <controls:SettingsCard x:Name="microsoftEdgeExtensionsHome"
+                Click="OpenMicrosoftEdgeExtensionsHome">
+                <controls:SettingsCard.Header>
+                    <TextBlock x:Name="microsoftEdgeExtensionsHomeHeader" Foreground="{ThemeResource TextFillColorDisabledBrush}">获取 Microsoft Edge 扩展</TextBlock>
+                </controls:SettingsCard.Header>
+                <controls:SettingsCard.ActionIcon>
+                    <FontIcon Glyph="&#xE8A7;"/>
+                </controls:SettingsCard.ActionIcon>
+            </controls:SettingsCard>
+        </StackPanel>
+    </ScrollView>
+</Page>
diff --git a/Settings/ExtensionsItem.xaml.cs b/Settings/ExtensionsItem.xaml.cs
new file mode 100644
index 0000000..04535fb
--- /dev/null
+++ b/Settings/ExtensionsItem.xaml.cs
@@ -0,0 +1,267 @@
+using Microsoft.UI.Xaml.Controls;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Windows.Storage;
+using Windows.Storage.Pickers;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.Web.WebView2.Core;
+
+namespace Edge
+{
+    public class ExtensionInfo
+    {
+        public string Name;
+        public string Id;
+        public bool IsEnabled;
+        public string OptionUri;
+    }
+
+    public sealed partial class ExtensionsItem : Page
+    {
+        public static readonly ObservableCollection<ExtensionInfo> Extensions = [];
+
+        public static Dictionary<string, string> ExtensionOptionUriSuffixes = new()
+        {
+            { "暴力猴", "/options/index.html#settings" }
+        };
+
+        public static Brush TextFillColorDisabledBrush = null;
+
+        public ExtensionsItem()
+        {
+            this.InitializeComponent();
+            InitializeExtensionsCollection();
+            extensionsList.ItemsSource = Extensions;
+            setToggleInjectExtensionsStore.IsOn = App.settings.InjectExtensionsStore;
+            microsoftEdgeExtensionsHome.IsClickEnabled = setToggleInjectExtensionsStore.IsOn;
+            if (TextFillColorDisabledBrush == null)
+            {
+                TextFillColorDisabledBrush = (Brush)Application.Current.Resources["TextFillColorDisabledBrush"];
+            }
+            if (setToggleInjectExtensionsStore.IsOn)
+            {
+                microsoftEdgeExtensionsHomeHeader.ClearValue(TextBlock.ForegroundProperty);
+            }
+            else
+            {
+                microsoftEdgeExtensionsHomeHeader.Foreground = TextFillColorDisabledBrush;
+            }
+        }
+
+        public static async void InitializeExtensionsCollection()
+        {
+            IReadOnlyList<CoreWebView2BrowserExtension> extensions = await App.CoreWebView2Profile.GetBrowserExtensionsAsync();
+            foreach (var extension in extensions)
+            {
+                if (Extensions.All(x => x.Id != extension.Id))
+                {
+                    string optionUriSuffix = ExtensionOptionUriSuffixes.GetValueOrDefault(extension.Name, null);
+                    Extensions.Add(new ExtensionInfo() {
+                        Name = extension.Name,
+                        Id = extension.Id,
+                        IsEnabled = extension.IsEnabled,
+                        OptionUri = optionUriSuffix == null ? null : $"chrome-extension://{extension.Id}{optionUriSuffix}"
+                    });
+                }
+            }
+        }
+
+        private void AddExtension(object sender, RoutedEventArgs e)
+        {
+            ExtensionsAddAsync(sender);
+        }
+
+        public static async void ExtensionsAddAsync(object sender)
+        {
+            var senderButton = sender as Button;
+            if (senderButton == null)
+            {
+                return;
+            }
+            senderButton.IsEnabled = false;
+            FolderPicker openPicker = new();
+            MainWindow window = App.GetWindowForElement(senderButton);
+            IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
+            WinRT.Interop.InitializeWithWindow.Initialize(openPicker, hWnd);
+            openPicker.FileTypeFilter.Add("*");
+            openPicker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
+            StorageFolder folder = await openPicker.PickSingleFolderAsync();
+            if (folder != null)
+            {
+                try
+                {
+                    CoreWebView2BrowserExtension extension = await App.CoreWebView2Profile.AddBrowserExtensionAsync(folder.Path);
+                    string optionUriSuffix = ExtensionOptionUriSuffixes.GetValueOrDefault(extension.Name, null);
+                    Extensions.Add(new ExtensionInfo() {
+                        Name = extension.Name,
+                        Id = extension.Id,
+                        IsEnabled = extension.IsEnabled,
+                        OptionUri = optionUriSuffix == null ? null : $"chrome-extension://{extension.Id}{optionUriSuffix}"
+                    });
+                    await new ContentDialog()
+                    {
+                        Content = $"{extension.Name} 已添加到 WinUIEdge",
+                        XamlRoot = senderButton.XamlRoot,
+                        CloseButtonText = "好的",
+                        DefaultButton = ContentDialogButton.Close
+                    }
+                    .ShowAsync();
+                }
+                catch (Exception exception)
+                {
+                    await new ContentDialog()
+                    {
+                        Content = $"加载扩展失败：{exception}",
+                        XamlRoot = senderButton.XamlRoot,
+                        CloseButtonText = "好的",
+                        DefaultButton = ContentDialogButton.Close
+                    }
+                    .ShowAsync();
+                }
+            }
+            senderButton.IsEnabled = true;
+        }
+
+        private void ToggleExtension(object sender, RoutedEventArgs e)
+        {
+            if (sender is ToggleSwitch { DataContext: ExtensionInfo extensionInfo })
+            {
+                ExtensionsToggleEnabledAsync(extensionInfo, this.XamlRoot);
+            }
+        }
+
+        public static async void ExtensionsToggleEnabledAsync(ExtensionInfo extensionInfo, XamlRoot xamlRoot)
+        {
+            IReadOnlyList<CoreWebView2BrowserExtension> extensions = await App.CoreWebView2Profile.GetBrowserExtensionsAsync();
+            bool found = false;
+            foreach (CoreWebView2BrowserExtension extension in extensions)
+            {
+                if (extension.Id == extensionInfo.Id)
+                {
+                    try
+                    {
+                        await extension.EnableAsync(extension.IsEnabled ? false : true);
+                    }
+                    catch (Exception exception) {
+                        await new ContentDialog() {
+                            Content = $"开关扩展失败，原因: {exception}",
+                            XamlRoot = xamlRoot,
+                            CloseButtonText = "好的",
+                            DefaultButton = ContentDialogButton.Close
+                        }
+                        .ShowAsync();
+                    }
+                    extensionInfo.IsEnabled = extension.IsEnabled;
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+            {
+                Extensions.Remove(extensionInfo);
+                await new ContentDialog()
+                {
+                    Content = "开关失败，原因：未找到该扩展",
+                    XamlRoot = xamlRoot,
+                    CloseButtonText = "好的",
+                    DefaultButton = ContentDialogButton.Close
+                }
+                .ShowAsync();
+            }
+        }
+
+        private void RemoveExtension(object sender, RoutedEventArgs e)
+        {
+            if (sender is Button { DataContext: ExtensionInfo extensionInfo }) {
+                ExtensionsRemoveAsync(extensionInfo, this.XamlRoot);
+            }
+        }
+
+
+        public static async void ExtensionsRemoveAsync(ExtensionInfo extensionInfo, XamlRoot xamlRoot)
+        {
+            if (await new ContentDialog()
+                {
+                    Content = $"是否从 WinUIEdge 中删除 \"{extensionInfo.Name}\"？",
+                    PrimaryButtonText = "删除",
+                    SecondaryButtonText = "取消",
+                    DefaultButton = ContentDialogButton.Primary,
+                    XamlRoot = xamlRoot
+                }
+                .ShowAsync() == ContentDialogResult.Primary)
+            {
+                IReadOnlyList<CoreWebView2BrowserExtension> extensions = await App.CoreWebView2Profile.GetBrowserExtensionsAsync();
+                bool found = false;
+                foreach (CoreWebView2BrowserExtension extension in extensions) {
+                    if (extension.Id == extensionInfo.Id)
+                    {
+                        try
+                        {
+                            await extension.RemoveAsync();
+                            Extensions.Remove(extensionInfo);
+                        }
+                        catch (Exception exception)
+                        {
+                            await new ContentDialog()
+                            {
+                                Content = $"移除失败，原因：{exception}",
+                                XamlRoot = xamlRoot,
+                                CloseButtonText = "好的",
+                                DefaultButton = ContentDialogButton.Close
+                            }
+                            .ShowAsync();
+                        }
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found)
+                {
+                    Extensions.Remove(extensionInfo);
+                    await new ContentDialog()
+                    {
+                        Content = "移除失败，原因：未找到该扩展",
+                        XamlRoot = xamlRoot,
+                        CloseButtonText = "好的",
+                        DefaultButton = ContentDialogButton.Close
+                    }
+                    .ShowAsync();
+                }
+            }
+        }
+
+        private void OpenExtensionOption(object sender, RoutedEventArgs e)
+        {
+            if (sender is Button { DataContext: ExtensionInfo { OptionUri: not null } extensionInfo }) {
+                MainWindow mainWindow = App.GetWindowForElement(this);
+                mainWindow.AddNewTab(new WebViewPage(new Uri(extensionInfo.OptionUri)));
+            }
+        }
+
+        void ToggleInjectExtensionsStore(object sender, RoutedEventArgs e)
+        {
+            if (App.settings.InjectExtensionsStore != setToggleInjectExtensionsStore.IsOn)
+            {
+                App.settings.InjectExtensionsStore = setToggleInjectExtensionsStore.IsOn;
+                microsoftEdgeExtensionsHome.IsClickEnabled = setToggleInjectExtensionsStore.IsOn;
+                if (setToggleInjectExtensionsStore.IsOn)
+                {
+                    microsoftEdgeExtensionsHomeHeader.ClearValue(TextBlock.ForegroundProperty);
+                }
+                else
+                {
+                    microsoftEdgeExtensionsHomeHeader.Foreground = TextFillColorDisabledBrush;
+                }
+            }
+        }
+
+        private void OpenMicrosoftEdgeExtensionsHome(object sender, RoutedEventArgs e)
+        {
+            MainWindow mainWindow = App.GetWindowForElement(this);
+            mainWindow.AddNewTab(new WebViewPage(new Uri("https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home")));
+        }
+    }
+}
diff --git a/Settings/Privacy/ClearDataItem.xaml.cs b/Settings/Privacy/ClearDataItem.xaml.cs
index c104446..0dca327 100644
--- a/Settings/Privacy/ClearDataItem.xaml.cs
+++ b/Settings/Privacy/ClearDataItem.xaml.cs
@@ -37,7 +37,7 @@ private async void ClearBrowsingData(object sender, RoutedEventArgs e)
             {
                 if (item.IsChecked)
                 {
-                    await App.WebView2.CoreWebView2.Profile.ClearBrowsingDataAsync(item.Kind);
+                    await App.CoreWebView2Profile.ClearBrowsingDataAsync(item.Kind);
                 }
             }
             ClearBrowsingDataButton.Description = "已清理选择的项目";
diff --git a/Settings/Privacy/SafetyItem.xaml.cs b/Settings/Privacy/SafetyItem.xaml.cs
index 11f5f70..ddf7c4b 100644
--- a/Settings/Privacy/SafetyItem.xaml.cs
+++ b/Settings/Privacy/SafetyItem.xaml.cs
@@ -8,12 +8,12 @@ public sealed partial class SafetyItem : Page
         public SafetyItem()
         {
             this.InitializeComponent();
-            msSmartScreen.IsOn = App.WebView2.CoreWebView2.Settings.IsReputationCheckingRequired;
+            msSmartScreen.IsOn = App.CoreWebView2.Settings.IsReputationCheckingRequired;
         }
 
         private void SmartScreenChanged(object sender, RoutedEventArgs e)
         {
-            App.WebView2.CoreWebView2.Settings.IsReputationCheckingRequired = (sender as ToggleSwitch).IsOn;
+            App.CoreWebView2.Settings.IsReputationCheckingRequired = (sender as ToggleSwitch).IsOn;
         }
     }
 }
diff --git a/Settings/Privacy/TrackItem.xaml.cs b/Settings/Privacy/TrackItem.xaml.cs
index c82c871..f1825a0 100644
--- a/Settings/Privacy/TrackItem.xaml.cs
+++ b/Settings/Privacy/TrackItem.xaml.cs
@@ -17,13 +17,13 @@ public TrackItem()
             this.InitializeComponent();
             trackBox.ItemsSource = tracks;
 
-            var level = App.WebView2.CoreWebView2.Profile.PreferredTrackingPreventionLevel;
+            var level = App.CoreWebView2Profile.PreferredTrackingPreventionLevel;
             trackBox.SelectedIndex = trackLevelList.IndexOf(level);
         }
 
         private void TrackLevelChanged(object sender, SelectionChangedEventArgs e)
         {
-            App.WebView2.CoreWebView2.Profile.PreferredTrackingPreventionLevel = trackLevelList[trackBox.SelectedIndex];
+            App.CoreWebView2Profile.PreferredTrackingPreventionLevel = trackLevelList[trackBox.SelectedIndex];
         }
     }
 }
diff --git a/Settings/SettingsPage.xaml b/Settings/SettingsPage.xaml
index 8fcc375..22a6a50 100644
--- a/Settings/SettingsPage.xaml
+++ b/Settings/SettingsPage.xaml
@@ -30,11 +30,21 @@
                     <FontIcon Glyph="&#xEc6c;" />
                 </NavigationViewItem.Icon>
             </NavigationViewItem>
+            <NavigationViewItem x:Uid="Extensions" Tag="ExtensionsItem">
+                <NavigationViewItem.Icon>
+                    <FontIcon Glyph="&#xea86;" />
+                </NavigationViewItem.Icon>
+            </NavigationViewItem>
             <NavigationViewItem x:Uid="Navigation_Downloads" Tag="DownloadItem">
                 <NavigationViewItem.Icon>
                     <FontIcon Glyph="&#xe896;" />
                 </NavigationViewItem.Icon>
             </NavigationViewItem>
+            <NavigationViewItem x:Uid="System" Tag="SystemItem">
+                <NavigationViewItem.Icon>
+                    <FontIcon Glyph="&#xE7F8;" />
+                </NavigationViewItem.Icon>
+            </NavigationViewItem>
             <NavigationViewItem x:Uid="Printers" Tag="PrinterItem">
                 <NavigationViewItem.Icon>
                     <FontIcon Glyph="&#xe749;" />
@@ -70,7 +80,7 @@
                         <BreadcrumbBarItem Content="{Binding}">
                             <BreadcrumbBarItem.ContentTemplate>
                                 <DataTemplate>
-                                    <TextBlock Text="{Binding Header}" />
+                                    <TextBlock Text="{Binding Header}" Style="{StaticResource TitleTextBlockStyle}"/>
                                 </DataTemplate>
                             </BreadcrumbBarItem.ContentTemplate>
                         </BreadcrumbBarItem>
diff --git a/Settings/SettingsPage.xaml.cs b/Settings/SettingsPage.xaml.cs
index 4cd3a7d..021553d 100644
--- a/Settings/SettingsPage.xaml.cs
+++ b/Settings/SettingsPage.xaml.cs
@@ -1,6 +1,7 @@
 using Microsoft.UI.Xaml.Controls;
 using System;
 using System.Collections.ObjectModel;
+using System.Linq;
 
 namespace Edge
 {
@@ -31,6 +32,19 @@ private void NavigationView_SelectionChanged(NavigationView sender, NavigationVi
             });
         }
 
+        public void Navigate(string tag)
+        {
+            NavigationViewItem menuItem = navigation.MenuItems.First(x => (string)(x as NavigationViewItem)?.Tag == tag) as  NavigationViewItem;
+            if (menuItem != null) {
+                navigation.SelectedItem = menuItem;
+                UpdateBreadcrumb(new()
+                {
+                    Header = menuItem.Content?.ToString(),
+                    Type = Type.GetType("Edge." + tag)
+                });
+            }
+        }
+
         private void UpdateBreadcrumb(NavigationItem item)
         {
             BreadcrumbBarItems.Clear();
diff --git a/Settings/SystemItem.xaml b/Settings/SystemItem.xaml
new file mode 100644
index 0000000..b7e7a8b
--- /dev/null
+++ b/Settings/SystemItem.xaml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Page
+    x:Class="Edge.SystemItem"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:Edge"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:controls="using:CommunityToolkit.WinUI.Controls"
+    mc:Ignorable="d">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <ScrollView Grid.Row="0">
+            <StackPanel
+                Spacing="{StaticResource NavigationViewFrameSpacing}"
+                Margin="{StaticResource NavigationViewFrameMargin}">
+                <TextBlock Text="系统"
+                    Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}"
+                    Margin="0,10,0,0"/>
+                <controls:SettingsCard
+                    Header="在可用时使用图形加速">
+                    <ToggleSwitch x:Name="setToggleEnableGpu"
+                        Toggled="ToggleEnableGpu"/>
+                </controls:SettingsCard>
+                <controls:SettingsCard
+                    Header="打开计算机的代理设置"
+                    Click="OpenSettingsProxy"
+                    IsClickEnabled="True">
+                    <controls:SettingsCard.ActionIcon>
+                        <FontIcon Glyph="&#xE8A7;"/>
+                    </controls:SettingsCard.ActionIcon>
+                </controls:SettingsCard>
+                <TextBlock Text="性能"
+                    Style="{ThemeResource FlyoutPickerTitleTextBlockStyle}"
+                    Margin="0,10,0,0"/>
+                <controls:SettingsCard
+                    Header="禁用后台页面的计时器任务限制"
+                    Description="将导致耗电增加">
+                    <ToggleSwitch x:Name="setToggleDisableBackgroundTimerThrottling"
+                        Toggled="ToggleDisableBackgroundTimerThrottling"/>
+                </controls:SettingsCard>
+            </StackPanel>
+        </ScrollView>
+        <InfoBar x:Name="restartInfoBar" Grid.Row="1" Message="若要使此设置更改生效，请重启浏览器" IsClosable="False" HorizontalAlignment="Center">
+            <InfoBar.ActionButton>
+                <Button Style="{StaticResource AccentButtonStyle}" Click="CloseAllWebviews">
+                    <StackPanel Orientation="Horizontal" Spacing="5">
+                        <FontIcon Glyph="&#xe777;"/>
+                        <TextBlock Text="重启"/>
+                    </StackPanel>
+                </Button>
+            </InfoBar.ActionButton>
+        </InfoBar>
+    </Grid>
+</Page>
diff --git a/Settings/SystemItem.xaml.cs b/Settings/SystemItem.xaml.cs
new file mode 100644
index 0000000..0aea50e
--- /dev/null
+++ b/Settings/SystemItem.xaml.cs
@@ -0,0 +1,68 @@
+using Microsoft.UI.Xaml.Controls;
+using System;
+using System.Collections.Generic;
+using Microsoft.UI.Xaml;
+
+namespace Edge
+{
+    public sealed partial class SystemItem : Page
+    {
+        public SystemItem()
+        {
+            this.InitializeComponent();
+            setToggleEnableGpu.IsOn = !App.settings.DisableGpu;
+            setToggleDisableBackgroundTimerThrottling.IsOn = App.settings.DisableBackgroundTimerThrottling;
+            restartInfoBar.IsOpen = App.NeedRestartEnvironment;
+        }
+
+        private void ToggleEnableGpu(object sender, RoutedEventArgs e)
+        {
+            if (App.settings.DisableGpu == setToggleEnableGpu.IsOn)
+            {
+                App.NeedRestartEnvironment = true;
+                App.settings.DisableGpu = !setToggleEnableGpu.IsOn;
+                restartInfoBar.IsOpen = true;
+            }
+        }
+
+        private async void OpenSettingsProxy(object sender, RoutedEventArgs e)
+        {
+            await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:network-proxy"));
+        }
+
+        private void ToggleDisableBackgroundTimerThrottling(object sender, RoutedEventArgs e)
+        {
+            if (App.settings.DisableBackgroundTimerThrottling != setToggleDisableBackgroundTimerThrottling.IsOn)
+            {
+                App.NeedRestartEnvironment = true;
+                App.settings.DisableBackgroundTimerThrottling = setToggleDisableBackgroundTimerThrottling.IsOn;
+                restartInfoBar.IsOpen = true;
+            }
+        }
+
+        private void CloseAllWebviews(object sender, RoutedEventArgs e)
+        {
+            if (App.NeedRestartEnvironment)
+            {
+                List<TabViewItem> tabs = [];
+                foreach (MainWindow window in App.mainWindows)
+                {
+                    foreach (object tabItem in window.TabView.TabItems)
+                    {
+                        if (tabItem is TabViewItem { Content: WebViewPage webViewPage } tabViewItem)
+                        {
+                            webViewPage.Close();
+                            tabs.Add(tabViewItem);
+                        }
+                    }
+                    foreach (TabViewItem tabViewItem in tabs)
+                    {
+                        window.TabView.TabItems.Remove(tabViewItem);
+                    }
+                }
+                App.WebView2.Close();
+            }
+            restartInfoBar.IsOpen = false;
+        }
+    }
+}
diff --git a/Strings/en-US/Resources.resw b/Strings/en-US/Resources.resw
index 6e76ddf..d2f605c 100644
--- a/Strings/en-US/Resources.resw
+++ b/Strings/en-US/Resources.resw
@@ -216,9 +216,15 @@
   <data name="Start.Content" xml:space="preserve">
     <value>Start, home, and new tab page</value>
   </data>
+  <data name="Extensions.Content" xml:space="preserve">
+    <value>Extensions</value>
+  </data>
   <data name="Navigation_Downloads.Content" xml:space="preserve">
     <value>Downloads</value>
   </data>
+  <data name="System.Content" xml:space="preserve">
+    <value>System and performance</value>
+  </data>
   <data name="Printers.Content" xml:space="preserve">
     <value>Printers</value>
   </data>
diff --git a/Strings/zh-CN/Resources.resw b/Strings/zh-CN/Resources.resw
index c79b9fe..f702de8 100644
--- a/Strings/zh-CN/Resources.resw
+++ b/Strings/zh-CN/Resources.resw
@@ -216,9 +216,15 @@
   <data name="Start.Content" xml:space="preserve">
     <value>启动、主页和新建选项卡页</value>
   </data>
+  <data name="Extensions.Content" xml:space="preserve">
+    <value>扩展</value>
+  </data>
   <data name="Navigation_Downloads.Content" xml:space="preserve">
     <value>下载</value>
   </data>
+  <data name="System.Content" xml:space="preserve">
+    <value>系统和性能</value>
+  </data>
   <data name="Printers.Content" xml:space="preserve">
     <value>打印机</value>
   </data>


Reviews:
user: login: wtcpython
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-03T00:42:44Z
user: login: XiaofengdiZhu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T02:07:44Z
user: login: XiaofengdiZhu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T02:55:34Z
user: login: wtcpython
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-03T04:16:04Z

Review comments:
path: Edge.csproj
user: login: wtcpython
body: Platforms 改为 Platform 会导致 VS 打开项目报错
created_at: 2025-05-03T00:42:44Z
author_association: OWNER
path: Edge.csproj
user: login: XiaofengdiZhu
body: 在Rider这边，当WindowsAppSDKSelfContained为true时，必须要设置Platform  
你vs试试这样做：把csproj的Platform和Platforms都删掉，把PublishProfile改成win-x64.pubxml，然后把WindowsAppSDKSelfContained移动到win-x64.pubxml里面。毕竟win-x64.pubxml里有Platform
created_at: 2025-05-03T02:07:44Z
author_association: CONTRIBUTOR
path: Edge.csproj
user: login: XiaofengdiZhu
body: 上面方法有效吗？
created_at: 2025-05-03T02:55:33Z
author_association: CONTRIBUTOR
path: Edge.csproj
user: login: wtcpython
body: 有点忙，还没试

created_at: 2025-05-03T04:16:04Z
author_association: OWNER

PR comments:
user: login: wtcpython
created_at: 2025-05-03T01:52:29Z
body: 这个 js 注入怎么好像没有生效？
author_association: OWNER
user: login: XiaofengdiZhu
created_at: 2025-05-03T02:14:35Z
body: > 这个 js 注入怎么好像没有生效？

我也重现了……网络好时，CoreWebView2_SourceChanged事件甚至比异步文件读取完成还要先发生，我改成启动APP时立即读取吧~
另外方便加Q方便聊吗？⑨①⑥⓪⓪⑥⑨④⑨
author_association: CONTRIBUTOR