Bug 1437942 - Bundle all search engines with Firefox. draft
authorMichael Kaply <mozilla@kaply.com>
Fri, 25 May 2018 10:40:51 -0500
changeset 800135 1fbec6532b8c0d047577ee780300e380875ac27b
parent 800025 94d7f0e1c4d0390450028972cfeb65e0550b9892
child 800136 2d73e1540c68090b91e1a931c1f4542e66c4957e
child 800143 b5c69b37165dad5bd0b5b6df9da1178c29402982
push id111281
push usernalexander@mozilla.com
push dateFri, 25 May 2018 23:11:59 +0000
bugs1437942
milestone62.0a1
Bug 1437942 - Bundle all search engines with Firefox. MozReview-Commit-ID: 2fS6erC93o9
browser/components/search/content/search.xml
browser/locales/Makefile.in
browser/locales/jar.mn
browser/modules/ContentSearch.jsm
toolkit/components/search/nsSearchService.js
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -62,16 +62,17 @@
     </content>
 
     <implementation implements="nsIObserver">
       <constructor><![CDATA[
         if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
           return;
 
         Services.obs.addObserver(this, "browser-search-engine-modified");
+        Services.obs.addObserver(this, "browser-search-service");
 
         this._initialized = true;
 
         (window.delayedStartupPromise || Promise.resolve()).then(() => {
           window.requestIdleCallback(() => {
             Services.search.init(aStatus => {
               // Bail out if the binding's been destroyed
               if (!this._initialized)
@@ -109,16 +110,17 @@
       ]]></destructor>
 
       <method name="destroy">
         <body><![CDATA[
         if (this._initialized) {
           this._initialized = false;
 
           Services.obs.removeObserver(this, "browser-search-engine-modified");
+          Services.obs.removeObserver(this, "browser-search-service");
         }
 
         // Make sure to break the cycle from _textbox to us. Otherwise we leak
         // the world. But make sure it's actually pointing to us.
         // Also make sure the textbox has ever been constructed, otherwise the
         // _textbox getter will cause the textbox constructor to run, add an
         // observer, and leak the world too.
         if (this._textboxInitialized && this._textbox.mController.input == this)
@@ -178,17 +180,18 @@
         ]]></body>
       </method>
 
       <method name="observe">
         <parameter name="aEngine"/>
         <parameter name="aTopic"/>
         <parameter name="aVerb"/>
         <body><![CDATA[
-          if (aTopic == "browser-search-engine-modified") {
+          if (aTopic == "browser-search-engine-modified" ||
+              (aTopic == "browser-search-service" && aVerb == "init-complete")) {
             // Make sure the engine list is refetched next time it's needed
             this._engines = null;
 
             // Update the popup header and update the display after any modification.
             this._textbox.popup.updateHeader();
             this.updateDisplay();
           }
         ]]></body>
@@ -1301,16 +1304,17 @@
         menu.addEventListener("popuphidden", aEvent => {
           this._ignoreMouseEvents = false;
           aEvent.stopPropagation();
         });
 
         // Add weak referenced observers to invalidate our cached list of engines.
         Services.prefs.addObserver("browser.search.hiddenOneOffs", this, true);
         Services.obs.addObserver(this, "browser-search-engine-modified", true);
+        Services.obs.addObserver(this, "browser-search-service", true);
 
         // Rebuild the buttons when the theme changes.  See bug 1357800 for
         // details.  Summary: On Linux, switching between themes can cause a row
         // of buttons to disappear.
         Services.obs.addObserver(this, "lightweight-theme-changed", true);
       ]]></constructor>
 
       <!-- This handles events outside the one-off buttons, like on the popup
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -56,19 +56,17 @@ libs:: searchplugins
 # Required for l10n.mk - defines a list of app sub dirs that should
 # be included in langpack xpis.
 DIST_SUBDIRS = $(DIST_SUBDIR)
 
 include $(topsrcdir)/config/rules.mk
 
 include $(topsrcdir)/toolkit/locales/l10n.mk
 
-$(list-json): $(call mkdir_deps,$(SEARCHPLUGINS_PATH)) $(if $(IS_LANGUAGE_REPACK),FORCE)
-	$(call py_action,generate_searchjson,$(srcdir)/search/list.json $(AB_CD) $(list-json))
-searchplugins:: $(list-json)
+searchplugins:: $(srcdir)/search/list.json
 
 libs-%: AB_CD=$*
 libs-%:
 	$(if $(filter en-US,$(AB_CD)),, @$(MAKE) merge-$*)
 	$(NSINSTALL) -D $(DIST)/install
 	@$(MAKE) -C ../../toolkit/locales libs-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -55,23 +55,18 @@
     locale/browser/feeds/subscribe.dtd              (%chrome/browser/feeds/subscribe.dtd)
     locale/browser/feeds/subscribe.properties       (%chrome/browser/feeds/subscribe.properties)
     locale/browser/migration/migration.dtd         (%chrome/browser/migration/migration.dtd)
     locale/browser/migration/migration.properties  (%chrome/browser/migration/migration.properties)
     locale/browser/preferences/preferences.properties     (%chrome/browser/preferences/preferences.properties)
     locale/browser/preferences/security.dtd           (%chrome/browser/preferences/security.dtd)
     locale/browser/syncBrand.dtd                (%chrome/browser/syncBrand.dtd)
     locale/browser/syncSetup.properties         (%chrome/browser/syncSetup.properties)
-#if BUILD_FASTER
     locale/browser/searchplugins/               (searchplugins/*.xml)
     locale/browser/searchplugins/list.json      (search/list.json)
-#else
-    locale/browser/searchplugins/               (.deps/generated_@AB_CD@/*.xml)
-    locale/browser/searchplugins/list.json      (.deps/generated_@AB_CD@/list.json)
-#endif
     locale/browser/searchplugins/images/amazon.ico     (searchplugins/images/amazon.ico)
     locale/browser/searchplugins/images/ebay.ico       (searchplugins/images/ebay.ico)
     locale/browser/searchplugins/images/wikipedia.ico  (searchplugins/images/wikipedia.ico)
     locale/browser/searchplugins/images/yandex-en.ico  (searchplugins/images/yandex-en.ico)
     locale/browser/searchplugins/images/yandex-ru.ico  (searchplugins/images/yandex-ru.ico)
 % locale browser-region @AB_CD@ %locale/browser-region/
     locale/browser-region/region.properties        (%chrome/browser-region/region.properties)
 # the following files are browser-specific overrides
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -102,16 +102,17 @@ var ContentSearch = {
   _destroyedPromise: null,
 
   // The current controller and browser in _onMessageGetSuggestions.  Allows
   // fetch cancellation from _cancelSuggestions.
   _currentSuggestion: null,
 
   init() {
     Services.obs.addObserver(this, "browser-search-engine-modified");
+    Services.obs.addObserver(this, "browser-search-service");
     Services.obs.addObserver(this, "shutdown-leaks-before-check");
     Services.prefs.addObserver("browser.search.hiddenOneOffs", this);
     this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
   },
 
   get searchSuggestionUIStrings() {
     if (this._searchSuggestionUIStrings) {
       return this._searchSuggestionUIStrings;
@@ -128,16 +129,17 @@ var ContentSearch = {
   },
 
   destroy() {
     if (this._destroyedPromise) {
       return this._destroyedPromise;
     }
 
     Services.obs.removeObserver(this, "browser-search-engine-modified");
+    Services.obs.removeObserver(this, "browser-search-service");
     Services.obs.removeObserver(this, "shutdown-leaks-before-check");
 
     this._eventQueue.length = 0;
     this._destroyedPromise = Promise.resolve(this._currentEventPromise);
     return this._destroyedPromise;
   },
 
   /**
@@ -187,16 +189,25 @@ var ContentSearch = {
     case "nsPref:changed":
     case "browser-search-engine-modified":
       this._eventQueue.push({
         type: "Observe",
         data,
       });
       this._processEventQueue();
       break;
+    case "browser-search-service":
+      if (data == "init-complete") {
+        this._eventQueue.push({
+          type: "Observe",
+          data,
+        });
+        this._processEventQueue();
+      }
+      break;
     case "shutdown-leaks-before-check":
       subj.wrappedJSObject.client.addBlocker(
         "ContentSearch: Wait until the service is destroyed", () => this.destroy());
       break;
     }
   },
 
   removeFormHistoryEntry(msg, entry) {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -51,16 +51,17 @@ const NS_APP_USER_PROFILE_50_DIR = "Prof
 
 // We load plugins from APP_SEARCH_PREFIX, where a list.txt
 // file needs to exist to list available engines.
 const APP_SEARCH_PREFIX = "resource://search-plugins/";
 
 // See documentation in nsIBrowserSearchService.idl.
 const SEARCH_ENGINE_TOPIC        = "browser-search-engine-modified";
 const REQ_LOCALES_CHANGED_TOPIC  = "intl:requested-locales-changed";
+const TOPIC_LOCALES_CHANGE       = "intl:app-locales-changed";
 const QUIT_APPLICATION_TOPIC     = "quit-application";
 
 const SEARCH_ENGINE_REMOVED      = "engine-removed";
 const SEARCH_ENGINE_ADDED        = "engine-added";
 const SEARCH_ENGINE_CHANGED      = "engine-changed";
 const SEARCH_ENGINE_LOADED       = "engine-loaded";
 const SEARCH_ENGINE_CURRENT      = "engine-current";
 const SEARCH_ENGINE_DEFAULT      = "engine-default";
@@ -2999,19 +3000,19 @@ SearchService.prototype = {
         await ensureKnownCountryCode(this);
         // Due to the HTTP requests done by ensureKnownCountryCode, it's possible that
         // at this point a synchronous init has been forced by other code.
         if (!gInitialized)
           await this._asyncLoadEngines(cache);
 
         // Typically we'll re-init as a result of a pref observer,
         // so signal to 'callers' that we're done.
+        gInitialized = true;
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
         this._recordEngineTelemetry();
-        gInitialized = true;
       } catch (err) {
         LOG("Reinit failed: " + err);
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-failed");
       } finally {
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-complete");
       }
     })();
   },
@@ -3409,24 +3410,35 @@ SearchService.prototype = {
       this._parseListTxt(list, uris);
     } else {
       this._parseListJSON(list, uris);
     }
     return uris;
   },
 
   _parseListJSON: function SRCH_SVC_parseListJSON(list, uris) {
-    let searchSettings;
+    let json;
     try {
-      searchSettings = JSON.parse(list);
+      json = JSON.parse(list);
     } catch (e) {
       LOG("failing to parse list.json: " + e);
       return;
     }
 
+    let searchSettings;
+    let locale = Services.locale.getAppLocaleAsBCP47();
+    if ("locales" in json &&
+        locale in json.locales) {
+      searchSettings = json.locales[locale];
+    } else {
+      // json.default should always be there.
+      // Should we assert?
+      searchSettings = json;
+    }
+
     // Check if we have a useable country specific list of visible default engines.
     // This will only be set if we got the list from the Mozilla search server;
     // it will not be set for distributions.
     let engineNames;
     let visibleDefaultEngines = this.getVerifiedGlobalAttr("visibleDefaultEngines");
     if (visibleDefaultEngines) {
       let jarNames = new Set();
       for (let region in searchSettings) {
@@ -3468,48 +3480,62 @@ SearchService.prototype = {
           "visibleDefaultEngines" in searchSettings[searchRegion]) {
         engineNames = searchSettings[searchRegion].visibleDefaultEngines;
       } else {
         engineNames = searchSettings.default.visibleDefaultEngines;
       }
     }
 
     // Remove any engine names that are supposed to be ignored.
-    // This pref is only allows in a partner distribution.
+    // This pref is only allowed in a partner distribution.
     let branch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
     if (isPartnerBuild() &&
         branch.getPrefType("ignoredJAREngines") == branch.PREF_STRING) {
       let ignoredJAREngines = branch.getCharPref("ignoredJAREngines")
                                     .split(",");
       let filteredEngineNames = engineNames.filter(e => !ignoredJAREngines.includes(e));
       // Don't allow all engines to be hidden
       if (filteredEngineNames.length > 0) {
         engineNames = filteredEngineNames;
       }
     }
 
+    if ("regionOverrides" in json &&
+        searchRegion in json.regionOverrides) {
+      for (let engine in json.regionOverrides[searchRegion]) {
+        let index = engineNames.indexOf(engine);
+        if (index > -1) {
+          engineNames[index] = json.regionOverrides[searchRegion][engine];
+        }
+      }
+    }
+
     for (let name of engineNames) {
       uris.push(APP_SEARCH_PREFIX + name + ".xml");
     }
 
     // Store this so that it can be used while writing the cache file.
     this._visibleDefaultEngines = engineNames;
 
     if (searchRegion && searchRegion in searchSettings &&
         "searchDefault" in searchSettings[searchRegion]) {
       this._searchDefault = searchSettings[searchRegion].searchDefault;
-    } else {
+    } else if ("searchDefault" in searchSettings.default) {
       this._searchDefault = searchSettings.default.searchDefault;
+    } else if ("searchDefault" in json.default) {
+      this._searchDefault = json.default.searchDefault;
     }
 
     if (searchRegion && searchRegion in searchSettings &&
         "searchOrder" in searchSettings[searchRegion]) {
       this._searchOrder = searchSettings[searchRegion].searchOrder;
     } else if ("searchOrder" in searchSettings.default) {
       this._searchOrder = searchSettings.default.searchOrder;
+    } else if ("searchOrder" in json.default) {
+      this._searchOrder = json.default.searchOrder;
     }
   },
 
   _parseListTxt: function SRCH_SVC_parseListTxt(list, uris) {
     let names = list.split("\n").filter(n => !!n);
     // This maps the names of our built-in engines to a boolean
     // indicating whether it should be hidden by default.
     let jarNames = new Map();
@@ -4479,16 +4505,17 @@ SearchService.prototype = {
         }
         break;
 
       case QUIT_APPLICATION_TOPIC:
         this._removeObservers();
         break;
 
       case REQ_LOCALES_CHANGED_TOPIC:
+      case TOPIC_LOCALES_CHANGE:
         // Locale changed. Re-init. We rely on observers, because we can't
         // return this promise to anyone.
         this._asyncReInit();
         break;
     }
   },
 
   // nsITimerCallback
@@ -4539,16 +4566,17 @@ SearchService.prototype = {
     this._observersAdded = true;
 
     Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC);
     Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC);
 
     if (AppConstants.MOZ_BUILD_APP == "mobile/android") {
       Services.obs.addObserver(this, REQ_LOCALES_CHANGED_TOPIC);
     }
+    Services.obs.addObserver(this, TOPIC_LOCALES_CHANGE);
 
     // The current stage of shutdown. Used to help analyze crash
     // signatures in case of shutdown timeout.
     let shutdownState = {
       step: "Not started",
       latestError: {
         message: undefined,
         stack: undefined