Skip to content

Commit

Permalink
[dotnet] Annotate nullability on Firefox profile (#15207)
Browse files Browse the repository at this point in the history
  • Loading branch information
RenderMichael authored Feb 2, 2025
1 parent bd2e454 commit 7b45115
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 76 deletions.
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

0 comments on commit 7b45115

Please sign in to comment.