Bug 1162569 - default engine files should be in the omni.ja file, r=markh,glandium.
authorFlorian Quèze <florian@queze.net>
Tue, 19 May 2015 15:57:08 +0200
changeset 244570 8a177d785dd5
parent 244569 4cedd3e2cc17
child 244571 a95b4233e363
push id28785
push usercbook@mozilla.com
push date2015-05-20 13:40 +0000
treeherdermozilla-central@9aca58de87d8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh, glandium
bugs1162569
milestone41.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 1162569 - default engine files should be in the omni.ja file, r=markh,glandium.
browser/app/profile/firefox.js
browser/installer/package-manifest.in
browser/locales/Makefile.in
browser/locales/jar.mn
toolkit/components/search/nsSearchService.js
toolkit/components/search/tests/xpcshell/data/engine-app.xml
toolkit/components/search/tests/xpcshell/head_search.js
toolkit/components/search/tests/xpcshell/test_645970.js
toolkit/components/search/tests/xpcshell/test_async.js
toolkit/components/search/tests/xpcshell/test_async_app.js
toolkit/components/search/tests/xpcshell/test_identifiers.js
toolkit/components/search/tests/xpcshell/test_json_cache.js
toolkit/components/search/tests/xpcshell/test_sync.js
toolkit/components/search/tests/xpcshell/test_sync_app.js
toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js
toolkit/components/search/tests/xpcshell/test_sync_fallback.js
toolkit/components/search/tests/xpcshell/xpcshell.ini
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -378,16 +378,20 @@ pref("browser.download.panel.shown", fal
 
 #ifndef XP_MACOSX
 pref("browser.helperApps.deleteTempFileOnExit", true);
 #endif
 
 // search engines URL
 pref("browser.search.searchEnginesURL",      "https://addons.mozilla.org/%LOCALE%/firefox/search-engines/");
 
+// Tell the search service to load search plugins from the locale JAR
+pref("browser.search.loadFromJars", true);
+pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/");
+
 // pointer to the default engine name
 pref("browser.search.defaultenginename",      "chrome://browser-region/locale/region.properties");
 
 // Ordering of Search Engines in the Engine list. 
 pref("browser.search.order.1",                "chrome://browser-region/locale/region.properties");
 pref("browser.search.order.2",                "chrome://browser-region/locale/region.properties");
 pref("browser.search.order.3",                "chrome://browser-region/locale/region.properties");
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -48,17 +48,16 @@
 @RESPATH@/chrome/@AB_CD@.manifest
 @RESPATH@/browser/defaults/profile/bookmarks.html
 @RESPATH@/browser/defaults/profile/chrome/*
 @RESPATH@/browser/defaults/profile/localstore.rdf
 @RESPATH@/browser/defaults/profile/mimeTypes.rdf
 @RESPATH@/dictionaries/*
 @RESPATH@/hyphenation/*
 @RESPATH@/browser/@PREF_DIR@/firefox-l10n.js
-@RESPATH@/browser/searchplugins/*
 #ifdef HAVE_MAKENSISU
 @BINPATH@/uninstall/helper.exe
 #endif
 #ifdef MOZ_UPDATER
 @RESPATH@/update.locale
 @RESPATH@/updater.ini
 #endif
 
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -1,18 +1,15 @@
 # vim:set ts=8 sw=8 sts=8 noet:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include $(topsrcdir)/config/config.mk
 
-vpath %.xml @srcdir@/en-US/searchplugins
-vpath %.xml $(LOCALE_SRCDIR)/searchplugins
-
 ifdef LOCALE_MERGEDIR
 vpath crashreporter%.ini $(LOCALE_MERGEDIR)/browser/crashreporter
 endif
 vpath crashreporter%.ini $(LOCALE_SRCDIR)/crashreporter
 ifdef LOCALE_MERGEDIR
 vpath crashreporter%.ini @srcdir@/en-US/crashreporter
 endif
 ifdef LOCALE_MERGEDIR
@@ -67,32 +64,42 @@ UNINSTALLER_PACKAGE_HOOK = $(RM) -r $(ST
 STUB_HOOK = $(NSINSTALL) -D '$(_ABS_DIST)/$(PKG_INST_PATH)'; \
     $(RM) '$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     cp ../installer/windows/l10ngen/stub.exe '$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     chmod 0755 '$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
     $(NULL)
 endif
 
 SEARCHPLUGINS_NAMES = $(shell cat $(call MERGE_FILE,/searchplugins/list.txt)) ddg
-SEARCHPLUGINS_PATH := $(FINAL_TARGET)/searchplugins
+SEARCHPLUGINS_PATH := .deps/generated_$(AB_CD)
 SEARCHPLUGINS_TARGET := libs searchplugins
 SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_NAMES)),$(or $(wildcard $(call EN_US_OR_L10N_FILE,searchplugins/$(plugin))),$(info Missing searchplugin: $(plugin))))
 # Some locale-specific search plugins may have preprocessor directives, but the
 # default en-US ones do not.
 SEARCHPLUGINS_FLAGS := --silence-missing-directive-warnings
 PP_TARGETS += SEARCHPLUGINS
 
+list-txt = $(SEARCHPLUGINS_PATH)/list.txt
+GARBAGE += $(list-txt)
+
+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-txt): $(call mkdir_deps,$(SEARCHPLUGINS_PATH)) $(if $(IS_LANGUAGE_REPACK),FORCE)
+	$(RM) $(list-txt)
+	$(foreach plugin,$(SEARCHPLUGINS_NAMES),printf '$(plugin)\n' >> $(list-txt);)
+searchplugins:: $(list-txt)
+
 $(STAGEDIST): $(DIST)/branding
 
 $(DIST)/branding:
 	$(NSINSTALL) -D $@
 
 PROFILE_FILES = \
 	localstore.rdf \
 	mimeTypes.rdf \
@@ -121,16 +128,17 @@ libs-%:
 	$(NSINSTALL) -D $(DIST)/install
 	@$(MAKE) -C ../../toolkit/locales libs-$*
 	@$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
 ifdef MOZ_WEBAPP_RUNTIME
 	@$(MAKE) -C ../../webapprt/locales AB_CD=$* XPI_NAME=locale-$*
 endif
 	@$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
+	@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
 	@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
 	@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
 
 repackage-win32-installer: WIN32_INSTALLER_OUT=$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
 repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
 	@echo 'Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT).'
 	$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
 	$(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
@@ -153,18 +161,17 @@ endif
 
 
 clobber-zip:
 	$(RM) $(STAGEDIST)/chrome/$(AB_CD).jar \
 	  $(STAGEDIST)/chrome/$(AB_CD).manifest \
 	  $(STAGEDIST)/webapprt/chrome/$(AB_CD).jar \
 	  $(STAGEDIST)/webapprt/chrome/$(AB_CD).manifest \
 	  $(STAGEDIST)/$(PREF_DIR)/firefox-l10n.js
-	$(RM) -rf  $(STAGEDIST)/searchplugins \
-	  $(STAGEDIST)/dictionaries \
+	$(RM) -rf  $(STAGEDIST)/dictionaries \
 	  $(STAGEDIST)/hyphenation \
 	  $(STAGEDIST)/defaults/profile \
 	  $(STAGEDIST)/chrome/$(AB_CD) \
 	  $(STAGEDIST)/webapprt/chrome/$(AB_CD)
 
 
 langpack: langpack-$(AB_CD)
 
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -143,16 +143,18 @@
     locale/browser/syncBrand.dtd                (%chrome/browser/syncBrand.dtd)
     locale/browser/syncSetup.dtd                (%chrome/browser/syncSetup.dtd)
     locale/browser/syncSetup.properties         (%chrome/browser/syncSetup.properties)
     locale/browser/syncGenericChange.properties         (%chrome/browser/syncGenericChange.properties)
     locale/browser/syncKey.dtd                  (%chrome/browser/syncKey.dtd)
     locale/browser/syncQuota.dtd                (%chrome/browser/syncQuota.dtd)
     locale/browser/syncQuota.properties         (%chrome/browser/syncQuota.properties)
 #endif
+    locale/browser/searchplugins/list.txt       (.deps/generated_@AB_CD@/list.txt)
+    locale/browser/searchplugins/               (.deps/generated_@AB_CD@/*.xml)
 % 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
     locale/browser/netError.dtd                (%chrome/overrides/netError.dtd)
     locale/browser/appstrings.properties       (%chrome/overrides/appstrings.properties)
     locale/browser/downloads/settingsChange.dtd  (%chrome/overrides/settingsChange.dtd)
 % override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
 % override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -3400,30 +3400,36 @@ SearchService.prototype = {
     let cacheEnabled = getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true);
     if (cacheEnabled) {
       let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
       cacheFile.append("search.json");
       if (cacheFile.exists())
         cache = this._readCacheFile(cacheFile);
     }
 
-    let loadDirs = [];
+    let loadDirs = [], chromeURIs = [], chromeFiles = [];
+
+    let loadFromJARs = false;
+    try {
+      loadFromJARs = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+                             .getBoolPref("loadFromJars");
+    } catch (ex) {}
+
+    if (loadFromJARs)
+      [chromeFiles, chromeURIs] = this._findJAREngines();
+
     let locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
     while (locations.hasMoreElements()) {
       let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+      if (loadFromJARs && dir.equals(getDir(NS_APP_SEARCH_DIR)))
+        continue;
       if (dir.directoryEntries.hasMoreElements())
         loadDirs.push(dir);
     }
 
-    let loadFromJARs = getBoolPref(BROWSER_SEARCH_PREF + "loadFromJars", false);
-    let chromeURIs = [];
-    let chromeFiles = [];
-    if (loadFromJARs)
-      [chromeFiles, chromeURIs] = this._findJAREngines();
-
     let toLoad = chromeFiles.concat(loadDirs);
 
     function modifiedDir(aDir) {
       return (!cache.directories || !cache.directories[aDir.path] ||
               cache.directories[aDir.path].lastModifiedTime != aDir.lastModifiedTime);
     }
 
     function notInCachePath(aPathToLoad)
@@ -3470,44 +3476,52 @@ SearchService.prototype = {
       // See if we have a cache file so we don't have to parse a bunch of XML.
       let cache = {};
       let cacheEnabled = getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true);
       if (cacheEnabled) {
         let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, "search.json");
         cache = yield checkForSyncCompletion(this._asyncReadCacheFile(cacheFilePath));
       }
 
-      // Add all the non-empty directories of NS_APP_SEARCH_DIR_LIST to
-      // loadDirs.
-      let loadDirs = [];
+      let loadDirs = [], chromeURIs = [], chromeFiles = [];
+
+      let loadFromJARs = false;
+      try {
+        loadFromJARs = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+                               .getBoolPref("loadFromJars");
+      } catch (ex) {}
+
+      if (loadFromJARs) {
+        Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
+        [chromeFiles, chromeURIs] =
+          yield checkForSyncCompletion(this._asyncFindJAREngines());
+      }
+
+      // Add the non-empty directories of NS_APP_SEARCH_DIR_LIST to
+      // loadDirs...
       let locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
       while (locations.hasMoreElements()) {
         let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+        // ... but skip the application directory if we are loading from JAR.
+        if (loadFromJARs && dir.equals(getDir(NS_APP_SEARCH_DIR)))
+          continue;
+
         let iterator = new OS.File.DirectoryIterator(dir.path,
                                                      { winPattern: "*.xml" });
         try {
           // Add dir to loadDirs if it contains any files.
           yield checkForSyncCompletion(iterator.next());
           loadDirs.push(dir);
         } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
           // Catch for StopIteration exception.
         } finally {
           iterator.close();
         }
       }
 
-      let loadFromJARs = getBoolPref(BROWSER_SEARCH_PREF + "loadFromJars", false);
-      let chromeURIs = [];
-      let chromeFiles = [];
-      if (loadFromJARs) {
-        Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
-        [chromeFiles, chromeURIs] =
-          yield checkForSyncCompletion(this._asyncFindJAREngines());
-      }
-
       let toLoad = chromeFiles.concat(loadDirs);
       function hasModifiedDir(aList) {
         return Task.spawn(function() {
           let modifiedDir = false;
 
           for (let dir of aList) {
             if (!cache.directories || !cache.directories[dir.path]) {
               modifiedDir = true;
@@ -3862,72 +3876,79 @@ SearchService.prototype = {
     }.bind(this));
   },
 
   _findJAREngines: function SRCH_SVC_findJAREngines() {
     LOG("_findJAREngines: looking for engines in JARs")
 
     let rootURIPref = ""
     try {
-      rootURIPref = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "jarURIs");
+      rootURIPref = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+                            .getCharPref("jarURIs");
     } catch (ex) {}
 
     if (!rootURIPref) {
       LOG("_findJAREngines: no JAR URIs were specified");
 
       return [[], []];
     }
 
     let rootURIs = rootURIPref.split(",");
     let uris = [];
     let chromeFiles = [];
 
     rootURIs.forEach(function (root) {
       // Find the underlying JAR file for this chrome package (_loadEngines uses
       // it to determine whether it needs to invalidate the cache)
-      let chromeFile;
+      let jarPackaging = false;
       try {
         let chromeURI = gChromeReg.convertChromeURL(makeURI(root));
-        let fileURI = chromeURI; // flat packaging
-        while (fileURI instanceof Ci.nsIJARURI)
-          fileURI = fileURI.JARFile; // JAR packaging
-        fileURI.QueryInterface(Ci.nsIFileURL);
-        chromeFile = fileURI.file;
+        if (chromeURI instanceof Ci.nsIJARURI) {
+          let fileURI = chromeURI;
+          while (fileURI instanceof Ci.nsIJARURI)
+            fileURI = fileURI.JARFile;
+          fileURI.QueryInterface(Ci.nsIFileURL);
+          chromeFiles.push(fileURI.file);
+          jarPackaging = true;
+        }
       } catch (ex) {
         LOG("_findJAREngines: failed to get chromeFile for " + root + ": " + ex);
+        return;
       }
 
-      if (!chromeFile)
-        return;
-
-      chromeFiles.push(chromeFile);
-
       // Read list.txt from the chrome package to find the engines we need to
       // load
       let listURL = root + "list.txt";
-      let names = [];
       try {
         let chan = NetUtil.ioService.newChannelFromURI2(makeURI(listURL),
                                                         null,      // aLoadingNode
                                                         Services.scriptSecurityManager.getSystemPrincipal(),
                                                         null,      // aTriggeringPrincipal
                                                         Ci.nsILoadInfo.SEC_NORMAL,
                                                         Ci.nsIContentPolicy.TYPE_OTHER);
         let sis = Cc["@mozilla.org/scriptableinputstream;1"].
                   createInstance(Ci.nsIScriptableInputStream);
         sis.init(chan.open());
         let list = sis.read(sis.available());
-        names = list.split("\n").filter(function (n) !!n);
+        let names = list.split("\n").filter(function (n) !!n);
+        for (let name of names) {
+          let uri = root + name + ".xml";
+          uris.push(uri);
+          if (!jarPackaging) {
+            // Flat packaging requires that _loadEngines checks the modification
+            // time of each engine file.
+            uri = gChromeReg.convertChromeURL(makeURI(uri));
+            chromeFiles.push(uri.QueryInterface(Ci.nsIFileURL).file);
+          }
+        }
       } catch (ex) {
         LOG("_findJAREngines: failed to retrieve list.txt from " + listURL + ": " + ex);
 
         return;
       }
-
-      names.forEach(function (n) uris.push(root + n + ".xml"));
     });
 
     return [chromeFiles, uris];
   },
 
   /**
    * Loads jar engines asynchronously.
    *
@@ -3935,49 +3956,48 @@ SearchService.prototype = {
    * succeeds.
    */
   _asyncFindJAREngines: function SRCH_SVC__asyncFindJAREngines() {
     return Task.spawn(function() {
       LOG("_asyncFindJAREngines: looking for engines in JARs")
 
       let rootURIPref = "";
       try {
-        rootURIPref = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "jarURIs");
+        rootURIPref = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
+                              .getCharPref("jarURIs");
       } catch (ex) {}
 
       if (!rootURIPref) {
         LOG("_asyncFindJAREngines: no JAR URIs were specified");
         throw new Task.Result([[], []]);
       }
 
       let rootURIs = rootURIPref.split(",");
       let uris = [];
       let chromeFiles = [];
 
       for (let root of rootURIs) {
         // Find the underlying JAR file for this chrome package (_loadEngines uses
         // it to determine whether it needs to invalidate the cache)
-        let chromeFile;
+        let jarPackaging = false;
         try {
           let chromeURI = gChromeReg.convertChromeURL(makeURI(root));
-          let fileURI = chromeURI; // flat packaging
-          while (fileURI instanceof Ci.nsIJARURI)
-            fileURI = fileURI.JARFile; // JAR packaging
-          fileURI.QueryInterface(Ci.nsIFileURL);
-          chromeFile = fileURI.file;
+          if (chromeURI instanceof Ci.nsIJARURI) {
+            let fileURI = chromeURI;
+            while (fileURI instanceof Ci.nsIJARURI)
+              fileURI = fileURI.JARFile;
+            fileURI.QueryInterface(Ci.nsIFileURL);
+            chromeFiles.push(fileURI.file);
+            jarPackaging = true;
+          }
         } catch (ex) {
           LOG("_asyncFindJAREngines: failed to get chromeFile for " + root + ": " + ex);
-        }
-
-        if (!chromeFile) {
           return;
         }
 
-        chromeFiles.push(chromeFile);
-
         // Read list.txt from the chrome package to find the engines we need to
         // load
         let listURL = root + "list.txt";
         let deferred = Promise.defer();
         let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                         createInstance(Ci.nsIXMLHttpRequest);
         request.overrideMimeType("text/plain");
         request.onload = function(aEvent) {
@@ -3988,17 +4008,26 @@ SearchService.prototype = {
           deferred.resolve("");
         };
         request.open("GET", NetUtil.newURI(listURL).spec, true);
         request.send();
         let list = yield deferred.promise;
 
         let names = [];
         names = list.split("\n").filter(function (n) !!n);
-        names.forEach(function (n) uris.push(root + n + ".xml"));
+        for (let name of names) {
+          let uri = root + name + ".xml";
+          uris.push(uri);
+          if (!jarPackaging) {
+            // Flat packaging requires that _loadEngines checks the modification
+            // time of each engine file.
+            uri = gChromeReg.convertChromeURL(makeURI(uri));
+            chromeFiles.push(uri.QueryInterface(Ci.nsIFileURL).file);
+          }
+        }
       }
       throw new Task.Result([chromeFiles, uris]);
     });
   },
 
 
   _saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
     LOG("SRCH_SVC_saveSortedEngineList: starting");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/engine-app.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>TestEngineApp</ShortName>
+<Description>A test search engine installed in the application directory</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Url type="text/html" method="GET" template="http://localhost/" resultdomain="localhost">
+  <Param name="q" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
--- a/toolkit/components/search/tests/xpcshell/head_search.js
+++ b/toolkit/components/search/tests/xpcshell/head_search.js
@@ -67,16 +67,41 @@ if (!isChild) {
 }
 
 function dumpn(text)
 {
   dump("search test: " + text + "\n");
 }
 
 /**
+ * Configure preferences to load engines from
+ * chrome://testsearchplugin/locale/searchplugins/
+ * unless the loadFromJars parameter is set to false.
+ */
+function configureToLoadJarEngines(loadFromJars = true)
+{
+  let defaultBranch = Services.prefs.getDefaultBranch(null);
+
+  let url = "chrome://testsearchplugin/locale/searchplugins/";
+  defaultBranch.setCharPref("browser.search.jarURIs", url);
+
+  defaultBranch.setBoolPref("browser.search.loadFromJars", loadFromJars);
+
+  // Give the pref a user set value that is the opposite of the default,
+  // to ensure user set values are ignored.
+  Services.prefs.setBoolPref("browser.search.loadFromJars", !loadFromJars)
+
+  // Ensure a test engine exists in the app dir anyway.
+  let dir = Services.dirsvc.get(NS_APP_SEARCH_DIR, Ci.nsIFile);
+  if (!dir.exists())
+    dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+  do_get_file("data/engine-app.xml").copyTo(dir, "app.xml");
+}
+
+/**
  * Clean the profile of any metadata files left from a previous run.
  */
 function removeMetadata()
 {
   let file = gProfD.clone();
   file.append("search-metadata.json");
   if (file.exists()) {
     file.remove(false);
--- a/toolkit/components/search/tests/xpcshell/test_645970.js
+++ b/toolkit/components/search/tests/xpcshell/test_645970.js
@@ -7,20 +7,16 @@
 /**
  * Test nsSearchService with nested jar: uris, without async initialization
  */
 function run_test() {
   updateAppInfo();
 
   do_load_manifest("data/chrome.manifest");
 
-  let url  = "chrome://testsearchplugin/locale/searchplugins/";
-  Services.prefs.setCharPref("browser.search.jarURIs", url);
-
-  Services.prefs.setBoolPref("browser.search.loadFromJars", true);
+  configureToLoadJarEngines();
 
   // The search service needs to be started after the jarURIs pref has been
   // set in order to initiate it correctly
   let engine = Services.search.getEngineByName("bug645970");
   do_check_neq(engine, null);
   Services.obs.notifyObservers(null, "quit-application", null);
 }
-
--- a/toolkit/components/search/tests/xpcshell/test_async.js
+++ b/toolkit/components/search/tests/xpcshell/test_async.js
@@ -4,33 +4,27 @@
 function run_test() {
   do_test_pending();
 
   removeMetadata();
   removeCacheFile();
 
   do_load_manifest("data/chrome.manifest");
 
-  let url  = "chrome://testsearchplugin/locale/searchplugins/";
-  Services.prefs.setCharPref("browser.search.jarURIs", url);
-  Services.prefs.setBoolPref("browser.search.loadFromJars", true);
+  configureToLoadJarEngines();
 
   do_check_false(Services.search.isInitialized);
 
   Services.search.init(function search_initialized(aStatus) {
     do_check_true(Components.isSuccessCode(aStatus));
     do_check_true(Services.search.isInitialized);
 
-    // test engines from dir are loaded.
+    // test engines from dir are not loaded.
     let engines = Services.search.getEngines();
-    do_check_true(engines.length > 1);
+    do_check_eq(engines.length, 1);
 
     // test jar engine is loaded ok.
     let engine = Services.search.getEngineByName("bug645970");
     do_check_neq(engine, null);
 
-    Services.prefs.clearUserPref("browser.search.jarURIs");
-    Services.prefs.clearUserPref("browser.search.loadFromJars");
-
     do_test_finished();
   });
 }
-
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_async_app.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  do_test_pending();
+
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines(false);
+
+  do_check_false(Services.search.isInitialized);
+
+  Services.search.init(function search_initialized(aStatus) {
+    do_check_true(Components.isSuccessCode(aStatus));
+    do_check_true(Services.search.isInitialized);
+
+    // test engine from dir is loaded.
+    let engine = Services.search.getEngineByName("TestEngineApp");
+    do_check_neq(engine, null);
+
+    // test jar engine is not loaded.
+    engine = Services.search.getEngineByName("bug645970");
+    do_check_eq(engine, null);
+
+    do_test_finished();
+  });
+}
--- a/toolkit/components/search/tests/xpcshell/test_identifiers.js
+++ b/toolkit/components/search/tests/xpcshell/test_identifiers.js
@@ -9,19 +9,17 @@
 
 const SEARCH_APP_DIR = 1;
 
 function run_test() {
   removeMetadata();
   removeCacheFile();
   do_load_manifest("data/chrome.manifest");
 
-  let url  = "chrome://testsearchplugin/locale/searchplugins/";
-  Services.prefs.setCharPref("browser.search.jarURIs", url);
-  Services.prefs.setBoolPref("browser.search.loadFromJars", true);
+  configureToLoadJarEngines();
 
   updateAppInfo();
 
   run_next_test();
 }
 
 add_test(function test_identifier() {
   let engineFile = gProfD.clone();
@@ -51,9 +49,8 @@ add_test(function test_identifier() {
     // the filename inside the JAR. (In this case it's the same as the name.)
     do_check_eq(jarEngine.identifier, "bug645970");
 
     removeMetadata();
     removeCacheFile();
     run_next_test();
   });
 });
-
--- a/toolkit/components/search/tests/xpcshell/test_json_cache.js
+++ b/toolkit/components/search/tests/xpcshell/test_json_cache.js
@@ -23,16 +23,20 @@ function getDir(aKey, aIFace) {
 
   if (!_dirSvc) {
     _dirSvc = Cc["@mozilla.org/file/directory_service;1"].
                getService(Ci.nsIProperties);
   }
   return _dirSvc.get(aKey, aIFace || Ci.nsIFile);
 }
 
+function makeURI(uri) {
+  return Services.io.newURI(uri, null, null);
+}
+
 let cacheTemplate, appPluginsPath, profPlugins;
 
 /**
  * Test reading from search.json
  */
 function run_test() {
   removeMetadata();
   removeCacheFile();
@@ -46,22 +50,69 @@ function run_test() {
   engineFile.append("searchplugins");
   engineFile.append("test-search-engine.xml");
   engineFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
   // Copy the test engine to the test profile.
   let engineTemplateFile = do_get_file("data/engine.xml");
   engineTemplateFile.copyTo(engineFile.parent, "test-search-engine.xml");
 
-  // Add the app's searchplugins directory to the cache so it won't be ignored.
-  let appSearchPlugins = getDir(NS_APP_SEARCH_DIR);
-  appPluginsPath = appSearchPlugins.path;
-  cacheTemplate.directories[appPluginsPath] = {};
-  cacheTemplate.directories[appPluginsPath].lastModifiedTime = appSearchPlugins.lastModifiedTime;
-  cacheTemplate.directories[appPluginsPath].engines = [];
+  // Add the application's built-in plugin locations to the cache so it won't be ignored.
+  let filesToIgnore = []
+  let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
+
+  let loadFromJARs = false;
+  try {
+    loadFromJARs = defaultBranch.getBoolPref("loadFromJars");
+  } catch (ex) {}
+
+  if (!loadFromJARs) {
+    filesToIgnore.push(getDir(NS_APP_SEARCH_DIR));
+  } else {
+    let rootURIPref = defaultBranch.getCharPref("jarURIs");
+    let rootURIs = rootURIPref.split(",");
+    for (let root of rootURIs) {
+      let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+                        getService(Ci.nsIChromeRegistry);
+      let chromeURI = chromeReg.convertChromeURL(makeURI(root));
+      if (chromeURI instanceof Ci.nsIJARURI) {
+        // JAR packaging, we only need the parent jar file.
+        let fileURI = chromeURI; // flat packaging
+        while (fileURI instanceof Ci.nsIJARURI)
+          fileURI = fileURI.JARFile;
+        fileURI.QueryInterface(Ci.nsIFileURL);
+        filesToIgnore.push(fileURI.file);
+      } else {
+        // flat packaging, we need to find each .xml file.
+        let listURL = root + "list.txt";
+        let chan = NetUtil.ioService.newChannelFromURI2(makeURI(listURL),
+                                                        null, // aLoadingNode
+                                                        Services.scriptSecurityManager.getSystemPrincipal(),
+                                                        null, // aTriggeringPrincipal
+                                                        Ci.nsILoadInfo.SEC_NORMAL,
+                                                        Ci.nsIContentPolicy.TYPE_OTHER);
+        let sis = Cc["@mozilla.org/scriptableinputstream;1"].
+                  createInstance(Ci.nsIScriptableInputStream);
+        sis.init(chan.open());
+        let list = sis.read(sis.available());
+        let names = list.split("\n").filter(n => !!n);
+        for (let name of names) {
+          let uri = chromeReg.convertChromeURL(makeURI(root + name + ".xml"));
+          filesToIgnore.push(uri.QueryInterface(Ci.nsIFileURL).file);
+        }
+      }
+    }
+  }
+
+  for (let file of filesToIgnore) {
+    cacheTemplate.directories[file.path] = {
+      lastModifiedTime: file.lastModifiedTime,
+      engines: []
+    };
+  }
 
   // Replace the profile placeholder with the correct path.
   profPlugins = engineFile.parent.path;
   cacheTemplate.directories[profPlugins] = cacheTemplate.directories["[profile]/searchplugins"];
   delete cacheTemplate.directories["[profile]/searchplugins"];
   cacheTemplate.directories[profPlugins].engines[0].filePath = engineFile.path;
   cacheTemplate.directories[profPlugins].lastModifiedTime = engineFile.parent.lastModifiedTime;
 
@@ -155,18 +206,22 @@ add_test(function test_cache_write() {
           return;
         }
         Services.obs.removeObserver(cacheWriteObserver, "browser-search-service");
         do_print("Cache write complete");
         do_check_true(cache.exists());
         // Check that the search.json cache matches the template
 
         let cacheWritten = readJSONFile(cache);
-        // Delete the app search plugins directory from the template since it's not currently written out.
-        delete cacheTemplate.directories[appPluginsPath];
+
+        // Delete the empty dirs from the template since they are not written out.
+        for (let dir of Object.keys(cacheTemplate.directories)) {
+          if (!cacheTemplate.directories[dir].engines.length)
+            delete cacheTemplate.directories[dir];
+        }
 
         do_print("Check search.json");
         isSubObjectOf(cacheTemplate, cacheWritten);
 
         run_next_test();
       }
     };
     Services.obs.addObserver(cacheWriteObserver, "browser-search-service", false);
--- a/toolkit/components/search/tests/xpcshell/test_sync.js
+++ b/toolkit/components/search/tests/xpcshell/test_sync.js
@@ -2,28 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
   removeMetadata();
   removeCacheFile();
 
   do_load_manifest("data/chrome.manifest");
 
-  let url  = "chrome://testsearchplugin/locale/searchplugins/";
-  Services.prefs.setCharPref("browser.search.jarURIs", url);
-  Services.prefs.setBoolPref("browser.search.loadFromJars", true);
+  configureToLoadJarEngines();
 
   do_check_false(Services.search.isInitialized);
 
-  // test engines from dir are loaded.
+  // test engines from dir are not loaded.
   let engines = Services.search.getEngines();
-  do_check_true(engines.length > 1);
+  do_check_eq(engines.length, 1);
 
   do_check_true(Services.search.isInitialized);
 
   // test jar engine is loaded ok.
   let engine = Services.search.getEngineByName("bug645970");
   do_check_neq(engine, null);
-
-  Services.prefs.clearUserPref("browser.search.jarURIs");
-  Services.prefs.clearUserPref("browser.search.loadFromJars");
 }
-
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_sync_app.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  removeMetadata();
+  removeCacheFile();
+
+  do_load_manifest("data/chrome.manifest");
+
+  configureToLoadJarEngines(false);
+
+  do_check_false(Services.search.isInitialized);
+
+  // test engine from dir is loaded.
+  let engine = Services.search.getEngineByName("TestEngineApp");
+  do_check_neq(engine, null);
+
+  do_check_true(Services.search.isInitialized);
+
+  // test jar engine is not loaded.
+  engine = Services.search.getEngineByName("bug645970");
+  do_check_eq(engine, null);
+}
--- a/toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js
+++ b/toolkit/components/search/tests/xpcshell/test_sync_delay_fallback.js
@@ -4,53 +4,48 @@
 function run_test() {
   do_test_pending();
 
   removeMetadata();
   removeCacheFile();
 
   do_load_manifest("data/chrome.manifest");
 
-  let url  = "chrome://testsearchplugin/locale/searchplugins/";
-  Services.prefs.setCharPref("browser.search.jarURIs", url);
-  Services.prefs.setBoolPref("browser.search.loadFromJars", true);
+  configureToLoadJarEngines();
 
   do_check_false(Services.search.isInitialized);
   let fallback = false;
 
   Services.search.init(function search_initialized(aStatus) {
     do_check_true(fallback);
     do_check_true(Components.isSuccessCode(aStatus));
     do_check_true(Services.search.isInitialized);
 
-    // test engines from dir are loaded.
+    // test engines from dir are not loaded.
     let engines = Services.search.getEngines();
-    do_check_true(engines.length > 1);
+    do_check_eq(engines.length, 1);
 
     // test jar engine is loaded ok.
     let engine = Services.search.getEngineByName("bug645970");
     do_check_neq(engine, null);
 
-    Services.prefs.clearUserPref("browser.search.jarURIs");
-    Services.prefs.clearUserPref("browser.search.loadFromJars");
-
     do_test_finished();
   });
 
   // Execute test for the sync fallback while the async code is being executed.
   Services.obs.addObserver(function searchServiceObserver(aResult, aTopic, aVerb) {
     if (aVerb == "find-jar-engines") {
       Services.obs.removeObserver(searchServiceObserver, aTopic);
       fallback = true;
 
       do_check_false(Services.search.isInitialized);
 
-      // test engines from dir are loaded.
+      // test engines from dir are not loaded.
       let engines = Services.search.getEngines();
-      do_check_true(engines.length > 1);
+      do_check_eq(engines.length, 1);
 
       // test jar engine is loaded ok.
       let engine = Services.search.getEngineByName("bug645970");
       do_check_neq(engine, null);
 
       do_check_true(Services.search.isInitialized);
     }
   }, "browser-search-service", false);
--- a/toolkit/components/search/tests/xpcshell/test_sync_fallback.js
+++ b/toolkit/components/search/tests/xpcshell/test_sync_fallback.js
@@ -4,45 +4,39 @@
 function run_test() {
   do_test_pending();
 
   removeMetadata();
   removeCacheFile();
 
   do_load_manifest("data/chrome.manifest");
 
-  let url  = "chrome://testsearchplugin/locale/searchplugins/";
-  Services.prefs.setCharPref("browser.search.jarURIs", url);
-  Services.prefs.setBoolPref("browser.search.loadFromJars", true);
+  configureToLoadJarEngines();
 
   do_check_false(Services.search.isInitialized);
 
   Services.search.init(function search_initialized(aStatus) {
     do_check_true(Components.isSuccessCode(aStatus));
     do_check_true(Services.search.isInitialized);
 
-    // test engines from dir are loaded.
+    // test engines from dir are not loaded.
     let engines = Services.search.getEngines();
-    do_check_true(engines.length > 1);
+    do_check_eq(engines.length, 1);
 
     // test jar engine is loaded ok.
     let engine = Services.search.getEngineByName("bug645970");
     do_check_neq(engine, null);
 
-    Services.prefs.clearUserPref("browser.search.jarURIs");
-    Services.prefs.clearUserPref("browser.search.loadFromJars");
-
     do_test_finished();
   });
 
   do_check_false(Services.search.isInitialized);
 
-  // test engines from dir are loaded.
+  // test engines from dir are not loaded.
   let engines = Services.search.getEngines();
-  do_check_true(engines.length > 1);
+  do_check_eq(engines.length, 1);
 
   do_check_true(Services.search.isInitialized);
 
   // test jar engine is loaded ok.
   let engine = Services.search.getEngineByName("bug645970");
   do_check_neq(engine, null);
 }
-
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -3,16 +3,17 @@ head = head_search.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   data/chrome.manifest
   data/engine.src
   data/engine.xml
   data/engine2.xml
+  data/engine-app.xml
   data/engine-fr.xml
   data/engineMaker.sjs
   data/engine-rel-searchform.xml
   data/engine-rel-searchform-post.xml
   data/engineImages.xml
   data/ico-size-16x16-png.ico
   data/invalid-engine.xml
   data/search-metadata.json
@@ -48,13 +49,15 @@ support-files =
 [test_parseSubmissionURL.js]
 [test_SearchStaticData.js]
 [test_addEngine_callback.js]
 [test_multipleIcons.js]
 [test_resultDomain.js]
 [test_serialize_file.js]
 [test_searchSuggest.js]
 [test_async.js]
+[test_async_app.js]
 [test_sync.js]
+[test_sync_app.js]
 [test_sync_fallback.js]
 [test_sync_delay_fallback.js]
 [test_rel_searchform.js]
 [test_selectedEngine.js]