Bug 862179 - Implement asynchronous loading of search engines. r=gavin

This commit is contained in:
Raymond Lee 2013-11-06 14:18:27 +08:00
Родитель 417ceab8ae
Коммит 092eb74ddd
6 изменённых файлов: 596 добавлений и 34 удалений

Просмотреть файл

@ -1209,19 +1209,18 @@ Engine.prototype = {
fileInStream.init(this._file, MODE_RDONLY, PERMS_FILE, false);
switch (this._dataType) {
case SEARCH_DATA_XML:
var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
var doc = domParser.parseFromStream(fileInStream, "UTF-8",
this._file.fileSize,
"text/xml");
if (this._dataType == SEARCH_DATA_XML) {
var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
var doc = domParser.parseFromStream(fileInStream, "UTF-8",
this._file.fileSize,
"text/xml");
this._data = doc.documentElement;
break;
default:
ERROR("Unsuppored engine _dataType in _initFromFile: \"" + this._dataType + "\"",
Cr.NS_ERROR_UNEXPECTED);
this._data = doc.documentElement;
} else {
ERROR("Unsuppored engine _dataType in _initFromFile: \"" +
this._dataType + "\"",
Cr.NS_ERROR_UNEXPECTED);
}
fileInStream.close();
@ -1230,14 +1229,41 @@ Engine.prototype = {
},
/**
* Retrieves the engine data from a URI.
* Retrieves the data from the engine's file asynchronously. If the engine's
* dataType is XML, the document element is placed in the engine's data field.
*
* @returns {Promise} A promise, resolved successfully if initializing from
* data succeeds, rejected if it fails.
*/
_initFromURI: function SRCH_ENG_initFromURI() {
_asyncInitFromFile: function SRCH_ENG__asyncInitFromFile() {
return TaskUtils.spawn(function() {
if (!this._file || !(yield OS.File.exists(this._file.path)))
FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
if (this._dataType == SEARCH_DATA_XML) {
let fileURI = NetUtil.ioService.newFileURI(this._file);
yield this._retrieveSearchXMLData(fileURI.spec);
} else {
ERROR("Unsuppored engine _dataType in _initFromFile: \"" +
this._dataType + "\"",
Cr.NS_ERROR_UNEXPECTED);
}
// Now that the data is loaded, initialize the engine object
this._initFromData();
}.bind(this));
},
/**
* Retrieves the engine data from a URI. Initializes the engine, flushes to
* disk, and notifies the search service once initialization is complete.
*/
_initFromURIAndLoad: function SRCH_ENG_initFromURIAndLoad() {
ENSURE_WARN(this._uri instanceof Ci.nsIURI,
"Must have URI when calling _initFromURI!",
"Must have URI when calling _initFromURIAndLoad!",
Cr.NS_ERROR_UNEXPECTED);
LOG("_initFromURI: Downloading engine from: \"" + this._uri.spec + "\".");
LOG("_initFromURIAndLoad: Downloading engine from: \"" + this._uri.spec + "\".");
var chan = NetUtil.ioService.newChannelFromURI(this._uri);
@ -1251,7 +1277,47 @@ Engine.prototype = {
chan.notificationCallbacks = listener;
chan.asyncOpen(listener, null);
},
/**
* Retrieves the engine data from a URI asynchronously and initializes it.
*
* @returns {Promise} A promise, resolved successfully if retrieveing data
* succeeds.
*/
_asyncInitFromURI: function SRCH_ENG__asyncInitFromURI() {
return TaskUtils.spawn(function() {
LOG("_asyncInitFromURI: Loading engine from: \"" + this._uri.spec + "\".");
yield this._retrieveSearchXMLData(this._uri.spec);
// Now that the data is loaded, initialize the engine object
this._initFromData();
}.bind(this));
},
/**
* Retrieves the engine data for a given URI asynchronously.
*
* @returns {Promise} A promise, resolved successfully if retrieveing data
* succeeds.
*/
_retrieveSearchXMLData: function SRCH_ENG__retrieveSearchXMLData(aURL) {
let deferred = Promise.defer();
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
request.overrideMimeType("text/xml");
request.onload = (aEvent) => {
let responseXML = aEvent.target.responseXML;
this._data = responseXML.documentElement;
deferred.resolve();
};
request.onerror = function(aEvent) {
deferred.resolve();
};
request.open("GET", aURL, true);
request.send();
return deferred.promise;
},
_initFromURISync: function SRCH_ENG_initFromURISync() {
ENSURE_WARN(this._uri instanceof Ci.nsIURI,
"Must have URI when calling _initFromURISync!",
@ -1575,7 +1641,6 @@ Engine.prototype = {
* Initialize this Engine object from the collected data.
*/
_initFromData: function SRCH_ENG_initFromData() {
ENSURE_WARN(this._data, "Can't init an engine with no data!",
Cr.NS_ERROR_UNEXPECTED);
@ -2642,6 +2707,26 @@ function executeSoon(func) {
Services.tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
}
/**
* Check for sync initialization has completed or not.
*
* @param {aPromise} A promise.
*
* @returns the value returned by the invoked method.
* @throws NS_ERROR_ALREADY_INITIALIZED if sync initialization has completed.
*/
function checkForSyncCompletion(aPromise) {
return aPromise.then(function(aValue) {
if (gInitialized) {
throw Components.Exception("Synchronous fallback was called and has " +
"finished so no need to pursue asynchronous " +
"initialization",
Cr.NS_ERROR_ALREADY_INITIALIZED);
}
return aValue;
});
}
// nsIBrowserSearchService
function SearchService() {
// Replace empty LOG function with the useful one if the log pref is set.
@ -2658,6 +2743,9 @@ SearchService.prototype = {
// initialization is complete, only if an error has been encountered so far.
_initRV: Cr.NS_OK,
// The boolean indicates that the initialization has started or not.
_initStarted: null,
// If initialization has not been completed yet, perform synchronous
// initialization.
// Throws in case of initialization error.
@ -2687,9 +2775,10 @@ SearchService.prototype = {
// Synchronous implementation of the initializer.
// Used by |_ensureInitialized| as a fallback if initialization is not
// complete. In this implementation, it is also used by |init|.
// complete.
_syncInit: function SRCH_SVC__syncInit() {
LOG("_syncInit start");
this._initStarted = true;
try {
this._syncLoadEngines();
} catch (ex) {
@ -2707,6 +2796,30 @@ SearchService.prototype = {
LOG("_syncInit end");
},
/**
* Asynchronous implementation of the initializer.
*
* @returns {Promise} A promise, resolved successfully if the initialization
* succeeds.
*/
_asyncInit: function SRCH_SVC__asyncInit() {
return TaskUtils.spawn(function() {
LOG("_asyncInit start");
try {
yield checkForSyncCompletion(this._asyncLoadEngines());
} catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
this._initRV = Cr.NS_ERROR_FAILURE;
LOG("_asyncInit: failure loading engines: " + ex);
}
this._addObservers();
gInitialized = true;
this._initObservers.resolve(this._initRV);
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
LOG("_asyncInit: Completed _asyncInit");
}.bind(this));
},
_engines: { },
__sortedEngines: null,
get _sortedEngines() {
@ -2880,6 +2993,115 @@ SearchService.prototype = {
LOG("_loadEngines: done");
},
/**
* Loads engines asynchronously.
*
* @returns {Promise} A promise, resolved successfully if loading data
* succeeds.
*/
_asyncLoadEngines: function SRCH_SVC__asyncLoadEngines() {
return TaskUtils.spawn(function() {
LOG("_asyncLoadEngines: start");
// 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 locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
while (locations.hasMoreElements()) {
let dir = locations.getNext().QueryInterface(Ci.nsIFile);
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 TaskUtils.spawn(function() {
let modifiedDir = false;
for (let dir of aList) {
if (!cache.directories || !cache.directories[dir.path]) {
modifiedDir = true;
break;
}
let info = yield OS.File.stat(dir.path);
if (cache.directories[dir.path].lastModifiedTime !=
info.lastModificationDate.getTime()) {
modifiedDir = true;
break;
}
}
throw new Task.Result(modifiedDir);
});
}
function notInCachePath(aPathToLoad)
cachePaths.indexOf(aPathToLoad.path) == -1;
let buildID = Services.appinfo.platformBuildID;
let cachePaths = [path for (path in cache.directories)];
let rebuildCache = !cache.directories ||
cache.version != CACHE_VERSION ||
cache.locale != getLocale() ||
cache.buildID != buildID ||
cachePaths.length != toLoad.length ||
toLoad.some(notInCachePath) ||
(yield checkForSyncCompletion(hasModifiedDir(toLoad)));
if (!cacheEnabled || rebuildCache) {
LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
let engines = [];
for (let loadDir of loadDirs) {
let enginesFromDir =
yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
engines = engines.concat(enginesFromDir);
}
let enginesFromURLs =
yield checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
engines = engines.concat(enginesFromURLs);
for (let engine of engines) {
this._addEngineToStore(engine);
}
if (cacheEnabled)
this._buildCache();
return;
}
LOG("_asyncLoadEngines: loading from cache directories");
for each (let dir in cache.directories)
this._loadEnginesFromCache(dir);
LOG("_asyncLoadEngines: done");
}.bind(this));
},
_readCacheFile: function SRCH_SVC__readCacheFile(aFile) {
let stream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
@ -2896,6 +3118,28 @@ SearchService.prototype = {
return false;
},
/**
* Read from a given cache file asynchronously.
*
* @param aPath the file path.
*
* @returns {Promise} A promise, resolved successfully if retrieveing data
* succeeds.
*/
_asyncReadCacheFile: function SRCH_SVC__asyncReadCacheFile(aPath) {
return TaskUtils.spawn(function() {
let json;
try {
let bytes = yield OS.File.read(aPath);
json = JSON.parse(new TextDecoder().decode(bytes));
} catch (ex) {
LOG("_asyncReadCacheFile: Error reading cache file: " + ex);
json = {};
}
throw new Task.Result(json);
});
},
_batchTimer: null,
_batchCacheInvalidation: function SRCH_SVC__batchCacheInvalidation() {
let callback = {
@ -3042,15 +3286,64 @@ SearchService.prototype = {
}
},
/**
* Loads engines from a given directory asynchronously.
*
* @param aDir the directory.
*
* @returns {Promise} A promise, resolved successfully if retrieveing data
* succeeds.
*/
_asyncLoadEnginesFromDir: function SRCH_SVC__asyncLoadEnginesFromDir(aDir) {
LOG("_asyncLoadEnginesFromDir: Searching in " + aDir.path + " for search engines.");
// Check whether aDir is the user profile dir
let isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
let iterator = new OS.File.DirectoryIterator(aDir.path);
return TaskUtils.spawn(function() {
let osfiles = yield iterator.nextBatch();
iterator.close();
let engines = [];
for (let osfile of osfiles) {
if (osfile.isDir || osfile.isSymLink)
continue;
let fileInfo = yield OS.File.stat(osfile.path);
if (fileInfo.size == 0)
continue;
let parts = osfile.path.split(".");
if (parts.length <= 1 || (parts.pop()).toLowerCase() != "xml") {
// Not an engine
continue;
}
let addedEngine = null;
try {
let file = new FileUtils.File(osfile.path);
let isWritable = isInProfile;
addedEngine = new Engine(file, SEARCH_DATA_XML, !isWritable);
yield checkForSyncCompletion(addedEngine._asyncInitFromFile());
} catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
LOG("_asyncLoadEnginesFromDir: Failed to load " + file.path + "!\n" + ex);
continue;
}
engines.push(addedEngine);
}
throw new Task.Result(engines);
}.bind(this));
},
_loadFromChromeURLs: function SRCH_SVC_loadFromChromeURLs(aURLs) {
aURLs.forEach(function (url) {
try {
LOG("_loadFromChromeURLs: loading engine from chrome url: " + url);
let engine = new Engine(makeURI(url), SEARCH_DATA_XML, true);
engine._initFromURISync();
this._addEngineToStore(engine);
} catch (ex) {
LOG("_loadFromChromeURLs: failed to load engine: " + ex);
@ -3058,6 +3351,31 @@ SearchService.prototype = {
}, this);
},
/**
* Loads engines from Chrome URLs asynchronously.
*
* @param aURLs a list of URLs.
*
* @returns {Promise} A promise, resolved successfully if loading data
* succeeds.
*/
_asyncLoadFromChromeURLs: function SRCH_SVC__asyncLoadFromChromeURLs(aURLs) {
return TaskUtils.spawn(function() {
let engines = [];
for (let url of aURLs) {
try {
LOG("_asyncLoadFromChromeURLs: loading engine from chrome url: " + url);
let engine = new Engine(NetUtil.newURI(url), SEARCH_DATA_XML, true);
yield checkForSyncCompletion(engine._asyncInitFromURI());
engines.push(engine);
} catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
LOG("_asyncLoadFromChromeURLs: failed to load engine: " + ex);
}
}
throw new Task.Result(engines);
}.bind(this));
},
_findJAREngines: function SRCH_SVC_findJAREngines() {
LOG("_findJAREngines: looking for engines in JARs")
@ -3119,6 +3437,77 @@ SearchService.prototype = {
return [chromeFiles, uris];
},
/**
* Loads jar engines asynchronously.
*
* @returns {Promise} A promise, resolved successfully if finding jar engines
* succeeds.
*/
_asyncFindJAREngines: function SRCH_SVC__asyncFindJAREngines() {
return TaskUtils.spawn(function() {
LOG("_asyncFindJAREngines: looking for engines in JARs")
let rootURIPref = "";
try {
rootURIPref = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "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;
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;
} 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.onload = function(aEvent) {
deferred.resolve(aEvent.target.responseText);
};
request.onerror = function(aEvent) {
LOG("_asyncFindJAREngines: failed to retrieve list.txt from " + listURL);
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"));
}
throw new Task.Result([chromeFiles, uris]);
});
},
_saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
LOG("SRCH_SVC_saveSortedEngineList: starting");
@ -3262,17 +3651,13 @@ SearchService.prototype = {
this._initStarted = true;
TaskUtils.spawn(function task() {
try {
yield engineMetadataService.init();
if (gInitialized) {
// No need to pursue asynchronous initialization,
// synchronous fallback had to be called and has finished.
return;
}
// Complete initialization. In the current implementation,
// this is done by calling the synchronous initializer.
// Future versions might introduce an actually synchronous
// implementation.
self._syncInit();
yield checkForSyncCompletion(engineMetadataService.init());
// Complete initialization by calling asynchronous initializer.
yield self._asyncInit();
TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
} catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
// No need to pursue asynchronous because synchronous fallback was
// called and has finished.
TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
} catch (ex) {
self._initObservers.reject(ex);
@ -3428,7 +3813,7 @@ SearchService.prototype = {
engine._installCallback = null;
};
}
engine._initFromURI();
engine._initFromURIAndLoad();
} catch (ex) {
// Drop the reference to the callback, if set
if (engine)
@ -3855,6 +4240,9 @@ var engineMetadataService = {
*/
syncInit: function epsSyncInit() {
LOG("metadata syncInit start");
if (this._initState == engineMetadataService._InitStates.FINISHED_SUCCESS) {
return;
}
switch (this._initState) {
case engineMetadataService._InitStates.NOT_STARTED:
let jsonFile = new FileUtils.File(this._jsonFile);
@ -4065,7 +4453,7 @@ var engineUpdateService = {
ULOG("updating " + engine.name + " from " + updateURI.spec);
testEngine = new Engine(updateURI, dataType, false);
testEngine._engineToUpdate = engine;
testEngine._initFromURI();
testEngine._initFromURIAndLoad();
} else
ULOG("invalid updateURI");

Просмотреть файл

@ -0,0 +1,36 @@
/* 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");
let url = "chrome://testsearchplugin/locale/searchplugins/";
Services.prefs.setCharPref("browser.search.jarURIs", url);
Services.prefs.setBoolPref("browser.search.loadFromJars", true);
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.
let engines = Services.search.getEngines();
do_check_true(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();
});
}

Просмотреть файл

@ -0,0 +1,29 @@
/* 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");
let url = "chrome://testsearchplugin/locale/searchplugins/";
Services.prefs.setCharPref("browser.search.jarURIs", url);
Services.prefs.setBoolPref("browser.search.loadFromJars", true);
do_check_false(Services.search.isInitialized);
// test engines from dir are loaded.
let engines = Services.search.getEngines();
do_check_true(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");
}

Просмотреть файл

@ -0,0 +1,57 @@
/* 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");
let url = "chrome://testsearchplugin/locale/searchplugins/";
Services.prefs.setCharPref("browser.search.jarURIs", url);
Services.prefs.setBoolPref("browser.search.loadFromJars", true);
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.
let engines = Services.search.getEngines();
do_check_true(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.
let engines = Services.search.getEngines();
do_check_true(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);
}

Просмотреть файл

@ -0,0 +1,48 @@
/* 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");
let url = "chrome://testsearchplugin/locale/searchplugins/";
Services.prefs.setCharPref("browser.search.jarURIs", url);
Services.prefs.setBoolPref("browser.search.loadFromJars", true);
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.
let engines = Services.search.getEngines();
do_check_true(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.
let engines = Services.search.getEngines();
do_check_true(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);
}

Просмотреть файл

@ -27,3 +27,7 @@ support-files =
[test_prefSync.js]
[test_notifications.js]
[test_addEngine_callback.js]
[test_async.js]
[test_sync.js]
[test_sync_fallback.js]
[test_sync_delay_fallback.js]