diff --git a/src/PerfView.Tests/GuiUtilities/WebBrowserWindowUserDataFolderTests.cs b/src/PerfView.Tests/GuiUtilities/WebBrowserWindowUserDataFolderTests.cs new file mode 100644 index 000000000..8c392b096 --- /dev/null +++ b/src/PerfView.Tests/GuiUtilities/WebBrowserWindowUserDataFolderTests.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using PerfView.GuiUtilities; +using Xunit; + +namespace PerfViewTests.GuiUtilities +{ + public class WebBrowserWindowUserDataFolderTests + { + [Theory] + [InlineData(@"C:\Users\Test\AppData\Local", @"C:\Users\Test\AppData\Local\PerfView\WebView2")] + [InlineData(@"C:\Users\Test User\AppData\Local", @"C:\Users\Test User\AppData\Local\PerfView\WebView2")] + [InlineData("C:\\Users\\T\u00e9st\\AppData\\Local", "C:\\Users\\T\u00e9st\\AppData\\Local\\PerfView\\WebView2")] + [InlineData(@"C:\Users\Test\AppData\Local\", @"C:\Users\Test\AppData\Local\PerfView\WebView2")] + [InlineData(@"D:\LocalData", @"D:\LocalData\PerfView\WebView2")] + public void UserDataFolderIsUnderLocalApplicationData(string localApplicationData, string expected) + { + Assert.Equal(expected, WebBrowserWindow.GetUserDataFolder(localApplicationData)); + } + + [Fact] + public void UserDataFolderUsesWindowsLocalApplicationData() + { + string localApplicationData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + + Assert.False(string.IsNullOrEmpty(localApplicationData)); + Assert.Equal( + Path.Combine(localApplicationData, "PerfView", "WebView2"), + WebBrowserWindow.GetUserDataFolder(localApplicationData)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("LocalData")] + [InlineData(@".\LocalData")] + [InlineData(@"..\LocalData")] + [InlineData(@"C:LocalData")] + [InlineData("C:")] + [InlineData(@"\LocalData")] + [InlineData("/LocalData")] + public void MissingOrRelativeLocalApplicationDataIsRejected(string localApplicationData) + { + var exception = Assert.Throws( + () => WebBrowserWindow.GetUserDataFolder(localApplicationData)); + + Assert.Contains("LocalApplicationData", exception.Message); + } + } +} diff --git a/src/PerfView/GuiUtilities/WebBrowser/WebBrowser.xaml.cs b/src/PerfView/GuiUtilities/WebBrowser/WebBrowser.xaml.cs index 64f58f2bc..6b734fd8c 100644 --- a/src/PerfView/GuiUtilities/WebBrowser/WebBrowser.xaml.cs +++ b/src/PerfView/GuiUtilities/WebBrowser/WebBrowser.xaml.cs @@ -6,7 +6,6 @@ using System.Windows.Input; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; -using Utilities; namespace PerfView.GuiUtilities @@ -141,7 +140,8 @@ private void Browser_Loaded(object sender, RoutedEventArgs e) return; } - var userDataFolder = Path.Combine(SupportFiles.SupportFileDir, "WebView2"); + // Support files may be installed in a read-only directory; browser state must remain per-user. + var userDataFolder = GetUserDataFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); Directory.CreateDirectory(userDataFolder); var environmentAwaiter = CoreWebView2Environment @@ -176,6 +176,19 @@ private void Browser_Loaded(object sender, RoutedEventArgs e) }); } + internal static string GetUserDataFolder(string localApplicationData) + { + // On Windows, roots such as "C:" and "\" still depend on the current directory or drive. + if (string.IsNullOrWhiteSpace(localApplicationData) || + !Path.IsPathRooted(localApplicationData) || + Path.GetPathRoot(localApplicationData).Length < 3) + { + throw new InvalidOperationException("A fully qualified LocalApplicationData directory is required for PerfView's WebView2 browser data."); + } + + return Path.Combine(localApplicationData, "PerfView", "WebView2"); + } + private const string CloseWindowMessage = "PerfView.CloseWindow"; private const string CloseWindowMessageJson = "\"" + CloseWindowMessage + "\""; private const string CloseWindowOnEscapeScript = @"