Backout ac2581b910b6 (bug 823547) and f50b8afa272f (bug 820834) for bc failures in browser_aboutHome.js
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 27 Feb 2013 19:54:15 +0100
changeset 123199 4bccbdd03dea827b24802a33de75f47b7c3ffc89
parent 123198 e85c2b64d0ed581189e37f7d4324791fa3590c88
child 123200 f3476411480a383c84e017a99af2f9222cd5ab77
push id24373
push userryanvm@gmail.com
push dateThu, 28 Feb 2013 01:36:21 +0000
treeherdermozilla-central@8cb9d6981978 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs823547, 820834
milestone22.0a1
backs outac2581b910b6a824084c74d8e2c234d3701ff11c
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
Backout ac2581b910b6 (bug 823547) and f50b8afa272f (bug 820834) for bc failures in browser_aboutHome.js
browser/base/content/abouthome/aboutHome.js
browser/base/content/browser.js
browser/base/content/test/browser_aboutHome.js
browser/modules/AboutHomeUtils.jsm
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -81,94 +81,30 @@ const DEFAULT_SNIPPETS_URLS = [
 
 const SNIPPETS_UPDATE_INTERVAL_MS = 86400000; // 1 Day.
 
 let gObserver = new MutationObserver(function (mutations) {
   for (let mutation of mutations) {
     if (mutation.attributeName == "searchEngineURL") {
       gObserver.disconnect();
       setupSearchEngine();
-      ensureSnippetsMapThen(loadSnippets);
+      loadSnippets();
       return;
     }
   }
 });
 
 window.addEventListener("load", function () {
   // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
   // later and may use asynchronous getters.
   window.gObserver.observe(document.documentElement, { attributes: true });
   fitToWidth();
   window.addEventListener("resize", fitToWidth);
 });
 
-// This object has the same interface as Map and is used to store and retrieve
-// the snippets data.  It is lazily initialized by ensureSnippetsMapThen(), so
-// be sure its callback returned before trying to use it.
-let gSnippetsMap;
-let gSnippetsMapCallbacks = [];
-
-/**
- * Ensure the snippets map is properly initialized.
- *
- * @param aCallback
- *        Invoked once the map has been initialized, gets the map as argument.
- * @note Snippets should never directly manage the underlying storage, since
- *       it may change inadvertently.
- */
-function ensureSnippetsMapThen(aCallback)
-{
-  if (gSnippetsMap) {
-    aCallback(gSnippetsMap);
-    return;
-  }
-
-  // Handle multiple requests during the async initialization.
-  gSnippetsMapCallbacks.push(aCallback);
-  if (gSnippetsMapCallbacks.length > 1) {
-    // We are already updating, the callbacks will be invoked when done.
-    return;
-  }
-
-  // TODO (bug 789348): use a real asynchronous storage here.  This setTimeout
-  // is done just to catch bugs with the asynchronous behavior.
-  setTimeout(function() {
-    // Populate the cache from the persistent storage.
-    let cache = new Map();
-    for (let key of [ "snippets-last-update",
-                      "snippets-cached-version",
-                      "snippets" ]) {
-      cache.set(key, localStorage[key]);
-    }
-
-    gSnippetsMap = Object.freeze({
-      get: function (aKey) cache.get(aKey),
-      set: function (aKey, aValue) {
-        localStorage[aKey] = aValue;
-        return cache.set(aKey, aValue);
-      },
-      has: function(aKey) cache.has(aKey),
-      delete: function(aKey) {
-        delete localStorage[aKey];
-        return cache.delete(aKey);
-      },
-      clear: function() {
-        localStorage.clear();
-        return cache.clear();
-      },
-      get size() cache.size
-    });
-
-    for (let callback of gSnippetsMapCallbacks) {
-      callback(gSnippetsMap);
-    }
-    gSnippetsMapCallbacks.length = 0;
-  }, 0);
-}
-
 function onSearchSubmit(aEvent)
 {
   let searchTerms = document.getElementById("searchText").value;
   let searchURL = document.documentElement.getAttribute("searchEngineURL");
 
   if (searchURL && searchTerms.length > 0) {
     const SEARCH_TOKENS = {
       "_searchTerms_": encodeURIComponent(searchTerms)
@@ -216,88 +152,54 @@ function setupSearchEngine()
   let searchText = document.getElementById("searchText");
   searchText.addEventListener("blur", function searchText_onBlur() {
     searchText.removeEventListener("blur", searchText_onBlur);
     searchText.removeAttribute("autofocus");
   });
 
 }
 
-/**
- * Update the local snippets from the remote storage, then show them through
- * showSnippets.
- */
 function loadSnippets()
 {
-  if (!gSnippetsMap)
-    throw new Error("Snippets map has not properly been initialized");
-
-  // Check cached snippets version.
-  let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
-  let currentVersion = document.documentElement.getAttribute("snippetsVersion");
-  if (cachedVersion < currentVersion) {
-    // The cached snippets are old and unsupported, restart from scratch.
-    gSnippetsMap.clear();
-  }
-
   // Check last snippets update.
-  let lastUpdate = gSnippetsMap.get("snippets-last-update");
+  let lastUpdate = localStorage["snippets-last-update"];
   let updateURL = document.documentElement.getAttribute("snippetsURL");
-  let shouldUpdate = !lastUpdate ||
-                     Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
-  if (updateURL && shouldUpdate) {
+  if (updateURL && (!lastUpdate ||
+                    Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS)) {
     // Try to update from network.
     let xhr = new XMLHttpRequest();
     try {
       xhr.open("GET", updateURL, true);
     } catch (ex) {
       showSnippets();
       return;
     }
     // Even if fetching should fail we don't want to spam the server, thus
     // set the last update time regardless its results.  Will retry tomorrow.
-    gSnippetsMap.set("snippets-last-update", Date.now());
+    localStorage["snippets-last-update"] = Date.now();
     xhr.onerror = function (event) {
       showSnippets();
     };
     xhr.onload = function (event)
     {
       if (xhr.status == 200) {
-        gSnippetsMap.set("snippets", xhr.responseText);
-        gSnippetsMap.set("snippets-cached-version", currentVersion);
+        localStorage["snippets"] = xhr.responseText;
       }
       showSnippets();
     };
     xhr.send(null);
   } else {
     showSnippets();
   }
 }
 
-/**
- * Shows locally cached remote snippets, or default ones when not available.
- *
- * @note: snippets should never invoke showSnippets(), or they may cause
- *        a "too much recursion" exception.
- */
-let _snippetsShown = false;
 function showSnippets()
 {
-  if (!gSnippetsMap)
-    throw new Error("Snippets map has not properly been initialized");
-  if (_snippetsShown) {
-    // There's something wrong with the remote snippets, just in case fall back
-    // to the default snippets.
-    showDefaultSnippets();
-    throw new Error("showSnippets should never be invoked multiple times");
-  }
-  _snippetsShown = true;
-
   let snippetsElt = document.getElementById("snippets");
-  let snippets = gSnippetsMap.get("snippets");
+  let snippets = localStorage["snippets"];
   // If there are remotely fetched snippets, try to to show them.
   if (snippets) {
     // Injecting snippets can throw if they're invalid XML.
     try {
       snippetsElt.innerHTML = snippets;
       // Scripts injected by innerHTML are inactive, so we have to relocate them
       // through DOM manipulation to activate their contents.
       Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) {
@@ -307,29 +209,17 @@ function showSnippets()
         elt.parentNode.replaceChild(relocatedScript, elt);
       });
       return;
     } catch (ex) {
       // Bad content, continue to show default snippets.
     }
   }
 
-  showDefaultSnippets();
-}
-
-/**
- * Clear snippets element contents and show default snippets.
- */
-function showDefaultSnippets()
-{
-  // Clear eventual contents...
-  let snippetsElt = document.getElementById("snippets");
-  snippetsElt.innerHTML = "";
-
-  // ...then show default snippets.
+  // Show default snippets otherwise.
   let defaultSnippetsElt = document.getElementById("defaultSnippets");
   let entries = defaultSnippetsElt.querySelectorAll("span");
   // Choose a random snippet.  Assume there is always at least one.
   let randIndex = Math.floor(Math.random() * entries.length);
   let entry = entries[randIndex];
   // Inject url in the eventual link.
   if (DEFAULT_SNIPPETS_URLS[randIndex]) {
     let links = entry.getElementsByTagName("a");
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2630,17 +2630,16 @@ function BrowserOnAboutPageLoad(doc) {
              getService(Components.interfaces.nsISessionStore);
     if (ss.canRestoreLastSession &&
         !PrivateBrowsingUtils.isWindowPrivate(window))
       doc.getElementById("launcher").setAttribute("session", "true");
 
     // Inject search engine and snippets URL.
     let docElt = doc.documentElement;
     docElt.setAttribute("snippetsURL", AboutHomeUtils.snippetsURL);
-    docElt.setAttribute("snippetsVersion", AboutHomeUtils.snippetsVersion);
     docElt.setAttribute("searchEngineName",
                         AboutHomeUtils.defaultSearchEngine.name);
     docElt.setAttribute("searchEngineURL",
                         AboutHomeUtils.defaultSearchEngine.searchURL);
 
 #ifdef MOZ_SERVICES_HEALTHREPORT
     doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
       BrowserSearch.recordSearchInHealthReport(e.detail, "abouthome");
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -1,19 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-  "resource://gre/modules/commonjs/sdk/core/promise.js");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-  "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
-  "resource:///modules/AboutHomeUtils.jsm");
-
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   try {
     Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   } catch (ex) {}
   try {
     Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   } catch (ex) {}
@@ -24,103 +17,110 @@ let gTests = [
 {
   desc: "Check that clearing cookies does not clear storage",
   setup: function ()
   {
     Cc["@mozilla.org/dom/storagemanager;1"]
       .getService(Ci.nsIObserver)
       .observe(null, "cookie-changed", "cleared");
   },
-  run: function (aSnippetsMap)
+  run: function ()
   {
-    isnot(aSnippetsMap.get("snippets-last-update"), null);
+    let storage = getStorage();
+    isnot(storage.getItem("snippets-last-update"), null);
+    executeSoon(runNextTest);
   }
 },
 
 {
   desc: "Check default snippets are shown",
-  setup: function () { },
+  setup: function ()
+  {
+  },
   run: function ()
   {
     let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
     let snippetsElt = doc.getElementById("snippets");
     ok(snippetsElt, "Found snippets element")
     is(snippetsElt.getElementsByTagName("span").length, 1,
        "A default snippet is visible.");
+    executeSoon(runNextTest);
   }
 },
 
 {
   desc: "Check default snippets are shown if snippets are invalid xml",
-  setup: function (aSnippetsMap)
+  setup: function ()
   {
+    let storage = getStorage();
     // This must be some incorrect xhtml code.
-    aSnippetsMap.set("snippets", "<p><b></p></b>");
+    storage.setItem("snippets", "<p><b></p></b>");
   },
-  run: function (aSnippetsMap)
+  run: function ()
   {
     let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
 
     let snippetsElt = doc.getElementById("snippets");
     ok(snippetsElt, "Found snippets element");
     is(snippetsElt.getElementsByTagName("span").length, 1,
        "A default snippet is visible.");
-
-    aSnippetsMap.delete("snippets");
+    let storage = getStorage();
+    storage.removeItem("snippets");
+    executeSoon(runNextTest);
   }
 },
-
 {
   desc: "Check that search engine logo has alt text",
-  setup: function () { },
+  setup: function ()
+  {
+  },
   run: function ()
   {
     let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
 
     let searchEngineLogoElt = doc.getElementById("searchEngineLogo");
     ok(searchEngineLogoElt, "Found search engine logo");
 
     let altText = searchEngineLogoElt.alt;
     ok(typeof altText == "string" && altText.length > 0,
        "Search engine logo's alt text is a nonempty string");
 
     isnot(altText, "undefined",
           "Search engine logo's alt text shouldn't be the string 'undefined'");
+
+    executeSoon(runNextTest);
   }
 },
-
 {
   desc: "Check that performing a search fires a search event.",
   setup: function () { },
   run: function () {
-    let deferred = Promise.defer();
     let doc = gBrowser.contentDocument;
 
     doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
       is(e.detail, doc.documentElement.getAttribute("searchEngineName"), "Detail is search engine name");
 
       gBrowser.stop();
-      deferred.resolve();
+      executeSoon(runNextTest);
     }, true, true);
 
     doc.getElementById("searchText").value = "it works";
     doc.getElementById("searchSubmit").click();
-    return deferred.promise;
-  }
+  },
 },
-
 {
   desc: "Check that performing a search records to Firefox Health Report.",
   setup: function () { },
   run: function () {
     if (!("@mozilla.org/datareporting/service;1" in Components.classes)) {
+      runNextTest();
       return;
     }
 
-    let deferred = Promise.defer();
+
     let doc = gBrowser.contentDocument;
 
     // We rely on the listener in browser.js being installed and fired before
     // this one. If this ever changes, we should add an executeSoon() or similar.
     doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
       executeSoon(gBrowser.stop.bind(gBrowser));
       let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
                                        .getService()
@@ -141,178 +141,75 @@ let gTests = [
 
           let day = data.days.getDay(now);
           let field = engineName + ".abouthome";
           ok(day.has(field), "Have data for about home on this engine.");
 
           // Note the search from the previous test.
           is(day.get(field), 2, "Have searches recorded.");
 
-          deferred.resolve();
+          executeSoon(runNextTest);
         });
 
       });
     }, true, true);
 
     doc.getElementById("searchText").value = "a search";
     doc.getElementById("searchSubmit").click();
-    return deferred.promise;
-  }
+  },
 },
-
-{
-  desc: "Check default snippets are shown if cached version is old and fetch fails",
-  setup: function (aSnippetsMap)
-  {
-    aSnippetsMap.set("snippets", "test");
-    aSnippetsMap.set("snippets-cached-version", 0);
-  },
-  run: function (aSnippetsMap)
-  {
-    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
-
-    let snippetsElt = doc.getElementById("snippets");
-    ok(snippetsElt, "Found snippets element");
-    is(snippetsElt.getElementsByTagName("span").length, 1,
-       "A default snippet is present.");
-
-    ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared");
-    ok(!aSnippetsMap.has("snippets-cached-version"),
-       "cached-version has been properly cleared");
-    ok(!aSnippetsMap.has("snippets-last-update"),
-       "last-update has been properly cleared");
-  }
-},
-
-{
-  desc: "Check cached snippets are shown if cached version is current",
-  setup: function (aSnippetsMap)
-  {
-    aSnippetsMap.set("snippets", "test");
-    aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
-  },
-  run: function (aSnippetsMap)
-  {
-    let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
-
-    let snippetsElt = doc.getElementById("snippets");
-    ok(snippetsElt, "Found snippets element");
-    is(snippetsElt.innerHTML, "test", "Cached snippet is present.");
-
-    is(aSnippetsMap.get("snippets"), "test", "snippets still cached");
-    is(aSnippetsMap.get("snippets-cached-version"),
-       AboutHomeUtils.snippetsVersion,
-       "cached-version is correct");
-    ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists");
-  }
-},
-
 ];
 
 function test()
 {
   waitForExplicitFinish();
 
-  Task.spawn(function () {
-    for (let test of gTests) {
-      info(test.desc);
-
-      let tab = yield promiseNewTabLoadEvent("about:home", "DOMContentLoaded");
-
-      // Must wait for both the snippets map and the browser attributes, since
-      // can't guess the order they will happen.
-      // So, start listening now, but verify the promise is fulfilled only
-      // after the snippets map setup.
-      let promise = promiseBrowserAttributes(tab);
-      // Prepare the snippets map with default values, then run the test setup.
-      let snippetsMap = yield promiseSetupSnippetsMap(tab, test.setup);
-      // Ensure browser has set attributes already, or wait for them.
-      yield promise;
-
-      yield test.run(snippetsMap);
-
-      gBrowser.removeCurrentTab();
-    }
+  // Ensure that by default we don't try to check for remote snippets since that
+  // could be tricky due to network bustages or slowness.
+  let storage = getStorage();
+  storage.setItem("snippets-last-update", Date.now());
+  storage.removeItem("snippets");
 
-    finish();
-  });
-}
-
-/**
- * Creates a new tab and waits for a load event.
- *
- * @param aUrl
- *        The url to load in a new tab.
- * @param aEvent
- *        The load event type to wait for.  Defaults to "load".
- * @return {Promise} resolved when the event is handled.  Gets the new tab.
- */
-function promiseNewTabLoadEvent(aUrl, aEventType="load")
-{
-  let deferred = Promise.defer();
-  let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
-  tab.linkedBrowser.addEventListener(aEventType, function load(event) {
-    tab.linkedBrowser.removeEventListener(aEventType, load, true);
-    deferred.resolve(tab);
-  }, true);
-  return deferred.promise;
+  executeSoon(runNextTest);
 }
 
-/**
- * Cleans up snippets and ensures that by default we don't try to check for
- * remote snippets since that may cause network bustage or slowness.
- *
- * @param aTab
- *        The tab containing about:home.
- * @param aSetupFn
- *        The setup function to be run.
- * @return {Promise} resolved when the snippets are ready.  Gets the snippets map.
- */
-function promiseSetupSnippetsMap(aTab, aSetupFn)
+function runNextTest()
 {
-  let deferred = Promise.defer();
-  let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
-  cw.ensureSnippetsMapThen(function (aSnippetsMap) {
-    // Don't try to update.
-    aSnippetsMap.set("snippets-last-update", Date.now());
-    // Clear snippets.
-    aSnippetsMap.delete("snippets");
-    aSetupFn(aSnippetsMap);
-    // Must be sure to continue after the page snippets map setup.
-    executeSoon(function() deferred.resolve(aSnippetsMap));
-  });
-  return deferred.promise;
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+
+  if (gTests.length) {
+    let test = gTests.shift();
+    info(test.desc);
+    test.setup();
+    let tab = gBrowser.selectedTab = gBrowser.addTab("about:home");
+    tab.linkedBrowser.addEventListener("load", function load(event) {
+      tab.linkedBrowser.removeEventListener("load", load, true);
+
+      let observer = new MutationObserver(function (mutations) {
+        for (let mutation of mutations) {
+          if (mutation.attributeName == "searchEngineURL") {
+            observer.disconnect();
+            executeSoon(test.run);
+            return;
+          }
+        }
+      });
+      let docElt = tab.linkedBrowser.contentDocument.documentElement;
+      observer.observe(docElt, { attributes: true });
+    }, true);
+  }
+  else {
+    finish();
+  }
 }
 
-/**
- * Waits for the attributes being set by browser.js and overwrites snippetsURL
- * to ensure we won't try to hit the network and we can force xhr to throw.
- *
- * @param aTab
- *        The tab containing about:home.
- * @return {Promise} resolved when the attributes are ready.
- */
-function promiseBrowserAttributes(aTab)
+function getStorage()
 {
-  let deferred = Promise.defer();
-
-  let docElt = aTab.linkedBrowser.contentDocument.documentElement;
-  //docElt.setAttribute("snippetsURL", "nonexistent://test");
-  let observer = new MutationObserver(function (mutations) {
-    for (let mutation of mutations) {
-      if (mutation.attributeName == "snippetsURL" &&
-          docElt.getAttribute("snippetsURL") != "nonexistent://test") {
-        docElt.setAttribute("snippetsURL", "nonexistent://test");
-      }
-
-      // Now we just have to wait for the last attribute.
-      if (mutation.attributeName == "searchEngineURL") {
-        observer.disconnect();
-        // Must be sure to continue after the page mutation observer.
-        executeSoon(function() deferred.resolve());
-        break;
-      }
-    }
-  });
-  observer.observe(docElt, { attributes: true });
-
-  return deferred.promise;
-}
+  let aboutHomeURI = Services.io.newURI("moz-safe-about:home", null, null);
+  let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"].
+                  getService(Components.interfaces.nsIScriptSecurityManager).
+                  getNoAppCodebasePrincipal(Services.io.newURI("about:home", null, null));
+  let dsm = Components.classes["@mozilla.org/dom/storagemanager;1"].
+            getService(Components.interfaces.nsIDOMStorageManager);
+  return dsm.getLocalStorageForPrincipal(principal, "");
+};
--- a/browser/modules/AboutHomeUtils.jsm
+++ b/browser/modules/AboutHomeUtils.jsm
@@ -8,21 +8,19 @@ this.EXPORTED_SYMBOLS = [ "AboutHomeUtil
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 // Url to fetch snippets, in the urlFormatter service format.
 const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl";
 
 // Should be bumped up if the snippets content format changes.
-const STARTPAGE_VERSION = 4;
+const STARTPAGE_VERSION = 3;
 
-this.AboutHomeUtils = {
-  get snippetsVersion() STARTPAGE_VERSION
-};
+this.AboutHomeUtils = new Object();
 
 /**
  * Returns an object containing the name and searchURL of the original default
  * search engine.
  */
 XPCOMUtils.defineLazyGetter(AboutHomeUtils, "defaultSearchEngine", function() {
   let defaultEngine = Services.search.originalDefaultEngine;
   let submission = defaultEngine.getSubmission("_searchTerms_", null, "homepage");