Preferences
Abstract: Preferences are used to store configuration parameters that can be modified at run-time rather than compile-time. This document describes the architecture of Chromium’s preferences system and explains how to use it in the context of extensions and policies.
Authors: battre, pamg
Introduction
Preferences are a means to store configuration options in Chromium. Each preference is identified by a key (a string like “proxy.mode”) and points to a value that we call the preference value. These values can be represented by any data-type that is defined in “values.h”, i.e. booleans, integers, strings, lists, dictionaries, et cetera. Preferences can be associated with a browser profile or with local state. Local state contains everything that is not directly associated to a specific profile but rather represents values that are associated with the user account on the host computer (e.g. “Was the browser shut down correctly last time?”, “When was the ‘GPU block list’ downloaded the last time?”, etc.). Due to its nature, local state is never synced across machines. The preference values that are effective at runtime can be set from various sources like default values, files of persisted settings, extensions and policies. The preference system is responsible for and allows for
- registering preferences with default values
- setting and getting of preference values
- while performing basic type validation
- while respecting precedences (priority) of preference value sources
- while providing thread safety (allow access only on UI thread, provide some helpers for subscribing to preference values on other threads)
- notification of preference value changes
These aspects are addressed in the following.
Basics
Registering preferences
Each preference needs to be registered before being used as in the following example:
PrefService* prefs = …;
prefs->RegisterBooleanPref(prefs::kSavingBrowserHistoryDisabled, false, PrefService::UNSYNCABLE_PREF);
where prefs::kSavingBrowserHistoryDisabled
is a string defined in
pref_names.{h,cc}
.
Here we register a boolean preference with the key
“history.saving_disabled”
and a default value of false
. The default
value determines the type of value that can be stored under the key (in this
case: boolean). Prefs associated with a profile may be labeled as sync-able and
un-sync-able (see sync).
Registering preferences is important to define their type and guarantee valid
default values but also because only registered preference keys can be queried
for their current value. Preferences are persisted to disk in the “Preferences”
file as JSON with nested dictionaries. The “.” in the preference key is used as
the nesting operator:
{
“history”: {
“saving_disabled”: false
}
}
This hierarchical namespace is not exposed within Chromium. You must not query
“history”
and expect a dictionary containing “saving_disabled”
. Always
access registered preferences by their full key.
To register new preferences, add your RegisterPrefs
call to
RegisterLocalState
or RegisterUserPrefs
in browser_prefs.cc
.
Accessing preference values
The preferences system lives entirely on the UI thread. In the following we
assume that we want to access it from there.
The preferences associated with the profile can be accessed using the
PrefService
provided by Profile::GetPrefs()
. The PrefService
responsible
for local_state is accessible by calling g_browser_process->local_state()
.
The PrefService provides getter and setter methods like
bool GetBoolean(const char* path) const;
void SetBoolean(const char* path, bool value);
As we want to be sure that modifications trigger notifications (see below),
GetDictionary()
and GetList()
return const-pointers. To modify the values,
there are two choices:
- Use
void PrefService::Set(const char* path, const Value& value)
to set a new dictionary value. - Use a
ScopedUserPrefUpdate
(seescoped_user_perf_update.h
) to modify a dictionary or list. For updating a dictionary, the code would look as follows:DictionaryPrefUpdate update(pref_service, prefs::kPrefName);
update->SetBoolean(“member_key”, true);
update->Remove(“other_key”, NULL);
At the time when “update” leaves its scope, a “value has changed” notification is automatically triggered.
You do not need to save modified preferences. All set and update operations
trigger a timer to write the modified preferences to disk. This reduces the
number of incremental write operations. If you really need to be sure that the
preferences are written to disk (during shutdown), use
PrefService::CommitPendingWrite()
.
The PrefChangeRegistrar allows to subscribe to preference change events. A class that wants to be notified of preferences changing can have a member variable
PrefChangeRegistrar registrar_;
In its constructor, the class would initialize the registrar as follows
registrar_.Init(pref_service);
registrar_.Add(prefs::kPreference, base::BindRepeating(/* … */));
passing either a base::RepeatingClosure
or a base::RepeatingCallback
taking
a const std::string&
(if the changed preference path is required). Then, any
changes to prefs::kPreference
will call the provided callback.
The PrefMember classes (BooleanPrefMember
, IntegerPrefMember
, …, see
pref_member.h
) are helper classes that stay in sync with preference values
beyond the scope of the UI thread. This allows simple reading of preference
values even outside the UI thread.
PrefStores and Precedences
Chromium knows several locations where preferences can be stored:
Store | Purpose |
managed_platform_prefs | contains all managed platform (non-cloud policy) preference values |
managed_cloud_prefs | contains all managed cloud policy preference values |
extension_prefs | contains preference values set by extensions |
command_line_prefs | contains preference values set by command-line switches |
user_prefs | contains all user-set preference values |
recommended_platform_prefs | contains all recommended platform policy preference values |
recommended_cloud_prefs | contains all recommended platform cloud preference values |
default_prefs | contains all default preference values |
The locations are sorted in decreasing order of precedence meaning that a
higher preference store overrides preference values returned by a lower
preference store. This is important because it means for example that an
extension can override command-line configured preferences and that policies can
overrule extension controlled preference values.
The default_prefs
PrefStore
contains the values set by
PrefService::Register...Value
(see above).
The four policy related PrefStore
s are discussed in a chapter below.
The user_prefs
PrefStore
represents a persisted pref store. It contains
preference values configured by Chromium’s “Preferences” dialogs (and also
preferences set by extensions stored under extension-specific preference keys
“extensions.settings.$extensionid.preferences
”).
The extension_prefs
PrefStore
maintains an in-memory view of the currently
effective preference values that are set by extensions. As multiple extensions
can try to override the same preference, this PrefStore respects their relative
preferences (more on this below). It provides a view over the
“extensions.settings.*.preferences
” paths in the user_prefs
.
The command_line_prefs
represent preferences activated by command-line flags.
Incognito Profile
The incognito profile is designed to overlay the regular profile. Preferences
defined in the regular profile are visible in the incognito profile unless they
are overridden.
The following table illustrates which PrefStore
s are shared between the
profiles and which are handled differently.
Store in regular profile | Incognito profile |
managed_platform_prefs | Same PrefStore as in regular profile |
managed_cloud_prefs | Same PrefStore as in regular profile |
extension_prefs | Special handling for different life-cycles (see below) |
command_line_prefs | Same PrefStore as in regular profile |
user_prefs | In-memory overlay that prevents persistence to disk |
recommended_platform_prefs | Same PrefStore as in regular profile |
recommended_cloud_prefs | Same PrefStore as in regular profile |
default_prefs | Same PrefStore as in regular profile |
- regular (inherited by incognito profile, persisted to disk)
- incognito_persistent (overrides regular, persisted to disk)
- incognito_session_only (overrides regular and incognito_persistent, not persisted to disk, life-time limited to one incognito session)
The in-memory overlay for user_prefs can be used to ensure that specific changes to the preferences do not get persisted in the regular user prefs.
Class Structure
The following figure illustrates the PrefStores
(the figures use UML syntax:
dashed lines with triangles represent inheritance; solid lines with arrows and
filled diamonds represent composition; solid lines with arrows and empty
diamonds represent aggregation):
PrefStore
is an interface that allows read access and subscription to changed
values.
The PersistentPrefStore
interface extends this with setter methods as well as
read and write operations to load and persist the preferences to disk. The
JsonPrefStore
implements this interface to store the “Preferences” file in the
profile on disk. The IncognitoUserPrefStore
provides means for an in-memory
overlay. This allows storing incognito preferences that are not written to disk
but forgotten when the last incognito window is closed.
The ConfigurationPolicyPrefStore
s handle policy driven preferences. They are
discussed in a separate chapter.
The CommandLinePrefStore
, DefaultPrefStore
and ExtensionPrefStore
store
the respective types of preference values. The ExtensionPrefStore
is discussed
in a separate chapter. These three PrefStores are based on the
ValueMapPrefStore
which provides an in-memory implementation of a PrefStore
and relies on the PrefValueMap
for this.
The PrefValueStore
is a facade around all PrefStores
that enforces the
precedence presented above. It maintains ownership of the respective
PrefStore
s and asks them one by one until the first PrefStore
contains a
specific key. PrefValueStore
also answers questions about where a preference
value comes from. For example, client code occasionally wants to know whether a
preference is under control of the user, i.e., not forced by configuration
management or extensions.
The PrefService
is the global interface to preferences. It supports
registering default values, and simple access to currently effective preference
values. At this abstraction layer, the PrefValueStore
becomes an
implementation detail of the PrefService
:
The PrefService
serves three purposes:
- Registering default values for preferences.
- Retrieving the currently effective preference value.
- Storing preference values in the persisted user_prefs.
The ExtensionPrefs
class manages preference values that extensions want to
set. It persists values to disk using the PrefService
and feeds values to the
ExtensionPrefStore
so that the PrefValueStore
can consider extension
controlled preferences for effective preference values. The latter happens
indirectly. The ExtensionPrefs
instance writes the preference values into the
ExtensionPrefValueMap
which knows about regular and incognito preferences. The
ExtensionPrefStore
, which is specific to either a regular profile or an
incognito profile, subscribes to the ExtensionPrefValueMap
and retrieves the
respective regular or incognito preferences.
Notification Mechanisms
The following figure indicates how preferences are set (blue arrows) and how change notifications are propagated to subscribers (red arrows).
The blue paths are triggered in three different ways:
- Registering a default preference value:
PrefService
→DefaultPrefStore
- Setting user preference (user modifies a setting in Chromium):
PrefService
→JsonPrefStore
- Extension sets a preference:
ExtensionPrefs
→ExtensionPrefValueMap
and for persistence beyond browser sessions:ExtensionPrefs
→PrefService
→JsonPrefStore
Helper classes like the PrefChangeRegistrar
and PrefMember
can be used to
subscribe to changes as outlined above.
Extension Controlled Preferences
Some (selected) preferences can be controlled by extensions (such as the proxy settings). These preferences need to be made available to extensions explicitly as we do not want to allow extensions to configure all preferences. Some particularities of extension controlled preferences need to be addressed:
- Two or more extensions might write different preference values for
the same preference. E.g. two extensions might want to set the proxy
configuration. In this case it is important to ensure that one
extension overrides the preference values of the other extension and
to guarantee a deterministic precedence. This means that each
time somebody requests the current value of a preference, he gets
the same reply. The precedence rule is defined as “last extension
installed wins”. This precedence order needs to be maintained even
between browser restarts where extensions are loaded asynchronously.
This is realized by adding a path
extensions.settings.$EXTENSION_ID.install_time
to the Preferences file. - The
ExtensionPrefStore
needs to provide values even before extensions are started. Imagine that a proxy extension provides proxy configuration values that are essential for any kind of network communication. Other extensions would not be able to communicate until the proxy extension has been started. For this reason, the Preferences file is extended byextensions.settings.$EXTENSION_ID.preferences
. At this point we store a dictionary, whose keys are un-expanded path names that identify the preferences. Maintaining these entries and updating theExtensionPrefStore
that represents the winning preferences of extensions is task of theExtensionPrefs
class. - We want to allow using different preference values for incognito
windows. We support two different kinds of incognito preferences:
- persistent_incognito preferences are persisted in the
Preferences file as
extension.settings.$EXTENSION_ID.incognito_preferences
. Their life-time spans several browser sessions (i.e. browser restart) and can be used for settings about incognito windows that are NOT related to the specific browsing history of an incognito session. Otherwise we would defeat the purpose of the incognito mode (don’t store any browsing behavior related data). - session_only_incognito preferences are not persisted. They can only be set once at least one incognito window has been opened and are cleared when the last incognito window is closed.
- persistent_incognito preferences are persisted in the
Preferences file as
Exposing preferences to extensions
In order to expose a preference to an extension, you need to follow these steps:
Add a property of type ChromeSettings to your extension namespace in the
respective file in chrome/common/extensions/api
. This is an example from the
proxy extension:
"properties": {
"settings": { // “settings” is the name of the preference exposed to the extension
"$ref": "ChromeSetting",
"description": "Proxy settings to be used. The value of this setting is a ProxyConfig object.",
"value": [
"proxy", // this is an internal, unique key referred to as “preference key” below
{"$ref": "ProxyConfig"} // this is the schema to which preferences need to comply
]
}
}
Add an PrefMappingEntry
to kPrefMapping[]
in extension_preference_api.cc
.
You require three entries:
- Name of the preference as exposed to the extension (referred to as “preference key” above)
- Name of the preference as handled within the browser (a string literal in the prefs:: namespace)
- Name of the permission required to modify the preference
Optionally you can register a PrefTransformer
to PrefMapping::PrefMapping()
in extension_preference_api.cc
. This allows for a transformation of how
preferences are structured within the browser and in the extension. For example,
the Proxy Settings API exposes preferences differently to extensions than they
are stored internally.
By following these steps, we guarantee that all preferences are made available
in the same way. We provide get()
, set()
, and clear()
functions as well as
an onChanged
event.
Configuration Policy
Configuration policy, a.k.a. admin policy, uses preferences as the main way of exposing policy settings in the Chromium code. There are two levels policy can come in:
- Mandatory policy: Settings that are forced upon the user by
their administrator. These policy settings are injected through the
managed_platform
andmanaged_cloud
ConfigurationPolicyPrefStore
s, which are the highest-precedencePrefStore
s. Any user-visible configuration settings (e.g. in chrome://settings) should be disabled and merely show the preference value if a policy-configured preference value is present. To facilitate implementing this, there isPrefService::Preference::IsManaged()
which will returntrue
if there's a managed policy setting. - Recommended policy: At the low-precedence side of the
PrefStore
stack, there are two moreConfigurationPolicyPrefStore
s:recommended_platform
andrecommended_cloud
. These contain policy-configured values that are not forced on the user, but merely defaults provided by the administrator, which the user can still override. From the perspective of the Chromium code, recommended policy shouldn't require special handling in most cases; the PrefService will just return the policy-configured default when appropriate.
The ConfigurationPolicyPrefStore
objects always keep the latest known policy
configuration. Behind the scenes, the policy settings are read from the
platform's native management APIs through the ConfigurationPolicyProvider
abstraction, which monitors the platform's policy configuration an notifies
ConfigurationPolicyPrefStore
to reexamine policy configuration as provided by
the ConfigurationPolicyProvider on changes. ConfigurationPolicyProvider will
then map the new configuration to preferences and expose them to PrefValueStore,
generating PREF_CHANGED
notifications as appropriate.
Covering the design of all ConfigurationPolicyProvider
implementations is out
of scope for this document, but here is a quick overview (see also
https://www.chromium.org/administrators):
- On Windows,
ConfigurationPolicyProviderWin
integrates with Windows Group Policy. Policy settings are read from the registry and Chromium watches for policy notifications to update policy settings when Group Policy changes. - Mac has a mechanism called "Managed Preferences" and policy-configured settings come as locked application preferences. They can be configured and distributed through the Workgroup Manager application and Apple's Directory Service.
- For Linux,
ConfigDirPolicyProvider
reads policy settings from JSON files in/etc
. The directories are monitored for changes so policy can be reloaded when files change. - On ChromeOS, there is code that can pull down policy settings from a device management service in the cloud.
Command line parameters
In order to map command line parameters to preference values, you can edit
command_line_pref_store.cc
and follow the examples therein. Note that it may
be advantageous to copy command-line flags into the CommandLinePrefStore
. This
decouples our code from a static global CommandLine
instance.