Bug 1109354 - prefer Firefox default engines over profile-installed plugins with the same name, r=markh.

This commit is contained in:
Florian Quèze 2015-06-15 18:32:09 +02:00
parent b54a6ba042
commit 55c7ff176c
17 changed files with 511 additions and 46 deletions

View File

@ -206,8 +206,36 @@ AppendDistroSearchDirs(nsIProperties* aDirSvc, nsCOMArray<nsIFile> &array)
NS_IMETHODIMP
DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
{
/**
* We want to preserve the following order, since the search service loads
* engines in first-loaded-wins order.
* - distro search plugin locations (Loaded by the search service using
* NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)
*
* - engines shipped in chrome (Loaded from jar files by the search
* service)
*
* Then other locations, from NS_APP_SEARCH_DIR_LIST:
* - extension search plugin locations (prepended below using
* NS_NewUnionEnumerator)
* - user search plugin locations (profile)
* - app search plugin location (shipped engines)
*/
nsresult rv;
if (!strcmp(aKey, NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)) {
nsCOMPtr<nsIProperties> dirSvc
(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
if (!dirSvc)
return NS_ERROR_FAILURE;
nsCOMArray<nsIFile> distroFiles;
AppendDistroSearchDirs(dirSvc, distroFiles);
return NS_NewArrayEnumerator(aResult, distroFiles);
}
if (!strcmp(aKey, NS_APP_SEARCH_DIR_LIST)) {
nsCOMPtr<nsIProperties> dirSvc
(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
@ -216,16 +244,6 @@ DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
nsCOMArray<nsIFile> baseFiles;
/**
* We want to preserve the following order, since the search service loads
* engines in first-loaded-wins order.
* - extension search plugin locations (prepended below using
* NS_NewUnionEnumerator)
* - distro search plugin locations
* - user search plugin locations (profile)
* - app search plugin location (shipped engines)
*/
AppendDistroSearchDirs(dirSvc, baseFiles);
AppendFileKey(NS_APP_USER_SEARCH_DIR, dirSvc, baseFiles);
AppendFileKey(NS_APP_SEARCH_DIR, dirSvc, baseFiles);

View File

@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const NS_APP_CACHE_PARENT_DIR = "cachePDir";
const NS_APP_SEARCH_DIR = "SrchPlugns";
const NS_APP_SEARCH_DIR_LIST = "SrchPluginsDL";
const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
@ -147,20 +148,24 @@ DirectoryProvider.prototype = {
},
getFiles: function(prop) {
if (prop != NS_APP_SEARCH_DIR_LIST)
return;
if (prop != NS_APP_SEARCH_DIR_LIST &&
prop != NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)
return null;
let result = [];
if (prop == NS_APP_DISTRIBUTION_SEARCH_DIR_LIST) {
this._appendDistroSearchDirs(result);
}
else {
/**
* We want to preserve the following order, since the search service loads
* engines in first-loaded-wins order.
* - distro search plugin locations
* We want to preserve the following order, since the search service
* loads engines in first-loaded-wins order.
* - distro search plugin locations (loaded separately by the search
* service)
* - user search plugin locations (profile)
* - app search plugin location (shipped engines)
*/
this._appendDistroSearchDirs(result);
let appUserSearchDir = FileUtils.getDir(NS_APP_USER_SEARCH_DIR, [], false);
if (appUserSearchDir.exists())
result.push(appUserSearchDir);
@ -168,6 +173,7 @@ DirectoryProvider.prototype = {
let appSearchDir = FileUtils.getDir(NS_APP_SEARCH_DIR, [], false);
if (appSearchDir.exists())
result.push(appSearchDir);
}
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),

View File

@ -51,6 +51,7 @@ const MODE_TRUNCATE = 0x20;
// Directory service keys
const NS_APP_SEARCH_DIR_LIST = "SrchPluginsDL";
const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
const NS_APP_SEARCH_DIR = "SrchPlugns";
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
@ -3485,8 +3486,7 @@ SearchService.prototype = {
cache = this._readCacheFile(cacheFile);
}
let loadDirs = [], chromeURIs = [], chromeFiles = [];
let chromeURIs = [], chromeFiles = [];
let loadFromJARs = false;
try {
loadFromJARs = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
@ -3496,16 +3496,33 @@ SearchService.prototype = {
if (loadFromJARs)
[chromeFiles, chromeURIs] = this._findJAREngines();
let locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
let distDirs = [];
let locations;
try {
locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
Ci.nsISimpleEnumerator);
} catch (e) {
// NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
// so this throws during unit tests (but not xpcshell tests).
locations = {hasMoreElements: () => false};
}
while (locations.hasMoreElements()) {
let dir = locations.getNext().QueryInterface(Ci.nsIFile);
if (dir.directoryEntries.hasMoreElements())
distDirs.push(dir);
}
let otherDirs = [];
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);
otherDirs.push(dir);
}
let toLoad = chromeFiles.concat(loadDirs);
let toLoad = chromeFiles.concat(distDirs, otherDirs);
function modifiedDir(aDir) {
return (!cache.directories || !cache.directories[aDir.path] ||
@ -3528,10 +3545,12 @@ SearchService.prototype = {
if (!cacheEnabled || rebuildCache) {
LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
loadDirs.forEach(this._loadEnginesFromDir, this);
distDirs.forEach(this._loadEnginesFromDir, this);
this._loadFromChromeURLs(chromeURIs);
otherDirs.forEach(this._loadEnginesFromDir, this);
if (cacheEnabled)
this._buildCache();
return;
@ -3561,8 +3580,7 @@ SearchService.prototype = {
cache = yield checkForSyncCompletion(this._asyncReadCacheFile(cacheFilePath));
}
let loadDirs = [], chromeURIs = [], chromeFiles = [];
let chromeURIs = [], chromeFiles = [];
let loadFromJARs = false;
try {
loadFromJARs = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
@ -3575,21 +3593,25 @@ SearchService.prototype = {
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);
// Get the non-empty distribution directories into distDirs...
let distDirs = [];
let locations;
try {
locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
Ci.nsISimpleEnumerator);
} catch (e) {
// NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
// so this throws during unit tests (but not xpcshell tests).
locations = {hasMoreElements: () => false};
}
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.
// Add dir to distDirs if it contains any files.
yield checkForSyncCompletion(iterator.next());
loadDirs.push(dir);
distDirs.push(dir);
} catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
// Catch for StopIteration exception.
} finally {
@ -3597,7 +3619,32 @@ SearchService.prototype = {
}
}
let toLoad = chromeFiles.concat(loadDirs);
// Add the non-empty directories of NS_APP_SEARCH_DIR_LIST to
// otherDirs...
let otherDirs = [];
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.
// Applications shipping JAR engines don't ship plain text
// engine files anymore.
if (loadFromJARs && dir.equals(getDir(NS_APP_SEARCH_DIR)))
continue;
let iterator = new OS.File.DirectoryIterator(dir.path,
{ winPattern: "*.xml" });
try {
// Add dir to otherDirs if it contains any files.
yield checkForSyncCompletion(iterator.next());
otherDirs.push(dir);
} catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
// Catch for StopIteration exception.
} finally {
iterator.close();
}
}
let toLoad = chromeFiles.concat(distDirs, otherDirs);
function hasModifiedDir(aList) {
return Task.spawn(function() {
let modifiedDir = false;
@ -3636,7 +3683,7 @@ SearchService.prototype = {
if (!cacheEnabled || rebuildCache) {
LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
let engines = [];
for (let loadDir of loadDirs) {
for (let loadDir of distDirs) {
let enginesFromDir =
yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
engines = engines.concat(enginesFromDir);
@ -3644,6 +3691,11 @@ SearchService.prototype = {
let enginesFromURLs =
yield checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
engines = engines.concat(enginesFromURLs);
for (let loadDir of otherDirs) {
let enginesFromDir =
yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
engines = engines.concat(enginesFromDir);
}
for (let engine of engines) {
this._addEngineToStore(engine);

View File

@ -0,0 +1,8 @@
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>addon</ShortName>
<Description>addon</Description>
<InputEncoding>UTF-8</InputEncoding>
<Url type="text/html" method="GET" template="http://searchtest.local">
<Param name="search" value="{searchTerms}"/>
</Url>
</SearchPlugin>

View File

@ -0,0 +1,8 @@
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>bug645970</ShortName>
<Description>override</Description>
<InputEncoding>UTF-8</InputEncoding>
<Url type="text/html" method="GET" template="http://searchtest.local">
<Param name="search" value="{searchTerms}"/>
</Url>
</SearchPlugin>

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>search-engine@tests.mozilla.org</em:id>
<em:unpack>true</em:unpack>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Search Engine</em:name>
</Description>
</RDF>

View File

@ -96,6 +96,90 @@ function configureToLoadJarEngines(loadFromJars = true)
do_get_file("data/engine-app.xml").copyTo(dir, "app.xml");
}
/**
* Fake the installation of an add-on in the profile, by creating the
* directory and registering it with the directory service.
*/
function installAddonEngine(name = "engine-addon")
{
const XRE_EXTENSIONS_DIR_LIST = "XREExtDL";
const gProfD = do_get_profile().QueryInterface(Ci.nsILocalFile);
let dir = gProfD.clone();
dir.append("extensions");
if (!dir.exists())
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
dir.append("search-engine@tests.mozilla.org");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/install.rdf").copyTo(dir, "install.rdf");
let addonDir = dir.clone();
dir.append("searchplugins");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/" + name + ".xml").copyTo(dir, "bug645970.xml");
Services.dirsvc.registerProvider({
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
Ci.nsIDirectoryServiceProvider2]),
getFile: function (prop, persistant) {
throw Cr.NS_ERROR_FAILURE;
},
getFiles: function (prop) {
let result = [];
switch (prop) {
case XRE_EXTENSIONS_DIR_LIST:
result.push(addonDir);
break;
default:
throw Cr.NS_ERROR_FAILURE;
}
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
hasMoreElements: () => result.length > 0,
getNext: () => result.shift()
};
}
});
}
/**
* Copy the engine-distribution.xml engine to a fake distribution
* created in the profile, and registered with the directory service.
*/
function installDistributionEngine()
{
const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
const gProfD = do_get_profile().QueryInterface(Ci.nsILocalFile);
let dir = gProfD.clone();
dir.append("distribution");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
let distDir = dir.clone();
dir.append("searchplugins");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
dir.append("common");
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml");
Services.dirsvc.registerProvider({
getFile: function(aProp, aPersistent) {
aPersistent.value = true;
if (aProp == XRE_APP_DISTRIBUTION_DIR)
return distDir.clone();
return null;
}
});
}
/**
* Clean the profile of any metadata files left from a previous run.
*/

View File

@ -0,0 +1,33 @@
/* 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();
installAddonEngine();
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 the add-on engine is loaded in addition to our jar engine
let engines = Services.search.getEngines();
do_check_eq(engines.length, 2);
// test jar engine is loaded ok.
let engine = Services.search.getEngineByName("addon");
do_check_neq(engine, null);
do_check_eq(engine.description, "addon");
do_test_finished();
});
}

View File

@ -0,0 +1,33 @@
/* 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();
installAddonEngine("engine-override");
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 the add-on engine isn't overriding our jar engine
let engines = Services.search.getEngines();
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_eq(engine.description, "bug645970");
do_test_finished();
});
}

View File

@ -0,0 +1,33 @@
/* 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();
installDistributionEngine();
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 that the engine from the distribution overrides our jar engine
let engines = Services.search.getEngines();
do_check_eq(engines.length, 1);
let engine = Services.search.getEngineByName("bug645970");
do_check_neq(engine, null);
// check the engine we have is actually the one from the distribution
do_check_eq(engine.description, "override");
do_test_finished();
});
}

View File

@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
function run_test() {
do_test_pending();
removeMetadata();
removeCacheFile();
do_load_manifest("data/chrome.manifest");
configureToLoadJarEngines();
// Copy an engine in [profile]/searchplugin/ and ensure it's not
// overriding the same file from a jar.
// The description in the file we are copying is 'profile'.
let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile);
if (!dir.exists())
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml");
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 not loaded.
let engines = Services.search.getEngines();
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_eq(engine.description, "bug645970");
do_test_finished();
});
}

View File

@ -0,0 +1,26 @@
/* 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();
installAddonEngine();
do_check_false(Services.search.isInitialized);
// test the add-on engine is loaded in addition to our jar engine
let engines = Services.search.getEngines();
do_check_eq(engines.length, 2);
do_check_true(Services.search.isInitialized);
// test jar engine is loaded ok.
let engine = Services.search.getEngineByName("addon");
do_check_neq(engine, null);
do_check_eq(engine.description, "addon");
}

View File

@ -0,0 +1,26 @@
/* 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();
installAddonEngine("engine-override");
do_check_false(Services.search.isInitialized);
// test the add-on engine isn't overriding our jar engine
let engines = Services.search.getEngines();
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);
do_check_eq(engine.description, "bug645970");
}

View File

@ -0,0 +1,26 @@
/* 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();
installDistributionEngine();
do_check_false(Services.search.isInitialized);
// test that the engine from the distribution overrides our jar engine
let engines = Services.search.getEngines();
do_check_eq(engines.length, 1);
do_check_true(Services.search.isInitialized);
let engine = Services.search.getEngineByName("bug645970");
do_check_neq(engine, null);
// check the engine we have is actually the one from the distribution
do_check_eq(engine.description, "override");
}

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
function run_test() {
removeMetadata();
removeCacheFile();
do_load_manifest("data/chrome.manifest");
configureToLoadJarEngines();
// Copy an engine in [profile]/searchplugin/ and ensure it's not
// overriding the same file from a jar.
// The description in the file we are copying is 'profile'.
let dir = Services.dirsvc.get(NS_APP_USER_SEARCH_DIR, Ci.nsIFile);
if (!dir.exists())
dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
do_get_file("data/engine-override.xml").copyTo(dir, "bug645970.xml");
do_check_false(Services.search.isInitialized);
// test engines from dir are not loaded.
let engines = Services.search.getEngines();
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);
do_check_eq(engine.description, "bug645970");
}

View File

@ -8,6 +8,8 @@ support-files =
data/engine.src
data/engine.xml
data/engine2.xml
data/engine-addon.xml
data/engine-override.xml
data/engine-app.xml
data/engine-fr.xml
data/engineMaker.sjs
@ -17,6 +19,7 @@ support-files =
data/engineImages.xml
data/ico-size-16x16-png.ico
data/invalid-engine.xml
data/install.rdf
data/search-metadata.json
data/search.json
data/search.sqlite
@ -58,9 +61,17 @@ support-files =
[test_searchSuggest.js]
[test_async.js]
[test_async_app.js]
[test_async_addon.js]
[test_async_addon_no_override.js]
[test_async_distribution.js]
[test_async_profile_engine.js]
[test_sync.js]
[test_sync_app.js]
[test_sync_addon.js]
[test_sync_addon_no_override.js]
[test_sync_distribution.js]
[test_sync_fallback.js]
[test_sync_delay_fallback.js]
[test_sync_profile_engine.js]
[test_rel_searchform.js]
[test_selectedEngine.js]

View File

@ -48,6 +48,7 @@
#define NS_APP_CHROME_DIR_LIST "AChromDL"
#define NS_APP_PLUGINS_DIR_LIST "APluginsDL"
#define NS_APP_SEARCH_DIR_LIST "SrchPluginsDL"
#define NS_APP_DISTRIBUTION_SEARCH_DIR_LIST "SrchPluginsDistDL"
// --------------------------------------------------------------------------------------
// Files and directories which exist on a per-profile basis