Bug 1488973: Register locale sources in all processes. r=mossop
authorZibi Braniecki <zbraniecki@mozilla.com>
Wed, 24 Oct 2018 17:42:09 +0000
changeset 491178 755d616d0cb9b7d43c6f587999ed53c38130e917
parent 491177 4f1fd54c853fd655e91975b364dd5640559f5f6e
child 491179 08b16a40d372d72f0a8eda2f56cad2064e4df6b3
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersmossop
bugs1488973
milestone65.0a1
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 appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 const { FluentBundle, FluentResource } = ChromeUtils.import("resource://gre/modules/Fluent.jsm", {});
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 
+const isParentProcess = appinfo.processType === 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"];