Background Updates

The purpose of the background update system is to perform application updates during times when Firefox is not running. It was originally implemented in bug 1689520.

The system has three main tasks it needs to handle:

  1. Determining whether background updates are possible

  2. Scheduling background tasks

  3. Checking for updates

Architecturally, the background task is an instance of Firefox running in a special background mode, not a separate tool. This allows it to leverage existing functionality in Firefox, including the existing update code, but also keep acceptable performance characteristics for a background task by controlling and limiting the parts of Firefox that are loaded.

Everything in this document applies only to Microsoft Windows systems. In the future, we would like to extend background update support to macOS (see bug 1653435), however support for Linux and other Unix variants is not planned due to the variation in OS-level scheduling affordances across distributions/configurations.

Lifecycle

When background updates are possible, the background update task will be invoked every 7 hours (by default). The first invocation initiates an update download which proceeds after the task exits using Windows BITS. The second invocation prepares and stages the update. The third invocation installs the update as it starts up, and then checks for a newer update, possibly initiating another update download. The cycle then continues. If the user launches Firefox at any point in this process, it will take over. If the background update task is invoked while Firefox proper is running, the task exits without doing any work. In the future, the second invocation will stage and then restart to finish installing the update, rather than waiting for the third invocation (see bug 1704855).

Determining whether background updates are possible

Configuration

Updating Firefox, by definition, is an operation that applies to a Firefox installation. However, Firefox configuration is generally done via preference values and other files which are stored in a Firefox profile, and in general profiles do not correspond 1:1 with installations. This raises the question of how the configuration for something like the background updater should be managed. We deal with this question in two different ways.

There are two main preferences specifically relevant to updates. Those are app.update.auto, which controls whether updates should be downloaded automatically at all, even if Firefox is running, and app.update.background.enabled, to specifically control whether to use the background update system. We store these preferences in the update root directory, which is located in a per-installation location outside of any profile. Any profile loaded in that installation can observe and control these settings.

But there are some other pieces of state which absolutely must come from a profile, such as the telemetry client ID and logging level settings (see BackgroundTasksUtils.jsm). We also need to have access to Normandy, so that we can control the rollout of the background updater itself, as well as support possible future experiments or staged rollouts. It isn’t possible to do that on a per-installation basis where multiple profiles could be affected, because the profiles could find themselves in different experiment groups, and confusion would abound (both for the user and for our unfortunate code). The regular per-profile preference app.update.background.scheduling.enabled is mirrored from the default profile to the per-installation default value of app.update.background.enabled for these purposes.

All of this means that, in addition to our per-installation prefs, we also need to be able to identify and load a profile. To do that, we leverage the profile service to determine what the default profile for the installation would be if we were running a normal browser session, and the background updater always uses it.

Criteria

The default profile must satisfy several conditions in order for background updates to be scheduled. None of these confounding factors are present in fully default configurations, but some are relatively common. See BackgroundUpdate.REASON for all the details.

In order for the background task to be scheduled:

  • The per-installation app.update.background.enabled pref must be true

  • The per-installation app.update.auto pref must be true (the default)

  • The installation must have been created by an installer executable and not by manually extracting an archive file

  • The current OS user must be capable of updating the installation based on its file system permissions, either by having permission to write to application files directly or by using the Mozilla Maintenance Service (which also requires that it be installed and enabled, as it is by default)

  • BITS must be enabled via app.update.BITS.enabled (the default)

  • Firefox proxy server settings must not be configured (the default)

  • app.update.langpack.enabled must be false, or otherwise there must be no langpacks installed. Background tasks cannot update addons such as langpacks, because they are installed into a profile, and langpacks that are not precisely matched with the version of Firefox that is installed can cause YSOD failures (see bug 1647443), so background updating in the presence of langpacks is too risky.

If any per-installation prefs are changed while the default profile is not running, the background update task will witness the changed prefs during its next scheduled run, and exit if appropriate. The background task will not be unscheduled at that point; that is delayed until a browser session is run with the default profile (it should be possible for the background update task to unschedule itself, but currently we prefer the simplicity of handling all scheduling tasks from a single location).

In the extremely unusual case when prefs belonging to the default profile are modified outside of Firefox (with a text editor, say), then the background task will generally pick up those changes with no action needed, because it will fish the changed settings directly from the profile.

Scheduling background tasks

We use OS-level scheduling mechanisms to schedule the command firefox --backgroundtask backgroundupdate to run on a particular cadence. This cadence is controlled by the app.background.update.interval preference, which defaults to 7 hours.

On Windows, we use the Task Scheduler API; on macOS this will use launchd. For platform-specific scheduling details, see the TaskScheduler.jsm module.

These background tasks are scheduled per OS user and run with that user’s permissions. No additional privileges are requested or needed, regardless of the user account’s status, because we have already verified that either the user has all the permissions they need or that the Maintenance Service can be used.

Scheduling is done from within Firefox (or a background task) itself. To reduce shared state, only the default Firefox profile will interact with the OS-level task scheduling mechanism.

Checking for updates

After verifying all the preconditions and exiting immediately if any do not hold, the backgroundupdate task then verifies that it is the only Firefox instance running (as determined by a multi-instance lock, see bug 1553982), since otherwise it would be unsafe to continue performing any update work.

The task then fishes configuration settings from the default profile, namely:

  • A subset of update specific preferences, such as app.update.log

  • Data reporting preferences, to ensure the task respects the user’s choices

  • The (legacy) Telemetry client ID, so that background update Telemetry can be correlated with other Firefox Telemetry

The background task creates a temporary profile for itself to load, because a profile must be present in order for most of the Firefox code that it relies on to function.

After setting up the temporary profile and reading all the configuration we need into it, the regular UpdateService.jsm check process is initiated. To the greatest extent possible, this process is identical to what happens during any regular browsing session.

Specific topics

User interface

The background update task must not produce any user-visible interface. If it did, whatever appeared would be *disembodied*, unconnected to any usage of Firefox itself and appearing to a user as a weird, scary popup that came out of nowhere. To this end, we disable all UI within the updater when invoking from a background task. See bug 1696276.

This point also means that we cannot prompt for user elevation (on Windows this would mean a UAC prompt) from within the task, so we have to make very sure that we will be able to perform an update without needing to elevate. By default on Windows we are able to do this because of the presence of the Maintenance Service, but it may be disabled or not installed, so we still have to check.

Staging

The background update task will follow the update staging setting in the user’s default profile. The default setting is to enable staging, so most users will have it. In the future, background update tasks will recognize when an update has been staged and try to restart to finalize the staged update (see bug 1704855). Background tasks cannot finalize a staged update in all cases however; for one example, see bug 1695797, where we ensure that background tasks do not finalize a staged update while other instances of the application are running.

Staging is enabled by default because it provides a marked improvement in startup time for a browsing session. Without staging, browser startup following retrieving an update would be blocked on extracting the update archive and patching each individual application file. Staging does all of that in advance, so that all that needs to be done to complete an update (and therefore all that needs to be done during the startup path), is to move the already patched (that is, staged) files into place, a much faster and less resource intensive job.