Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dotnet] Annotate nullability on Firefox profile #15207

Merged
merged 1 commit into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 40 additions & 43 deletions dotnet/src/webdriver/Firefox/FirefoxProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@
using OpenQA.Selenium.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Text.Json;

#nullable enable

namespace OpenQA.Selenium.Firefox
{
/// <summary>
Expand All @@ -32,13 +35,10 @@ namespace OpenQA.Selenium.Firefox
public class FirefoxProfile
{
private const string UserPreferencesFileName = "user.js";

private string profileDir;
private string sourceProfileDir;
private bool deleteSource;
private bool deleteOnClean = true;
private Preferences profilePreferences;
private Dictionary<string, FirefoxExtension> extensions = new Dictionary<string, FirefoxExtension>();
private readonly string? sourceProfileDir;
private readonly bool deleteSource;
private readonly Preferences profilePreferences;
private readonly Dictionary<string, FirefoxExtension> extensions = new Dictionary<string, FirefoxExtension>();

/// <summary>
/// Initializes a new instance of the <see cref="FirefoxProfile"/> class.
Expand All @@ -53,7 +53,7 @@ public FirefoxProfile()
/// specific profile directory.
/// </summary>
/// <param name="profileDirectory">The directory containing the profile.</param>
public FirefoxProfile(string profileDirectory)
public FirefoxProfile(string? profileDirectory)
: this(profileDirectory, false)
{
}
Expand All @@ -64,31 +64,24 @@ public FirefoxProfile(string profileDirectory)
/// </summary>
/// <param name="profileDirectory">The directory containing the profile.</param>
/// <param name="deleteSourceOnClean">Delete the source directory of the profile upon cleaning.</param>
public FirefoxProfile(string profileDirectory, bool deleteSourceOnClean)
public FirefoxProfile(string? profileDirectory, bool deleteSourceOnClean)
{
this.sourceProfileDir = profileDirectory;
this.deleteSource = deleteSourceOnClean;
this.ReadDefaultPreferences();
this.profilePreferences = this.ReadDefaultPreferences();
this.profilePreferences.AppendPreferences(this.ReadExistingPreferences());
}

/// <summary>
/// Gets the directory containing the profile.
/// </summary>
public string ProfileDirectory
{
get { return this.profileDir; }
}
public string? ProfileDirectory { get; private set; }

/// <summary>
/// Gets or sets a value indicating whether to delete this profile after use with
/// the <see cref="FirefoxDriver"/>.
/// </summary>
public bool DeleteAfterUse
{
get { return this.deleteOnClean; }
set { this.deleteOnClean = value; }
}
public bool DeleteAfterUse { get; set; } = true;

/// <summary>
/// Converts a base64-encoded string into a <see cref="FirefoxProfile"/>.
Expand Down Expand Up @@ -130,6 +123,7 @@ public void AddExtension(string extensionToInstall)
/// </summary>
/// <param name="name">The name of the preference to add.</param>
/// <param name="value">A <see cref="string"/> value to add to the profile.</param>
/// <exception cref="ArgumentNullException">If <paramref name="name"/> or <paramref name="value"/> are <see langword="null"/>.</exception>
public void SetPreference(string name, string value)
{
this.profilePreferences.SetPreference(name, value);
Expand All @@ -140,6 +134,7 @@ public void SetPreference(string name, string value)
/// </summary>
/// <param name="name">The name of the preference to add.</param>
/// <param name="value">A <see cref="int"/> value to add to the profile.</param>
/// <exception cref="ArgumentNullException">If <paramref name="name"/> is <see langword="null"/>.</exception>
public void SetPreference(string name, int value)
{
this.profilePreferences.SetPreference(name, value);
Expand All @@ -150,6 +145,7 @@ public void SetPreference(string name, int value)
/// </summary>
/// <param name="name">The name of the preference to add.</param>
/// <param name="value">A <see cref="bool"/> value to add to the profile.</param>
/// <exception cref="ArgumentNullException">If <paramref name="name"/> is <see langword="null"/>.</exception>
public void SetPreference(string name, bool value)
{
this.profilePreferences.SetPreference(name, value);
Expand All @@ -158,22 +154,23 @@ public void SetPreference(string name, bool value)
/// <summary>
/// Writes this in-memory representation of a profile to disk.
/// </summary>
[MemberNotNull(nameof(ProfileDirectory))]
public void WriteToDisk()
{
this.profileDir = GenerateProfileDirectoryName();
this.ProfileDirectory = GenerateProfileDirectoryName();
if (!string.IsNullOrEmpty(this.sourceProfileDir))
{
FileUtilities.CopyDirectory(this.sourceProfileDir, this.profileDir);
FileUtilities.CopyDirectory(this.sourceProfileDir, this.ProfileDirectory);
}
else
{
Directory.CreateDirectory(this.profileDir);
Directory.CreateDirectory(this.ProfileDirectory);
}

this.InstallExtensions();
this.DeleteLockFiles();
this.DeleteExtensionsCache();
this.UpdateUserPreferences();
this.InstallExtensions(this.ProfileDirectory);
this.DeleteLockFiles(this.ProfileDirectory);
this.DeleteExtensionsCache(this.ProfileDirectory);
this.UpdateUserPreferences(this.ProfileDirectory);
}

/// <summary>
Expand All @@ -185,9 +182,9 @@ public void WriteToDisk()
/// is deleted.</remarks>
public void Clean()
{
if (this.deleteOnClean && !string.IsNullOrEmpty(this.profileDir) && Directory.Exists(this.profileDir))
if (this.DeleteAfterUse && !string.IsNullOrEmpty(this.ProfileDirectory) && Directory.Exists(this.ProfileDirectory))
{
FileUtilities.DeleteDirectory(this.profileDir);
FileUtilities.DeleteDirectory(this.ProfileDirectory);
}

if (this.deleteSource && !string.IsNullOrEmpty(this.sourceProfileDir) && Directory.Exists(this.sourceProfileDir))
Expand All @@ -202,17 +199,17 @@ public void Clean()
/// <returns>A base64-encoded string containing the contents of the profile.</returns>
public string ToBase64String()
{
string base64zip = string.Empty;
string base64zip;
this.WriteToDisk();

using (MemoryStream profileMemoryStream = new MemoryStream())
{
using (ZipArchive profileZipArchive = new ZipArchive(profileMemoryStream, ZipArchiveMode.Create, true))
{
string[] files = Directory.GetFiles(this.profileDir, "*.*", SearchOption.AllDirectories);
string[] files = Directory.GetFiles(this.ProfileDirectory, "*.*", SearchOption.AllDirectories);
foreach (string file in files)
{
string fileNameInZip = file.Substring(this.profileDir.Length + 1).Replace(Path.DirectorySeparatorChar, '/');
string fileNameInZip = file.Substring(this.ProfileDirectory.Length + 1).Replace(Path.DirectorySeparatorChar, '/');
profileZipArchive.CreateEntryFromFile(file, fileNameInZip);
}
}
Expand All @@ -236,20 +233,20 @@ private static string GenerateProfileDirectoryName()
/// <summary>
/// Deletes the lock files for a profile.
/// </summary>
private void DeleteLockFiles()
private void DeleteLockFiles(string profileDirectory)
{
File.Delete(Path.Combine(this.profileDir, ".parentlock"));
File.Delete(Path.Combine(this.profileDir, "parent.lock"));
File.Delete(Path.Combine(profileDirectory, ".parentlock"));
File.Delete(Path.Combine(profileDirectory, "parent.lock"));
}

/// <summary>
/// Installs all extensions in the profile in the directory on disk.
/// </summary>
private void InstallExtensions()
private void InstallExtensions(string profileDirectory)
{
foreach (string extensionKey in this.extensions.Keys)
{
this.extensions[extensionKey].Install(this.profileDir);
this.extensions[extensionKey].Install(profileDirectory);
}
}

Expand All @@ -259,10 +256,10 @@ private void InstallExtensions()
/// <remarks>If the extensions cache does not exist for this profile, the
/// <see cref="DeleteExtensionsCache"/> method performs no operations, but
/// succeeds.</remarks>
private void DeleteExtensionsCache()
private void DeleteExtensionsCache(string profileDirectory)
{
DirectoryInfo ex = new DirectoryInfo(Path.Combine(this.profileDir, "extensions"));
string cacheFile = Path.Combine(ex.Parent.FullName, "extensions.cache");
DirectoryInfo ex = new DirectoryInfo(Path.Combine(profileDirectory, "extensions"));
string cacheFile = Path.Combine(ex.Parent!.FullName, "extensions.cache");
if (File.Exists(cacheFile))
{
File.Delete(cacheFile);
Expand All @@ -272,9 +269,9 @@ private void DeleteExtensionsCache()
/// <summary>
/// Writes the user preferences to the profile.
/// </summary>
private void UpdateUserPreferences()
private void UpdateUserPreferences(string profileDirectory)
{
string userPrefs = Path.Combine(this.profileDir, UserPreferencesFileName);
string userPrefs = Path.Combine(profileDirectory, UserPreferencesFileName);
if (File.Exists(userPrefs))
{
try
Expand All @@ -300,7 +297,7 @@ private void UpdateUserPreferences()
this.profilePreferences.WriteToFile(userPrefs);
}

private void ReadDefaultPreferences()
private Preferences ReadDefaultPreferences()
{
using (Stream defaultPrefsStream = ResourceUtilities.GetResourceStream("webdriver_prefs.json", "webdriver_prefs.json"))
{
Expand All @@ -309,7 +306,7 @@ private void ReadDefaultPreferences()
JsonElement immutableDefaultPreferences = defaultPreferences.RootElement.GetProperty("frozen");
JsonElement editableDefaultPreferences = defaultPreferences.RootElement.GetProperty("mutable");

this.profilePreferences = new Preferences(immutableDefaultPreferences, editableDefaultPreferences);
return new Preferences(immutableDefaultPreferences, editableDefaultPreferences);
}
}

Expand Down
44 changes: 13 additions & 31 deletions dotnet/src/webdriver/Firefox/FirefoxProfileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
using System.Collections.ObjectModel;
using System.IO;

#nullable enable

namespace OpenQA.Selenium.Firefox
{
/// <summary>
Expand All @@ -45,52 +47,32 @@ public FirefoxProfileManager()
/// Gets a <see cref="ReadOnlyCollection{T}"/> containing <see cref="FirefoxProfile">FirefoxProfiles</see>
/// representing the existing named profiles for Firefox.
/// </summary>
public ReadOnlyCollection<string> ExistingProfiles
{
get
{
List<string> profileList = new List<string>(this.profiles.Keys);
return profileList.AsReadOnly();
}
}
public ReadOnlyCollection<string> ExistingProfiles => new List<string>(this.profiles.Keys).AsReadOnly();

/// <summary>
/// Gets a <see cref="FirefoxProfile"/> with a given name.
/// </summary>
/// <param name="profileName">The name of the profile to get.</param>
/// <returns>A <see cref="FirefoxProfile"/> with a given name.
/// Returns <see langword="null"/> if no profile with the given name exists.</returns>
public FirefoxProfile GetProfile(string profileName)
public FirefoxProfile? GetProfile(string? profileName)
{
FirefoxProfile profile = null;
if (!string.IsNullOrEmpty(profileName))
if (profileName is not null && this.profiles.TryGetValue(profileName, out string? profile))
{
if (this.profiles.ContainsKey(profileName))
{
profile = new FirefoxProfile(this.profiles[profileName]);
}
return new FirefoxProfile(profile);
}

return profile;
return null;
}

private static string GetApplicationDataDirectory()
{
string appDataDirectory = string.Empty;
switch (Environment.OSVersion.Platform)
string appDataDirectory = Environment.OSVersion.Platform switch
{
case PlatformID.Unix:
appDataDirectory = Path.Combine(".mozilla", "firefox");
break;

case PlatformID.MacOSX:
appDataDirectory = Path.Combine("Library", Path.Combine("Application Support", "Firefox"));
break;

default:
appDataDirectory = Path.Combine("Mozilla", "Firefox");
break;
}
PlatformID.Unix => Path.Combine(".mozilla", "firefox"),
PlatformID.MacOSX => Path.Combine("Library", Path.Combine("Application Support", "Firefox")),
_ => Path.Combine("Mozilla", "Firefox"),
};

return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appDataDirectory);
}
Expand All @@ -109,7 +91,7 @@ private void ReadProfiles(string appDataDirectory)
string name = reader.GetValue(sectionName, "name");
bool isRelative = reader.GetValue(sectionName, "isrelative") == "1";
string profilePath = reader.GetValue(sectionName, "path");
string fullPath = string.Empty;
string fullPath;
if (isRelative)
{
fullPath = Path.Combine(appDataDirectory, profilePath);
Expand Down
8 changes: 6 additions & 2 deletions dotnet/src/webdriver/Internal/FileUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static bool CopyDirectory(string sourceDirectory, string destinationDirec
/// </summary>
/// <param name="directoryToDelete">The directory to delete.</param>
/// <remarks>This method does not throw an exception if the delete fails.</remarks>
public static void DeleteDirectory(string directoryToDelete)
public static void DeleteDirectory(string? directoryToDelete)
{
int numberOfRetries = 0;
while (Directory.Exists(directoryToDelete) && numberOfRetries < 10)
Expand Down Expand Up @@ -210,7 +210,11 @@ public static string GetCurrentDirectory()
/// <param name="directoryPattern">The pattern to use in creating the directory name, following standard
/// .NET string replacement tokens.</param>
/// <returns>The full path to the random directory name in the temporary directory.</returns>
public static string GenerateRandomTempDirectoryName(string directoryPattern)
public static string GenerateRandomTempDirectoryName(
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.CompositeFormat)]
#endif
string directoryPattern)
{
string directoryName = string.Format(CultureInfo.InvariantCulture, directoryPattern, Guid.NewGuid().ToString("N"));
return Path.Combine(Path.GetTempPath(), directoryName);
Expand Down