Bug 1190323: Part 1 - [webext] Fix @@extension_id locale string substitutions. r?billm draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 08 Jan 2016 16:26:22 -0800
changeset 320195 ff7738b62dc61b06de96a3eb5499f95de70f0cd3
parent 320169 895e44661a5fcfd826d95c560990ecea25228b25
child 320196 7c298b4a2db9a8611a3a2390761821abaa4391b7
push id9154
push usermaglione.k@gmail.com
push dateSat, 09 Jan 2016 00:28:18 +0000
reviewersbillm
bugs1190323
milestone46.0a1
Bug 1190323: Part 1 - [webext] Fix @@extension_id locale string substitutions. r?billm
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionUtils.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -421,16 +421,18 @@ this.ExtensionData = function(rootURI) {
   this.id = null;
   this.localeData = null;
   this._promiseLocales = null;
 
   this.errors = [];
 };
 
 ExtensionData.prototype = {
+  builtinMessages: null,
+
   get logger() {
     let id = this.id || "<unknown>";
     return Log.repository.getLogger(LOGGER_ID_BASE + id);
   },
 
   // Report an error about the extension's manifest file.
   manifestError(message) {
     this.packagingError(`Reading manifest: ${message}`);
@@ -604,30 +606,35 @@ ExtensionData.prototype = {
         let entries = yield this.readDirectory("_locales");
         for (let file of entries) {
           if (file.isDir) {
             let locale = this.normalizeLocaleCode(file.name);
             locales.set(locale, file.name);
           }
         }
 
+        this.localeData = new LocaleData({
+          defaultLocale: this.defaultLocale,
+          locales,
+          builtinMessages: this.builtinMessages,
+        });
+
         return locales;
       }.bind(this));
     }
 
     return this._promiseLocales;
   },
 
   // Reads the locale messages for all locales, and returns a promise which
   // resolves to a Map of locale messages upon completion. Each key in the map
   // is a Gecko-compatible locale code, and each value is a locale data object
   // as returned by |readLocaleFile|.
   initAllLocales: Task.async(function* () {
     let locales = yield this.promiseLocales();
-    this.localeData = new LocaleData({ defaultLocale: this.defaultLocale, locales });
 
     yield Promise.all(Array.from(locales.keys(),
                                  locale => this.readLocaleFile(locale)));
 
     let defaultLocale = this.defaultLocale;
     if (defaultLocale) {
       if (!locales.has(defaultLocale)) {
         this.manifestError('Value for "default_locale" property must correspond to ' +
@@ -646,18 +653,16 @@ ExtensionData.prototype = {
   // default locale if no locale code is given, and sets it as the currently
   // selected locale on success.
   //
   // Pre-loads the default locale for fallback message processing, regardless
   // of the locale specified.
   //
   // If no locales are unavailable, resolves to |null|.
   initLocale: Task.async(function* (locale = this.defaultLocale) {
-    this.localeData = new LocaleData({ defaultLocale: this.defaultLocale });
-
     if (locale == null) {
       return null;
     }
 
     let promises = [this.readLocaleFile(locale)];
 
     let { defaultLocale } = this;
     if (locale != defaultLocale && !this.localeData.has(defaultLocale)) {
@@ -965,16 +970,22 @@ Extension.prototype = extend(Object.crea
   callOnClose(obj) {
     this.onShutdown.add(obj);
   },
 
   forgetOnClose(obj) {
     this.onShutdown.delete(obj);
   },
 
+  get builtinMessages() {
+    return new Map([
+      ["@@extension_id", this.uuid],
+    ]);
+  },
+
   // Reads the locale file for the given Gecko-compatible locale code, or if
   // no locale is given, the available locale closest to the UI locale.
   // Sets the currently selected locale on success.
   initLocale: Task.async(function* (locale = undefined) {
     if (locale === undefined) {
       let locales = yield this.promiseLocales();
 
       let localeList = Array.from(locales.keys(), locale => {
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -108,42 +108,48 @@ DefaultWeakMap.prototype = {
   },
 };
 
 function LocaleData(data) {
   this.defaultLocale = data.defaultLocale;
   this.selectedLocale = data.selectedLocale;
   this.locales = data.locales || new Map();
 
-  // Map(locale-name -> Map(message-key -> localized-strings))
+  // Map(locale-name -> Map(message-key -> localized-string))
   //
   // Contains a key for each loaded locale, each of which is a
   // Map of message keys to their localized strings.
   this.messages = data.messages || new Map();
+
+  if (data.builtinMessages) {
+    this.messages.set(this.BUILTIN, data.builtinMessages);
+  }
 }
 
 LocaleData.prototype = {
   // Representation of the object to send to content processes. This
   // should include anything the content process might need.
   serialize() {
     return {
       defaultLocale: this.defaultLocale,
       selectedLocale: this.selectedLocale,
       messages: this.messages,
       locales: this.locales,
     };
   },
 
+  BUILTIN: "@@BUILTIN_MESSAGES",
+
   has(locale) {
     return this.messages.has(locale);
   },
 
   // https://developer.chrome.com/extensions/i18n
   localizeMessage(message, substitutions = [], locale = this.selectedLocale, defaultValue = "??") {
-    let locales = new Set([locale, this.defaultLocale]
+    let locales = new Set([this.BUILTIN, locale, this.defaultLocale]
                           .filter(locale => this.messages.has(locale)));
 
     // Message names are case-insensitive, so normalize them to lower-case.
     message = message.toLowerCase();
     for (let locale of locales) {
       let messages = this.messages.get(locale);
       if (messages.has(message)) {
         let str = messages.get(message);
@@ -165,25 +171,17 @@ LocaleData.prototype = {
             return dollarSigns;
           }
         };
         return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer);
       }
     }
 
     // Check for certain pre-defined messages.
-    if (message == "@@extension_id") {
-      if ("uuid" in this) {
-        // Per Chrome, this isn't available before an ID is guaranteed
-        // to have been assigned, namely, in manifest files.
-        // This should only be present in instances of the |Extension|
-        // subclass.
-        return this.uuid;
-      }
-    } else if (message == "@@ui_locale") {
+    if (message == "@@ui_locale") {
       // Return the browser locale, but convert it to a Chrome-style
       // locale code.
       return Locale.getLocale().replace(/-/g, "_");
     } else if (message.startsWith("@@bidi_")) {
       let registry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
       let rtl = registry.isLocaleRTL("global");
 
       if (message == "@@bidi_dir") {