Skip to content

Commit

Permalink
Add support to firefox channels (#2707)
Browse files Browse the repository at this point in the history
* Add support to firefox channels

* fix default

* fix nightly path

* Fix profile creation on instance with a profile argument set

* Check for quotes in directory
  • Loading branch information
kblok authored Jul 22, 2024
1 parent e4528ad commit cd4aa95
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 45 deletions.
91 changes: 80 additions & 11 deletions lib/PuppeteerSharp.Tests/Browsers/Firefox/FirefoxDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,86 @@ namespace PuppeteerSharp.Tests.Browsers.Firefox
{
public class FirefoxDataTests
{
[Test, Retry(2), PuppeteerTest("firefox-data.spec", "Firefox", "should resolve download URLs")]
public void ShouldResolveDownloadUrls()
[Test, Retry(2), PuppeteerTest("firefox-data.spec", "Firefox", "should resolve URLs for Nightly")]
public void ShouldResolveUrlsForNightly()
{
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/111.0a1/linux-x86_64/en-US/firefox-111.0a1.tar.bz2",
"https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.linux-x86_64.tar.bz2",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Linux, "111.0a1", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/111.0a1/mac/en-US/Firefox 111.0a1.dmg",
"https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOS, "111.0a1", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/111.0a1/mac/en-US/Firefox 111.0a1.dmg",
"https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.mac.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOSArm64, "111.0a1", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/111.0a1/win32/en-US/Firefox Setup 111.0a1.exe",
"https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win32.zip",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win32, "111.0a1", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/111.0a1/win64/en-US/Firefox Setup 111.0a1.exe",
"https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/firefox-111.0a1.en-US.win64.zip",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win64, "111.0a1", null));
}

[Test, Retry(2), PuppeteerTest("firefox-data.spec", "Firefox", "should resolve URLs for beta")]
public void ShouldResolveUrlsForBeta()
{
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/115.0b8/linux-x86_64/en-US/firefox-115.0b8.tar.bz2",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Linux, "beta_115.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOS, "beta_115.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/115.0b8/mac/en-US/Firefox 115.0b8.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOSArm64, "beta_115.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/115.0b8/win32/en-US/Firefox Setup 115.0b8.exe",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win32, "beta_115.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/115.0b8/win64/en-US/Firefox Setup 115.0b8.exe",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win64, "beta_115.0b8", null));
}

[Test, Retry(2), PuppeteerTest("firefox-data.spec", "Firefox", "should resolve URLs for stable")]
public void ShouldResolveUrlsForStable()
{
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/114.0/linux-x86_64/en-US/firefox-114.0.tar.bz2",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Linux, "stable_114.0", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/114.0/mac/en-US/Firefox 114.0.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOS, "stable_114.0", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/114.0/mac/en-US/Firefox 114.0.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOSArm64, "stable_114.0", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/114.0/win32/en-US/Firefox Setup 114.0.exe",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win32, "stable_114.0", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/firefox/releases/114.0/win64/en-US/Firefox Setup 114.0.exe",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win64, "stable_114.0", null));
}

[Test, Retry(2), PuppeteerTest("firefox-data.spec", "Firefox", "should resolve URLs for devedition")]
public void ShouldResolveUrlsForDevedition()
{
Assert.AreEqual(
"https://archive.mozilla.org/pub/devedition/releases/114.0b8/linux-x86_64/en-US/firefox-114.0b8.tar.bz2",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Linux, "devedition_114.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/devedition/releases/114.0b8/mac/en-US/Firefox 114.0b8.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOS, "devedition_114.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/devedition/releases/114.0b8/mac/en-US/Firefox 114.0b8.dmg",
BrowserData.Firefox.ResolveDownloadUrl(Platform.MacOSArm64, "devedition_114.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/devedition/releases/114.0b8/win32/en-US/Firefox Setup 114.0b8.exe",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win32, "devedition_114.0b8", null));
Assert.AreEqual(
"https://archive.mozilla.org/pub/devedition/releases/114.0b8/win64/en-US/Firefox Setup 114.0b8.exe",
BrowserData.Firefox.ResolveDownloadUrl(Platform.Win64, "devedition_114.0b8", null));
}

[Test, Retry(2), PuppeteerTest("firefox-data.spec", "Firefox", "should resolve executable paths")]
public void ShouldResolveExecutablePath()
{
Expand All @@ -35,7 +95,7 @@ public void ShouldResolveExecutablePath()

Assert.AreEqual(
Path.Combine(
"Firefox.app",
"Firefox Nightly.app",
"Contents",
"MacOS",
"firefox"
Expand All @@ -44,20 +104,29 @@ public void ShouldResolveExecutablePath()

Assert.AreEqual(
Path.Combine(
"Firefox.app",
"Firefox Nightly.app",
"Contents",
"MacOS",
"firefox"
),
BrowserData.Firefox.RelativeExecutablePath(Platform.MacOSArm64, "111.0a1"));

Assert.AreEqual(
Path.Combine("core", "firefox.exe"),
Path.Combine(
"Firefox.app",
"Contents",
"MacOS",
"firefox"
),
BrowserData.Firefox.RelativeExecutablePath(Platform.MacOSArm64, "stable_111.0a1"));

Assert.AreEqual(
Path.Combine("firefox", "firefox.exe"),
BrowserData.Firefox.RelativeExecutablePath(Platform.Win32, "111.0a1"));

Assert.AreEqual(
BrowserData.Firefox.RelativeExecutablePath(Platform.Win64, "111.0a1"),
Path.Combine("core", "firefox.exe"));
Path.Combine("firefox", "firefox.exe"));
}
}
}
132 changes: 110 additions & 22 deletions lib/PuppeteerSharp/BrowserData/Firefox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PuppeteerSharp.Helpers;

namespace PuppeteerSharp.BrowserData
{
Expand All @@ -19,15 +20,43 @@ public static class Firefox

private static readonly Dictionary<string, string> _cachedBuildIds = [];

internal static Task<string> GetDefaultBuildIdAsync() => Task.FromResult(DefaultBuildId);
internal static Task<string> GetDefaultBuildIdAsync() => ResolveBuildIdAsync(FirefoxChannel.Nightly);

internal static string ResolveDownloadUrl(Platform platform, string buildId, string baseUrl)
=>
$"{baseUrl ?? "https://archive.mozilla.org/pub/firefox/releases"}/{string.Join("/", ResolveDownloadPath(platform, buildId))}";
{
var (channel, resolvedBuildId) = ParseBuildId(buildId);

baseUrl ??= channel switch
{
FirefoxChannel.Nightly => "https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central",
FirefoxChannel.DevEdition => "https://archive.mozilla.org/pub/devedition/releases",
FirefoxChannel.Beta or FirefoxChannel.Stable or FirefoxChannel.Esr =>
"https://archive.mozilla.org/pub/firefox/releases",
_ => throw new ArgumentException("Invalid buildId"),
};

internal static async Task<string> ResolveBuildIdAsync(string channel)
return channel switch
{
FirefoxChannel.Nightly => $"{baseUrl}/{string.Join("/", ResolveDownloadPath(platform, resolvedBuildId))}",
FirefoxChannel.DevEdition or FirefoxChannel.Beta or FirefoxChannel.Stable or FirefoxChannel.Esr =>
$"{baseUrl}/{resolvedBuildId}/{GetPlatformNameForUrl(platform)}/en-US/{GetArchive(platform, resolvedBuildId)}",
_ => throw new ArgumentException("Invalid buildId"),
};
}

internal static async Task<string> ResolveBuildIdAsync(FirefoxChannel channel)
{
if (_cachedBuildIds.TryGetValue(channel, out var build))
var versionKey = channel switch
{
FirefoxChannel.Nightly => "FIREFOX_NIGHTLY",
FirefoxChannel.DevEdition => "FIREFOX_DEVEDITION",
FirefoxChannel.Beta => "FIREFOX_DEVEDITION",
FirefoxChannel.Stable => "LATEST_FIREFOX_VERSION",
FirefoxChannel.Esr => "FIREFOX_ESR",
_ => throw new ArgumentException("Invalid channel", nameof(channel)),
};

if (_cachedBuildIds.TryGetValue(versionKey, out var build))
{
return build;
}
Expand All @@ -36,30 +65,56 @@ internal static async Task<string> ResolveBuildIdAsync(string channel)
.GetAsync<Dictionary<string, string>>("https://product-details.mozilla.org/1.0/firefox_versions.json")
.ConfigureAwait(false);

if (!version.TryGetValue(channel, out var buildId))
if (!version.TryGetValue(versionKey, out var buildId))
{
throw new PuppeteerException($"Channel {channel} not found.");
throw new PuppeteerException($"Channel {versionKey} not found.");
}

_cachedBuildIds[channel] = buildId;
_cachedBuildIds[versionKey] = buildId;
return buildId;
}

internal static string RelativeExecutablePath(Platform platform, string buildId)
=> platform switch
{
var (channel, _) = ParseBuildId(buildId);

switch (channel)
{
Platform.MacOS or Platform.MacOSArm64 => Path.Combine(
"Firefox.app",
"Contents",
"MacOS",
"firefox"),
Platform.Linux => Path.Combine("firefox", "firefox"),
Platform.Win32 or Platform.Win64 => Path.Combine("core", "firefox.exe"),
_ => throw new ArgumentException("Invalid platform", nameof(platform)),
};
case FirefoxChannel.Nightly:
return platform switch
{
Platform.MacOS or Platform.MacOSArm64 => Path.Combine(
"Firefox Nightly.app",
"Contents",
"MacOS",
"firefox"),
Platform.Linux => Path.Combine("firefox", "firefox"),
Platform.Win32 or Platform.Win64 => Path.Combine("firefox", "firefox.exe"),
_ => throw new ArgumentException("Invalid platform", nameof(platform)),
};
default:
return platform switch
{
Platform.MacOS or Platform.MacOSArm64 => Path.Combine(
"Firefox.app",
"Contents",
"MacOS",
"firefox"),
Platform.Linux => Path.Combine("firefox", "firefox"),
Platform.Win32 or Platform.Win64 => Path.Combine("core", "firefox.exe"),
_ => throw new ArgumentException("Invalid platform", nameof(platform)),
};
}
}

internal static void CreateProfile(string tempUserDataDirectory, Dictionary<string, object> preferences)
{
// If the tempUserDataDirectory begins and ends with a quote, remove the quote
if (tempUserDataDirectory.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && tempUserDataDirectory.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
{
tempUserDataDirectory = tempUserDataDirectory.Substring(1, tempUserDataDirectory.Length - 2);
}

var defaultPreferences = GetDefaultPreferences(preferences);

File.WriteAllText(
Expand All @@ -73,10 +128,25 @@ internal static void CreateProfile(string tempUserDataDirectory, Dictionary<stri
File.WriteAllText(Path.Combine(tempUserDataDirectory, "prefs.js"), string.Empty);
}

private static (FirefoxChannel Channel, string BuildId) ParseBuildId(string buildId)
{
// Iterate through all the FirefoxChannel enum values as string
foreach (var value in Enum.GetValues(typeof(FirefoxChannel)).Cast<FirefoxChannel>().Select(v => v.ToValueString()))
{
if (buildId.StartsWith(value, StringComparison.OrdinalIgnoreCase))
{
buildId = buildId.Substring(value.Length + 1);
return (value.ToEnum<FirefoxChannel>(), buildId);
}
}

return (FirefoxChannel.Nightly, buildId);
}

private static string[] ResolveDownloadPath(Platform platform, string buildId)
=> [buildId, GetFirefoxPlatform(platform), "en-US", GetArchive(platform, buildId)];
=> [GetArchiveNightly(platform, buildId)];

private static string GetFirefoxPlatform(Platform platform)
private static string GetPlatformNameForUrl(Platform platform)
=> platform switch
{
Platform.Linux => "linux-x86_64",
Expand All @@ -86,13 +156,31 @@ private static string GetFirefoxPlatform(Platform platform)
_ => throw new PuppeteerException($"Unknown platform: {platform}"),
};

private static string GetFirefoxPlatform(Platform platform)
=> platform switch
{
Platform.Linux => "linux",
Platform.MacOS => "mac",
Platform.MacOSArm64 => "mac_arm",
Platform.Win32 => "win32",
Platform.Win64 => "win64",
_ => throw new PuppeteerException($"Unknown platform: {platform}"),
};

private static string GetArchiveNightly(Platform platform, string buildId)
=> platform switch
{
Platform.Linux => $"firefox-{buildId}.en-US.{GetFirefoxPlatform(platform)}-x86_64.tar.bz2",
Platform.MacOS or Platform.MacOSArm64 => $"firefox-{buildId}.en-US.mac.dmg",
Platform.Win32 or Platform.Win64 => $"firefox-{buildId}.en-US.{GetFirefoxPlatform(platform)}.zip",
_ => throw new PuppeteerException($"Unknown platform: {platform}"),
};

private static string GetArchive(Platform platform, string buildId)
=> platform switch
{
Platform.Linux => $"firefox-{buildId}.tar.bz2",
Platform.MacOS or Platform.MacOSArm64 => $"Firefox {buildId}.dmg",

// Windows archive name changed at r591479.
Platform.Win32 or Platform.Win64 =>
$"Firefox Setup {buildId}.exe",
_ => throw new PuppeteerException($"Unknown platform: {platform}"),
Expand Down
61 changes: 61 additions & 0 deletions lib/PuppeteerSharp/BrowserData/FirefoxChannel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// * MIT License
// *
// * Copyright (c) Darío Kondratiuk
// *
// * Permission is hereby granted, free of charge, to any person obtaining a copy
// * of this software and associated documentation files (the "Software"), to deal
// * in the Software without restriction, including without limitation the rights
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// * copies of the Software, and to permit persons to whom the Software is
// * furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included in all
// * copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// * SOFTWARE.

using System.Runtime.Serialization;

namespace PuppeteerSharp.BrowserData;

/// <summary>
/// Firefox channels.
/// </summary>
public enum FirefoxChannel
{
/// <summary>
/// Stable.
/// </summary>
[EnumMember(Value = "stable")]
Stable = 0,

/// <summary>
/// Beta.
/// </summary>
[EnumMember(Value = "beta")]
Beta = 1,

/// <summary>
/// Nightly.
/// </summary>
[EnumMember(Value = "nightly")]
Nightly = 2,

/// <summary>
/// ESR.
/// </summary>
[EnumMember(Value = "esr")]
Esr = 3,

/// <summary>
/// Developer Edition.
/// </summary>
[EnumMember(Value = "devedition")]
DevEdition = 4,
}
Loading

0 comments on commit cd4aa95

Please sign in to comment.