Backed out changeset 6ae4ca55a6d9 (bug 1059674) for mass bustage.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 10 Oct 2014 21:17:07 -0400
changeset 225638 27c4a0956f761cd679635cb52f81dcd8cad86566
parent 225637 d847271bce7b18fac52f1a18f484f49e581eb25b
child 225639 41199adb9b7064da2cf2be2919bc412a7b3c724f
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1059674
milestone34.0a2
backs out6ae4ca55a6d9ae4a1e32fb8693aeca6eb5dbd0b2
Backed out changeset 6ae4ca55a6d9 (bug 1059674) for mass bustage.
browser/experiments/Experiments.jsm
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/LightweightThemeManager.jsm
toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
toolkit/mozapps/extensions/internal/PluginProvider.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_provider_shutdown.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -2255,18 +2255,16 @@ this.Experiments.PreviousExperimentProvi
   this._experiments = experiments;
   this._experimentList = [];
   this._log = Log.repository.getLoggerWithMessagePrefix(
     "Browser.Experiments.Experiments",
     "PreviousExperimentProvider #" + gPreviousProviderCounter++ + "::");
 }
 
 this.Experiments.PreviousExperimentProvider.prototype = Object.freeze({
-  get name() "PreviousExperimentProvider",
-
   startup: function () {
     this._log.trace("startup()");
     Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false);
   },
 
   shutdown: function () {
     this._log.trace("shutdown()");
     try {
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -117,18 +117,16 @@ let logger = Log.repository.getLogger(LO
 // If the "extensions.logging.enabled" preference is
 // missing or 'false', messages at the WARNING and higher
 // severity should be logged to the JS console and standard error.
 // If "extensions.logging.enabled" is set to 'true', messages
 // at DEBUG and higher should go to JS console and standard error.
 const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
 
-const UNNAMED_PROVIDER = "<unnamed-provider>";
-
 /**
  * Preference listener which listens for a change in the
  * "extensions.logging.enabled" preference and changes the logging level of the
  * parent 'addons' level logger accordingly.
  */
 var PrefObserver = {
     init: function PrefObserver_init() {
       Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
@@ -461,30 +459,27 @@ var gStartupComplete = false;
 var gCheckCompatibility = true;
 var gStrictCompatibility = true;
 var gCheckUpdateSecurityDefault = true;
 var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
 var gUpdateEnabled = true;
 var gAutoUpdateDefault = true;
 var gHotfixID = null;
 var gShutdownBarrier = null;
-var gRepoShutdownState = "";
-var gShutdownInProgress = false;
 
 /**
  * This is the real manager, kept here rather than in AddonManager to keep its
  * contents hidden from API users.
  */
 var AddonManagerInternal = {
   managerListeners: [],
   installListeners: [],
   addonListeners: [],
   typeListeners: [],
   providers: [],
-  providerShutdowns: new Map(),
   types: {},
   startupChanges: {},
   // Store telemetry details per addon provider
   telemetryDetails: {},
 
   // A read-only wrapper around the types dictionary
   typesProxy: Proxy.create({
     getOwnPropertyDescriptor: function typesProxy_getOwnPropertyDescriptor(aName) {
@@ -614,43 +609,16 @@ var AddonManagerInternal = {
       appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
     }
     catch (e) {
       logger.warn("Failed to copy the application shipped blocklist to the profile", e);
     }
   },
 
   /**
-   * Start up a provider, and register its shutdown hook if it has one
-   */
-  _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
-    if (!gStarted)
-      throw Components.Exception("AddonManager is not initialized",
-                                 Cr.NS_ERROR_NOT_INITIALIZED);
-
-    callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
-    if ('shutdown' in aProvider) {
-      let name = aProvider.name || "Provider";
-      let AMProviderShutdown = () => {
-        return new Promise((resolve, reject) => {
-            logger.debug("Calling shutdown blocker for " + name);
-            resolve(aProvider.shutdown());
-          })
-          .catch(err => {
-            logger.warn("Failure during shutdown of " + name, err);
-            AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err);
-          });
-      };
-      logger.debug("Registering shutdown blocker for " + name);
-      this.providerShutdowns.set(aProvider, AMProviderShutdown);
-      AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
-    }
-  },
-
-  /**
    * Initializes the AddonManager, loading any known providers and initializing
    * them.
    */
   startup: function AMI_startup() {
     try {
       if (gStarted)
         return;
 
@@ -769,27 +737,25 @@ var AddonManagerInternal = {
         catch (e) {
           AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
           logger.error("Exception loading provider " + entry + " from category \"" +
                 url + "\"", e);
         }
       }
 
       // Register our shutdown handler with the AsyncShutdown manager
-      gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down.");
-      AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.",
-                                                   this.shutdownManager.bind(this),
-                                                   {fetchState: this.shutdownState.bind(this)});
+      gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for clients to shut down.");
+      AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers",
+                                                   this.shutdownManager.bind(this));
 
       // Once we start calling providers we must allow all normal methods to work.
       gStarted = true;
 
-      for (let provider of this.providers) {
-        this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion);
-      }
+      this.callProviders("startup", appChanged, oldAppVersion,
+                         oldPlatformVersion);
 
       // If this is a new profile just pretend that there were no changes
       if (appChanged === undefined) {
         for (let type in this.startupChanges)
           delete this.startupChanges[type];
       }
 
       gStartupComplete = true;
@@ -842,29 +808,25 @@ var AddonManagerInternal = {
         }
         else {
           this.types[aType.id].providers.push(aProvider);
         }
       }, this);
     }
 
     // If we're registering after startup call this provider's startup.
-    if (gStarted) {
-      this._startProvider(aProvider);
-    }
+    if (gStarted)
+      callProvider(aProvider, "startup");
   },
 
   /**
    * Unregisters an AddonProvider.
    *
    * @param  aProvider
    *         The provider to unregister
-   * @return Whatever the provider's 'shutdown' method returns (if anything).
-   *         For providers that have async shutdown methods returning Promises,
-   *         the caller should wait for that Promise to resolve.
    */
   unregisterProvider: function AMI_unregisterProvider(aProvider) {
     if (!aProvider || typeof aProvider != "object")
       throw Components.Exception("aProvider must be specified",
                                  Cr.NS_ERROR_INVALID_ARG);
 
     let pos = 0;
     while (pos < this.providers.length) {
@@ -884,29 +846,19 @@ var AddonManagerInternal = {
         for (let listener of typeListeners) {
           safeCall(function listenerSafeCall() {
             listener.onTypeRemoved(oldType);
           });
         }
       }
     }
 
-    // If we're unregistering after startup but before shutting down,
-    // remove the blocker for this provider's shutdown and call it.
-    // If we're already shutting down, just let gShutdownBarrier call it to avoid races.
-    if (gStarted && !gShutdownInProgress) {
-      logger.debug("Unregistering shutdown blocker for " + (aProvider.name || "Provider"));
-      let shutter = this.providerShutdowns.get(aProvider);
-      if (shutter) {
-        this.providerShutdowns.delete(aProvider);
-        gShutdownBarrier.client.removeBlocker(shutter);
-        return shutter();
-      }
-    }
-    return undefined;
+    // If we're unregistering after startup call this provider's shutdown.
+    if (gStarted)
+      callProvider(aProvider, "shutdown");
   },
 
   /**
    * Calls a method on all registered providers if it exists and consumes any
    * thrown exception. Return values are ignored. Any parameters after the
    * method parameter are passed to the provider's method.
    *
    * @param  aMethod
@@ -927,92 +879,105 @@ var AddonManagerInternal = {
       catch (e) {
         AddonManagerPrivate.recordException("AMI", "provider " + aMethod, e);
         logger.error("Exception calling provider " + aMethod, e);
       }
     }
   },
 
   /**
-   * Report the current state of asynchronous shutdown
+   * Calls a method on all registered providers, if the provider implements
+   * the method. The called method is expected to return a promise, and
+   * callProvidersAsync returns a promise that resolves when every provider
+   * method has either resolved or rejected. Rejection reasons are logged
+   * but otherwise ignored. Return values are ignored. Any parameters after the
+   * method parameter are passed to the provider's method.
+   *
+   * @param  aMethod
+   *         The method name to call
+   * @see    callProvider
    */
-  shutdownState() {
-    let state = [];
-    if (gShutdownBarrier) {
-      state.push({
-        name: gShutdownBarrier.client.name,
-        state: gShutdownBarrier.state
-      });
+  callProvidersAsync: function AMI_callProviders(aMethod, ...aArgs) {
+    if (!aMethod || typeof aMethod != "string")
+      throw Components.Exception("aMethod must be a non-empty string",
+                                 Cr.NS_ERROR_INVALID_ARG);
+
+    let allProviders = [];
+
+    let providers = this.providers.slice(0);
+    for (let provider of providers) {
+      try {
+        if (aMethod in provider) {
+          // Resolve a new promise with the result of the method, to handle both
+          // methods that return values (or nothing) and methods that return promises.
+          let providerResult = provider[aMethod].apply(provider, aArgs);
+          let nextPromise = Promise.resolve(providerResult);
+          // Log and swallow the errors from methods that do return promises.
+          nextPromise = nextPromise.then(
+              null,
+              e => logger.error("Exception calling provider " + aMethod, e));
+          allProviders.push(nextPromise);
+        }
+      }
+      catch (e) {
+        logger.error("Exception calling provider " + aMethod, e);
+      }
     }
-    state.push({
-      name: "AddonRepository: async shutdown",
-      state: gRepoShutdownState
-    });
-    return state;
+    // Because we use promise.then to catch and log all errors above, Promise.all()
+    // will never exit early because of a rejection.
+    return Promise.all(allProviders);
   },
 
   /**
    * Shuts down the addon manager and all registered providers, this must clean
    * up everything in order for automated tests to fake restarts.
    * @return Promise{null} that resolves when all providers and dependent modules
    *                       have finished shutting down
    */
-  shutdownManager: Task.async(function* () {
+  shutdownManager: function() {
     logger.debug("shutdown");
-    gRepoShutdownState = "pending";
-    gShutdownInProgress = true;
     // Clean up listeners
     Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
     Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
     Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
     Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
 
-    let savedError = null;
-    // Only shut down providers if they've been started.
+    // Only shut down providers if they've been started. Shut down
+    // AddonRepository after providers (if any).
+    let shuttingDown = null;
     if (gStarted) {
-      try {
-        yield gShutdownBarrier.wait();
-      }
-      catch(err) {
-        savedError = err;
-        logger.error("Failure during wait for shutdown barrier", err);
-        AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
-      }
+      shuttingDown = gShutdownBarrier.wait()
+        .then(null, err => logger.error("Failure during wait for shutdown barrier", err))
+        .then(() => this.callProvidersAsync("shutdown"))
+        .then(null,
+              err => logger.error("Failure during async provider shutdown", err))
+        .then(() => AddonRepository.shutdown());
     }
-
-    // Shut down AddonRepository after providers (if any).
-    try {
-      gRepoShutdownState = "in progress";
-      yield AddonRepository.shutdown();
-      gRepoShutdownState = "done";
+    else {
+      shuttingDown = AddonRepository.shutdown();
     }
-    catch(err) {
-      savedError = err;
-      logger.error("Failure during AddonRepository shutdown", err);
-      AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
-    }
-
-    logger.debug("Async provider shutdown done");
-    this.managerListeners.splice(0, this.managerListeners.length);
-    this.installListeners.splice(0, this.installListeners.length);
-    this.addonListeners.splice(0, this.addonListeners.length);
-    this.typeListeners.splice(0, this.typeListeners.length);
-    this.providerShutdowns.clear();
-    for (let type in this.startupChanges)
-      delete this.startupChanges[type];
-    gStarted = false;
-    gStartupComplete = false;
-    gShutdownBarrier = null;
-    gShutdownInProgress = false;
-    if (savedError) {
-      throw savedError;
-    }
-  }),
+
+    shuttingDown.then(val => logger.debug("Async provider shutdown done"),
+                      err => logger.error("Failure during AddonRepository shutdown", err))
+      .then(() => {
+        this.managerListeners.splice(0, this.managerListeners.length);
+        this.installListeners.splice(0, this.installListeners.length);
+        this.addonListeners.splice(0, this.addonListeners.length);
+        this.typeListeners.splice(0, this.typeListeners.length);
+        for (let type in this.startupChanges)
+          delete this.startupChanges[type];
+        gStarted = false;
+        gStartupComplete = false;
+        gShutdownBarrier = null;
+      });
+
+    return shuttingDown;
+  },
 
   /**
    * Notified when a preference we're interested in has changed.
    *
    * @see nsIObserver
    */
   observe: function AMI_observe(aSubject, aTopic, aData) {
     switch (aData) {
--- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -65,18 +65,16 @@ this.__defineSetter__("_maxUsedThemes", 
 
 // Holds the ID of the theme being enabled or disabled while sending out the
 // events so cached AddonWrapper instances can return correct values for
 // permissions and pendingOperations
 var _themeIDBeingEnabled = null;
 var _themeIDBeingDisabled = null;
 
 this.LightweightThemeManager = {
-  get name() "LightweightThemeManager",
-
   get usedThemes () {
     try {
       return JSON.parse(_prefs.getComplexValue("usedThemes",
                                                Ci.nsISupportsString).data);
     } catch (e) {
       return [];
     }
   },
--- a/toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
+++ b/toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
@@ -240,18 +240,16 @@ let OpenH264Wrapper = {
   },
 
   get isInstalled() {
     return this.version.length > 0;
   },
 };
 
 let OpenH264Provider = {
-  get name() "OpenH264Provider",
-
   startup: function() {
     configureLogging();
     this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.OpenH264Provider",
                                                           "OpenH264Provider" + "::");
     OpenH264Wrapper._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.OpenH264Provider",
                                                                      "OpenH264Wrapper" + "::");
     this.gmpPath = null;
     if (OpenH264Wrapper.isInstalled) {
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -43,18 +43,16 @@ function getIDHashForString(aStr) {
   return "{" + hash.substr(0, 8) + "-" +
                hash.substr(8, 4) + "-" +
                hash.substr(12, 4) + "-" +
                hash.substr(16, 4) + "-" +
                hash.substr(20) + "}";
 }
 
 var PluginProvider = {
-  get name() "PluginProvider",
-
   // A dictionary mapping IDs to names and descriptions
   plugins: null,
 
   startup: function PL_startup() {
     Services.obs.addObserver(this, LIST_UPDATED_TOPIC, false);
     Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
   },
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1593,18 +1593,16 @@ function recordAddonTelemetry(aAddon) {
     if (loc.name)
       XPIProvider.setTelemetry(aAddon.id, "name", loc.name);
     if (loc.creator)
       XPIProvider.setTelemetry(aAddon.id, "creator", loc.creator);
   }
 }
 
 this.XPIProvider = {
-  get name() "XPIProvider",
-
   // An array of known install locations
   installLocations: null,
   // A dictionary of known install locations by name
   installLocationsByName: null,
   // An array of currently active AddonInstalls
   installs: null,
   // The default skin for the application
   defaultSkin: "classic/1.0",
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -21,40 +21,52 @@ const TIMEOUT_MS = 900000;
 Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 Components.utils.import("resource://gre/modules/Promise.jsm");
 Components.utils.import("resource://gre/modules/Task.jsm");
 Components.utils.import("resource://gre/modules/osfile.jsm");
-Components.utils.import("resource://gre/modules/AsyncShutdown.jsm");
 
 Services.prefs.setBoolPref("toolkit.osfile.log", true);
 
 // We need some internal bits of AddonManager
 let AMscope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
 let AddonManager = AMscope.AddonManager;
 let AddonManagerInternal = AMscope.AddonManagerInternal;
 // Mock out AddonManager's reference to the AsyncShutdown module so we can shut
 // down AddonManager from the test
 let MockAsyncShutdown = {
   hook: null,
-  status: null,
   profileBeforeChange: {
-    addBlocker: function(aName, aBlocker, aOptions) {
+    addBlocker: function(aName, aBlocker) {
       do_print("Mock profileBeforeChange blocker for '" + aName + "'");
       MockAsyncShutdown.hook = aBlocker;
-      MockAsyncShutdown.status = aOptions.fetchState;
     }
   },
-  // We can use the real Barrier
-  Barrier: AsyncShutdown.Barrier
+  Barrier: function (name) {
+    this.name = name;
+    this.client.addBlocker = (name, blocker) => {
+      do_print("Mock Barrier blocker for '" + name + "' for barrier '" + this.name + "'");
+      this.blockers.push({name: name, blocker: blocker});
+    };
+  },
 };
 
+MockAsyncShutdown.Barrier.prototype = Object.freeze({
+  blockers: [],
+  client: {},
+  wait: Task.async(function* () {
+    for (let b of this.blockers) {
+      yield b.blocker();
+    }
+  }),
+});
+
 AMscope.AsyncShutdown = MockAsyncShutdown;
 
 var gInternalManager = null;
 var gAppInfo = null;
 var gAddonsList;
 
 var gPort = null;
 var gUrlToFileMap = {};
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_provider_shutdown.js
+++ /dev/null
@@ -1,98 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Verify that we report shutdown status for Addon Manager providers
-// and AddonRepository correctly.
-
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
-
-// Make a mock AddonRepository that just lets us hang shutdown.
-// Needs two promises - one to let us know that AM has called shutdown,
-// and one for us to let AM know that shutdown is done.
-function mockAddonProvider(aName) {
-  let mockProvider = {
-    donePromise: null,
-    doneResolve: null,
-    doneReject: null,
-    shutdownPromise: null,
-    shutdownResolve: null,
-
-    get name() aName,
-
-    shutdown() {
-      this.shutdownResolve();
-      return this.donePromise;
-    },
-  };
-  mockProvider.donePromise = new Promise((resolve, reject) => {
-    mockProvider.doneResolve = resolve;
-    mockProvider.doneResject = reject;
-  });
-  mockProvider.shutdownPromise = new Promise((resolve, reject) => {
-    mockProvider.shutdownResolve = resolve;
-  });
-  return mockProvider;
-};
-
-function run_test() {
-  run_next_test();
-}
-
-// Helper to find a particular shutdown blocker's status in the JSON blob
-function findInStatus(aStatus, aName) {
-  for (let {name, state} of aStatus.state) {
-    if (name == aName) {
-      return state;
-    }
-  }
-  return null;
-}
-
-/*
- * Make sure we report correctly when an add-on provider or AddonRepository block shutdown
- */
-add_task(function* blockRepoShutdown() {
-  // Reach into the AddonManager scope and inject our mock AddonRepository
-  let realAddonRepo = AMscope.AddonRepository;
-  // the mock provider behaves enough like AddonRepository for the purpose of this test
-  let mockRepo = mockAddonProvider("Mock repo");
-  AMscope.AddonRepository = mockRepo;
-
-  let mockProvider = mockAddonProvider("Mock provider");
-
-  startupManager();
-  AddonManagerPrivate.registerProvider(mockProvider);
-
-  // Start shutting the manager down
-  let managerDown = promiseShutdownManager();
-
-  // Wait for manager to call provider shutdown.
-  yield mockProvider.shutdownPromise;
-  // check AsyncShutdown state
-  let status = MockAsyncShutdown.status();
-  equal(findInStatus(status[0], "Mock provider"), "(none)");
-  equal(status[1].name, "AddonRepository: async shutdown");
-  equal(status[1].state, "pending");
-  // let the provider finish
-  mockProvider.doneResolve();
-
-  // Wait for manager to call repo shutdown and start waiting for it
-  yield mockRepo.shutdownPromise;
-  // Check the shutdown state
-  status = MockAsyncShutdown.status();
-  do_print(JSON.stringify(status));
-  equal(status[0].name, "AddonManager: Waiting for providers to shut down.");
-  equal(status[0].state, "Complete");
-  equal(status[1].name, "AddonRepository: async shutdown");
-  equal(status[1].state, "in progress");
-
-  // Now finish our shutdown, and wait for the manager to wrap up
-  mockRepo.doneResolve();
-  yield managerDown;
-
-  // Check the shutdown state again
-  status = MockAsyncShutdown.status();
-  equal(status[0].name, "AddonRepository: async shutdown");
-  equal(status[0].state, "done");
-});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -11,11 +11,10 @@ support-files =
 [include:xpcshell-shared.ini]
 
 [test_addon_path_service.js]
 [test_asyncBlocklistLoad.js]
 [test_DeferredSave.js]
 [test_metadata_update.js]
 [test_openh264.js]
 run-if = appname == "firefox"
-[test_provider_shutdown.js]
 [test_shutdown.js]
 [test_XPIcancel.js]