Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a8ac2cd
Add emsdk manifest entries for downloading Firefox.
juj Sep 27, 2025
45112a3
Add support for installing Firefox on Linux
juj Sep 27, 2025
e64ba76
Add git branch markers
juj Sep 28, 2025
8c0b743
Fix daily path
juj Sep 28, 2025
4dd06a2
Autoinstall mozdownload
juj Sep 28, 2025
6c58b2a
Add firefox 90.0
juj Sep 28, 2025
b48c933
Firefox 120
juj Sep 28, 2025
bd4cab3
Add Firefox 130
juj Sep 28, 2025
c5fbe8a
Add Firefox 125
juj Sep 28, 2025
9ce1de9
125.0.1
juj Sep 28, 2025
801073c
Add 122.0
juj Sep 28, 2025
8ebe2eb
MacOS support
juj Sep 28, 2025
fd0aeb7
Add Firefox 123.0
juj Sep 28, 2025
6003b3e
Fix is_firefox_installed on MacOS.
juj Sep 28, 2025
65521c7
Add Firefox 100.0 and 110.0
juj Sep 28, 2025
8a3b828
Add Firefox esr
juj Sep 28, 2025
3377c89
Only attempt mozdownload reinstall if mozdownload import does not work.
juj Sep 28, 2025
6516449
flake
juj Sep 28, 2025
bf39cf7
Fix MacOS for Firefox Nightly
juj Sep 28, 2025
ec0cf2b
Fix attach
juj Sep 28, 2025
2687a33
fix 2
juj Sep 28, 2025
7d9ac48
daily -> nightly (they are the same)
juj Sep 28, 2025
faac848
Add 140.3.1esr
juj Sep 28, 2025
a273a9b
nightly->daily
juj Sep 28, 2025
ab1547e
Fix typo
juj Sep 28, 2025
9c1bdf5
Fix mac cfg
juj Sep 28, 2025
d78bb89
Fix save version
juj Sep 28, 2025
1cc37cd
no f
juj Sep 28, 2025
ac87ea1
Add Firefox beta
juj Sep 28, 2025
fe8c557
ImportError
juj Sep 28, 2025
214c810
Cleanup
juj Sep 29, 2025
538cdab
Fix typo
juj Sep 29, 2025
ab8744a
Rename config field for browsers
juj Oct 1, 2025
cdb956d
Set Firefox 79 as the minimum supported version
juj Oct 2, 2025
199f4c9
Revert "Set Firefox 79 as the minimum supported version"
juj Oct 2, 2025
4e59bf9
Remove the redundant temp directory after move
juj Oct 3, 2025
886e67f
Add Linux ARM64 download support
juj Oct 3, 2025
bf8924d
Remove Firefox 65
juj Oct 4, 2025
4d5276b
Hide old Firefox versions from install list by default
juj Oct 4, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ __pycache__
/mingw
/spidermonkey
/binaryen
/firefox
190 changes: 184 additions & 6 deletions emsdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import subprocess
import sys
import sysconfig
import tarfile
import zipfile
if os.name == 'nt':
try:
Expand Down Expand Up @@ -1257,6 +1258,165 @@ def build_ccache(tool):
return success


def download_firefox(tool):
debug_print('download_firefox(' + str(tool) + ')')

# Use mozdownload to acquire Firefox versions.
try:
from mozdownload import FactoryScraper
except ImportError:
# If mozdownload is not available, invoke pip to install it.
subprocess.check_call([sys.executable, "-m", "pip", "install", "mozdownload"])
from mozdownload import FactoryScraper

if WINDOWS:
extension = 'exe'
elif MACOS:
extension = 'dmg'
else:
# N.b. on Linux even when we ask .tar.xz, we might sometimes get .tar.bz2,
# depending on what is available on Firefox servers for the particular
# version. So prepare to handle both further down below.
extension = 'tar.xz'

platform = None
if LINUX and 'arm' in ARCH:
platform = 'linux-arm64'

if tool.version == 'nightly':
scraper = FactoryScraper('daily', extension=extension, locale='en-US', platform=platform)
else:
scraper = FactoryScraper('release', extension=extension, locale='en-US', platform=platform, version=tool.version)

if tool.version == 'nightly':
firefox_version = os.path.basename(scraper.filename).split(".en-US")[0]
else:
firefox_version = os.path.basename(scraper.filename).split("firefox-")[1].split(".en-US")[0]

print('Target Firefox version: ' + firefox_version)
if tool.version in ['latest', 'latest-esr', 'latest-beta', 'nightly']:
pretend_version_dir = os.path.normpath(tool.installation_path())
orig_version = tool.version
tool.version = firefox_version
root = os.path.normpath(tool.installation_path())
tool.version = orig_version
else:
pretend_version_dir = None
root = os.path.normpath(tool.installation_path())

# For moving installer packages, e.g. "nightly", "latest", "latest-esr",
# store a text file to specify the actual installation directory.
def save_actual_version():
if os.path.isfile(firefox_exe) and pretend_version_dir:
print(pretend_version_dir)
os.makedirs(pretend_version_dir, exist_ok=True)
open(os.path.join(pretend_version_dir, 'actual.txt'), 'w').write(os.path.relpath(root, EMSDK_PATH))

# Check if already installed
print('Firefox installation root directory: ' + root)
exe_dir = os.path.join(root, 'Contents', 'MacOS') if MACOS else root
firefox_exe = os.path.join(exe_dir, exe_suffix('firefox'))
if os.path.isfile(firefox_exe):
print(firefox_exe + ' is already installed, skipping..')
save_actual_version()
return True

print('Downloading Firefox from ' + scraper.url)
filename = scraper.download()
print('Finished downloading ' + filename)

if not MACOS:
os.makedirs(root, exist_ok=True)

if extension == 'exe':
# Uncompress the NSIS installer to 'install' Firefox
run(['C:\\Program Files\\7-Zip\\7z.exe', 'x', '-y', filename, '-o' + root])

if '.tar.' in filename:
if filename.endswith('.tar.bz2'):
tar_type = 'r:bz2'
elif filename.endswith('.tar.xz'):
tar_type = 'r:xz'
else:
raise Exception('Unknown archive type!')

with tarfile.open(filename, tar_type) as tar:
tar.extractall(path=root)
collapse_subdir = os.path.join(root, 'firefox')

elif filename.endswith('.dmg'):
mount_point = '/Volumes/Firefox Nightly' if tool.version == 'nightly' else '/Volumes/Firefox'
app_name = 'Firefox Nightly.app' if tool.version == 'nightly' else 'Firefox.app'

# If a previous mount point exists, detach it first
if os.path.exists(mount_point):
run(['hdiutil', 'detach', mount_point])

# Abort if detaching was not successful
if os.path.exists(mount_point):
raise Exception('Previous mount of Firefox already exists at "' + mount_point + '", unable to proceed.')

# Mount the archive
run(['hdiutil', 'attach', filename])
firefox_dir = os.path.join(mount_point, app_name)
if not os.path.isdir(firefox_dir):
raise Exception('Unable to find Firefox directory "' + firefox_dir + '" inside app image.')

# And install by copying the files from the archive
shutil.copytree(firefox_dir, root)
run(['hdiutil', 'detach', mount_point])
collapse_subdir = None

elif filename.endswith('.exe'):
# NSIS installer package has a core/ directory, remove it as redundant.
collapse_subdir = os.path.join(root, 'core')

# Remove a redundant subdirectory by moving installed files up one directory.
if collapse_subdir and os.path.isdir(collapse_subdir):
# Rename the parent subdirectory first, since we will be handling a nested `firefox/firefox/`
collapse = collapse_subdir + '_temp_renamed'
os.rename(collapse_subdir, collapse)

# Move all files up by one directory
for f in os.listdir(collapse):
shutil.move(os.path.join(collapse, f), os.path.dirname(collapse))

# The root directory should now be empty
os.rmdir(collapse)

# Original installer is now done.
os.remove(filename)

# Write a policy file that prevents Firefox from auto-updating itself.
if MACOS:
distribution_path = os.path.join(root, 'Contents', 'Resources', 'distribution')
else:
distribution_path = os.path.join(root, 'distribution')
os.makedirs(distribution_path, exist_ok=True)
open(os.path.join(distribution_path, 'policies.json'), 'w').write('''{
"policies": {
"AppAutoUpdate": false,
"DisableAppUpdate": true
}
}''')

save_actual_version()

# If we didn't get a Firefox executable, then installation failed.
return os.path.isfile(firefox_exe)


def is_firefox_installed(tool):
actual_file = os.path.join(tool.installation_dir(), 'actual.txt')
if not os.path.isfile(actual_file):
return False

actual_installation_dir = sdk_path(open(actual_file).read())
exe_dir = os.path.join(actual_installation_dir, 'Contents', 'MacOS') if MACOS else actual_installation_dir
firefox_exe = os.path.join(exe_dir, exe_suffix('firefox'))
return os.path.isfile(firefox_exe)


# Finds the newest installed version of a given tool
def find_latest_installed_tool(name):
for t in reversed(tools):
Expand Down Expand Up @@ -1683,6 +1843,12 @@ def expand_vars(self, str):
str = str.replace('%cmake_build_type_on_win%', (decide_cmake_build_type(self) + '/') if WINDOWS else '')
if '%installation_dir%' in str:
str = str.replace('%installation_dir%', sdk_path(self.installation_dir()))
if '%actual_installation_dir%' in str:
actual_file = os.path.join(self.installation_dir(), 'actual.txt')
if os.path.isfile(actual_file):
str = str.replace('%actual_installation_dir%', sdk_path(open(actual_file).read()))
else:
str = str.replace('%actual_installation_dir%', '__NOT_INSTALLED__')
if '%generator_prefix%' in str:
str = str.replace('%generator_prefix%', cmake_generator_prefix())
str = str.replace('%.exe%', '.exe' if WINDOWS else '')
Expand Down Expand Up @@ -1734,21 +1900,30 @@ def installation_dir(self):
# Returns the configuration item that needs to be added to .emscripten to make
# this Tool active for the current user.
def activated_config(self):
if not hasattr(self, 'activated_cfg'):
if MACOS and hasattr(self, 'mac_activated_cfg'):
activated_cfg = self.mac_activated_cfg
elif hasattr(self, 'activated_cfg'):
activated_cfg = self.activated_cfg
else:
return {}

config = OrderedDict()
expanded = to_unix_path(self.expand_vars(self.activated_cfg))
expanded = to_unix_path(self.expand_vars(activated_cfg))
for specific_cfg in expanded.split(';'):
name, value = specific_cfg.split('=')
config[name] = value.strip("'")
return config

def activated_environment(self):
if hasattr(self, 'activated_env'):
return self.expand_vars(self.activated_env).split(';')
if MACOS and hasattr(self, 'mac_activated_env'):
activated_env = self.mac_activated_env
elif hasattr(self, 'activated_env'):
activated_env = self.activated_env
else:
return []

return self.expand_vars(activated_env).split(';')

def compatible_with_this_arch(self):
if hasattr(self, 'arch'):
if self.arch != ARCH:
Expand Down Expand Up @@ -1833,6 +2008,8 @@ def is_installed(self, skip_version_check=False):
if hasattr(self, 'custom_is_installed_script'):
if self.custom_is_installed_script == 'is_binaryen_installed':
return is_binaryen_installed(self)
elif self.custom_is_installed_script == 'is_firefox_installed':
return is_firefox_installed(self)
else:
raise Exception('Unknown custom_is_installed_script directive "' + self.custom_is_installed_script + '"!')

Expand Down Expand Up @@ -1968,7 +2145,8 @@ def install_tool(self):
'build_llvm': build_llvm,
'build_ninja': build_ninja,
'build_ccache': build_ccache,
'download_node_nightly': download_node_nightly
'download_node_nightly': download_node_nightly,
'download_firefox': download_firefox
}
if hasattr(self, 'custom_install_script') and self.custom_install_script in custom_install_scripts:
success = custom_install_scripts[self.custom_install_script](self)
Expand All @@ -1986,7 +2164,7 @@ def install_tool(self):
if hasattr(self, 'custom_install_script'):
if self.custom_install_script == 'emscripten_npm_install':
success = emscripten_npm_install(self, self.installation_path())
elif self.custom_install_script in ('build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly'):
elif self.custom_install_script in ('build_llvm', 'build_ninja', 'build_ccache', 'download_node_nightly', 'download_firefox'):
# 'build_llvm' is a special one that does the download on its
# own, others do the download manually.
pass
Expand Down
135 changes: 135 additions & 0 deletions emsdk_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,141 @@
"activated_env": "EMSDK_PYTHON=%installation_dir%/bin/python3;SSL_CERT_FILE=%installation_dir%/lib/python3.13/site-packages/certifi/cacert.pem"
},

{
"id": "firefox",
"version": "68.12.0esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"is_old": true
},
{
"id": "firefox",
"version": "78.15.0esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"is_old": true
},
{
"id": "firefox",
"version": "91.13.0esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"is_old": true
},
{
"id": "firefox",
"version": "102.15.1esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"is_old": true
},
{
"id": "firefox",
"version": "115.28.0esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"is_old": true
},
{
"id": "firefox",
"version": "128.14.0esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox"
},
{
"id": "firefox",
"version": "140.3.1esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox"
},
{
"id": "firefox",
"version": "latest",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"git_branch": "dummy field, to instruct emsdk to attempt to reinstall this tool even if it is installed, to check for new version",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"custom_is_installed_script": "is_firefox_installed"
},
{
"id": "firefox",
"version": "latest-esr",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"git_branch": "dummy field, to instruct emsdk to attempt to reinstall this tool even if it is installed, to check for new version",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"custom_is_installed_script": "is_firefox_installed"
},
{
"id": "firefox",
"version": "latest-beta",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"git_branch": "dummy field, to instruct emsdk to attempt to reinstall this tool even if it is installed, to check for new version",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"custom_is_installed_script": "is_firefox_installed"
},
{
"id": "firefox",
"version": "nightly",
"bitness": 64,
"url": "downloaded via mozdownload script, but a dummy directive is placed here so emsdk understands this Tool to be downloaded from the web",
"git_branch": "dummy field, to instruct emsdk to attempt to reinstall this tool even if it is installed, to check for new version",
"activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"activated_env": "EMTEST_BROWSER=%actual_installation_dir%/firefox%.exe%",
"mac_activated_cfg": "EMSDK_ACTIVATED_TEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"mac_activated_env": "EMTEST_BROWSER=%actual_installation_dir%/Contents/MacOS/firefox%.exe%",
"custom_install_script": "download_firefox",
"custom_is_installed_script": "is_firefox_installed"
},

{
"id": "emscripten",
"version": "tag-%tag%",
Expand Down