Bug 716736 - Re-add performance data to daily add-on update check; r=Unfocused, a=keybl
authorGregory Szorc <gps@mozilla.com>
Tue, 31 Jan 2012 17:22:42 -0800
changeset 88333 10b4c33374b7b85d1f276b3eb9e9fc0fe2c525ec
parent 88332 68fb495e3183754b2692f438dbe664a1c48cbea9
child 88334 b577cc1e8d9b105aa8c7ec0a65aa787bf9a1d459
push idunknown
push userunknown
push dateunknown
reviewersUnfocused, keybl
bugs716736
milestone12.0a2
Bug 716736 - Re-add performance data to daily add-on update check; r=Unfocused, a=keybl
browser/app/profile/firefox.js
build/automation.py.in
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/AddonRepository.jsm
toolkit/mozapps/extensions/test/browser/Makefile.in
toolkit/mozapps/extensions/test/browser/browser_addonrepository_performance.js
toolkit/mozapps/extensions/test/browser/head.js
toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js
toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js
toolkit/mozapps/extensions/test/xpcshell/test_update.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -60,16 +60,17 @@ pref("extensions.strictCompatibility", f
 // Specifies a minimum maxVersion an addon needs to say it's compatible with
 // for it to be compatible by default.
 pref("extensions.minCompatibleAppVersion", "4.0");
 
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.maxResults", 15);
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%");
+pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%");
 pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
 pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
 
 // Blocklist preferences
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 // Controls what level the blocklist switches from warning about items to forcibly
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -397,16 +397,17 @@ user_pref("browser.safebrowsing.provider
 user_pref("extensions.update.url", "http://%(server)s/extensions-dummy/updateURL");
 user_pref("extensions.blocklist.url", "http://%(server)s/extensions-dummy/blocklistURL");
 user_pref("extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL");
 // Make sure opening about:addons won't hit the network
 user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
 // Make sure AddonRepository won't hit the network
 user_pref("extensions.getAddons.maxResults", 0);
 user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
+user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
 user_pref("extensions.getAddons.search.browseURL", "http://%(server)s/extensions-dummy/repositoryBrowseURL");
 user_pref("extensions.getAddons.search.url", "http://%(server)s/extensions-dummy/repositorySearchURL");
 """ % { "server" : self.webServer + ":" + str(self.httpPort) }
     prefs.append(part)
 
     if useServerLocations == False:
       part = """
 user_pref("capability.principal.codebase.p1.granted", "UniversalXPConnect");
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -757,17 +757,17 @@ var AddonManagerInternal = {
 
       pendingUpdates++;
       this.getAllAddons(function getAddonsCallback(aAddons) {
         // If there is a known hotfix then exclude it from the list of add-ons to update.
         var ids = [a.id for each (a in aAddons) if (a.id != hotfixID)];
 
         // Repopulate repository cache first, to ensure compatibility overrides
         // are up to date before checking for addon updates.
-        scope.AddonRepository.repopulateCache(ids, function BUC_repopulateCacheCallback() {
+        scope.AddonRepository.backgroundUpdateCheck(ids, function BUC_backgroundUpdateCheckCallback() {
           AddonManagerInternal.updateAddonRepositoryData(function BUC_updateAddonCallback() {
 
             pendingUpdates += aAddons.length;
             aAddons.forEach(function BUC_forEachCallback(aAddon) {
               if (aAddon.id == hotfixID) {
                 notifyComplete();
                 return;
               }
--- a/toolkit/mozapps/extensions/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/AddonRepository.jsm
@@ -52,16 +52,17 @@ Components.utils.import("resource://gre/
 
 var EXPORTED_SYMBOLS = [ "AddonRepository" ];
 
 const PREF_GETADDONS_CACHE_ENABLED       = "extensions.getAddons.cache.enabled";
 const PREF_GETADDONS_CACHE_TYPES         = "extensions.getAddons.cache.types";
 const PREF_GETADDONS_CACHE_ID_ENABLED    = "extensions.%ID%.getAddons.cache.enabled"
 const PREF_GETADDONS_BROWSEADDONS        = "extensions.getAddons.browseAddons";
 const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
+const PREF_GETADDONS_BYIDS_PERFORMANCE   = "extensions.getAddons.getWithPerformance.url";
 const PREF_GETADDONS_BROWSERECOMMENDED   = "extensions.getAddons.recommended.browseURL";
 const PREF_GETADDONS_GETRECOMMENDED      = "extensions.getAddons.recommended.url";
 const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
 const PREF_GETADDONS_GETSEARCHRESULTS    = "extensions.getAddons.search.url";
 
 const PREF_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
 
 const BRANCH_REGEXP                   = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
@@ -605,16 +606,20 @@ var AddonRepository = {
    * the cache is completely removed.
    *
    * @param  aIds
    *         The array of add-on ids to repopulate the cache with
    * @param  aCallback
    *         The optional callback to call once complete
    */
   repopulateCache: function(aIds, aCallback) {
+    this._repopulateCache(aIds, aCallback, false);
+  },
+
+  _repopulateCache: function(aIds, aCallback, aSendPerformance) {
     // Completely remove cache if caching is not enabled
     if (!this.cacheEnabled) {
       this._addons = null;
       this._pendingCallbacks = null;
       AddonDatabase.delete(aCallback);
       return;
     }
 
@@ -623,28 +628,28 @@ var AddonRepository = {
       // Completely remove cache if there are no add-ons to cache
       if (aAddons.length == 0) {
         self._addons = null;
         self._pendingCallbacks = null;
         AddonDatabase.delete(aCallback);
         return;
       }
 
-      self.getAddonsByIDs(aAddons, {
+      self._beginGetAddons(aAddons, {
         searchSucceeded: function(aAddons) {
           self._addons = {};
           aAddons.forEach(function(aAddon) { self._addons[aAddon.id] = aAddon; });
           AddonDatabase.repopulate(aAddons, aCallback);
         },
         searchFailed: function() {
           WARN("Search failed when repopulating cache");
           if (aCallback)
             aCallback();
         }
-      });
+      }, aSendPerformance);
     });
   },
 
   /**
    * Asynchronously add add-ons to the cache corresponding to the specified
    * ids. If caching is disabled, the cache is unchanged and the callback is
    * immediatly called if it is defined.
    *
@@ -742,24 +747,66 @@ var AddonRepository = {
    * passed to the given callback.
    *
    * @param  aIDs
    *         The array of ids to search for
    * @param  aCallback
    *         The callback to pass results to
    */
   getAddonsByIDs: function(aIDs, aCallback) {
+    return this._beginGetAddons(aIDs, aCallback, false);
+  },
+
+  /**
+   * Begins a search of add-ons, potentially sending performance data.
+   *
+   * @param  aIDs
+   *         Array of ids to search for.
+   * @param  aCallback
+   *         Function to pass results to.
+   * @param  aSendPerformance
+   *         Boolean indicating whether to send performance data with the
+   *         request.
+   */
+  _beginGetAddons: function(aIDs, aCallback, aSendPerformance) {
     let ids = aIDs.slice(0);
 
     let params = {
       API_VERSION : API_VERSION,
       IDS : ids.map(encodeURIComponent).join(',')
     };
 
-    let url = this._formatURLPref(PREF_GETADDONS_BYIDS, params);
+    let pref = PREF_GETADDONS_BYIDS;
+
+    if (aSendPerformance) {
+      let type = Services.prefs.getPrefType(PREF_GETADDONS_BYIDS_PERFORMANCE);
+      if (type == Services.prefs.PREF_STRING) {
+        pref = PREF_GETADDONS_BYIDS_PERFORMANCE;
+
+        let startupInfo = Cc["@mozilla.org/toolkit/app-startup;1"].
+                          getService(Ci.nsIAppStartup).
+                          getStartupInfo();
+
+        if (startupInfo.process) {
+          if (startupInfo.main) {
+            params.TIME_MAIN = startupInfo.main - startupInfo.process;
+          }
+          if (startupInfo.firstPaint) {
+            params.TIME_FIRST_PAINT = startupInfo.firstPaint -
+                                      startupInfo.process;
+          }
+          if (startupInfo.sessionRestored) {
+            params.TIME_SESSION_RESTORED = startupInfo.sessionRestored -
+                                           startupInfo.process;
+          }
+        }
+      }
+    }
+
+    let url = this._formatURLPref(pref, params);
 
     let self = this;
     function handleResults(aElements, aTotalResults, aCompatData) {
       // Don't use this._parseAddons() so that, for example,
       // incompatible add-ons are not filtered out
       let results = [];
       for (let i = 0; i < aElements.length && results.length < self._maxResults; i++) {
         let result = self._parseAddon(aElements[i], null, aCompatData);
@@ -797,16 +844,33 @@ var AddonRepository = {
       // aTotalResults irrelevant
       self._reportSuccess(results, -1);
     }
 
     this._beginSearch(url, ids.length, aCallback, handleResults);
   },
 
   /**
+   * Performs the daily background update check.
+   *
+   * This API both searches for the add-on IDs specified and sends performance
+   * data. It is meant to be called as part of the daily update ping. It should
+   * not be used for any other purpose. Use repopulateCache instead.
+   *
+   * @param  aIDs
+   *         Array of add-on IDs to repopulate the cache with.
+   * @param  aCallback
+   *         Function to call when data is received. Function must be an object
+   *         with the keys searchSucceeded and searchFailed.
+   */
+  backgroundUpdateCheck: function(aIDs, aCallback) {
+    this._repopulateCache(aIDs, aCallback, true);
+  },
+
+  /**
    * Begins a search for recommended add-ons in this repository. Results will
    * be passed to the given callback.
    *
    * @param  aMaxResults
    *         The maximum number of results to return
    * @param  aCallback
    *         The callback to pass results to
    */
--- a/toolkit/mozapps/extensions/test/browser/Makefile.in
+++ b/toolkit/mozapps/extensions/test/browser/Makefile.in
@@ -91,16 +91,17 @@ include $(DEPTH)/config/autoconf.mk
   browser_openDialog.js \
   browser_types.js \
   browser_inlinesettings.js \
   browser_tabsettings.js \
   $(NULL)
 
 _TEST_FILES = \
   head.js \
+  browser_addonrepository_performance.js \
   browser_bug557956.js \
   browser_bug616841.js \
   browser_hotfix.js \
   browser_updatessl.js \
   browser_installssl.js \
   browser_newaddon.js \
   browser_select_selection.js \
   browser_select_compatoverrides.js \
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_addonrepository_performance.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that the metadata request includes startup time measurements
+
+Components.utils.import("resource://gre/modules/AddonRepository.jsm");
+
+var gManagerWindow;
+var gProvider;
+
+function parseParams(aQuery) {
+  let params = {};
+
+  aQuery.split("&").forEach(function(aParam) {
+    let [key, value] = aParam.split("=");
+    params[key] = value;
+  });
+
+  return params;
+}
+
+function test() {
+  waitForExplicitFinish();
+
+  var gSeenRequest = false;
+
+  gProvider = new MockProvider();
+  gProvider.createAddons([{
+    id: "test1@tests.mozilla.org",
+    name: "Test add-on"
+  }]);
+
+  function observe(aSubject, aTopic, aData) {
+    aSubject.QueryInterface(Ci.nsIChannel);
+    let url = aSubject.URI.QueryInterface(Ci.nsIURL);
+    if (url.filePath != "/extensions-dummy/metadata") {
+      return;
+    }
+    info(url.query);
+
+    let params = parseParams(url.query);
+
+    is(params.appOS, Services.appinfo.OS, "OS should be correct");
+    is(params.appVersion, Services.appinfo.version, "Version should be correct");
+    ok(params.tMain >= 0, "Should be a sensible tMain");
+    ok(params.tFirstPaint >= 0, "Should be a sensible tFirstPaint");
+    ok(params.tSessionRestored >= 0, "Should be a sensible tSessionRestored");
+
+    gSeenRequest = true;
+  }
+
+  const PREF = "extensions.getAddons.getWithPerformance.url";
+
+  // Watch HTTP requests
+  Services.obs.addObserver(observe, "http-on-modify-request", false);
+  Services.prefs.setCharPref(PREF,
+    "http://127.0.0.1:8888/extensions-dummy/metadata?appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
+
+  registerCleanupFunction(function() {
+    Services.obs.removeObserver(observe, "http-on-modify-request");
+  });
+
+  AddonRepository._beginGetAddons(["test1@tests.mozilla.org"], {
+    searchFailed: function() {
+      ok(gSeenRequest, "Should have seen metadata request");
+      finish();
+    }
+  }, true);
+}
+
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -50,16 +50,17 @@ var gTestsRun = 0;
 var gTestStart = null;
 
 var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window);
 
 var gRestorePrefs = [{name: PREF_LOGGING_ENABLED},
                      {name: "extensions.webservice.discoverURL"},
                      {name: "extensions.update.url"},
                      {name: "extensions.getAddons.get.url"},
+                     {name: "extensions.getAddons.getWithPerformance.url"},
                      {name: "extensions.getAddons.search.browseURL"},
                      {name: "extensions.getAddons.search.url"},
                      {name: "extensions.getAddons.cache.enabled"},
                      {name: PREF_SEARCH_MAXRESULTS},
                      {name: PREF_STRICT_COMPAT},
                      {name: PREF_CHECK_COMPATIBILITY}];
 
 gRestorePrefs.forEach(function(aPref) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_AddonRepository_cache.js
@@ -10,16 +10,17 @@ do_load_httpd_js();
 let gServer;
 
 const PORT      = 4444;
 const BASE_URL  = "http://localhost:" + PORT;
 
 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
 const PREF_GETADDONS_CACHE_TYPES   = "extensions.getAddons.cache.types";
 const PREF_GETADDONS_BYIDS         = "extensions.getAddons.get.url";
+const PREF_GETADDONS_BYIDS_PERF    = "extensions.getAddons.getWithPerformance.url";
 const GETADDONS_RESULTS            = BASE_URL + "/data/test_AddonRepository_cache.xml";
 const GETADDONS_EMPTY              = BASE_URL + "/data/test_AddonRepository_empty.xml";
 const GETADDONS_FAILED             = BASE_URL + "/data/test_AddonRepository_failed.xml";
 
 const FILE_DATABASE = "addons.sqlite";
 const ADDON_NAMES = ["test_AddonRepository_1",
                      "test_AddonRepository_2",
                      "test_AddonRepository_3"];
@@ -699,17 +700,17 @@ function run_test_14() {
       run_test_15();
     });
   });
 }
 
 // Tests that the XPI add-ons correctly use the repository properties when
 // caching is enabled and the repository information is available
 function run_test_15() {
-  Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, GETADDONS_RESULTS);
+  Services.prefs.setCharPref(PREF_GETADDONS_BYIDS_PERF, GETADDONS_RESULTS);
 
   trigger_background_update(function() {
     AddonManager.getAddonsByIDs(ADDON_IDS, function(aAddons) {
       check_results(aAddons, WITH_CACHE);
       run_test_16();
     });
   });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_compatoverrides.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests compatibility overrides, for when strict compatibility checking is
 // disabled. See bug 693906.
 
 
 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
-const PREF_GETADDONS_BYIDS         = "extensions.getAddons.get.url";
+const PREF_GETADDONS_BYIDS         = "extensions.getAddons.getWithPerformance.url";
 
 const PORT            = 4444;
 const BASE_URL        = "http://localhost:" + PORT;
 const DEFAULT_URL     = "about:blank";
 const REQ_URL         = "/data.xml";
 
 Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // This verifies that add-on update checks work
 
 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
-const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url";
+const PREF_GETADDONS_BYIDS = "extensions.getAddons.getWithPerformance.url";
 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
 
 // The test extension uses an insecure update url.
 Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
 Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
 // This test requires lightweight themes update to be enabled even if the app
 // doesn't support lightweight themes.
 Services.prefs.setBoolPref("lightweightThemes.update.enabled", true);