Simply put web apps are sites that the user installs onto their machine mimicking a native app installed on their respective operating system.
Sites that meet our install promotion requirements will have an install prompt appear in the omnibox on the right. Users can also install any site they like viaMenu > More tools > Create shortcut
....
Users can see all of their web apps onchrome://apps (viewable on non-ChromeOS).
Sites customize how their installed site integrates at the OS level using aweb app manifest. See developer guides for in depth overviews:
Seehttps://tinyurl.com/dpwa-architecture-public for presentation slides.
Usechrome://web-app-internals to inspect internal web app state. For Chromium versions prior to M93 usechrome://internals/web-app.
To test the behavior of the web app itself, Chrome DevTools Protocol can be used. SeeInstruction of using PWA via CDP.
The task of turning websites into “apps” in the user's OS environment has many parts to it. Before going into the parts, here is where they live:
See sourcehere.
WebAppProvider
core system lives on theProfile
object.WebAppUiManagerImpl
also lives on theProfile
object (to avoid deps issues).AppBrowserController
(typicallyWebAppBrowserController
for our interests) lives on theBrowser
object.WebAppTabHelper
lives on theWebContents
object.While most on-disk storage is done in theWebAppSyncBridge
, the system also sometimes uses thePrefService
. Most of these prefs live on theProfile
(profile->GetPrefs()
), but some prefs are in the global browser prefs (g_browser_process->local_state()
).
Presentation:https://tinyurl.com/dpwa-architecture-public
Older presentation:https://tinyurl.com/bmo-public
There are a lot of great guidelines within Chromium
Other than general guidance of minimal complexity and having single-responsibility classes, some goals of our system:
This public interface should (and will) be improved, however this is the basic state as of 2022/11/09:
WebAppCommandScheduler
. Internally this schedulesWebAppCommand
s to do safe operations on the system.AppRegistrarObserver
orWebAppInstallManagerObserver
. However, users of these MUST NOT modify the web app system in the observation call - this can cause race conditions and weird re-entry bugs.WebAppFilter
. This is the preferred way to find apps that match a given criteria.WebAppRegistrar
ScopedRegistryUpdate
and theWebAppSyncBridge
.WebAppIconManager
supports icon fetch for a given web app.Some parts of the system that are used within commands:
WebAppUrlLoader
&WebAppDataRetriever
are used in commands, but this interface could be improved & does not have a formal factory yet.WebAppInstallFinalizer
is used in commands and could be improved.The goal is to have all of these behind an abstraction that has a fake to allow easy unit testing of our system. Some of these dependencies are behind a nice fake-able interface, and some are not (yet).
PreinstalledWebAppManager
.content::WebContents
: The WebAppProvider system interacts withcontent::WebContents
for various tasks like loading URLs (viaWebAppUrlLoader
), retrieving web app manifest data and icons (viaWebAppDataRetriever
andWebAppIconDownloader
respectively), and observing navigations and destruction. TheWebContentsManager
serves as a centralized point of dependency for these interactions and acts as a factory for these components, allowing for easier management and faking in tests.OsIntegrationManger
manages this, which has a fake version.FakeWebAppProvider
.WebAppUiManager
, and faked by theFakeWebAppUiManager
.These store data for our system. Some of it is per-web-app, and some of it is global.
WebAppRegistrar
: This attempts to unify the reading of much of this data, and also holds an in-memory copy of the database data (in WebApp objects).WebAppDatabase
/WebAppSyncBridge
: This stores the web_app.proto object in a database, which is the preferred place to store information about a web app.WebAppIconManager
and stored on disk in the user's profile.PrefService
is used to store information that is either global, or needs to persist after a web app is uninstalled. Most of these prefs live on theProfile
(profile->GetPrefs()
), but some prefs are in the global browser prefs (g_browser_process->local_state()
). Some users of prefs:None of this information should be accessed without an applicable ‘lock’ on the system.
These are used to encapsulate common responsibilities or in-memory state that needs to be stored.
WebContentsManager
content::WebContents
for the WebAppProvider system. It acts as a factory forWebAppUrlLoader
,WebAppDataRetriever
, andWebAppIconDownloader
.chrome/browser/web_applications/web_contents/web_contents_manager.h
Commands are used to encapsulate operations in the system, and use Locks to ensure that your operation has isolation from other operations.
ToDebugValue()
method that is logged on completion and exposed in thechrome://web-app-internals. This can be very helpful for debugging and bug reports.Note: There are DVLOGs in theWebAppCommandManager
that can be helpful.
WebAppLockManager
Locks allow operations to receive appropriate protections for what they are doing. For example, anAppLock
will guarantee that no one is modifying, installing, or uninstalling that AppId while it is granted.
Locks contain assessors that allow the user to access parts of the web app system. This is the safest way to read from the system.
Note: There are DVLOGs in theWebAppLockManager
that can be helpful.
Anything that involves talking to the operating system. Usually has to do with adding, modifying, or removing the os entity that we register for the web app.
See thepublic interface section about which areas are generally “publicly available”.
The system is generally unit-test-compabible through theFakeWebAppProvider
, which is created by default in theTestingProfile
. Sometimes tests require using theAwaitStartWebAppProviderAndSubsystems
function in the setup function of the test to actually start the web app system & wait for it to complete startup. Seetesting.md for more information.
There is a long-term goal of having the system be easily fake-able for customers using it. The best current ‘public interface’ distinction of the system is theWebAppCommandScheduler
.
To access or change information about a web app:
WebAppLockManager
, or (preferably) create a command with the relevant lock description.WebAppRegistrar
to get the data you need. This unifies many of our data sources into one place.ScopedRegistryUpdate
.WebAppSyncBridge
, but can be pulled out.Other guides:
This page allows you to see all of the internal information about the WebAppProvider system, including a truncated log of the debug information of the last run commands.
It is often very useful to ask users to attach a copy of this page in bug reports.
The integration tests will print out thecontents of this page if a test fails, which can help debug that failure as well.
The codebase has a number of useful DVLOGs:
Please seetesting.md.
WebAppProvider
This is a per-profile object housing all the various web app subsystems. This is the “main()” of the web app implementation where everything starts.
WebApp
This is the representation of an installed web app in RAM. Its member fields largely reflect all the ways a site can configure theirweb app manifest plus miscellaneous internal bookkeeping and user settings.
WebAppRegistrar
This is where all theWebApps
live in memory, and what many other subsystems query to look up any given web app's fields. Mutations to the registry have to go via ScopedRegistryUpdate orWebAppSyncBridge.
Accessing the registrar should happen through a Lock. If you access it through theWebAppProvider
, then know that you are reading uncommitted (and thus unsafe) data.
Why is it full ofGetAppXYZ()
getters for every field instead of just returning aWebApp
reference? This is primarily done because the value may depend on multiple sources of truth. For example, whether the app should be run on OS login depends on both the user preference (stored in our database) and the administrator's policy (stored separately & given to us in-memory using prefs) Historically this was originally done because WebApps used be stored both in our database and extensions, and this served to unify the two.
WebAppSyncBridge
This is “bridge” between the WebAppProvider system‘s in-memory representation of web apps and the sync system’s database representation (along with sync system functionality like add/remove/modify operations). This integration is a little complex and deserves it's own document, but it basically:Stores all WebApps into a database and updates the database if any fields change. Updates the system when there are changes from the sync system.Installs new apps, uninstalls apps the user uninstalled elsewhere, updates metadata like user display mode preference, etc. Tells the sync system if there are local changes (installs, uninstalls, etc).
There is also a slide in a presentationhere which illustrates how this system works, but it may be out of date.
Note: This only stores per-web-app data, and that data will be deleted if the web app is uninstalled. To store data that persists after uninstall, or applies to a more general scope than a single web app, then thePrefService
can be used, either on theProfile
object (per-profile data,profile->GetPrefs()
) or on the browser process(
g_browser_process->local_state()``). Example of needing prefs: Storing if an app was previously installed as a preinstalled app in the past. Information is needed during chrome startup before profiles are loaded. A feature needs to store global data - e.g. “When was the last time we showed the in-product-help banner for any webapp?”
ExternallyManagedAppManager
This is for all installs that are not initiated by the user. This includespreinstalled apps,policy installed apps andsystem web apps.
These all specify a set ofinstall URLs which theExternallyManagedAppManager
synchronises the set of currently installed web apps with.
WebAppInstallFinalizer
This is the tail end of the installation process where we write all our web app metadata todisk and deploy OS integrations (likedesktop shortcuts andfile handlers using theOsIntegrationManager
.
WebAppUiManager
Sometimes we need to query window state from chrome/browser/ui land even though our BUILD.gn targets disallow this as it would be a circular dependency. Thisabstract class +impl injects the dependency at link time (see [WebAppUiManager::Create()
's][32]
declaration and definition locations`).
AppShimRegistry
On Mac OS we sometimes need to reason about the state of installed PWAs in all profiles without loading those profiles into memory. For this purpose,AppShimRegistry
stores the needed information in Chrome's “Local State” (global preferences). The information stored here includes:
This information is used when launching a web app (to determine what profile or profiles to open the web app in), as well as when updating an App Shim (to make sure all file and protocol handlers for the app are accounted for).