Bug 1488973: Register locale sources in all processes. r=mossop
☠☠ backed out by deb666fd309c ☠ ☠
authorZibi Braniecki <zbraniecki@mozilla.com>
Tue, 23 Oct 2018 18:59:19 +0000
changeset 442738 c06b2c594294
parent 442737 819df51e3083
child 442739 6eab8a1b17ce
push id71613
push userzbraniecki@mozilla.com
push dateWed, 24 Oct 2018 07:17:02 +0000
treeherderautoland@6eab8a1b17ce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop
bugs1488973
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1488973: Register locale sources in all processes. r=mossop Depends on D7961 Differential Revision: https://phabricator.services.mozilla.com/D7962
intl/l10n/L10nRegistry.jsm
--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -1,14 +1,16 @@
 const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
 const { FluentBundle, FluentResource } = ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 
+const isParentProcess =
+  Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
 /**
  * L10nRegistry is a localization resource management system for Gecko.
  *
  * It manages the list of resource sources provided with the app and allows
  * for additional sources to be added and updated.
  *
  * It's primary purpose is to allow for building an iterator over FluentBundle objects
  * that will be utilized by a localization API.
@@ -71,32 +73,43 @@ XPCOMUtils.defineLazyGlobalGetters(this,
  *
  * This allows the localization API to consume the FluentBundle and lazily fallback
  * on the next in case of a missing string or error.
  *
  * If during the life-cycle of the app a new source is added, the generator can be called again
  * and will produce a new set of permutations placing the language pack provided resources
  * at the top.
  */
-const L10nRegistry = {
-  sources: new Map(),
-  bootstrap: null,
+class L10nRegistryService {
+  constructor() {
+    this.sources = new Map();
+
+    if (!isParentProcess) {
+      this._setSourcesFromSharedData();
+      Services.cpmm.sharedData.addEventListener("change", this);
+    }
+  }
+
+  handleEvent(event) {
+    if (event.type === "change") {
+      if (event.changedKeys.includes("L10nRegistry:Sources")) {
+        this._setSourcesFromSharedData();
+      }
+    }
+  }
 
   /**
    * Based on the list of requested languages and resource Ids,
    * this function returns an lazy iterator over message context permutations.
    *
    * @param {Array} requestedLangs
    * @param {Array} resourceIds
    * @returns {AsyncIterator<FluentBundle>}
    */
   async* generateBundles(requestedLangs, resourceIds) {
-    if (this.bootstrap !== null) {
-      await this.bootstrap;
-    }
     const sourcesOrder = Array.from(this.sources.keys()).reverse();
     const pseudoNameFromPref = Services.prefs.getStringPref("intl.l10n.pseudo", "");
     for (const locale of requestedLangs) {
       for await (const dataSets of generateResourceSetsForLocale(locale, sourcesOrder, resourceIds)) {
         const bundle = new FluentBundle(locale, {
           ...MSG_CONTEXT_OPTIONS,
           transform: PSEUDO_STRATEGIES[pseudoNameFromPref],
         });
@@ -104,74 +117,114 @@ const L10nRegistry = {
           if (data === null) {
             return;
           }
           bundle.addResource(data);
         }
         yield bundle;
       }
     }
-  },
+  }
 
   /**
    * Adds a new resource source to the L10nRegistry.
    *
    * @param {FileSource} source
    */
   registerSource(source) {
     if (this.sources.has(source.name)) {
       throw new Error(`Source with name "${source.name}" already registered.`);
     }
     this.sources.set(source.name, source);
-    Services.locale.availableLocales = this.getAvailableLocales();
-  },
+
+    if (isParentProcess) {
+      this._synchronizeSharedData();
+      Services.locale.availableLocales = this.getAvailableLocales();
+    }
+  }
 
   /**
    * Updates an existing source in the L10nRegistry
    *
    * That will usually happen when a new version of a source becomes
    * available (for example, an updated version of a language pack).
    *
    * @param {FileSource} source
    */
   updateSource(source) {
     if (!this.sources.has(source.name)) {
       throw new Error(`Source with name "${source.name}" is not registered.`);
     }
     this.sources.set(source.name, source);
-    Services.locale.availableLocales = this.getAvailableLocales();
-  },
+    if (isParentProcess) {
+      this._synchronizeSharedData();
+      Services.locale.availableLocales = this.getAvailableLocales();
+    }
+  }
 
   /**
    * Removes a source from the L10nRegistry.
    *
    * @param {String} sourceId
    */
   removeSource(sourceName) {
     this.sources.delete(sourceName);
-    Services.locale.availableLocales = this.getAvailableLocales();
-  },
+    if (isParentProcess) {
+      this._synchronizeSharedData();
+      Services.locale.availableLocales = this.getAvailableLocales();
+    }
+  }
+
+  _synchronizeSharedData() {
+    const sources = new Map();
+    for (const [name, source] of this.sources.entries()) {
+      if (source.indexed) {
+        continue;
+      }
+      sources.set(name, {
+        locales: source.locales,
+        prePath: source.prePath,
+      });
+    }
+    Services.ppmm.sharedData.set("L10nRegistry:Sources", sources);
+    Services.ppmm.sharedData.flush();
+  }
+
+  _setSourcesFromSharedData() {
+    let sources = Services.cpmm.sharedData.get("L10nRegistry:Sources");
+    for (let [name, data] of sources.entries()) {
+      if (!this.sources.has(name)) {
+        const source = new FileSource(name, data.locales, data.prePath);
+        this.registerSource(source);
+      }
+    }
+    for (let name of this.sources.keys()) {
+      if (!sources.has(name)) {
+        this.removeSource(name);
+      }
+    }
+  }
 
   /**
    * Returns a list of locales for which at least one source
    * has resources.
    *
    * @returns {Array<String>}
    */
   getAvailableLocales() {
     const locales = new Set();
 
     for (const source of this.sources.values()) {
       for (const locale of source.locales) {
         locales.add(locale);
       }
     }
     return Array.from(locales);
-  },
-};
+  }
+}
 
 /**
  * This function generates an iterator over FluentBundles for a single locale
  * for a given list of resourceIds for all possible combinations of sources.
  *
  * This function is called recursively to generate all possible permutations
  * and uses the last, optional parameter, to pass the already resolved
  * sources order.
@@ -479,32 +532,33 @@ class IndexedFileSource extends FileSour
     super(name, locales, prePath);
     this.indexed = true;
     for (const path of paths) {
       this.cache[path] = true;
     }
   }
 }
 
+this.L10nRegistry = new L10nRegistryService();
+
 /**
  * The low level wrapper around Fetch API. It unifies the error scenarios to
  * always produce a promise rejection.
  *
  * We keep it as a method to make it easier to override for testing purposes.
  *
  * @param {string} url
  *
  * @returns {Promise<string>}
  */
-L10nRegistry.load = function(url) {
+this.L10nRegistry.load = function(url) {
   return fetch(url).then(response => {
     if (!response.ok) {
       return Promise.reject(response.statusText);
     }
     return response.text();
   });
 };
 
-this.L10nRegistry = L10nRegistry;
 this.FileSource = FileSource;
 this.IndexedFileSource = IndexedFileSource;
 
 var EXPORTED_SYMBOLS = ["L10nRegistry", "FileSource", "IndexedFileSource"];