The architecture of the Fennec Webpush implementation

Overview of the Push API

The Push API is a Web API that allows web applications to “wake up” the User Agent (the browser, Fennec), even when the application is not visible (or even loaded in a tab). The Push API can only be used by secure sites that register a Service Worker.

There are four major components in Push:

  1. A web application;
  2. The User Agent (Fennec);
  3. An app server associated to the web application;
  4. A push service backend.

These are listed roughly in the order that they appear in a successful push message. First, the web application registers a Service Worker and requests a push subscription. Fennec arranges a push channel and returns a subscription, including a User-Agent-specific push endpoint URL to the web application. The web application then provides that push endpoint URL to its app server. When the app server wishes to push a message to the web application, it posts the message and some additional meta-data to the provided push endpoint URL. The message is received by the push service backend and delivered by some unspecified out-of-band mechanism to the User Agent.

Two notes on terminology

“Push notifications” mean many things to many people. In this system, “push notifications” may or may not notify the user. Therefore, we use the term “push messages” exclusively. This avoids confusing the push system with the “Web Notification” API, which provides the familiar pop-up and system notification based user interface.

Throughout, we use Fennec to refer to the Java-and-Android application shell, and Gecko to refer to the embedded Gecko platform.

Overview of the Fennec Push implementation

Fennec uses the Google Cloud Messaging (GCM) service to deliver messages to the User Agent. Fennec registers for Google Cloud Messaging directly, like any other Android App; however, consumers do not interact with GCM directly. To provide a uniform interface to all consumers across all implementations, Fennec intermediates through the Mozilla-specific autopush service. Autopush maintains User Agent identification and authentication, provides per-web application messaging channels, and bridges unauthenticated push messages to the GCM delivery queue.

The Fennec Push implementation is designed to address the following technical challenge: GCM events, including incoming push messages, can occur when Gecko is not running. In case of an incoming push message, if Gecko is not running Fennec will request startup of necessary Gecko services and queue incoming push messages in the meantime. Once services are running, messages are sent over.

It’s worth noting that Fennec uses push to implement internal functionality like Sync and Firefox Accounts, and that these background services are not tied to Gecko being available.

Therefore, the principal constraints and requirements are:

1) Fennec must be able to service GCM events, including incoming push messages, independently of Gecko.

2) Gecko must be able to maintain push subscriptions across its entire life-cycle.

3) Fennec must be able to use push messages for non-Gecko purposes independently of Gecko.

Significant previous experience building Fennec background services has shown that configuring such services across the Gecko-Fennec interface is both valuable and difficult. Therefore, we add the following requirement:

  1. Gecko must own the push configuration details where appropriate and possible.

We explicitly do not care to support push messages across multiple processes. This will matter more in a post-e10s-on-Android world.

Push component architecture

Fennec components

The two major components are the PushManager and the associated PushManagerStorage. The PushManager interacts with the GCM system on the device and the autopush service, updating the PushManagerStorage as the system state changes.

There is a unique PushManagerStorage instance per-App that may only be accessed by the main process. The PushManagerStorage maintains two mappings. The first is a one-to-one mapping from a Gecko profile to a PushRegistration: a datum of User Agent state. The PushManager maintains each profile’s registration across Android life-cycle events, Gecko events, and GCM events. Each PushRegistration includes:

  • autopush server configuration details;
  • debug settings;
  • profile details;
  • access tokens and invalidation timestamps.

The second mapping is a one-to-many mapping from push registrations to PushSubscription instances. A push subscription corresponds to a unique push message channel from the autopush server to Fennec. Each PushSubscription includes:

  • a Fennec service identifier, one of “webpush” or “fxa”;
  • an associated Gecko profile;
  • a unique channel identifier.

The PushManager uses the PushSubscription service and profile maintained in the PushManagerStorage to determine how to deliver incoming GCM push messages.

Each PushRegistration corresponds to a unique uaid (User-Agent ID) on the autopush server. Each uaid is long-lived; a healthy client will maintain the same uaid until the client’s configuration changes or the service expires the registration due to inactivity or an unexpected server event. Each PushSubscription is associated to a given uaid and corresponds to a unique (per-uaid) chid (Channel ID) on the autopush server. An individual chid is potentially long-lived, but clients must expect the service to expire *chid*s as part of regular maintenance. The PushManager uses an AutopushClient instance to interact with the autopush server.

Between the PushManager, the PushManagerStorage, and assorted GCM event broadcast receivers, push messages that do not target Gecko can be implemented.

Gecko components

The Gecko side of the architecture is implemented in JavaScript by the PushServiceAndroidGCM.jsm module. This registers a PushService, like the Web Socket and HTTP2 backed services, which simply delegates to the Fennec PushManager using Messaging.jsm and friends.

There are some complications: first, Gecko must maintain the autopush configuration; and second, it is possible for the push system to change while Gecko is not running. Therefore, the communication is bi-directional throughout, so that Gecko can react to Fennec events after-the-fact.