: Etherpad users! We are developing an extension that will allow you to create pages from etherpads quickly and easily. Please visit our sandbox and help us test it.

Services/Sync/Features/Addon Sync

From MozillaWiki
< Services‎ | Sync
Revision as of 15:55, 30 August 2011 by Gszorc (Talk | contribs)

Jump to: navigation, search
Please use "Edit with form" above to edit this page.


Addon Sync
Stage Definition
Status In progress
Release target TBD
Health Blocked
Status note Product Management needs to flesh out the feature page so that development can be unblocked.


Product manager Jennifer Arguello
Directly Responsible Individual Jennifer Arguello
Lead engineer Gregory Szorc
Security lead Yvan Boily
Privacy lead `
Localization lead Axel Hecht
Accessibility lead `
QA lead Tracy Walker
UX lead Alex Faaborg
Product marketing lead Jaclyn Fu
Operations lead `
Additional members Ibai Garcia (SUMO)

Open issues/risks


Stage 1: Definition

1. Feature overview

Add-ons are synchronized between sync clients.

2. Users & use cases

User installs an add-on on one browser. When a sync occurs, the add-on is automagically installed on other sync clients.

3. Dependencies


4. Requirements




Stage 2: Design

5. Functional specification

The Addon Manager maintainers would like to see Sync support all add-on providers so as to not introduce 1st and 2nd class providers. This will require some APIs.

The solution to this problem will consist of the following:

  1. Optional properties on add-on objects that describe the add-on for purposes of use in Sync
  2. Functions on AddonManager that take above properties and perform actions

For add-on objects, providers wishing to opt in to Sync will expose the following read-only, optional properties:

a string that describes data necessary to synchronize the add-on between clients. The format of the string is opaque to Sync code. From the perspective of add-on providers, it should be a JSON object with a *type* field that is used to route it to the proper provider.
a GUID used to identify this add-on across Sync client instances. The GUID should be generated using Utils.makeGUID() from the Sync code. It is basically a Base64-encoded representation of 9 random bytes.

(These properties will need to be documented at https://developer.mozilla.org/en/Addons/Add-on_Manager/Addon)

The AddonManager will support the following APIs:

getAddonBySyncGUID(syncGUID, callback) 
Obtain an add-on from its Sync GUID. Calls the supplied function when that add-on is retrieved. The callback receives null on unknown add-on or the add-on object (generated from the underlying provider) on success. This API technically isn't required, but it makes the Add-on engine's createRecord(syncGUID) API much simpler. Without it, Sync code would probably query for all add-ons and iterate until it finds a match. By pushing the logic to AddonManager, optimization can be done there, if needed.
applySyncDataRecords(records, callbackObj) 
This applies an array of records that contain add-on metadata and makes the current state of the world agree with that data as much as possible.

The callbackObj is an object containing the following optional keys:

  • onSuccess - Invoked when an individual record is processed successfully. Arguments are the record itself.
  • onFailure - Invoked when an individual record could not be processed successfully. Arguments are the record itself.
  • onFinished - Invoked when all records have finished processing.

Each record is an object containing the following keys:

  • syncGUID (required) - The Sync GUID for this record
  • syncData (optional) - The .syncData field from an add-on
  • isDeleted (optional) - If evaluates to true, indicates that the record was deleted. Application of this record should involve trying to delete this add-on if present or no-op if not present.

The implementation of applySyncDataRecords() will resemble the following pseudocode:

foreach record in records:
  if record.isDeleted:
    found = this.getAddonBySyncGUID(record.syncGUID)
    if found:
    # else nothing to do since record not found

  existing = this.getExistingAddonBySyncData(record.syncData)
  result = null
  if existing:
    result = existing.updateFromSyncData(record.syncData)
    result = this.installAddonFromSyncData(syncGUID, syncData)

  result ? callbackObj.onSuccess(record) : callbackObj.onFailure(record)


The add-ons Sync engine will discover the set of add-ons that can be synced via the following procedure:

  1. Query AddonManager.getAllAddons()
  2. Filter the returned list by items that have the *syncData* and *syncGUID* properties and are installed in the profile add-on scope (addon.scope == AddonManager.SCOPE_PROFILE)

The engine will process incoming records by converting them to the API accepted by AddonManager.applySyncRecords() and will invoke that function with them. This is similar to how the history engine works.

The Sync add-on tracker will install add-on listeners in the AddonManager and will listen for the following events:

  • onEnabled
  • onDisabled
  • onInstalled
  • onUninstalled

When these are observed, the tracker will:

  1. Verify the changed add-on is in the set of Sync-able add-ons (using same heuristics as add-on discovery documented above)
  2. Mark the GUID as changed
  3. This will result in a new record for that GUID being created automagically. The createRecord() procedure will create the necessary record which will be queued for upload to the Sync server.

On start-up, the add-on engine will query AddonManager.getStartupChanges() for changes applied on application start-up. These are not observed by Sync because they occur before Sync is registered and running. Even if Sync does catch them in its tracker, it should be safe to mark records as changed. The only side-effect will be a slightly different modified time.

AddonManager.getStartupChanges() will be queried for the following change types:


TODO: Sync should catch all startup change types (the constants above) and react to them. We may need an API or some kind of verification/test to ensure newly-introduced startup changes/constants aren't missed by Sync. (This is all because getStartupChanges() takes a type as a parameter.)

6. User experience design


Stage 3: Planning

7. Implementation plan


8. Reviews

Security review


Privacy review


Localization review




Quality Assurance review


Operations review


Stage 4: Development

9. Implementation


Stage 5: Release

10. Landing criteria


The given value was not understood.

Feature details

Priority P2
Rank 999
Theme / Goal Experience
Roadmap Sync
Secondary roadmap `
Feature list Services
Project `
Engineering team Sync
The given value was not understood.

Team status notes

  status notes
Products ` `
Engineering ` `
Security ` `
Privacy ` `
Localization ` `
Accessibility ` `
Quality assurance ` `
User experience ` `
Product marketing ` `
Operations ` `
The given value was not understood.

Discussion notes from 2011-08-16:

  • add 'guid' column to 'addon' table in extensions.sqlite
  • add GUID support to AddonsManager (have it automatically generated, 12 chars base64url)
  • findings addons shouldn't count as daily update pings, or if they do, is it a problem?
  • generate one record per app per addon, silently drop records from other apps and unknown addons (unknown to AMO)
  • add option to locally disable enabled state sync (the "web developer case") in about:config