Commit b46dee67 authored by Nigel Tao's avatar Nigel Tao Committed by Commit Bot

Add chrome/services/app_service design doc

BUG=826982

Change-Id: Icc1398c6c6a87484e8348039c7e7b9b8918d483e
Reviewed-on: https://chromium-review.googlesource.com/c/1328626
Commit-Queue: Nigel Tao <nigeltao@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarcalamity <calamity@chromium.org>
Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608256}
parent 6d2626df
# Chrome Apps Platform Foundation # App Service
Chrome features multiple app platforms, including PWAs (desktop and mobile), Chrome, and especially Chrome OS, has apps, e.g. chat apps and camera apps.
ARC++ (Chrome OS), and Chrome apps (now deprecated). Each of these is based on
different underlying technologies, but UX-wise, it is an explicit goal to There are a number (lets call it `M`) of different places or app Consumers,
minimize the differences between them and give users the feeling that all apps usually UI (user interfaces) related, where users interact with their installed
have as similar properties, management, and features as possible. apps: e.g. launcher, search bar, shelf, New Tab Page, the `chrome://apps` page,
permissions or settings pages, picking and running default handlers for URLs,
This directory contains generic code to facilitate app platforms on Chrome MIME types or intents, etc.
communicating with the browser and each other. This layer consists of
app-platform-agnostic code, which each app platform's custom implementation There is also a different number (`N`) of app platforms or app Providers:
then plugs into. Example features include: built-in apps, extension-backed apps, PWAs (progressive web apps), ARC++
(Android apps), Crostini (Linux apps), etc.
* `app_service` - a central service for information on installed apps
* `app_registry` - the central registry for installed apps contained within Historically, each of the `M` Consumers hard-coded each of the `N` Providers,
the app service leading to `M×N` code paths that needed maintaining, or updating every time `M`
or `N` was incremented.
Over time, it is intended to reduce the dependencies of this layer on Chrome
browser through refactoring work, with the eventual goal of moving this This document describes the App Service, an intermediary between app Consumers
directory to chrome/services. and app Providers. This simplifies `M×N` code paths to `M+N` code paths, each
side with a uniform API, reducing code duplication and improving behavioral
consistency. This service (a Mojo service) could potentially be spun out into a
new process, for the usual
[Servicification](https://www.chromium.org/servicification) benefits (e.g.
self-contained services are easier to test and to sandbox), and would also
facilitate Chrome OS apps that aren't tied to the browser, e.g. Ash apps.
The App Service can be decomposed into a number of aspects. In all cases, it
provides to Consumers a uniform API over the various Provider implementations,
for these aspects:
- App Registry: list the installed apps.
- App Icon Factory: load an app's icon, at various resolutions.
- App Runner: launch apps and track app instances.
- App Installer: install, uninstall and update apps.
- App Coordinator: keep system-wide settings, e.g. default handlers.
Some things are still the responsbility of individual Consumers or Providers.
For example, the order in which the apps' icons are presented in the launcher
is a launcher-specific detail, not a system-wide detail, and is managed by the
launcher, not the App Service. Similarly, Android-specific VM configuration is
Android-specific, not generalizable system-wide, and is managed by the Android
provider (ARC++).
## Profiles
Talk of *the* App Service is an over-simplification. There will be *an* App
Service instance per Profile, as apps can be installed for *a* Profile.
Note that this doesn't require the App Service to know about Profiles. Instead,
Profile-specific code (e.g. a KeyedService) finds the Mojo service Connector
for a Profile, creates an App Service and binds the two (App Service and
Connector), but the App Service itself doesn't know about Profiles per se.
# App Registry
The App Registry's one-liner mission is:
- I would like to be able to for-each over all the apps in Chrome.
An obvious initial design for the App Registry involves three actors (Consumers
⇔ Service ⇔ Providers) with the middle actor (the App Registry Mojo service)
being a relatively thick implementation with a traditional `GetFoo`, `SetBar`,
`AddObserver` style API. The drawback is that Consumers are often UI surfaces
and UI code likes synchronous APIs, but Mojo APIs are generally asynchronous,
especially as it may cross process boundaries.
Instead, we use four actors (Consumers ↔ Proxy ⇔ Service ⇔ Providers), with the
Consumers ↔ Proxy connection being synchronous and in-process, lighter than the
async / out-of-process ⇔ connections. The Proxy implementation is relatively
thick and the Service implementation is relatively thin, almost trivially so.
Being able to for-each over all the apps is:
for (const auto& app : GetAppServiceProxy(profile).GetCache().GetAllApps()) {
DoSomethingWith(app);
}
The Proxy is expected to be in the same process as its Consumers, and the Proxy
would be a singleton (per Profile) within that process: Consumers would connect
to *the* in-process Proxy. If all of the app UI code is in the browser process,
the Proxy would also be in the browser process. If app UI code migrated to e.g.
a separate Ash process, then the Proxy would move with them. There might be
multiple Proxies, one per process (per Profile).
## Code Location
Some code is tied to a particular process, some code is not. For example, the
per-Profile `AppServiceProxy` obviously contains Profile-related code (i.e. a
`KeyedService`, so that browser code can find *the* `AppServiceProxy` for a
given Profile) that is tied to being in the browser process. The
`AppServiceProxy` also contains process-agnostic code (code that could
conceivably be used by an `AppServiceProxy` living in an Ash process), such as
code to cache and update the set of known apps (as in, the `App` Mojo type).
Specifically, the `AppServiceProxy` code is split into two locations, one under
`//chrome/browser` and one not:
- `//chrome/browser/apps/foundation/app_service`
- `//chrome/services/app_service`
On the Provider side, code specific to extension-backed applications or web
applications (as opposed to ARC++ or Crostini applications) lives under:
- `//chrome/browser/extensions`
- `//chrome/browser/web_applications`
## Matchmaking and Publish / Subscribe
The `AppService` itself does not have an `GetAllApps` method. It doesn't do
much, and it doesn't keep much state. Instead, the App Registry aspect of the
`AppService` is little more than a well known meeting place for `Publisher`s
(i.e. Providers) and `Subscriber`s (i.e. Proxies) to discover each other. An
analogy is that it's a matchmaker for `Publisher`s and `Subscriber`s, although
it matches all to all instead of one to one. `Publisher`s don't meet
`Subscriber`s directly, they meet the matchmaker, who introduces them to
`Subscriber`s.
Once a `Publisher` and `Subscriber` connect, the Pub-side sends the Sub-side a
stream of `App`s (calling the `Subscriber`'s `OnApps` method). On the initial
connection, the `Publisher` calls `OnApps` with "here's all the apps that I
(the `Publisher`) know about", with additional `OnApps` calls made as apps are
installed, uninstalled, updated, etc.
As mentioned, the App Registry aspect of the `AppService` doesn't do much. Its
part of the `AppService` Mojo interface is:
interface AppService {
// App Registry methods.
RegisterPublisher(Publisher publisher, AppType app_type);
RegisterSubscriber(Subscriber subscriber, ConnectOptions? opts);
// Some additional methods; not App Registry related.
};
interface Publisher {
// App Registry methods.
Connect(Subscriber subscriber, ConnectOptions? opts);
// Some additional methods; not App Registry related.
};
interface Subscriber {
OnApps(array<App> apps);
};
enum AppType {
kUnknown,
kArc,
kCrostini,
kWeb,
};
struct ConnectOptions {
// TBD: some way to represent l10n info such as the UI language.
};
Whenever a new `Publisher` is registered, it is connected with all of the
previously registered `Subscriber`s, and vice versa. Once a `Publisher` is
connected directly to a `Subscriber`, the `AppService` is no longer involved.
Even as new apps are installed, uninstalled, updated, etc., the app's
`Publisher` talks directly to each of its (previously connected) `Subscriber`s,
without involving the `AppService`.
TBD: whether we need un-registration and dis-connect mechanisms.
## The App Type
The one Mojo struct type, `App`, represents both a state, "an app that's ready
to run", and a delta or change in state, "here's what's new about an app".
Deltas include events like "an app was just installed" or "just uninstalled" or
"its icon was updated".
This is achieved by having every `App` field (other than `App.app_type` and
`App.app_id`) be optional. Either optional in the Mojo sense, with type `T?`
instead of a plain `T`, or if that isn't possible in Mojo (e.g. for integer or
enum fields), as a semantic convention above the Mojo layer: 0 is reserved to
mean "unknown". For example, the `App.show_in_launcher` field is a `Ternary`,
not a `bool`.
An `App.readiness` field represents whether an app is installed (i.e. ready to
launch), uninstalled or otherwise disabled. "An app was just installed" is
represented by a delta whose `readiness` is `kReady` and the old state's
`readiness` being some other value. This is at the Mojo level. At the C++
level, the `AppUpdate` wrapper type (see below) can provide helper
`WasJustInstalled` methods.
The `App`, `Readiness` and `Ternary` types are:
struct App {
AppType app_type;
string app_id;
// The fields above are mandatory. Everything else below is optional.
Readiness readiness;
string? name;
IconKey? icon_key;
Ternary show_in_launcher;
// etc.
};
enum Readiness {
kUnknown = 0,
kReady, // Installed and launchable.
kDisabledByBlacklist, // Disabled by SafeBrowsing.
kDisabledByPolicy, // Disabled by admin policy.
kDisabledByUser, // Disabled by explicit user action.
kUninstalledByUser,
};
enum Ternary {
kUnknown = 0,
kNo,
kYes,
};
// struct IconKey is discussed in the "App Icon Factory" section.
A new state can be mechanically computed from an old state and a delta (both of
which have the same type: `App`). Specifically, last known value wins. Any
known field in the delta overwrites the corresponding field in the old state,
any unknown field in the delta is ignored. For example, if an app's name
changed but its icon didn't, the delta's `App.name` field (a
`base::Optional<std::string>`) would be known (not `base::nullopt`) and copied
over but its `App.icon` field would be unknown (`base::nullopt`) and not copied
over.
The current state is thus the merger or sum of all previous deltas, including
the initial state being a delta against the ground state of "all unknown". The
`AppServiceProxy` tracks the state of its apps, and implements the
(in-process) Observer pattern so that UI surfaces can e.g. update themselves as
new apps are installed. There's only one method, `OnAppUpdate`, as opposed to
separate `OnAppInstalled`, `OnAppUninstalled`, `OnAppNameChanged`, etc.
methods. An `AppUpdate` is a pair of `App` values: old state and delta.
class AppRegistryCache {
public:
class Observer : public base::CheckedObserver {
public:
~Observer() override {}
virtual void OnAppUpdate(const AppUpdate& update) = 0;
};
// Etc.
};
# App Icon Factory
Icon data (even compressed as a PNG) is bulky, relative to the rest of the
`App` type. `Publisher`s will generally serve icon data lazily, on demand,
especially as the desired icon resolutions (e.g. 64px or 256px) aren't known
up-front. Instead of sending an icon at all possible resolutions, the
`Publisher` sends an `IconKey`: enough information (when combined with the
`AppType app_type` and `string app_id`) to load the icon at given resoultions.
The `IconKey` is an `IconType icon_type` plus additional data (`uint64 u_key`
and `string s_key`) whose semantics depend on the `icon_type`.
For example, some icons are statically built into the Chrome or Chrome OS
binary, as PNG-formatted resources, and can be loaded (synchronously, without
sandboxing). They can be loaded from a `u_key` resource ID. Some icons are
dynamically (and asynchronously) loaded from the extension database on disk.
They can be loaded from the `app_id` alone.
TBD: for extension-backed icons, some sort of timestamp or version in the
`IconKey` (probably encoded in the `u_key`) so that an icon update results in a
different `IconKey`.
Consumers (via the `AppServiceProxy`) can always ask the `AppService` to load
an icon. As an optimization, if the `AppServiceProxy` knows how to load an icon
for a given `IconKey`, it can skip the Mojo round trip and bulk data IPC and
load it directly instead. For example, it may know how to load icons from a
statically built resource ID.
interface AppService {
// App Icon Factory methods.
LoadIcon(AppType app_type,
string app_id, IconKey icon_key, LoadIconOptions? opts) => (gfx.mojom.ImageSkia image);
// Some additional methods; not App Icon Factory related.
};
interface Publisher {
// App Icon Factory methods.
LoadIcon(string app_id, IconKey icon_key, LoadIconOptions? opts) => (gfx.mojom.ImageSkia image);
// Some additional methods; not App Icon Factory related.
};
enum IconType {
kUnknown,
kExtension,
kResource,
};
struct IconKey {
IconType icon_type;
// The semantics of u_key and s_key depend on the icon_type.
uint64 u_key;
string s_key;
};
struct LoadIconOptions {
// TBD: desired resolutions (e.g. 64px or 256px). This could move out of
// LoadIconOptions to be a mandatory (not optional) LoadIcon arg.
};
TBD: post-processing effects like rounded corners, badges or grayed out (for
disabled apps) icons.
# App Runner
Each `Publisher` has (`Publisher`-specific) implementations of e.g. launching an
app and populating a context menu. The `AppService` presents a uniform API to
trigger these, forwarding each call on to the relevant `Publisher`:
interface AppService {
// App Runner methods.
Launch(AppType app_type, string app_id, LaunchOptions? opts);
// etc.
// Some additional methods; not App Runner related.
};
interface Publisher {
// App Runner methods.
Launch(string app_id, LaunchOptions? opts);
// etc.
// Some additional methods; not App Runner related.
};
struct LaunchOptions {
// TBD.
};
TBD: details for context menus.
TBD: be able to for-each over all the app *instances*, including multiple
instances (e.g. multiple windows) of the one app.
# App Installer
This includes Provider-facing API (not Consumer-facing API like the majority of
the `AppService`) to help install and uninstall apps consistently. For example,
one part of app installation is adding an icon shortcut (e.g. on the Desktop
for Windows, on the Shelf for Chrome OS). This helper code should be written
once (in the `AppService`), not `N` times in `N` Providers.
TBD: details.
# App Coordinator
This keeps system-wide or for-apps-as-a-whole preferences and settings, e.g.
out of all of the installed apps, which app has the user preferred for photo
editing. Consumer- or Provider-specific settings, e.g. icon order in the Chrome
OS shelf, or Crostini VM configuration, is out of scope of the App Service.
TBD: details.
---
Updated on 2018-11-15.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment