Bug 1255605 - search.json.mozlz4 should always contain search engines. r=adw, a=sledru
authorFlorian Quèze <florian@queze.net>
Mon, 14 Mar 2016 18:45:33 +0100
changeset 323449 a0b6df7c6f43c373abd59a2297241bebf2416e8a
parent 323448 df42969f5a97bd55f86bdbe6887cd6058e5c9bb2
child 323450 8c6365c22bb8194b2e9de66b891a3c2c2463145f
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw, sledru
bugs1255605
milestone47.0a2
Bug 1255605 - search.json.mozlz4 should always contain search engines. r=adw, a=sledru
toolkit/components/search/nsSearchService.js
toolkit/components/search/tests/xpcshell/head_search.js
toolkit/components/search/tests/xpcshell/test_hidden.js
toolkit/components/search/tests/xpcshell/test_require_engines_in_cache.js
toolkit/components/search/tests/xpcshell/xpcshell.ini
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -2851,16 +2851,19 @@ SearchService.prototype = {
     cache.metaData = this._metaData;
     cache.engines = [];
 
     for (let name in this._engines) {
       cache.engines.push(this._engines[name]);
     }
 
     try {
+      if (!cache.engines.length)
+        throw "cannot write without any engine.";
+
       LOG("_buildCache: Writing to cache file.");
       let path = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
       let data = gEncoder.encode(JSON.stringify(cache));
       let promise = OS.File.writeAtomic(path, data, {compression: "lz4",
                                                      tmpPath: path + ".tmp"});
 
       promise.then(
         function onSuccess() {
@@ -3169,17 +3172,20 @@ SearchService.prototype = {
                   .createInstance(Ci.nsIBinaryInputStream);
       bis.setInputStream(stream);
 
       let count = stream.available();
       let array = new Uint8Array(count);
       bis.readArrayBuffer(count, array.buffer);
 
       let bytes = Lz4.decompressFileContent(array);
-      return JSON.parse(new TextDecoder().decode(bytes));
+      let json = JSON.parse(new TextDecoder().decode(bytes));
+      if (!json.engines || !json.engines.length)
+        throw "no engine in the file";
+      return json;
     } catch(ex) {
       LOG("_readCacheFile: Error reading cache file: " + ex);
     } finally {
       stream.close();
     }
 
     try {
       cacheFile.leafName = "search-metadata.json";
@@ -3222,16 +3228,18 @@ SearchService.prototype = {
    */
   _asyncReadCacheFile: function SRCH_SVC__asyncReadCacheFile() {
     return Task.spawn(function() {
       let json;
       try {
         let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
         let bytes = yield OS.File.read(cacheFilePath, {compression: "lz4"});
         json = JSON.parse(new TextDecoder().decode(bytes));
+        if (!json.engines || !json.engines.length)
+          throw "no engine in the file";
         this._cacheFileJSON = json;
       } catch (ex) {
         LOG("_asyncReadCacheFile: Error reading cache file: " + ex);
         json = {};
 
         let oldMetadata =
           OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json");
         try {
--- a/toolkit/components/search/tests/xpcshell/head_search.js
+++ b/toolkit/components/search/tests/xpcshell/head_search.js
@@ -210,16 +210,22 @@ function getSearchMetadata()
 function promiseCacheData() {
   return new Promise(resolve => Task.spawn(function* () {
     let path = OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME);
     let bytes = yield OS.File.read(path, {compression: "lz4"});
     resolve(JSON.parse(new TextDecoder().decode(bytes)));
   }));
 }
 
+function promiseSaveCacheData(data) {
+  return OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME),
+                             new TextEncoder().encode(JSON.stringify(data)),
+                             {compression: "lz4"});
+}
+
 function promiseEngineMetadata() {
   return new Promise(resolve => Task.spawn(function* () {
     let cache = yield promiseCacheData();
     let data = {};
     for (let engine of cache.engines) {
       data[engine._shortName] = engine._metaData;
     }
     resolve(data);
@@ -232,41 +238,42 @@ function promiseGlobalMetadata() {
     resolve(cache.metaData);
   }));
 }
 
 function promiseSaveGlobalMetadata(globalData) {
   return new Promise(resolve => Task.spawn(function* () {
     let data = yield promiseCacheData();
     data.metaData = globalData;
-    yield OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, CACHE_FILENAME),
-                              new TextEncoder().encode(JSON.stringify(data)),
-                              {compression: "lz4"});
+    yield promiseSaveCacheData(data);
     resolve();
   }));
 }
 
 var forceExpiration = Task.async(function* () {
   let metadata = yield promiseGlobalMetadata();
 
   // Make the current geodefaults expire 1s ago.
   metadata.searchDefaultExpir = Date.now() - 1000;
   yield promiseSaveGlobalMetadata(metadata);
 });
 
 /**
  * Clean the profile of any cache file left from a previous run.
+ * Returns a boolean indicating if the cache file existed.
  */
 function removeCacheFile()
 {
   let file = gProfD.clone();
   file.append(CACHE_FILENAME);
   if (file.exists()) {
     file.remove(false);
+    return true;
   }
+  return false;
 }
 
 /**
  * isUSTimezone taken from nsSearchService.js
  */
 function isUSTimezone() {
   // Timezone assumptions! We assume that if the system clock's timezone is
   // between Newfoundland and Hawaii, that the user is in North America.
--- a/toolkit/components/search/tests/xpcshell/test_hidden.js
+++ b/toolkit/components/search/tests/xpcshell/test_hidden.js
@@ -40,35 +40,37 @@ add_task(function* async_init() {
   do_check_neq(engine, null);
 
   // The next test does a sync init, which won't do the geoSpecificDefaults XHR,
   // so it depends on the metadata having been written to disk.
   yield commitPromise;
 });
 
 add_task(function* sync_init() {
+  let unInitPromise = waitForSearchNotification("uninit-complete");
   let reInitPromise = asyncReInit();
+  yield unInitPromise;
+  do_check_false(Services.search.isInitialized);
+
   // Synchronously check the current default engine, to force a sync init.
-  do_check_false(Services.search.isInitialized);
   do_check_eq(Services.search.currentEngine.name, "hidden");
   do_check_true(Services.search.isInitialized);
 
   let engines = Services.search.getEngines();
   do_check_eq(engines.length, 1);
 
   // The default test jar engine has been hidden.
   let engine = Services.search.getEngineByName("bug645970");
   do_check_eq(engine, null);
 
   // The hidden engine is visible.
   engine = Services.search.getEngineByName("hidden");
   do_check_neq(engine, null);
 
   yield reInitPromise;
-  yield promiseAfterCache();
 });
 
 add_task(function* invalid_engine() {
   // Trigger a new request.
   yield forceExpiration();
 
   // Set the visibleDefaultEngines list to something that contains a non-existent engine.
   // This should cause the search service to ignore the list altogether and fallback to
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_require_engines_in_cache.js
@@ -0,0 +1,74 @@
+/* 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();
+  do_check_false(Services.search.isInitialized);
+
+  run_next_test();
+}
+
+add_task(function* ignore_cache_files_without_engines() {
+  let commitPromise = promiseAfterCache()
+  yield asyncInit();
+
+  let engineCount = Services.search.getEngines().length;
+  do_check_eq(engineCount, 1);
+
+  // Wait for the file to be saved to disk, so that we can mess with it.
+  yield commitPromise;
+
+  // Remove all engines from the cache file.
+  let cache = yield promiseCacheData();
+  cache.engines = [];
+  yield promiseSaveCacheData(cache);
+
+  // Check that after an async re-initialization, we still have the same engine count.
+  commitPromise = promiseAfterCache()
+  yield asyncReInit();
+  do_check_eq(engineCount, Services.search.getEngines().length);
+  yield commitPromise;
+
+  // Check that after a sync re-initialization, we still have the same engine count.
+  yield promiseSaveCacheData(cache);
+  let unInitPromise = waitForSearchNotification("uninit-complete");
+  let reInitPromise = asyncReInit();
+  yield unInitPromise;
+  do_check_false(Services.search.isInitialized);
+  // Synchronously check the engine count; will force a sync init.
+  do_check_eq(engineCount, Services.search.getEngines().length);
+  do_check_true(Services.search.isInitialized);
+  yield reInitPromise;
+});
+
+add_task(function* skip_writing_cache_without_engines() {
+  let unInitPromise = waitForSearchNotification("uninit-complete");
+  let reInitPromise = asyncReInit();
+  yield unInitPromise;
+
+  // Configure so that no engines will be found.
+  do_check_true(removeCacheFile());
+  let resProt = Services.io.getProtocolHandler("resource")
+                        .QueryInterface(Ci.nsIResProtocolHandler);
+  resProt.setSubstitution("search-plugins",
+                          Services.io.newURI("about:blank", null, null));
+
+  // Let the async-reInit happen.
+  yield reInitPromise;
+  do_check_eq(0, Services.search.getEngines().length);
+
+  // Trigger yet another re-init, to flush of any pending cache writing task.
+  unInitPromise = waitForSearchNotification("uninit-complete");
+  reInitPromise = asyncReInit();
+  yield unInitPromise;
+
+  // Now check that a cache file doesn't exist.
+  do_check_false(removeCacheFile());
+
+  yield reInitPromise;
+});
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -83,8 +83,9 @@ tags = addons
 [test_sync_migration.js]
 [test_sync_profile_engine.js]
 [test_rel_searchform.js]
 [test_remove_profile_engine.js]
 [test_selectedEngine.js]
 [test_geodefaults.js]
 [test_hidden.js]
 [test_currentEngine_fallback.js]
+[test_require_engines_in_cache.js]