Update Implementation
This page will describe, at a high level, all the bits of Firefox Application Update and how they interact. It is recommended that you familiarize yourself with the Application Update Concepts page first as those concepts will be referred to here.
A Top Level Overview
Very broadly speaking application update follows these steps:
Firefox is built on our build server. This creates the installers and MAR files.
The installers are uploaded to the website. The MARs are uploaded to our update server (Balrog).
Users download, install, and run Firefox. We ensure, on installation, that we delete any pending updates so that they are not improperly installed at startup. Note that the instance of Firefox that runs may be a regular, interactive instance of Firefox or an instance of the Background Update Task. In either case, the process is quite similar.
An update check is initiated. This can either happen via a timer, or be initiated by a user’s interaction with the update UI. The Background Update Task initiates update using a very similar mechanism to what a user would. There is also a mechanism that initiates an update check quickly after startup if the current version is especially old.
If the check found an update, Firefox starts downloading it. The update may list an available partial and complete MAR in which case we default to downloading the partial, falling back to the complete on certain kinds of failures. At this point, the Background Update Task immediately exits, allowing the download happen in the background using BITS.
When the download completes, we begin staging, if we are able to do so. Either way, we set up the update directory to have the MAR in the right place and the update status set correctly.
At this point, we are waiting for Firefox to restart. But, we also continue checking for updates. If we find one, we download it also and swap out the update we had for the newer one. Note that we only swap out partial MARs for other partial MARs. Since complete MARs are much larger, downloading many of them that never actually end up being used is unappealing.
Eventually, Firefox is restarted. It doesn’t matter whether this is the result of clicking a “Restart to Update” prompt, or any other method of Firefox being closed and re-launched. Early in startup, Firefox reads the
update.statusfile. If the status ispendingorpending-service, it launches the update binary and exits.The update binary installs the update, sets
update.statusto convey the results back to Firefox, re-launches Firefox with the appropriate arguments, and exits.Firefox eventually initiates the Update Service. This happens earlier when
update.statusexists, but not nearly as early as potentially launching the update binary. This analyzes the results of the update. If necessary, it falls back to other ways of updating. For example, we might fall back from a partial MAR to a complete MAR or fall back from updating with the Mozilla Maintenance Service to updating without it.If relevant, the “What’s New Page” (WNP) is shown to the user to notify them of changes to this version.
Return to checking for updates.
Mozilla Maintenance Service
The Mozilla Maintenance Service (MMS) exists so that Windows users do not have to accept a User Account Control prompt every time Firefox updates. It is installed by default if the user accepts the User Account Control prompt when installing Firefox. In addition to installing the Maintenance Service, the installer also writes several keys to the Windows Registry (in a location that requires elevation, to prevent tampering). These are specific to the install directory and contain certificate information that the MMS uses to verify that the Update Binary is a valid updater signed by Mozilla. This is very important to ensure that it doesn’t grant elevation to processes inappropriately.
The MMS is installed as a Windows Service, but it does not follow the conventions of a normal Windows Service. Rather than running persistently in the background, it is run on-demand. The only real reason for it to be a Service is that low-privilege processes can request that a Service be started with a particular set of arguments and Windows will run it with the privileges associated with the Service rather than the privileges of the calling process.
The unelevated updater decides whether to launch the MMS based on several criteria including whether elevation appears to actually be required for the update and whether the installation is in the Program Files directory. It passes the MMS pretty much the same arguments that it was passed, which will be passed on to the elevated updater. The MMS does parse these arguments because it needs several of them to safely and successfully launch the elevated updater. It passes the arguments on to the elevated updater unchanged.
Even for multiple installations of Firefox, we only install a single MMS. In
order to make sure it is accessible by 32-bit installations as well, we always
install into the 32-bit install directory (Program Files (x86)).
Update Binary
This is a standalone binary. Most of the source for this lives in
toolkit/mozapps/update/updater/updater.cpp. This is the program that is
responsible for performing staging, replace requests, and non-staged updates.
Staging is very similar to doing a non-staged update, except that we effectively install a separate copy of the installation into a temporary directory.
A replace request takes place after staging and simply involves deleting the
original installation and swapping it with the contents of the updated
directory.
A non-staged update (also known as an “in place update”) involves copying files out of the MAR and potentially patching existing files with patches stored in the MAR. Some files may also be deleted. The updater tracks each change made and, if an error is encountered, can roll all the changes back to restore the installation to its original state.
The update process often requires elevation. Elevating is only supported on macOS and Windows. The macOS process is documented here. The Windows process involves:
Attempting to create and open a file in the installation directory to test if elevation is needed.
If the file cannot be opened, several checks are performed to see if the Maintenance Service can be used. If it cannot, elevation is obtained via a User Account Control (UAC) prompt. When staging, we exit with failure rather than use the UAC prompt as we don’t want to show one when the user is not expecting it.
A new, elevated process is created which does the actual update. It is very important, for security reasons, not to attempt to write to any non-secured areas with this extra permissions as filesystem links can cause us to be tricked into overwriting important files. Thus, status information is written into the installation directory for the Maintenance Service.
The elevated update process exits.
The unelevated update process continues running and goes to look for status information in the Maintenance Service install directory. It copies this back to the update directory.
Except when staging, there may be a post update process that is run before the
updater exits. Currently, we only do this on Windows. The Windows post update
process involves running the updated uninstaller binary with the /PostUpdate
argument. Note that during an elevated Windows update, we run the post update
process twice. Once with elevation and once without it. The reasons for the post
update process are (a) so that we can do some things that the installer does
(such as changing registry keys around to reflect our current configuration)
without needing to have separate NSIS and C++ copies of that code and (b) so
that we can run code from the updated version of Firefox, allowing the update
process to perform actions that the old version of the updater wasn’t aware of.
When the update is complete (successfully or otherwise), Firefox is re-launched with its original arguments (which it passes to the update binary when invoking it).
Update Driver
The update driver is the part of Firefox that invokes the update binary in order
to stage or install an update. The update driver’s functionality is declared in
toolkit/xre/nsUpdateDriver.h and implements nsIUpdateProcessor in order to
be accessible from Javascript. It is invoked from the update service where it is
used to stage updates and from toolkit/xre/nsAppRunner.cpp to install an
update on Firefox startup.
Update Checker
The update checker has the interface nsIUpdateChecker and is implemented by
CheckerService in toolkit/mozapps/update/UpdateService.sys.mjs. This
interface allows foreground or background checks (foreground checks set the
force parameter). The returned result will
resolve with an object containing an array of updates that were found. Using
this interface does not cause an update to be downloaded automatically.
Update Manager
The update manager lives in toolkit/mozapps/update/UpdateService.sys.mjs and
keeps track of the various Update objects. This includes the update history,
downloadingUpdate, and readyUpdate. In-progress updates are stored in
downloadingUpdate while they download and are moved into readyUpdate when
downloading completes. Similarly, the downloading and ready updates are stored
in different subdirectories of the
update directory. This allows there to
potentially be a separate downloading and ready update, simultaneously. When
updates complete (successfully or otherwise), they are moved to the update
history.
Update Listener
The update listener lives in toolkit/mozapps/update/UpdateListener.sys.mjs. It
listens for update status notifications and shows UI to the user, generally
prompting the user to take action. When an update is ready to be downloaded, but
the user has
set the updater to require permission before downloading,
it asks for this permission. When an update is ready to be installed, it prompts
the user to restart the browser. And when there is a persistent update failure,
it prompts the user to download a new installer so that they can update that
way.
Notifications can be shown in two different ways: as a badge or as a doorhanger. The badge shows an update badge icon in the corner of the hamburger menu button and adds an entry to the hamburger menu asking the user to restart to update. The doorhanger is a small popup notification anchored to the hamburger menu that the user can accept or dismiss. If it is dismissed, it effectively turns into a badge notification.
The notification prompting the user to restart is generally not shown
immediately. There is a configurable “Badge Wait Time” and “Prompt Wait Time”
that dictates how long we wait to show the badge and the doorhanger,
respectively. Both can be controlled by prefs: app.update.badgeWaitTime and
app.update.promptWaitTime, respectively. The prompt wait time can also be
set via the Update object sent by Balrog in the
update XML. It isn’t really useful to have a
badge wait time that is longer than the prompt wait time. If it is greater,
the badge and the prompt are shown at the same time.
Update Service
The UpdateService lives in toolkit/mozapps/update/UpdateService.sys.mjs and
is the heart of the in-app half of the Application Update system (with the
Update Binary being the out-of-app half). It handles
downloading and staging updates, staging langpacks,
the post update process, state management,
update mutex management, and
error handling. When using the update UI, AppUpdater takes care of certain parts of the
process, calling into the update checker directly, for example, but the update
service still manages the download. Timer-initiated
background updates, however, are entirely
handled by the update service.
Shortly after Firefox launches, we initialize the UpdateServiceStub. Its
purpose is entirely to defer launching the entirety of
toolkit/mozapps/update/UpdateService.sys.mjs early in startup, if possible.
However, it does some very basic analysis of the current state to check to see
if an update is in-progress or has just completed. In these cases, the stub
launches the update service earlier than usual.
When the update service initializes, it reads update.status and
active-update.xml in order to figure out where the update process left off the
last time Firefox ran. If the state is inconsistent, it is deleted and we
restart the update process from the beginning. If a download was in-progress, it
is resumed. If a completed update is found, we move it to the update history. If
it completed successfully, we provide some information during Firefox startup
so that the “What’s New” page can be displayed. If it failed, we try to fall
back, potentially downloading and installing a different update MAR.
The update download and staging process generally follows this flow:
Call the
selectUpdatemethod of the update service to pick an update to install.Call the
downloadUpdatemethod of the update service.The update service creates a
Downloaderinstance and registers to be notified of download updates.Eventually the download completes and
Downloader.onStopRequestis called. If we are not able to stage, the AUS state is set toSTATE_PENDINGand we wait for the user to restart to install the update. If we are able to stage, the steps below will additionally be followed.Downloader.onStopRequestcalls into the Update Driver to initiate staging.The update driver waits for the updater to complete with staging, then calls the update service’s
refreshUpdateStatusmethod.On staging failure,
refreshUpdateStatushandles error fallbacks. On success, the AUS state is set toSTATE_PENDINGand we wait for the user to restart to install the update.
When in STATE_PENDING, the update service can download additional updates.
This follows pretty much the same process as downloading the first update, but
with a few changes:
Neither the AUS state nor
update.statuswill reflect that we are downloading an update.If we restart Firefox while the download is in-progress, we will install the ready update and discard the downloading update.
When the download completes, the AUS state will briefly change to
STATE_SWAPwhile we are moving the downloading files into the ready update directory. If we can stage, the state will then switch toSTATE_STAGING. Either way, it will then change back toSTATE_PENDING.
AppUpdater
AppUpdater in toolkit/mozapps/update/AppUpdater.sys.mjs is an interface for
running the update process interactively. It is used by Firefox’s Application
Update User Interfaces and also by the
Background Update Task. The entry point is
always the check method, regardless of what state update is currently in. When
check is called, AppUpdater will look for and track the progress of an
existing update, if there is one. If there isn’t, it will perform an update
check and, if configured to do so, automatically start downloading it.
AppUpdater’s progress and state can be monitored with the addListener method
and by examining the update property.
AppUpdater provides the ability to shut itself down quickly and cleanly,
regardless of what update is doing. This can be slightly tricky because some of
the promises that it uses only resolve when a large download completes or when
the user accepts an update download (which they may not do this session). To
make this easier, promises are typically wrapped in an AbortablePromise via
makeAbortable, which keeps track of all running promises and provides an
abort method for each to force it to reject immediately. These can be invoked
by calling AppUpdater’s stop method. Note that doing this only stops an
in-progress update if it is called before the update starts downloading. After
that, the entire process has been handed off to the update service and will
continue without any further interaction needed from AppUpdater.