Bug 995027 - Wait for async getAllAddons, correctly clean up timers. r=bsmedberg, a=test-only
☠☠ backed out by 59e467fdbcb8 ☠ ☠
authorIrving Reid <irving@mozilla.com>
Thu, 08 May 2014 11:41:36 -0400
changeset 199152 7006de4e71d40dc257729c58483b3bcf07b21906
parent 199151 27d2efbc5929ba533a878c2163c945e2aba0715b
child 199153 1ee6ba74d90f14d79ac6347f55fe05946eff72d7
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, test-only
bugs995027
milestone31.0a2
Bug 995027 - Wait for async getAllAddons, correctly clean up timers. r=bsmedberg, a=test-only
toolkit/mozapps/extensions/test/browser/head.js
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1,18 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
 let tmp = {};
 Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
+Components.utils.import("resource://gre/modules/Log.jsm", tmp);
 let AddonManager = tmp.AddonManager;
 let AddonManagerPrivate = tmp.AddonManagerPrivate;
+let Log = tmp.Log;
 
 var pathParts = gTestPath.split("/");
 // Drop the test filename
 pathParts.splice(pathParts.length - 1, pathParts.length);
 
 var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]);
 
 // Drop the UI type
@@ -119,27 +121,26 @@ registerCleanupFunction(function() {
       Services.prefs.setCharPref(pref.name, pref.value);
   }
 
   // Throw an error if the add-ons manager window is open anywhere
   checkOpenWindows("Addons:Manager");
   checkOpenWindows("Addons:Compatibility");
   checkOpenWindows("Addons:Install");
 
-  // We can for now know that getAllInstalls actually calls its callback before
-  // it returns so this will complete before the next test start.
-  AddonManager.getAllInstalls(function(aInstalls) {
-    for (let install of aInstalls) {
-      if (install instanceof MockInstall)
-        continue;
+  return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve))
+    .then(aInstalls => {
+      for (let install of aInstalls) {
+        if (install instanceof MockInstall)
+          continue;
 
-      ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state);
-      install.cancel();
-    }
-  });
+        ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state);
+        install.cancel();
+      }
+    });
 });
 
 function log_exceptions(aCallback, ...aArgs) {
   try {
     return aCallback.apply(null, aArgs);
   }
   catch (e) {
     info("Exception thrown: " + e);
@@ -578,16 +579,17 @@ function addCertOverride(host, bits) {
 }
 
 /***** Mock Provider *****/
 
 function MockProvider(aUseAsyncCallbacks, aTypes) {
   this.addons = [];
   this.installs = [];
   this.callbackTimers = [];
+  this.timerLocations = new Map();
   this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks;
   this.types = (aTypes === undefined) ? [{
     id: "extension",
     name: "Extensions",
     uiPriority: 4000,
     flags: AddonManager.TYPE_UI_VIEW_LIST
   }] : aTypes;
 
@@ -601,16 +603,17 @@ function MockProvider(aUseAsyncCallbacks
 }
 
 MockProvider.prototype = {
   addons: null,
   installs: null,
   started: null,
   apiDelay: 10,
   callbackTimers: null,
+  timerLocations: null,
   useAsyncCallbacks: null,
   types: null,
 
   /***** Utility functions *****/
 
   /**
    * Register this provider with the AddonManager
    */
@@ -781,21 +784,30 @@ MockProvider.prototype = {
 
   /**
    * Called when the provider should shutdown.
    */
   shutdown: function MP_shutdown() {
     if (this.callbackTimers.length) {
       info("MockProvider: pending callbacks at shutdown(): calling immediately");
     }
-    for (let timer of this.callbackTimers) {
-      timer.callback();
-      timer.cancel();
+    while (this.callbackTimers.length > 0) {
+      // When we notify the callback timer, it removes itself from our array
+      let timer = this.callbackTimers[0];
+      try {
+        let setAt = this.timerLocations.get(timer);
+        info("Notifying timer set at " + (setAt || "unknown location"));
+        timer.callback.notify(timer);
+        timer.cancel();
+      } catch(e) {
+        info("Timer notify failed: " + e);
+      }
     }
     this.callbackTimers = [];
+    this.timerLocations = null;
 
     this.started = false;
   },
 
   /**
    * Called to get an Addon with a particular ID.
    *
    * @param  aId
@@ -968,31 +980,36 @@ MockProvider.prototype = {
    * The delay is specified by the apiDelay property, in milliseconds.
    * Parameters to send to the callback should be specified as arguments after
    * the aCallback argument.
    *
    * @param aCallback Callback to eventually call
    */
   _delayCallback: function MP_delayCallback(aCallback, ...aArgs) {
     if (!this.useAsyncCallbacks) {
-      aCallback.apply(null, aArgs);
+      aCallback(...aArgs);
       return;
     }
 
-    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     // Need to keep a reference to the timer, so it doesn't get GC'ed
     this.callbackTimers.push(timer);
+    // Capture a stack trace where the timer was set
+    // needs the 'new Error' hack until bug 1007656
+    this.timerLocations.set(timer, Log.stackTrace(new Error("dummy")));
     timer.initWithCallback(() => {
       let idx = this.callbackTimers.indexOf(timer);
       if (idx == -1) {
-        info("MockProvider._delayCallback lost track of a timer.");
+        dump("MockProvider._delayCallback lost track of timer set at "
+             + (this.timerLocations.get(timer) || "unknown location") + "\n");
       } else {
         this.callbackTimers.splice(idx, 1);
       }
-      aCallback.apply(null, aArgs);
+      this.timerLocations.delete(timer);
+      aCallback(...aArgs);
     }, this.apiDelay, timer.TYPE_ONE_SHOT);
   }
 };
 
 /***** Mock Addon object for the Mock Provider *****/
 
 function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
   // Only set required attributes.