Bug 1089867 - Uplift all patches for the download of Adobe EME and integration in addons manager. r=gfritzsche, r=cpearce, a=lmandel
authorStephen Pohl <spohl.mozilla.bugs@gmail.com>
Sat, 21 Feb 2015 19:10:00 -0500
changeset 249926 a18ed479269928f03a95c8b64b4db10209dd0682
parent 249925 550f936c3293dcb7b2e78b3c0a9b04d33559662b
child 249927 1a9f7a862d18b74fa0542659342f148d51b53136
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgfritzsche, cpearce, lmandel
bugs1089867
milestone37.0a2
Bug 1089867 - Uplift all patches for the download of Adobe EME and integration in addons manager. r=gfritzsche, r=cpearce, a=lmandel
browser/app/profile/firefox.js
dom/media/eme/MediaKeySystemAccess.cpp
mobile/android/app/mobile.js
modules/libpref/init/all.js
toolkit/modules/GMPInstallManager.jsm
toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/content/gmpPrefs.xul
toolkit/mozapps/extensions/content/openH264Prefs.xul
toolkit/mozapps/extensions/extensions.manifest
toolkit/mozapps/extensions/internal/GMPProvider.jsm
toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
toolkit/mozapps/extensions/internal/moz.build
toolkit/mozapps/extensions/jar.mn
toolkit/mozapps/extensions/test/browser/browser.ini
toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
toolkit/mozapps/extensions/test/browser/browser_openH264.js
toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js
toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
toolkit/mozapps/extensions/test/xpcshell/test_openh264.js
toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1760,18 +1760,18 @@ pref("browser.translation.ui.show", fals
 
 // Telemetry experiments settings.
 pref("experiments.enabled", true);
 pref("experiments.manifest.fetchIntervalSeconds", 86400);
 pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
 // Whether experiments are supported by the current application profile.
 pref("experiments.supported", true);
 
-// Enable the OpenH264 plugin support in the addon manager.
-pref("media.gmp-gmpopenh264.provider.enabled", true);
+// Enable GMP support in the addon manager.
+pref("media.gmp-provider.enabled", true);
 
 pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
 
 #ifdef NIGHTLY_BUILD
 pref("browser.polaris.enabled", false);
 pref("privacy.trackingprotection.ui.enabled", false);
 #endif
 
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -103,17 +103,17 @@ MediaKeySystemAccess::IsKeySystemSupport
       HaveGMPFor(mps,
                  NS_LITERAL_CSTRING("org.w3.clearkey"),
                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
     return true;
   }
 
 #ifdef XP_WIN
   if (aKeySystem.EqualsLiteral("com.adobe.access") &&
-      Preferences::GetBool("media.eme.adobe-access.enabled", false) &&
+      Preferences::GetBool("media.gmp-eme-adobe.enabled", false) &&
       IsVistaOrLater() && // Win Vista and later only.
       HaveGMPFor(mps,
                  NS_LITERAL_CSTRING("com.adobe.access"),
                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
       return true;
   }
 #endif
 
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -863,10 +863,10 @@ pref("home.sync.updateMode", 0);
 pref("home.sync.checkIntervalSecs", 3600);
 
 // Enable device storage API
 pref("device.storage.enabled", true);
 
 // Enable meta-viewport support for font inflation code
 pref("dom.meta-viewport.enabled", true);
 
-// Enable the OpenH264 plugin support in the addon manager.
-pref("media.gmp-gmpopenh264.provider.enabled", true);
+// Enable GMP support in the addon manager.
+pref("media.gmp-provider.enabled", true);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4457,19 +4457,16 @@ pref("browser.search.geoip.timeout", 200
 #ifdef MOZ_OFFICIAL_BRANDING
 // {moz:official} expands to "official"
 pref("browser.search.official", true);
 #endif
 
 #ifndef MOZ_WIDGET_GONK
 // GMPInstallManager prefs
 
-// Enables some extra logging (can reduce performance)
-pref("media.gmp-manager.log", false);
-
 // User-settable override to media.gmp-manager.url for testing purposes.
 //pref("media.gmp-manager.url.override", "");
 
 // Update service URL for GMP install/updates:
 pref("media.gmp-manager.url", "https://aus4.mozilla.org/update/3/GMP/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 
 // When |media.gmp-manager.cert.requireBuiltIn| is true or not specified the
 // final certificate and all certificates the connection is redirected to before
@@ -4493,10 +4490,13 @@ pref("media.gmp-manager.cert.requireBuil
 // the |media.gmp-manager.url.override| preference should ONLY be used for testing.
 // IMPORTANT! app.update.certs.* prefs should also be updated if these
 // are updated.
 pref("media.gmp-manager.cert.checkAttributes", true);
 pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert Secure Server CA,O=DigiCert Inc,C=US");
 pref("media.gmp-manager.certs.1.commonName", "aus4.mozilla.org");
 pref("media.gmp-manager.certs.2.issuerName", "CN=Thawte SSL CA,O=\"Thawte, Inc.\",C=US");
 pref("media.gmp-manager.certs.2.commonName", "aus4.mozilla.org");
+
+// Adobe EME is currently pref'd off by default and hidden in the addon manager.
+pref("media.gmp-eme-adobe.hidden", true);
 #endif
 
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -9,46 +9,40 @@ this.EXPORTED_SYMBOLS = [];
 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
   Components;
 // Chunk size for the incremental downloader
 const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
 // Incremental downloader interval
 const DOWNLOAD_INTERVAL  = 0;
 // 1 day default
 const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;
+
+// Global pref to enable/disable all EME plugins
+const EME_ENABLED = "media.eme.enabled";
+
+
+// GMP IDs
 const OPEN_H264_ID = "gmp-gmpopenh264";
+const EME_ADOBE_ID = "gmp-eme-adobe";
+const GMP_ADDONS = [ OPEN_H264_ID, EME_ADOBE_ID ];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/ctypes.jsm");
 
 this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader",
                          "GMPAddon", "GMPPrefs", "OPEN_H264_ID"];
 
 var gLocale = null;
-const PARENT_LOGGER_ID = "GMPInstallManager";
-
-// Used to determine if logging should be enabled
-XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function() {
-  return Preferences.get("media.gmp-manager.log", false);
-});
-
-// Setup the parent logger with dump logging. It'll only be used if logging is
-// enabled though.  We don't actually have any fatal logging errors, so setting
-// the log level to fatal effectively disables it.
-let parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
-parentLogger.level = gLogEnabled ? Log.Level.Debug : Log.Level.Fatal;
-let appender = new Log.DumpAppender();
-parentLogger.addAppender(appender);
 
 // Shared code for suppressing bad cert dialogs
 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
   let temp = { };
   Cu.import("resource://gre/modules/CertUtils.jsm", temp);
   return temp;
 });
 
@@ -63,31 +57,31 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  * antivirus behavior). This timeout is a defensive measure to ensure
  * that we fail cleanly in such case.
  */
 const CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS = 20000;
 
 function getScopedLogger(prefix) {
   // `PARENT_LOGGER_ID.` being passed here effectively links this logger
   // to the parentLogger.
-  return Log.repository.getLogger(PARENT_LOGGER_ID + "." + prefix);
+  return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " ");
 }
 
 /**
  * Manages preferences for GMP addons
  */
 let GMPPrefs = {
   /**
    * Obtains the specified preference in relation to the specified addon
    * @param key The GMPPrefs key value to use
+   * @param defaultValue The default value if no preference exists
    * @param addon The addon to scope the preference to
-   * @param defaultValue The default value if no preference exists
-   * @return The obtained preference value, or the defaultVlaue if none exists
+   * @return The obtained preference value, or the defaultValue if none exists
    */
-  get: function(key, addon, defaultValue) {
+  get: function(key, defaultValue, addon) {
     if (key === GMPPrefs.KEY_APP_DISTRIBUTION ||
         key === GMPPrefs.KEY_APP_DISTRIBUTION_VERSION) {
       let prefValue = "default";
       try {
         prefValue = Services.prefs.getDefaultBranch(null).getCharPref(key);
       } catch (e) {
         // use default when pref not found
       }
@@ -98,33 +92,33 @@ let GMPPrefs = {
   },
   /**
    * Sets the specified preference in relation to the specified addon
    * @param key The GMPPrefs key value to use
    * @param val The value to set
    * @param addon The addon to scope the preference to
    */
   set: function(key, val, addon) {
-    let log = getScopedLogger("GMPPrefs.set");
+    let log = getScopedLogger("GMPInstallManager.jsm GMPPrefs.set");
     log.info("Setting pref: " + this._getPrefKey(key, addon) +
              " to value: " + val);
     return Preferences.set(this._getPrefKey(key, addon), val);
   },
   _getPrefKey: function(key, addon) {
     return  key.replace("{0}", addon || "");
   },
 
   /**
    * List of keys which can be used in get and set
    */
-  KEY_LOG_ENABLED: "media.gmp-manager.log",
   KEY_ADDON_ENABLED: "media.{0}.enabled",
   KEY_ADDON_LAST_UPDATE: "media.{0}.lastUpdate",
   KEY_ADDON_VERSION: "media.{0}.version",
   KEY_ADDON_AUTOUPDATE: "media.{0}.autoupdate",
+  KEY_ADDON_HIDDEN: "media.{0}.hidden",
   KEY_URL: "media.gmp-manager.url",
   KEY_URL_OVERRIDE: "media.gmp-manager.url.override",
   KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes",
   KEY_CERT_REQUIREBUILTIN: "media.gmp-manager.cert.requireBuiltIn",
   KEY_UPDATE_LAST_CHECK: "media.gmp-manager.lastCheck",
   KEY_UPDATE_SECONDS_BETWEEN_CHECKS: "media.gmp-manager.secondsBetweenChecks",
   KEY_APP_DISTRIBUTION: "distribution.id",
   KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
@@ -300,17 +294,17 @@ function GMPInstallManager() {
 /**
  * Temp file name used for downloading
  */
 GMPInstallManager.prototype = {
   /**
    * Obtains a URL with replacement of vars
    */
   _getURL: function() {
-    let log = getScopedLogger("_getURL");
+    let log = getScopedLogger("GMPInstallManager._getURL");
     // Use the override URL if it is specified.  The override URL is just like
     // the normal URL but it does not check the cert.
     let url = GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE);
     if (url) {
       log.info("Using override url: " + url);
     } else {
       url = GMPPrefs.get(GMPPrefs.KEY_URL);
       log.info("Using url: " + url);
@@ -342,33 +336,32 @@ GMPInstallManager.prototype = {
    * @return a promise which will be resolved or rejected.
    *         The promise is resolved with an array of GMPAddons
    *         The promise is rejected with an object with properties:
    *           target: The XHR request object
    *           status: The HTTP status code
    *           type: Sometimes specifies type of rejection
    */
   checkForAddons: function() {
-    let log = getScopedLogger("checkForAddons");
+    let log = getScopedLogger("GMPInstallManager.checkForAddons");
     if (this._deferred) {
         log.error("checkForAddons already called");
         return Promise.reject({type: "alreadycalled"});
     }
     this._deferred = Promise.defer();
     let url = this._getURL();
 
     this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
                     createInstance(Ci.nsISupports);
     // This is here to let unit test code override XHR
     if (this._request.wrappedJSObject) {
       this._request = this._request.wrappedJSObject;
     }
     this._request.open("GET", url, true);
-    let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS,
-                                        undefined, true);
+    let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true);
     this._request.channel.notificationCallbacks =
       new gCertUtils.BadCertHandler(allowNonBuiltIn);
     // Prevent the request from reading from the cache.
     this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
     // Prevent the request from writing to the cache.
     this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
     this._request.overrideMimeType("text/xml");
@@ -395,115 +388,143 @@ GMPInstallManager.prototype = {
    * Installs the specified addon and calls a callback when done.
    * @param gmpAddon The GMPAddon object to install
    * @return a promise which will be resolved or rejected
    *         The promise will resolve with an array of paths that were extracted
    *         The promise will reject with an error object:
    *           target: The XHR request object
    *           status: The HTTP status code
    *           type: A string to represent the type of error
-   *                 downloaderr, or verifyerr
+   *                 downloaderr, verifyerr or previouserrorencountered
    */
   installAddon: function(gmpAddon) {
     if (this._deferred) {
-        log.error("checkForAddons already called");
-        return Promise.reject({type: "alreadycalled"});
+        log.error("previous error encountered");
+        return Promise.reject({type: "previouserrorencountered"});
     }
     this.gmpDownloader = new GMPDownloader(gmpAddon);
     return this.gmpDownloader.start();
   },
   _getTimeSinceLastCheck: function() {
     let now = Math.round(Date.now() / 1000);
     // Default to 0 here because `now - 0` will be returned later if that case
     // is hit. We want a large value so a check will occur.
-    let lastCheck = GMPPrefs.get(GMPPrefs.KEY_UPDATE_LAST_CHECK,
-                                 undefined, 0);
+    let lastCheck = GMPPrefs.get(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
     // Handle clock jumps, return now since we want it to represent
     // a lot of time has passed since the last check.
     if (now < lastCheck) {
       return now;
     }
     return now - lastCheck;
   },
+  get _isEMEEnabled() {
+    return Preferences.get(EME_ENABLED, true);
+  },
+  _isAddonUpdateEnabled: function(aAddon) {
+    return GMPPrefs.get(GMPPrefs.KEY_ADDON_ENABLED, true, aAddon) &&
+           GMPPrefs.get(GMPPrefs.KEY_ADDON_AUTOUPDATE, true, aAddon);
+  },
   _updateLastCheck: function() {
     let now = Math.round(Date.now() / 1000);
     GMPPrefs.set(GMPPrefs.KEY_UPDATE_LAST_CHECK, now);
   },
   /**
    * Wrapper for checkForAddons and installAddon.
    * Will only install if not already installed and will log the results.
-   * This will only install/update the OpenH264 plugin
+   * This will only install/update the OpenH264 and EME plugins
+   * @return a promise which will be resolved if all addons could be installed
+   *         successfully, rejected otherwise.
    */
-  simpleCheckAndInstall: function() {
-    let log = getScopedLogger("simpleCheckAndInstall");
-
-    let autoUpdate = GMPPrefs.get(GMPPrefs.KEY_ADDON_ENABLED, OPEN_H264_ID,
-                                  true) &&
-                     GMPPrefs.get(GMPPrefs.KEY_ADDON_AUTOUPDATE, OPEN_H264_ID,
-                                  true);
-    if (!autoUpdate) {
-        log.info("Auto-update is off for openh264, aborting check.");
-        return Promise.resolve({status: "check-disabled"});
-    }
+  simpleCheckAndInstall: Task.async(function*() {
+    let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall");
 
     let secondsBetweenChecks =
-      GMPPrefs.get(GMPPrefs.KEY_UPDATE_SECONDS_BETWEEN_CHECKS, undefined,
+      GMPPrefs.get(GMPPrefs.KEY_UPDATE_SECONDS_BETWEEN_CHECKS,
                    DEFAULT_SECONDS_BETWEEN_CHECKS)
     let secondsSinceLast = this._getTimeSinceLastCheck();
     log.info("Last check was: " + secondsSinceLast +
              " seconds ago, minimum seconds: " + secondsBetweenChecks);
     if (secondsBetweenChecks > secondsSinceLast) {
       log.info("Will not check for updates.");
-      return Promise.resolve({status: "too-frequent-no-check"});
+      return {status: "too-frequent-no-check"};
     }
 
-    let deferred = Promise.defer();
-    let promise = this.checkForAddons();
-    promise.then(gmpAddons => {
+    try {
+      let gmpAddons = yield this.checkForAddons();
       this._updateLastCheck();
       log.info("Found " + gmpAddons.length + " addons advertised.");
-      let addonsToInstall = gmpAddons.filter(gmpAddon => {
+      let addonsToInstall = gmpAddons.filter(function(gmpAddon) {
         log.info("Found addon: " + gmpAddon.toString());
-        return gmpAddon.isValid && gmpAddon.isOpenH264 &&
-               !gmpAddon.isInstalled
-      });
+
+        if (gmpAddon.isHidden || !gmpAddon.isValid || gmpAddon.isInstalled) {
+          log.info("Addon hidden, invalid or already installed.");
+          return false;
+        }
+
+        // We're dealing with an EME GMP if the id starts with "gmp-eme-".
+        if (gmpAddon.id.indexOf("gmp-eme-") == 0 && !this._isEMEEnabled) {
+          log.info("Auto-update is off for all EME plugins, skipping check.");
+          return false;
+        }
+
+        let addonUpdateEnabled = false;
+        if (GMP_ADDONS.indexOf(gmpAddon.id) >= 0) {
+          addonUpdateEnabled = this._isAddonUpdateEnabled(gmpAddon.id);
+          if (!addonUpdateEnabled) {
+            log.info("Auto-update is off for " + gmpAddon.id +
+                     ", skipping check.");
+          }
+        } else {
+          // Currently, we only support installs of OpenH264 and EME plugins.
+          log.info("Auto-update is off for unknown plugin '" + gmpAddon.id +
+                   "', skipping check.");
+        }
+
+        return addonUpdateEnabled;
+      }, this);
+
       if (!addonsToInstall.length) {
         log.info("No new addons to install, returning");
-        return deferred.resolve({status: "nothing-new-to-install"});
+        return {status: "nothing-new-to-install"};
       }
-      // Only 1 addon will be returned because of the gmpAddon.isOpenH264
-      // check above.
-      addonsToInstall.forEach(gmpAddon => {
-        promise = this.installAddon(gmpAddon);
-        promise.then(extractedPaths => {
-          // installed!
-          log.info("Addon installed successfully: " + gmpAddon.toString());
-          return deferred.resolve({status: "addon-install"});
-        }, () => {
-          if (!GMPPrefs.get(GMPPrefs.KEY_LOG_ENABLED)) {
-            Cu.reportError(gmpAddon.toString() +
-                           " could not be installed. Enable " +
-                           GMPPrefs.KEY_LOG_ENABLED + " for details!");
-          }
-          log.error("Could not install addon: " + gmpAddon.toString());
-          deferred.reject();
-        });
-      });
-    }, () => {
-        log.error("Could not check for addons");
-        deferred.reject();
-    });
-    return deferred.promise;
-  },
+
+      let installResults = [];
+      let failureEncountered = false;
+      for (let addon of addonsToInstall) {
+        try {
+          yield this.installAddon(addon);
+          installResults.push({
+            id:     addon.id,
+            result: "succeeded",
+          });
+        } catch (e) {
+          failureEncountered = true;
+          installResults.push({
+            id:     addon.id,
+            result: "failed",
+          });
+        }
+      }
+      if (failureEncountered) {
+        throw {status:  "failed",
+               results: installResults};
+      }
+      return {status:  "succeeded",
+              results: installResults};
+    } catch(e) {
+      log.error("Could not check for addons", e);
+      throw e;
+    }
+  }),
 
   /**
    * Makes sure everything is cleaned up
    */
   uninit: function() {
-    let log = getScopedLogger("GMPDownloader.uninit");
+    let log = getScopedLogger("GMPInstallManager.uninit");
     if (this._request) {
       log.info("Aborting request");
       this._request.abort();
     }
     if (this._deferred) {
         log.info("Rejecting deferred");
         this._deferred.reject({type: "uninitialized"});
     }
@@ -516,27 +537,27 @@ GMPInstallManager.prototype = {
    */
   overrideLeaveDownloadedZip: false,
 
   /**
    * The XMLHttpRequest succeeded and the document was loaded.
    * @param event The nsIDOMEvent for the load
   */
   onLoadXML: function(event) {
-    let log = getScopedLogger("onLoadXML");
+    let log = getScopedLogger("GMPInstallManager.onLoadXML");
     try {
       log.info("request completed downloading document");
       let certs = null;
       if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
-          GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, undefined, true)) {
+          GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
         certs = gCertUtils.readCertPrefs(GMPPrefs.CERTS_BRANCH);
       }
 
       let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
-        undefined, true);
+                                          true);
       log.info("allowNonBuiltIn: " + allowNonBuiltIn);
 
       gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
 
       this.parseResponseXML();
     } catch (ex) {
       log.error("could not load xml: " + ex);
       this._deferred.reject({
@@ -547,17 +568,17 @@ GMPInstallManager.prototype = {
       delete this._deferred;
     }
   },
 
   /**
    * Returns the status code for the XMLHttpRequest
    */
   _getChannelStatus: function(request) {
-    let log = getScopedLogger("_getChannelStatus");
+    let log = getScopedLogger("GMPInstallManager._getChannelStatus");
     let status = null;
     try {
       status = request.status;
       log.info("request.status is: " + request.status);
     }
     catch (e) {
     }
 
@@ -570,37 +591,37 @@ GMPInstallManager.prototype = {
   /**
    * There was an error of some kind during the XMLHttpRequest.  This
    * error may have been caused by external factors (e.g. network
    * issues) or internally (by a timeout).
    *
    * @param event The nsIDOMEvent for the error
    */
   onFailXML: function(failure, event) {
-    let log = getScopedLogger(failure);
+    let log = getScopedLogger("GMPInstallManager.onFailXML " + failure);
     let request = event.target;
     let status = this._getChannelStatus(request);
-    let message = "request.status: " + status +  "(" + event.type + ")";
+    let message = "request.status: " + status +  " (" + event.type + ")";
     log.warn(message);
     this._deferred.reject({
       target: request,
       status: status,
       message: message
     });
     delete this._deferred;
   },
 
   /**
    * Returns an array of GMPAddon objects discovered by the update check.
    * Or returns an empty array if there were any problems with parsing.
    * If there's an error, it will be logged if logging is enabled.
    */
   parseResponseXML: function() {
     try {
-      let log = getScopedLogger("parseResponseXML");
+      let log = getScopedLogger("GMPInstallManager.parseResponseXML");
       let updatesElement = this._request.responseXML.documentElement;
       if (!updatesElement) {
         let message = "empty updates document";
         log.warn(message);
         this._deferred.reject({
           target: this._request,
           message: message
         });
@@ -626,18 +647,18 @@ GMPInstallManager.prototype = {
         let updatesChildElement = updatesElement.childNodes.item(i);
         if (updatesChildElement.nodeType != ELEMENT_NODE) {
           continue;
         }
         if (updatesChildElement.localName == "addons") {
           gmpResults = GMPAddon.parseGMPAddonsNode(updatesChildElement);
         }
       }
-       this._deferred.resolve(gmpResults);
-       delete this._deferred;
+      this._deferred.resolve(gmpResults);
+      delete this._deferred;
     } catch (e) {
       this._deferred.reject({
         target: this._request,
         message: e
       });
       delete this._deferred;
     }
   },
@@ -694,40 +715,35 @@ GMPAddon.parseGMPAddonsNode = function(a
 GMPAddon.prototype = {
   /**
    * Returns a string representation of the addon
    */
   toString: function() {
     return this.id + " (" +
            "isValid: " + this.isValid +
            ", isInstalled: " + this.isInstalled +
-           ", isOpenH264: " + this.isOpenH264 +
            ", hashFunction: " + this.hashFunction+
            ", hashValue: " + this.hashValue +
            (this.size !== undefined ? ", size: " + this.size : "" ) +
            ")";
   },
   /**
    * If all the fields aren't specified don't consider this addon valid
    * @return true if the addon is parsed and valid
    */
   get isValid() {
     return this.id && this.URL && this.version &&
       this.hashFunction && !!this.hashValue;
   },
-  /**
-   * Open H264 has special handling.
-   * @return true if the plugin is the openh264 plugin
-   */
-  get isOpenH264() {
-    return this.id === OPEN_H264_ID;
-  },
   get isInstalled() {
     return this.version &&
-      GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, this.id) === this.version;
+      GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, "", this.id) === this.version;
+  },
+  get isHidden() {
+    return GMPPrefs.get(GMPPrefs.KEY_ADDON_HIDDEN, false, this.id);
   }
 };
 /**
  * Constructs a GMPExtractor object which is used to extract a GMP zip
  * into the specified location. (Which typically leties per platform)
  * @param zipPath The path on disk of the zip file to extract
  */
 function GMPExtractor(zipPath, installToDirPath) {
--- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
+++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
@@ -10,172 +10,171 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm")
 
 do_get_profile();
 
 function run_test() {Cu.import("resource://gre/modules/Preferences.jsm")
-  Preferences.set("media.gmp-manager.log", true);
+  Preferences.set("media.gmp.log.dump", true);
+  Preferences.set("media.gmp.log.level", 0);
   run_next_test();
 }
 
 /**
  * Tests that the helper used for preferences works correctly
  */
-add_test(function test_prefs() {
+add_task(function* test_prefs() {
   let addon1 = "addon1", addon2 = "addon2";
 
-  GMPPrefs.set(GMPPrefs.KEY_LOG_ENABLED, true);
   GMPPrefs.set(GMPPrefs.KEY_URL, "http://not-really-used");
   GMPPrefs.set(GMPPrefs.KEY_URL_OVERRIDE, "http://not-really-used-2");
   GMPPrefs.set(GMPPrefs.KEY_ADDON_LAST_UPDATE, "1", addon1);
   GMPPrefs.set(GMPPrefs.KEY_ADDON_VERSION, "2", addon1);
   GMPPrefs.set(GMPPrefs.KEY_ADDON_LAST_UPDATE, "3", addon2);
   GMPPrefs.set(GMPPrefs.KEY_ADDON_VERSION, "4", addon2);
   GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, false, addon2);
   GMPPrefs.set(GMPPrefs.KEY_CERT_CHECKATTRS, true);
 
-  do_check_true(GMPPrefs.get(GMPPrefs.KEY_LOG_ENABLED));
   do_check_eq(GMPPrefs.get(GMPPrefs.KEY_URL), "http://not-really-used");
-  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE), "http://not-really-used-2");
-  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_LAST_UPDATE, addon1), "1");
-  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, addon1), "2");
-  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_LAST_UPDATE, addon2), "3");
-  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, addon2), "4");
-  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_AUTOUPDATE, addon2), false);
+  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_URL_OVERRIDE),
+              "http://not-really-used-2");
+  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_LAST_UPDATE, "", addon1), "1");
+  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, "", addon1), "2");
+  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_LAST_UPDATE, "", addon2), "3");
+  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, "", addon2), "4");
+  do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_AUTOUPDATE, undefined, addon2),
+              false);
   do_check_true(GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS));
   GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, true, addon2);
-  run_next_test();
 });
 
 /**
  * Tests that an uninit without a check works fine
  */
-add_test(function test_checkForAddons_noResponse() {
+add_task(function* test_checkForAddons_uninitWithoutCheck() {
   let installManager = new GMPInstallManager();
   installManager.uninit();
-  run_next_test();
 });
 
 /**
  * Tests that an uninit without an install works fine
  */
-add_test(function test_checkForAddons_noResponse() {
+add_test(function test_checkForAddons_uninitWithoutInstall() {
   overrideXHR(200, "");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
-    do_throw("no repsonse should reject");
-  }, function(err) {
-      installManager.uninit();
+  promise.then(() => {
+    do_throw("no response should reject");
+  }, err => {
+    do_check_true(!!err);
+    installManager.uninit();
+    run_next_test();
   });
-  run_next_test();
 });
 
 /**
  * Tests that no response returned rejects
  */
 add_test(function test_checkForAddons_noResponse() {
   overrideXHR(200, "");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
-    do_throw("no repsonse should reject");
-  }, function(err) {
+  promise.then(() => {
+    do_throw("no response should reject");
+  }, err => {
     do_check_true(!!err);
     installManager.uninit();
     run_next_test();
   });
 });
 
 /**
  * Tests that no addons element returned resolves with no addons
  */
-add_task(function test_checkForAddons_noAddonsElement() {
+add_task(function* test_checkForAddons_noAddonsElement() {
   overrideXHR(200, "<updates></updates>");
   let installManager = new GMPInstallManager();
   let gmpAddons = yield installManager.checkForAddons();
   do_check_eq(gmpAddons.length, 0);
   installManager.uninit();
 });
 
 /**
  * Tests that empty addons element returned resolves with no addons
  */
-add_task(function test_checkForAddons_noAddonsElement() {
+add_task(function* test_checkForAddons_emptyAddonsElement() {
   overrideXHR(200, "<updates><addons/></updates>");
   let installManager = new GMPInstallManager();
   let gmpAddons = yield installManager.checkForAddons();
   do_check_eq(gmpAddons.length, 0);
   installManager.uninit();
 });
 
 /**
  * Tests that a response with the wrong root element rejects
  */
 add_test(function test_checkForAddons_wrongResponseXML() {
   overrideXHR(200, "<digits_of_pi>3.141592653589793....</digits_of_pi>");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function(err, gmpAddons) {
+  promise.then(() => {
     do_throw("response with the wrong root element should reject");
-  }, function(err) {
+  }, err => {
     do_check_true(!!err);
     installManager.uninit();
     run_next_test();
   });
 });
 
-
 /**
  * Tests that a 404 error works as expected
  */
 add_test(function test_checkForAddons_404Error() {
   overrideXHR(404, "");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
+  promise.then(() => {
     do_throw("404 response should reject");
-  }, function(err) {
+  }, err => {
     do_check_true(!!err);
     do_check_eq(err.status, 404);
     installManager.uninit();
     run_next_test();
   });
 });
 
 /**
  * Tests that a xhr abort() works as expected
  */
 add_test(function test_checkForAddons_abort() {
   let xhr = overrideXHR(200, "", { dropRequest: true} );
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
   xhr.abort();
-  promise.then(function() {
+  promise.then(() => {
     do_throw("abort() should reject");
-  }, function(err) {
+  }, err => {
     do_check_eq(err.status, 0);
     installManager.uninit();
     run_next_test();
   });
 });
 
 /**
  * Tests that a defensive timeout works as expected
  */
 add_test(function test_checkForAddons_timeout() {
   overrideXHR(200, "", { dropRequest: true, timeout: true });
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function() {
+  promise.then(() => {
     do_throw("Defensive timeout should reject");
-  }, function(err) {
+  }, err => {
     do_check_eq(err.status, 0);
     installManager.uninit();
     run_next_test();
   });
 });
 
 /**
  * Tests that we throw correctly in case of ssl certification error.
@@ -192,20 +191,21 @@ add_test(function test_checkForAddons_ba
   let PREF_CERTS_BRANCH_DOT_ONE_BACKUP =
     Preferences.get(CERTS_BRANCH_DOT_ONE, undefined);
   Services.prefs.setCharPref(CERTS_BRANCH_DOT_ONE, "funky value");
 
 
   overrideXHR(200, "");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function() {
+  promise.then(() => {
     do_throw("Defensive timeout should reject");
-  }, function(err) {
-    do_check_true(err.message.contains("SSL is required and URI scheme is not https."));
+  }, err => {
+    do_check_true(err.message.contains("SSL is required and URI scheme is " +
+                                       "not https."));
     installManager.uninit();
     if (PREF_KEY_URL_OVERRIDE_BACKUP) {
       Preferences.set(GMPPrefs.KEY_URL_OVERRIDE,
         PREF_KEY_URL_OVERRIDE_BACKUP);
     }
     if (PREF_CERTS_BRANCH_DOT_ONE_BACKUP) {
       Preferences.set(CERTS_BRANCH_DOT_ONE,
         PREF_CERTS_BRANCH_DOT_ONE_BACKUP);
@@ -216,106 +216,94 @@ add_test(function test_checkForAddons_ba
 
 /**
  * Tests that gettinga a funky non XML response works as expected
  */
 add_test(function test_checkForAddons_notXML() {
   overrideXHR(200, "3.141592653589793....");
   let installManager = new GMPInstallManager();
   let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
+  promise.then(() => {
     do_throw("non XML response should reject");
-  }, function(err) {
+  }, err => {
     do_check_true(!!err);
     installManager.uninit();
     run_next_test();
   });
 });
 
 /**
  * Tests that getting a response with a single addon works as expected
  */
-add_test(function test_checkForAddons_singleAddonNoUpdates() {
+add_task(function* test_checkForAddons_singleAddon() {
   let responseXML =
     "<?xml version=\"1.0\"?>" +
     "<updates>" +
     "    <addons>" +
     "        <addon id=\"gmp-gmpopenh264\"" +
     "               URL=\"http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip\"" +
     "               hashFunction=\"sha256\"" +
     "               hashValue=\"1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee\"" +
     "               version=\"1.1\"/>" +
     "  </addons>" +
     "</updates>"
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
-  let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
-    do_check_eq(gmpAddons.length, 1);
-    let gmpAddon= gmpAddons[0];
-    do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
-    do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
-    do_check_eq(gmpAddon.hashFunction, "sha256");
-    do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
-    do_check_eq(gmpAddon.version, "1.1");
-    do_check_eq(gmpAddon.size, undefined);
-    do_check_true(gmpAddon.isValid);
-    do_check_true(gmpAddon.isOpenH264);
-    do_check_false(gmpAddon.isInstalled);
-    installManager.uninit();
-    run_next_test();
-  }, function(err) {
-    do_throw("1 addon found should not reject");
-  });
+  let gmpAddons = yield installManager.checkForAddons();
+  do_check_eq(gmpAddons.length, 1);
+  let gmpAddon= gmpAddons[0];
+  do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
+  do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
+  do_check_eq(gmpAddon.hashFunction, "sha256");
+  do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
+  do_check_eq(gmpAddon.version, "1.1");
+  do_check_eq(gmpAddon.size, undefined);
+  do_check_true(gmpAddon.isValid);
+  do_check_false(gmpAddon.isInstalled);
+  installManager.uninit();
 });
 
 /**
  * Tests that getting a response with a single addon with the optional size
  * attribute parses as expected.
  */
-add_test(function test_checkForAddons_singleAddonNoUpdates() {
+add_task(function* test_checkForAddons_singleAddonWithSize() {
   let responseXML =
     "<?xml version=\"1.0\"?>" +
     "<updates>" +
     "    <addons>" +
     "        <addon id=\"openh264-plugin-no-at-symbol\"" +
     "               URL=\"http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip\"" +
     "               hashFunction=\"sha256\"" +
     "               size=\"42\"" +
     "               hashValue=\"1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee\"" +
     "               version=\"1.1\"/>" +
     "  </addons>" +
     "</updates>"
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
-  let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
-    do_check_eq(gmpAddons.length, 1);
-    let gmpAddon= gmpAddons[0];
-    do_check_eq(gmpAddon.id, "openh264-plugin-no-at-symbol");
-    do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
-    do_check_eq(gmpAddon.hashFunction, "sha256");
-    do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
-    do_check_eq(gmpAddon.size, 42);
-    do_check_eq(gmpAddon.version, "1.1");
-    do_check_true(gmpAddon.isValid);
-    do_check_false(gmpAddon.isOpenH264);
-    do_check_false(gmpAddon.isInstalled);
-    installManager.uninit();
-    run_next_test();
-  }, function(err) {
-    do_throw("1 addon found should not reject");
-  });
+  let gmpAddons = yield installManager.checkForAddons();
+  do_check_eq(gmpAddons.length, 1);
+  let gmpAddon = gmpAddons[0];
+  do_check_eq(gmpAddon.id, "openh264-plugin-no-at-symbol");
+  do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
+  do_check_eq(gmpAddon.hashFunction, "sha256");
+  do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
+  do_check_eq(gmpAddon.size, 42);
+  do_check_eq(gmpAddon.version, "1.1");
+  do_check_true(gmpAddon.isValid);
+  do_check_false(gmpAddon.isInstalled);
+  installManager.uninit();
 });
 
 /**
  * Tests that checking for multiple addons work correctly.
  * Also tests that invalid addons work correctly.
  */
-add_test(function test_checkForAddons_multipleAddonNoUpdatesSomeInvalid() {
+add_task(function* test_checkForAddons_multipleAddonNoUpdatesSomeInvalid() {
   let responseXML =
     "<?xml version=\"1.0\"?>" +
     "<updates>" +
     "    <addons>" +
     // valid openh264
     "        <addon id=\"gmp-gmpopenh264\"" +
     "               URL=\"http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip\"" +
     "               hashFunction=\"sha256\"" +
@@ -356,114 +344,101 @@ add_test(function test_checkForAddons_mu
     "               URL=\"http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip\"" +
     "               hashFunction=\"sha512\"" +
     "               hashValue=\"141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee\"" +
     "               notversion=\"9.1\"/>" +
     "  </addons>" +
     "</updates>"
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
-  let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
-    do_check_eq(gmpAddons.length, 7);
-    let gmpAddon= gmpAddons[0];
-    do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
-    do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
-    do_check_eq(gmpAddon.hashFunction, "sha256");
-    do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
-    do_check_eq(gmpAddon.version, "1.1");
-    do_check_true(gmpAddon.isValid);
-    do_check_true(gmpAddon.isOpenH264);
-    do_check_false(gmpAddon.isInstalled);
+  let gmpAddons = yield installManager.checkForAddons();
+  do_check_eq(gmpAddons.length, 7);
+  let gmpAddon= gmpAddons[0];
+  do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
+  do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
+  do_check_eq(gmpAddon.hashFunction, "sha256");
+  do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
+  do_check_eq(gmpAddon.version, "1.1");
+  do_check_true(gmpAddon.isValid);
+  do_check_false(gmpAddon.isInstalled);
 
-    gmpAddon= gmpAddons[1];
-    do_check_eq(gmpAddon.id, "NOT-gmp-gmpopenh264");
-    do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip");
-    do_check_eq(gmpAddon.hashFunction, "sha512");
-    do_check_eq(gmpAddon.hashValue, "141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
-    do_check_eq(gmpAddon.version, "9.1");
-    do_check_true(gmpAddon.isValid);
-    do_check_false(gmpAddon.isOpenH264);
-    do_check_false(gmpAddon.isInstalled);
+  gmpAddon= gmpAddons[1];
+  do_check_eq(gmpAddon.id, "NOT-gmp-gmpopenh264");
+  do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip");
+  do_check_eq(gmpAddon.hashFunction, "sha512");
+  do_check_eq(gmpAddon.hashValue, "141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
+  do_check_eq(gmpAddon.version, "9.1");
+  do_check_true(gmpAddon.isValid);
+  do_check_false(gmpAddon.isInstalled);
 
-    for (let i = 2; i < gmpAddons.length; i++) {
-      do_check_false(gmpAddons[i].isValid);
-      do_check_false(gmpAddons[i].isInstalled);
-    }
-    installManager.uninit();
-    run_next_test();
-  }, function(err) {
-    do_throw("multiple addons found should not reject");
-  });
+  for (let i = 2; i < gmpAddons.length; i++) {
+    do_check_false(gmpAddons[i].isValid);
+    do_check_false(gmpAddons[i].isInstalled);
+  }
+  installManager.uninit();
 });
 
 /**
  * Tests that checking for addons when there are also updates available
  * works as expected.
  */
-add_test(function test_checkForAddons_updatesWithAddons() {
+add_task(function* test_checkForAddons_updatesWithAddons() {
   let responseXML =
     "<?xml version=\"1.0\"?>" +
     "    <updates>" +
     "        <update type=\"minor\" displayVersion=\"33.0a1\" appVersion=\"33.0a1\" platformVersion=\"33.0a1\" buildID=\"20140628030201\">" +
     "        <patch type=\"complete\" URL=\"http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/2014/06/2014-06-28-03-02-01-mozilla-central/firefox-33.0a1.en-US.mac.complete.mar\" hashFunction=\"sha512\" hashValue=\"f3f90d71dff03ae81def80e64bba3e4569da99c9e15269f731c2b167c4fc30b3aed9f5fee81c19614120230ca333e73a5e7def1b8e45d03135b2069c26736219\" size=\"85249896\"/>" +
     "    </update>" +
     "    <addons>" +
     "        <addon id=\"gmp-gmpopenh264\"" +
     "               URL=\"http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip\"" +
     "               hashFunction=\"sha256\"" +
     "               hashValue=\"1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee\"" +
     "               version=\"1.1\"/>" +
     "  </addons>" +
     "</updates>"
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
-  let promise = installManager.checkForAddons();
-  promise.then(function(gmpAddons) {
-    do_check_eq(gmpAddons.length, 1);
-    let gmpAddon= gmpAddons[0];
-    do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
-    do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
-    do_check_eq(gmpAddon.hashFunction, "sha256");
-    do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
-    do_check_eq(gmpAddon.version, "1.1");
-    do_check_true(gmpAddon.isValid);
-    do_check_true(gmpAddon.isOpenH264);
-    do_check_false(gmpAddon.isInstalled);
-    installManager.uninit();
-    run_next_test();
-  }, function(err) {
-    do_throw("updates with addons should not reject");
-  });
+  let gmpAddons = yield installManager.checkForAddons();
+  do_check_eq(gmpAddons.length, 1);
+  let gmpAddon= gmpAddons[0];
+  do_check_eq(gmpAddon.id, "gmp-gmpopenh264");
+  do_check_eq(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip");
+  do_check_eq(gmpAddon.hashFunction, "sha256");
+  do_check_eq(gmpAddon.hashValue, "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee");
+  do_check_eq(gmpAddon.version, "1.1");
+  do_check_true(gmpAddon.isValid);
+  do_check_false(gmpAddon.isInstalled);
+  installManager.uninit();
 });
 
 /**
  * Tests that installing found addons works as expected
  */
-function test_checkForAddons_installAddon(id, includeSize,wantInstallReject) {
-  do_print("Running installAddon for includeSize: " + includeSize +
+function* test_checkForAddons_installAddon(id, includeSize, wantInstallReject) {
+  do_print("Running installAddon for id: " + id +
+           ", includeSize: " + includeSize +
            " and wantInstallReject: " + wantInstallReject);
   let httpServer = new HttpServer();
   let dir = FileUtils.getDir("TmpD", [], true);
   httpServer.registerDirectory("/", dir);
   httpServer.start(-1);
   let testserverPort = httpServer.identity.primaryPort;
   let zipFileName = "test_" + id + "_GMP.zip";
 
   let zipURL = URL_HOST + ":" + testserverPort + "/" + zipFileName;
   do_print("zipURL: " + zipURL);
 
   let data = "e~=0.5772156649";
   let zipFile = createNewZipFile(zipFileName, data);
   let hashFunc = "sha256";
   let expectedDigest = yield GMPDownloader.computeHash(hashFunc, zipFile);
-
-  let fileSize = zipFile.size;
+  let fileSize = zipFile.fileSize;
   if (wantInstallReject) {
-      fileSize = 1;
+    fileSize = 1;
   }
 
   let responseXML =
     "<?xml version=\"1.0\"?>" +
     "<updates>" +
     "    <addons>" +
     "        <addon id=\"" + id + "-gmp-gmpopenh264\"" +
     "               URL=\"" + zipURL + "\"" +
@@ -471,123 +446,133 @@ function test_checkForAddons_installAddo
     "               hashValue=\"" + expectedDigest + "\"" +
     (includeSize ? " size=\"" + fileSize + "\"" : "") +
     "               version=\"1.1\"/>" +
     "  </addons>" +
     "</updates>"
 
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
-  let checkPromise = installManager.checkForAddons();
-  checkPromise.then(function(gmpAddons) {
-    do_check_eq(gmpAddons.length, 1);
-    let gmpAddon = gmpAddons[0];
-    do_check_false(gmpAddon.isInstalled);
+  let gmpAddons = yield installManager.checkForAddons();
+  do_check_eq(gmpAddons.length, 1);
+  let gmpAddon = gmpAddons[0];
+  do_check_false(gmpAddon.isInstalled);
 
-    GMPInstallManager.overrideLeaveDownloadedZip = true;
-    let installPromise = installManager.installAddon(gmpAddon);
-    installPromise.then(function(extractedPaths) {
-      if (wantInstallReject) {
-        do_throw("install update should reject");
-      }
-      do_check_eq(extractedPaths.length, 1);
-      let extractedPath = extractedPaths[0];
+  GMPInstallManager.overrideLeaveDownloadedZip = true;
+  try {
+    let extractedPaths = yield installManager.installAddon(gmpAddon);
+    if (wantInstallReject) {
+      do_check_true(false); // installAddon() should have thrown.
+    }
+    do_check_eq(extractedPaths.length, 1);
+    let extractedPath = extractedPaths[0];
 
-      do_print("Extracted path: " + extractedPath);
+    do_print("Extracted path: " + extractedPath);
 
-      let extractedFile = Cc["@mozilla.org/file/local;1"].
-                          createInstance(Ci.nsIFile);
-      extractedFile.initWithPath(extractedPath);
-      do_check_true(extractedFile.exists());
-      let readData = readStringFromFile(extractedFile);
-      do_check_eq(readData, data);
+    let extractedFile = Cc["@mozilla.org/file/local;1"].
+                        createInstance(Ci.nsIFile);
+    extractedFile.initWithPath(extractedPath);
+    do_check_true(extractedFile.exists());
+    let readData = readStringFromFile(extractedFile);
+    do_check_eq(readData, data);
 
-      // Check that the downloaded zip matches the offered zip exactly
-      let downloadedGMPFile = FileUtils.getFile("TmpD",
-        [gmpAddon.id + ".zip"]);
-      do_check_true(downloadedGMPFile.exists());
-      let downloadedBytes = getBinaryFileData(downloadedGMPFile);
-      let sourceBytes = getBinaryFileData(zipFile);
-      do_check_true(compareBinaryData(downloadedBytes, sourceBytes));
+    // Check that the downloaded zip matches the offered zip exactly
+    let downloadedGMPFile = FileUtils.getFile("TmpD",
+      [gmpAddon.id + ".zip"]);
+    do_check_true(downloadedGMPFile.exists());
+    let downloadedBytes = getBinaryFileData(downloadedGMPFile);
+    let sourceBytes = getBinaryFileData(zipFile);
+    do_check_true(compareBinaryData(downloadedBytes, sourceBytes));
 
-      // Make sure the prefs are set correctly
-      do_check_true(!!GMPPrefs.get(GMPPrefs.KEY_ADDON_LAST_UPDATE,
-                                   gmpAddon.id, ""));
-      do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, gmpAddon.id, ""),
-                               "1.1");
-      // Make sure it reports as being installed
-      do_check_true(gmpAddon.isInstalled);
+    // Make sure the prefs are set correctly
+    do_check_true(!!GMPPrefs.get(GMPPrefs.KEY_ADDON_LAST_UPDATE, "",
+                                 gmpAddon.id));
+    do_check_eq(GMPPrefs.get(GMPPrefs.KEY_ADDON_VERSION, "", gmpAddon.id),
+                             "1.1");
+    // Make sure it reports as being installed
+    do_check_true(gmpAddon.isInstalled);
 
-      // Cleanup
-      extractedFile.parent.remove(true);
-      zipFile.remove(false);
-      httpServer.stop(function() {});
-      do_print("Removing downloaded GMP file: " + downloadedGMPFile.path);
-      downloadedGMPFile.remove(false);
-      installManager.uninit();
-    }, function(err) {
-      zipFile.remove(false);
-      let downloadedGMPFile = FileUtils.getFile("TmpD",
-        [gmpAddon.id + ".zip"]);
-      do_print("Removing from err downloaded GMP file: " +
-               downloadedGMPFile.path);
-      downloadedGMPFile.remove(false);
-      if (!wantInstallReject) {
-        do_throw("install update should not reject");
-      }
-    });
-  }, function(err) {
-    do_throw("checking updates to install them should not reject");
-  });
+    // Cleanup
+    extractedFile.parent.remove(true);
+    zipFile.remove(false);
+    httpServer.stop(function() {});
+    do_print("Removing downloaded GMP file: " + downloadedGMPFile.path);
+    downloadedGMPFile.remove(false);
+    installManager.uninit();
+  } catch(ex) {
+    zipFile.remove(false);
+    let downloadedGMPFile = FileUtils.getFile("TmpD",
+      [gmpAddon.id + ".zip"]);
+    do_print("Removing downloaded GMP file from exception handler: " +
+             downloadedGMPFile.path);
+    downloadedGMPFile.remove(false);
+    if (!wantInstallReject) {
+      do_throw("install update should not reject");
+    }
+  }
 }
 
 add_task(test_checkForAddons_installAddon.bind(null, "1", true, false));
 add_task(test_checkForAddons_installAddon.bind(null, "2", false, false));
 add_task(test_checkForAddons_installAddon.bind(null, "3", true, true));
 
 /**
- * Tests simpleCheckAndInstall autoupdate disabled
+ * Tests simpleCheckAndInstall when autoupdate is disabled for a GMP
  */
-add_task(function test_simpleCheckAndInstall() {
-    GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, false, OPEN_H264_ID);
-    let installManager = new GMPInstallManager();
-    let promise = installManager.simpleCheckAndInstall();
-    promise.then((result) => {
-        do_check_eq(result.status, "check-disabled");
-    }, () => {
-        do_throw("simple check should not reject");
-    });
+add_task(function* test_simpleCheckAndInstall_autoUpdateDisabled() {
+  GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, false, OPEN_H264_ID);
+  let responseXML =
+    "<?xml version=\"1.0\"?>" +
+    "<updates>" +
+    "    <addons>" +
+    // valid openh264
+    "        <addon id=\"gmp-gmpopenh264\"" +
+    "               URL=\"http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip\"" +
+    "               hashFunction=\"sha256\"" +
+    "               hashValue=\"1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee\"" +
+    "               version=\"1.1\"/>" +
+    "  </addons>" +
+    "</updates>"
+
+  overrideXHR(200, responseXML);
+  let installManager = new GMPInstallManager();
+  let result = yield installManager.simpleCheckAndInstall();
+  do_check_eq(result.status, "nothing-new-to-install");
+  Preferences.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK);
+  GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, true, OPEN_H264_ID);
 });
 
 /**
  * Tests simpleCheckAndInstall nothing to install
  */
-add_task(function test_simpleCheckAndInstall() {
-    GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, true, OPEN_H264_ID);
-    let installManager = new GMPInstallManager();
-    let promise = installManager.simpleCheckAndInstall();
-    promise.then((result) => {
-        do_check_eq(result.status, "nothing-new-to-install");
-    }, () => {
-        do_throw("simple check should not reject");
-    });
+add_task(function* test_simpleCheckAndInstall_nothingToInstall() {
+  let responseXML =
+    "<?xml version=\"1.0\"?>" +
+    "<updates>" +
+    "</updates>"
+
+  overrideXHR(200, responseXML);
+  let installManager = new GMPInstallManager();
+  let result = yield installManager.simpleCheckAndInstall();
+  do_check_eq(result.status, "nothing-new-to-install");
 });
 
 /**
  * Tests simpleCheckAndInstall too frequent
  */
-add_task(function test_simpleCheckAndInstall() {
-    GMPPrefs.set(GMPPrefs.KEY_ADDON_AUTOUPDATE, true, OPEN_H264_ID);
-    let installManager = new GMPInstallManager();
-    let promise = installManager.simpleCheckAndInstall();
-    promise.then((result) => {
-        do_check_eq(result.status, "too-frequent-no-check");
-    }, () => {
-        do_throw("simple check should not reject");
-    });
+add_task(function* test_simpleCheckAndInstall_tooFrequent() {
+  let responseXML =
+    "<?xml version=\"1.0\"?>" +
+    "<updates>" +
+    "</updates>"
+
+  overrideXHR(200, responseXML);
+  let installManager = new GMPInstallManager();
+  let result = yield installManager.simpleCheckAndInstall();
+  do_check_eq(result.status, "too-frequent-no-check");
 });
 
 /**
  * Tests that installing addons when there is no server works as expected
  */
 add_test(function test_installAddon_noServer() {
   let dir = FileUtils.getDir("TmpD", [], true);
   let zipFileName = "test_GMP.zip";
@@ -606,31 +591,31 @@ add_test(function test_installAddon_noSe
     "               hashValue=\"11221cbda000347b054028b527a60e578f919cb10f322ef8077d3491c6fcb474\"" +
     "               version=\"1.1\"/>" +
     "  </addons>" +
     "</updates>"
 
   overrideXHR(200, responseXML);
   let installManager = new GMPInstallManager();
   let checkPromise = installManager.checkForAddons();
-  checkPromise.then(function(gmpAddons) {
+  checkPromise.then(gmpAddons => {
     do_check_eq(gmpAddons.length, 1);
     let gmpAddon= gmpAddons[0];
 
     GMPInstallManager.overrideLeaveDownloadedZip = true;
     let installPromise = installManager.installAddon(gmpAddon);
-    installPromise.then(function(extractedPaths) {
-        do_throw("No server for install should reject");
-    }, function(err) {
+    installPromise.then(extractedPaths => {
+      do_throw("No server for install should reject");
+    }, err => {
       do_check_true(!!err);
       installManager.uninit();
       run_next_test();
     });
-  }, function(err) {
-    do_throw("check should not reject for installn o server");
+  }, () => {
+    do_throw("check should not reject for install no server");
   });
 });
 
 /**
  * Returns the read stream into a string
  */
 function readStringFromInputStream(inputStream) {
   let sis = Cc["@mozilla.org/scriptableinputstream;1"].
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -48,18 +48,16 @@ const SEARCH_SCORE_MATCH_SUBSTRING = 3;
 
 const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
 const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl";
 
 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
 
 const VIEW_DEFAULT = "addons://discover/";
 
-const OPENH264_ADDON_ID = "gmp-gmpopenh264";
-
 var gStrings = {};
 XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
                                    "@mozilla.org/intl/stringbundle;1",
                                    "nsIStringBundleService");
 
 XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() {
   return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
 });
@@ -1028,18 +1026,21 @@ var gViewController = {
         let remoteEnabled = Services.prefs.
                             getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED);
         return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled;
       }
     },
 
     cmd_showItemPreferences: {
       isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
-        if (!aAddon || (!aAddon.isActive && aAddon.id != OPENH264_ADDON_ID) || !aAddon.optionsURL)
+        if (!aAddon ||
+            (!aAddon.isActive && !aAddon.isGMPlugin) ||
+            !aAddon.optionsURL) {
           return false;
+        }
         if (gViewController.currentViewObj == gDetailView &&
             aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
           return false;
         }
         if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO)
           return false;
         return true;
       },
@@ -2793,23 +2794,24 @@ var gDetailView = {
       screenshot.hidden = true;
     }
 
     var desc = document.getElementById("detail-desc");
     desc.textContent = aAddon.description;
 
     var fullDesc = document.getElementById("detail-fulldesc");
     if (aAddon.fullDescription) {
-      // The following is part of an awful hack to include the OpenH264 license
-      // without having bug 624602 fixed yet, and intentionally ignores
+      // The following is part of an awful hack to include the licenses for GMP
+      // plugins without having bug 624602 fixed yet, and intentionally ignores
       // localisation.
-      if (aAddon.id == OPENH264_ADDON_ID)
+      if (aAddon.isGMPlugin) {
         fullDesc.innerHTML = aAddon.fullDescription;
-      else
+      } else {
         fullDesc.textContent = aAddon.fullDescription;
+      }
 
       fullDesc.hidden = false;
     } else {
       fullDesc.hidden = true;
     }
 
     var contributions = document.getElementById("detail-contributions");
     if ("contributionURL" in aAddon && aAddon.contributionURL) {
@@ -3106,17 +3108,17 @@ var gDetailView = {
         document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
           "details.notification.vulnerableNoUpdate",
           [this._addon.name], 1
         );
         var errorLink = document.getElementById("detail-error-link");
         errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
         errorLink.href = this._addon.blocklistURL;
         errorLink.hidden = false;
-      } else if (this._addon.id == OPENH264_ADDON_ID && !this._addon.isInstalled) {
+      } else if (this._addon.isGMPlugin && !this._addon.isInstalled) {
         this.node.setAttribute("notification", "warning");
         let warning = document.getElementById("detail-warning");
         warning.textContent = gStrings.ext.GetStringFromName("details.notification.openH264Pending");
       } else {
         this.node.removeAttribute("notification");
       }
     }
 
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -1289,17 +1289,17 @@
               this.setAttribute("notification", "error");
               this._error.textContent = gStrings.ext.formatStringFromName(
                 "notification.vulnerableNoUpdate",
                 [this.mAddon.name], 1
               );
               this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
               this._errorLink.href = this.mAddon.blocklistURL;
               this._errorLink.hidden = false;
-            } else if (this.mAddon.id == "gmp-gmpopenh264" && !this.mAddon.isInstalled) {
+            } else if (this.mAddon.isGMPlugin && !this.mAddon.isInstalled) {
               this.setAttribute("notification", "warning");
               this._warning.textContent = gStrings.ext.GetStringFromName("notification.openH264Pending");
             } else {
               this.removeAttribute("notification");
             }
           }
 
           this._preferencesBtn.hidden = (!this.mAddon.optionsURL) ||
rename from toolkit/mozapps/extensions/content/openH264Prefs.xul
rename to toolkit/mozapps/extensions/content/gmpPrefs.xul
--- a/toolkit/mozapps/extensions/content/openH264Prefs.xul
+++ b/toolkit/mozapps/extensions/content/gmpPrefs.xul
@@ -1,8 +1,8 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
 - License, v. 2.0. If a copy of the MPL was not distributed with this file,
 - You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<!-- This is intentionally empty and a dummy to let the OpenH264 provider
+<!-- This is intentionally empty and a dummy to let the GMPProvider
      have a preferences button in the list view. -->
--- a/toolkit/mozapps/extensions/extensions.manifest
+++ b/toolkit/mozapps/extensions/extensions.manifest
@@ -11,10 +11,10 @@ contract @mozilla.org/uriloader/content-
 component {0f38e086-89a3-40a5-8ffc-9b694de1d04a} amWebInstallListener.js
 contract @mozilla.org/addons/web-install-listener;1 {0f38e086-89a3-40a5-8ffc-9b694de1d04a}
 component {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} amInstallTrigger.js
 contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fddf1}
 category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
 #ifndef MOZ_WIDGET_ANDROID
 category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
 #endif
-category addon-provider-module OpenH264Provider resource://gre/modules/addons/OpenH264Provider.jsm
+category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.jsm
 #endif
rename from toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
rename to toolkit/mozapps/extensions/internal/GMPProvider.jsm
--- a/toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -13,109 +13,215 @@ this.EXPORTED_SYMBOLS = [];
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
-                                  "resource://gre/modules/GMPInstallManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(
+  this, "GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm");
 
-const URI_EXTENSION_STRINGS    = "chrome://mozapps/locale/extensions/extensions.properties";
-const STRING_TYPE_NAME         = "type.%ID%.name";
+const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME       = "type.%ID%.name";
 
-const SEC_IN_A_DAY              = 24 * 60 * 60;
+const SEC_IN_A_DAY           = 24 * 60 * 60;
 
-const EME_PREF_ENABLED         = "media.eme.enabled";
-const NS_GRE_BIN_DIR           = "GreBinD";
-const CLEARKEY_PLUGIN_ID       = "gmp-clearkey";
-const CLEARKEY_VERSION         = "0.1";
+const NS_GRE_BIN_DIR         = "GreD";
+const CLEARKEY_PLUGIN_ID     = "gmp-clearkey";
+const CLEARKEY_VERSION       = "0.1";
 
-const OPENH264_PLUGIN_ID       = "gmp-gmpopenh264";
-const OPENH264_PREF_BRANCH     = "media." + OPENH264_PLUGIN_ID + ".";
-const OPENH264_PREF_ENABLED    = "enabled";
-const OPENH264_PREF_VERSION    = "version";
-const OPENH264_PREF_LASTUPDATE = "lastUpdate";
-const OPENH264_PREF_AUTOUPDATE = "autoupdate";
-const OPENH264_PREF_PROVIDERENABLED = "provider.enabled";
-const OPENH264_PREF_LOGGING    = "provider.logging";
-const OPENH264_PREF_LOGGING_LEVEL = OPENH264_PREF_LOGGING + ".level"; // media.gmp-gmpopenh264.provider.logging.level
-const OPENH264_PREF_LOGGING_DUMP = OPENH264_PREF_LOGGING + ".dump"; // media.gmp-gmpopenh264.provider.logging.dump
-const OPENH264_HOMEPAGE_URL    = "http://www.openh264.org/";
-const OPENH264_OPTIONS_URL     = "chrome://mozapps/content/extensions/openH264Prefs.xul";
+/**
+ * Keys which can be used via GMPPrefs.
+ */
+const KEY_PROVIDER_ENABLED   = "media.gmp-provider.enabled";
+const KEY_PROVIDER_LASTCHECK = "media.gmp-manager.lastCheck";
+const KEY_LOG_BASE           = "media.gmp.log.";
+const KEY_LOGGING_LEVEL      = KEY_LOG_BASE + "level";
+const KEY_LOGGING_DUMP       = KEY_LOG_BASE + "dump";
+const KEY_EME_ENABLED        = "media.eme.enabled"; // Global pref to enable/disable all EME plugins
+const KEY_PLUGIN_ENABLED     = "media.{0}.enabled";
+const KEY_PLUGIN_LAST_UPDATE = "media.{0}.lastUpdate";
+const KEY_PLUGIN_VERSION     = "media.{0}.version";
+const KEY_PLUGIN_AUTOUPDATE  = "media.{0}.autoupdate";
+const KEY_PLUGIN_HIDDEN      = "media.{0}.hidden";
 
-const GMP_PREF_LASTCHECK       = "media.gmp-manager.lastCheck";
-
-// The following is part of an awful hack to include the OpenH264 license
-// without having bug 624602 fixed yet, and intentionally ignores localisation.
-const OPENH264_FULLDESCRIPTION = "<xhtml:a href=\"chrome://mozapps/content/extensions/OpenH264-license.txt\" target=\"_blank\">License information</xhtml:a>.";
+// Note regarding the value of |fullDescription| below: This is part of an awful
+// hack to include the licenses for GMP plugins without having bug 624602 fixed
+// yet, and intentionally ignores localisation.
+const GMP_PLUGINS = [
+  {
+    id:              "gmp-gmpopenh264",
+    name:            "openH264_name",
+    description:     "openH264_description",
+    fullDescription: "<xhtml:a href=\"chrome://mozapps/content/extensions/OpenH264-license.txt\" target=\"_blank\">License information</xhtml:a>.",
+    homepageURL:     "http://www.openh264.org/",
+    optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul"
+  },
+  {
+    id:              "gmp-eme-adobe",
+    name:            "Primetime Content Decryption Module provided by Adobe Systems, Incorporated",
+    description:     "Play back protected web video.",
+    fullDescription: "<xhtml:a href=\"\" target=\"_blank\">License information</xhtml:a>.",
+    homepageURL:     "https://www.adobe.com/",
+    optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
+    isEME:           true
+  }];
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
-XPCOMUtils.defineLazyGetter(this, "prefs",
-  () => new Preferences(OPENH264_PREF_BRANCH));
 XPCOMUtils.defineLazyGetter(this, "gmpService",
   () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginService));
 
 let gLogger;
-let gLogDumping = false;
 let gLogAppenderDump = null;
 
 function configureLogging() {
   if (!gLogger) {
-    gLogger = Log.repository.getLogger("Toolkit.OpenH264Provider");
+    gLogger = Log.repository.getLogger("Toolkit.GMP");
     gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
   }
-  gLogger.level = prefs.get(OPENH264_PREF_LOGGING_LEVEL, Log.Level.Warn);
+  gLogger.level = GMPPrefs.get(KEY_LOGGING_LEVEL, Log.Level.Warn);
 
-  let logDumping = prefs.get(OPENH264_PREF_LOGGING_DUMP, false);
-  if (logDumping != gLogDumping) {
+  let logDumping = GMPPrefs.get(KEY_LOGGING_DUMP, false);
+  if (logDumping != !!gLogAppenderDump) {
     if (logDumping) {
       gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
       gLogger.addAppender(gLogAppenderDump);
     } else {
       gLogger.removeAppender(gLogAppenderDump);
       gLogAppenderDump = null;
     }
-    gLogDumping = logDumping;
   }
 }
 
 /**
- * The OpenH264Wrapper provides the info for the OpenH264 GMP plugin to public callers through the API.
+ * Manages preferences for GMP addons
  */
+let GMPPrefs = {
+  /**
+   * Obtains the specified preference in relation to the specified plugin.
+   * @param aKey The preference key value to use.
+   * @param aDefaultValue The default value if no preference exists.
+   * @param aPlugin The plugin to scope the preference to.
+   * @return The obtained preference value, or the defaultValue if none exists.
+   */
+  get: function(aKey, aDefaultValue, aPlugin) {
+    return Preferences.get(this.getPrefKey(aKey, aPlugin), aDefaultValue);
+  },
+  /**
+   * Sets the specified preference in relation to the specified plugin.
+   * @param aKey The preference key value to use.
+   * @param aVal The value to set.
+   * @param aPlugin The plugin to scope the preference to.
+   */
+  set: function(aKey, aVal, aPlugin) {
+    let log =
+      Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+                                                "GMPProvider.jsm " +
+                                                "GMPPrefs.set ");
+    log.trace("Setting pref: " + this.getPrefKey(aKey, aPlugin) +
+             " to value: " + aVal);
+    Preferences.set(this.getPrefKey(aKey, aPlugin), aVal);
+  },
+  /**
+   * Checks whether or not the specified preference is set in relation to the
+   * specified plugin.
+   * @param aKey The preference key value to use.
+   * @param aPlugin The plugin to scope the preference to.
+   * @return true if the preference is set, false otherwise.
+   */
+  isSet: function(aKey, aPlugin) {
+    return Preferences.isSet(GMPPrefs.getPrefKey(aKey, aPlugin));
+  },
+  /**
+   * Resets the specified preference in relation to the specified plugin to its
+   * default.
+   * @param aKey The preference key value to use.
+   * @param aPlugin The plugin to scope the preference to.
+   */
+  reset: function(aKey, aPlugin) {
+    Preferences.reset(this.getPrefKey(aKey, aPlugin));
+  },
+  /**
+   * Scopes the specified preference key to the specified plugin.
+   * @param aKey The preference key value to use.
+   * @param aPlugin The plugin to scope the preference to.
+   * @return A preference key scoped to the specified plugin.
+   */
+  getPrefKey: function(aKey, aPlugin) {
+    return aKey.replace("{0}", aPlugin || "");
+  },
+};
 
-let OpenH264Wrapper = {
-  // An active task that checks for OpenH264 updates and installs them.
+/**
+ * The GMPWrapper provides the info for the various GMP plugins to public
+ * callers through the API.
+ */
+function GMPWrapper(aPluginInfo) {
+  this._plugin = aPluginInfo;
+  this._log =
+    Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+                                              "GMPWrapper(" +
+                                              this._plugin.id + ") ");
+  Preferences.observe(GMPPrefs.getPrefKey(KEY_PLUGIN_ENABLED, this._plugin.id),
+                      this.onPrefEnabledChanged, this);
+  Preferences.observe(GMPPrefs.getPrefKey(KEY_PLUGIN_VERSION, this._plugin.id),
+                      this.onPrefVersionChanged, this);
+  if (this._plugin.isEME) {
+    Preferences.observe(GMPPrefs.getPrefKey(KEY_EME_ENABLED, this._plugin.id),
+                        this.onPrefEnabledChanged, this);
+  }
+}
+
+GMPWrapper.prototype = {
+  // An active task that checks for plugin updates and installs them.
   _updateTask: null,
-
-  _log: null,
+  _gmpPath: null,
 
   optionsType: AddonManager.OPTIONS_TYPE_INLINE,
-  optionsURL: OPENH264_OPTIONS_URL,
+  get optionsURL() { return this._plugin.optionsURL; },
 
-  get id() { return OPENH264_PLUGIN_ID; },
+  set gmpPath(aPath) { this._gmpPath = aPath; },
+  get gmpPath() {
+    if (!this._gmpPath && this.isInstalled) {
+      this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
+                                   this._plugin.id,
+                                   GMPPrefs.get(KEY_PLUGIN_VERSION, null,
+                                                this._plugin.id));
+    }
+    return this._gmpPath;
+  },
+
+  get id() { return this._plugin.id; },
   get type() { return "plugin"; },
   get isGMPlugin() { return true; },
-  get name() { return pluginsBundle.GetStringFromName("openH264_name"); },
+  get name() { return this._plugin.name; },
   get creator() { return null; },
-  get homepageURL() { return OPENH264_HOMEPAGE_URL; },
+  get homepageURL() { return this._plugin.homepageURL; },
 
-  get description() { return pluginsBundle.GetStringFromName("openH264_description"); },
-  get fullDescription() { return OPENH264_FULLDESCRIPTION; },
+  get description() { return this._plugin.description; },
+  get fullDescription() { return this._plugin.fullDescription; },
 
-  get version() { return prefs.get(OPENH264_PREF_VERSION, ""); },
+  get version() { return GMPPrefs.get(KEY_PLUGIN_VERSION, null,
+                                      this._plugin.id); },
 
   get isActive() { return !this.userDisabled; },
   get appDisabled() { return false; },
 
-  get userDisabled() { return !prefs.get(OPENH264_PREF_ENABLED, true); },
-  set userDisabled(aVal) { prefs.set(OPENH264_PREF_ENABLED, aVal === false); },
+  get userDisabled() {
+    if (this._plugin.isEME && !GMPPrefs.get(KEY_EME_ENABLED, true)) {
+      // If "media.eme.enabled" is false, all EME plugins are disabled.
+      return true;
+    }
+    return !GMPPrefs.get(KEY_PLUGIN_ENABLED, true, this._plugin.id);
+  },
+  set userDisabled(aVal) { GMPPrefs.set(KEY_PLUGIN_ENABLED, aVal === false,
+                                        this._plugin.id); },
 
   get blocklistState() { return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; },
   get size() { return 0; },
   get scope() { return AddonManager.SCOPE_APPLICATION; },
   get pendingOperations() { return AddonManager.PENDING_NONE; },
 
   get operationsRequiringRestart() { return AddonManager.OP_NEEDS_RESTART_NONE },
 
@@ -124,17 +230,18 @@ let OpenH264Wrapper = {
     if (!this.appDisabled) {
       permissions |= this.userDisabled ? AddonManager.PERM_CAN_ENABLE :
                                          AddonManager.PERM_CAN_DISABLE;
     }
     return permissions;
   },
 
   get updateDate() {
-    let time = Number(prefs.get(OPENH264_PREF_LASTUPDATE, null));
+    let time = Number(GMPPrefs.get(KEY_PLUGIN_LAST_UPDATE, null,
+                                   this._plugin.id));
     if (time !== NaN && this.isInstalled) {
       return new Date(time * 1000)
     }
     return null;
   },
 
   get isCompatible() {
     return true;
@@ -152,75 +259,86 @@ let OpenH264Wrapper = {
     return false;
   },
 
   isCompatibleWith: function(aAppVersion, aPlatformVersion) {
     return true;
   },
 
   get applyBackgroundUpdates() {
-    if (!prefs.isSet(OPENH264_PREF_AUTOUPDATE)) {
+    if (!GMPPrefs.isSet(KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
       return AddonManager.AUTOUPDATE_DEFAULT;
     }
 
-    return prefs.get(OPENH264_PREF_AUTOUPDATE, true) ?
+    return GMPPrefs.get(KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id) ?
       AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
   },
 
   set applyBackgroundUpdates(aVal) {
     if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
-      prefs.reset(OPENH264_PREF_AUTOUPDATE);
+      GMPPrefs.reset(KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
     } else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
-      prefs.set(OPENH264_PREF_AUTOUPDATE, true);
+      GMPPrefs.set(KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
     } else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
-      prefs.set(OPENH264_PREF_AUTOUPDATE, false);
+      GMPPrefs.set(KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
     }
   },
 
   findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
-    this._log.trace("findUpdates() - reason=" + aReason);
+    this._log.trace("findUpdates() - " + this._plugin.id + " - reason=" +
+                    aReason);
 
     AddonManagerPrivate.callNoUpdateListeners(this, aListener);
 
     if (aReason === AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
       if (!AddonManager.shouldAutoUpdate(this)) {
-        this._log.trace("findUpdates() - no autoupdate");
+        this._log.trace("findUpdates() - " + this._plugin.id +
+                        " - no autoupdate");
         return Promise.resolve(false);
       }
 
-      let secSinceLastCheck = Date.now() / 1000 - Preferences.get(GMP_PREF_LASTCHECK, 0);
+      let secSinceLastCheck =
+        Date.now() / 1000 - Preferences.get(KEY_PROVIDER_LASTCHECK, 0);
       if (secSinceLastCheck <= SEC_IN_A_DAY) {
-        this._log.trace("findUpdates() - last check was less then a day ago");
+        this._log.trace("findUpdates() - " + this._plugin.id +
+                        " - last check was less then a day ago");
         return Promise.resolve(false);
       }
     } else if (aReason !== AddonManager.UPDATE_WHEN_USER_REQUESTED) {
-      this._log.trace("findUpdates() - unsupported reason");
+      this._log.trace("findUpdates() - " + this._plugin.id +
+                      " - the given reason to update is not supported");
       return Promise.resolve(false);
     }
 
     if (this._updateTask !== null) {
-      this._log.trace("findUpdates() - update task already running");
+      this._log.trace("findUpdates() - " + this._plugin.id +
+                      " - update task already running");
       return this._updateTask;
     }
 
-    this._updateTask = Task.spawn(function* OpenH264Provider_updateTask() {
+    this._updateTask = Task.spawn(function* GMPProvider_updateTask() {
       this._log.trace("findUpdates() - updateTask");
       try {
         let installManager = new GMPInstallManager();
-        let addons = yield installManager.checkForAddons();
-        let openH264 = addons.find(addon => addon.isOpenH264);
-        if (openH264 && !openH264.isInstalled) {
-          this._log.trace("findUpdates() - found update, installing");
-          yield installManager.installAddon(openH264);
+        let gmpAddons = yield installManager.checkForAddons();
+        let update = gmpAddons.find(function(aAddon) {
+          return aAddon.id === this._plugin.id;
+        }, this);
+        if (update && update.isValid && !update.isInstalled) {
+          this._log.trace("findUpdates() - found update for " +
+                          this._plugin.id + ", installing");
+          yield installManager.installAddon(update);
         } else {
-          this._log.trace("findUpdates() - no updates");
+          this._log.trace("findUpdates() - no updates for " + this._plugin.id);
         }
-        this._log.info("findUpdates() - updateTask succeeded");
+        this._log.info("findUpdates() - updateTask succeeded for " +
+                       this._plugin.id);
       } catch (e) {
-        this._log.error("findUpdates() - updateTask threw: " + e);
+        this._log.error("findUpdates() - updateTask for " + this._plugin.id +
+                        " threw", e);
         throw e;
       } finally {
         this._updateTask = null;
         return true;
       }
     }.bind(this));
 
     return this._updateTask;
@@ -232,164 +350,230 @@ let OpenH264Wrapper = {
       let path = this.version;
       return [path];
     }
     return [];
   },
   get pluginFullpath() {
     if (this.isInstalled) {
       let path = OS.Path.join(OS.Constants.Path.profileDir,
-                              OPENH264_PLUGIN_ID,
+                              this._plugin.id,
                               this.version);
       return [path];
     }
     return [];
   },
 
   get isInstalled() {
-    return this.version.length > 0;
+    return this.version && this.version.length > 0;
+  },
+
+  onPrefEnabledChanged: function() {
+    AddonManagerPrivate.callAddonListeners(this.isActive ?
+                                           "onEnabling" : "onDisabling",
+                                           this, false);
+    if (this._gmpPath) {
+      if (this.isActive) {
+        this._log.info("onPrefEnabledChanged() - adding gmp directory " +
+                       this._gmpPath);
+        gmpService.addPluginDirectory(this._gmpPath);
+      } else {
+        this._log.info("onPrefEnabledChanged() - removing gmp directory " +
+                       this._gmpPath);
+        gmpService.removePluginDirectory(this._gmpPath);
+      }
+    }
+    AddonManagerPrivate.callAddonListeners(this.isActive ?
+                                           "onEnabled" : "onDisabled",
+                                           this);
+  },
+
+  onPrefVersionChanged: function() {
+    AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
+    if (this._gmpPath) {
+      this._log.info("onPrefVersionChanged() - unregistering gmp directory " +
+                     this._gmpPath);
+      gmpService.removePluginDirectory(this._gmpPath);
+    }
+    AddonManagerPrivate.callAddonListeners("onUninstalled", this);
+
+    AddonManagerPrivate.callInstallListeners("onExternalInstall", null, this,
+                                             null, false);
+    this._gmpPath = null;
+    if (this.isInstalled) {
+      this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
+                                   this._plugin.id,
+                                   GMPPrefs.get(KEY_PLUGIN_VERSION, null,
+                                                this._plugin.id));
+    }
+    if (this._gmpPath && this.isActive) {
+      this._log.info("onPrefVersionChanged() - registering gmp directory " +
+                     this._gmpPath);
+      gmpService.addPluginDirectory(this._gmpPath);
+    }
+    AddonManagerPrivate.callAddonListeners("onInstalled", this);
+  },
+
+  shutdown: function() {
+    Preferences.ignore(GMPPrefs.getPrefKey(KEY_PLUGIN_ENABLED, this._plugin.id),
+                       this.onPrefEnabledChanged, this);
+    Preferences.ignore(GMPPrefs.getPrefKey(KEY_PLUGIN_VERSION, this._plugin.id),
+                       this.onPrefVersionChanged, this);
+    if (this._isEME) {
+      Preferences.ignore(GMPPrefs.getPrefKey(KEY_EME_ENABLED, this._plugin.id),
+                         this.onPrefEnabledChanged, this);
+    }
+    return this._updateTask;
   },
 };
 
-let OpenH264Provider = {
-  get name() "OpenH264Provider",
+let GMPProvider = {
+  get name() { return "GMPProvider"; },
+
+  _plugins: null,
 
   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) {
-      this.gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
-                                  OPENH264_PLUGIN_ID,
-                                  prefs.get(OPENH264_PREF_VERSION, null));
-    }
-    let enabled = prefs.get(OPENH264_PREF_ENABLED, true);
-    this._log.trace("startup() - enabled=" + enabled + ", gmpPath="+this.gmpPath);
+    this._log = Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP",
+                                                          "GMPProvider.");
+    let telemetry = {};
+    this.buildPluginList();
+
+    Preferences.observe(KEY_LOG_BASE, configureLogging);
+
+    for (let [id, plugin] of this._plugins) {
+      let wrapper = plugin.wrapper;
+      let gmpPath = wrapper.gmpPath;
+      let isEnabled = wrapper.isActive;
+      this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
+                      gmpPath);
 
-
-    Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
-    prefs.observe(OPENH264_PREF_ENABLED, this.onPrefEnabledChanged, this);
-    prefs.observe(OPENH264_PREF_VERSION, this.onPrefVersionChanged, this);
-    prefs.observe(OPENH264_PREF_LOGGING, configureLogging);
+      if (gmpPath && isEnabled) {
+        this._log.info("startup - adding gmp directory " + gmpPath);
+        try {
+          gmpService.addPluginDirectory(gmpPath);
+        } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+          this._log.warn("startup - adding gmp directory failed with " +
+                         e.name + " - sandboxing not available?", e);
+        }
+      }
 
-    if (this.gmpPath && enabled) {
-      this._log.info("startup() - adding gmp directory " + this.gmpPath);
-      try {
-        gmpService.addPluginDirectory(this.gmpPath);
-      } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
-        this._log.warn("startup() - adding gmp directory failed with " + e.name + " - sandboxing not available?");
+      if (this.isEnabled) {
+        telemetry[id] = {
+          userDisabled: wrapper.userDisabled,
+          version: wrapper.version,
+          applyBackgroundUpdates: wrapper.applyBackgroundUpdates,
+        };
       }
     }
 
-    if (Preferences.get(EME_PREF_ENABLED, false)) {
+    if (Preferences.get(KEY_EME_ENABLED, false)) {
       try {
-        let greBinDir = Services.dirsvc.get(NS_GRE_BIN_DIR, Ci.nsILocalFile);
-        gmpService.addPluginDirectory(OS.Path.join(greBinDir.path,
-                                                   CLEARKEY_PLUGIN_ID,
-                                                   CLEARKEY_VERSION));
+        let greBinDir = Services.dirsvc.get(NS_GRE_BIN_DIR,
+                                            Ci.nsILocalFile);
+        let clearkeyPath = OS.Path.join(greBinDir.path,
+                                        CLEARKEY_PLUGIN_ID,
+                                        CLEARKEY_VERSION);
+        this._log.info("startup - adding clearkey CDM directory " +
+                       clearkeyPath);
+        gmpService.addPluginDirectory(clearkeyPath);
       } catch (e) {
-        this._log.warn("startup() - adding clearkey CDM failed", e);
+        this._log.warn("startup - adding clearkey CDM failed", e);
       }
     }
 
-    let telemetry = {};
-    if (this.isEnabled) {
-      telemetry[OPENH264_PLUGIN_ID] = {
-	userDisabled: OpenH264Wrapper.userDisabled,
-	version: OpenH264Wrapper.version,
-	applyBackgroundUpdates: OpenH264Wrapper.applyBackgroundUpdates,
-      };
-    }
     AddonManagerPrivate.setTelemetryDetails("GMP", telemetry);
   },
 
   shutdown: function() {
-    this._log.trace("shutdown()");
-    Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
-    prefs.ignore(OPENH264_PREF_ENABLED, this.onPrefEnabledChanged, this);
-    prefs.ignore(OPENH264_PREF_VERSION, this.onPrefVersionChanged, this);
-    prefs.ignore(OPENH264_PREF_LOGGING, configureLogging);
-
-    return OpenH264Wrapper._updateTask;
-  },
-
-  onPrefEnabledChanged: function() {
-    let wrapper = OpenH264Wrapper;
+    this._log.trace("shutdown");
+    Preferences.ignore(KEY_LOG_BASE, configureLogging);
 
-    AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
-                                           "onEnabling" : "onDisabling",
-                                           wrapper, false);
-    if (this.gmpPath) {
-      if (wrapper.isActive) {
-        this._log.info("onPrefEnabledChanged() - adding gmp directory " + this.gmpPath);
-        gmpService.addPluginDirectory(this.gmpPath);
-      } else {
-        this._log.info("onPrefEnabledChanged() - removing gmp directory " + this.gmpPath);
-        gmpService.removePluginDirectory(this.gmpPath);
-      }
-    }
-    AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
-                                           "onEnabled" : "onDisabled",
-                                           wrapper);
-  },
+    let shutdownTask = Task.spawn(function* GMPProvider_shutdownTask() {
+      this._log.trace("shutdown - shutdownTask");
+      let shutdownSucceeded = true;
 
-  onPrefVersionChanged: function() {
-    let wrapper = OpenH264Wrapper;
-
-    AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
-    if (this.gmpPath) {
-      this._log.info("onPrefVersionChanged() - unregistering gmp directory " + this.gmpPath);
-      gmpService.removePluginDirectory(this.gmpPath);
-    }
-    AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+      for (let plugin of this._plugins.values()) {
+        try {
+          yield plugin.wrapper.shutdown();
+        } catch (e) {
+          shutdownSucceeded = false;
+        }
+      }
 
-    AddonManagerPrivate.callInstallListeners("onExternalInstall", null, wrapper, null, false);
-    this.gmpPath = null;
-    if (OpenH264Wrapper.isInstalled) {
-      this.gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
-                                  OPENH264_PLUGIN_ID,
-                                  prefs.get(OPENH264_PREF_VERSION, null));
-    }
-    if (this.gmpPath && wrapper.isActive) {
-      this._log.info("onPrefVersionChanged() - registering gmp directory " + this.gmpPath);
-      gmpService.addPluginDirectory(this.gmpPath);
-    }
-    AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
-  },
+      this._plugins = null;
 
-  buildWrapper: function() {
-    let description = pluginsBundle.GetStringFromName("openH264_description");
-    return new OpenH264Wrapper(OPENH264_PLUGIN_ID,
-                               OPENH264_PLUGIN_NAME,
-                               description);
+      if (!shutdownSucceeded) {
+        throw new Error("Shutdown failed");
+      }
+    }.bind(this));
+
+    return shutdownTask;
   },
 
   getAddonByID: function(aId, aCallback) {
-    if (this.isEnabled && aId == OPENH264_PLUGIN_ID) {
-      aCallback(OpenH264Wrapper);
+    if (!this.isEnabled) {
+      aCallback(null);
+      return;
+    }
+
+    let plugin = this._plugins.get(aId);
+    if (plugin) {
+      aCallback(plugin.wrapper);
     } else {
       aCallback(null);
     }
   },
 
   getAddonsByTypes: function(aTypes, aCallback) {
-    if (!this.isEnabled || aTypes && aTypes.indexOf("plugin") < 0) {
+    if (!this.isEnabled ||
+        (aTypes && aTypes.indexOf("plugin") < 0)) {
       aCallback([]);
       return;
     }
 
-    aCallback([OpenH264Wrapper]);
+    let results = [p.wrapper for ([id, p] of this._plugins)];
+    aCallback(results);
   },
 
   get isEnabled() {
-    return prefs.get(OPENH264_PREF_PROVIDERENABLED, false);
+    return GMPPrefs.get(KEY_PROVIDER_ENABLED, false);
+  },
+
+  buildPluginList: function() {
+    let map = new Map();
+    GMP_PLUGINS.forEach(aPlugin => {
+      // Only show GMPs in addon manager that aren't hidden.
+      if (!GMPPrefs.get(KEY_PLUGIN_HIDDEN, false, aPlugin.id)) {
+        let plugin = {
+          id: aPlugin.id,
+          fullDescription: aPlugin.fullDescription,
+          homepageURL: aPlugin.homepageURL,
+          optionsURL: aPlugin.optionsURL,
+          wrapper: null,
+          isEME: aPlugin.isEME
+        };
+
+        plugin.name = aPlugin.name;
+        try {
+          plugin.name = pluginsBundle.GetStringFromName(aPlugin.name);
+        } catch (ex) { } // Not all GMPs have localized names.
+
+        plugin.description = aPlugin.description;
+        try {
+          plugin.description =
+            pluginsBundle.GetStringFromName(aPlugin.description);
+        } catch (ex) { } // Not all GMPs have localized descriptions.
+
+        plugin.wrapper = new GMPWrapper(plugin);
+        map.set(plugin.id, plugin);
+      }
+    });
+    this._plugins = map;
   },
 };
 
-AddonManagerPrivate.registerProvider(OpenH264Provider, [
+AddonManagerPrivate.registerProvider(GMPProvider, [
   new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
                                     STRING_TYPE_NAME,
                                     AddonManager.VIEW_TYPE_LIST, 6000,
                                     AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
 ]);
--- a/toolkit/mozapps/extensions/internal/moz.build
+++ b/toolkit/mozapps/extensions/internal/moz.build
@@ -5,18 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXTRA_JS_MODULES.addons += [
     'AddonLogging.jsm',
     'AddonRepository.jsm',
     'AddonRepository_SQLiteMigrator.jsm',
     'AddonUpdateChecker.jsm',
     'Content.js',
+    'GMPProvider.jsm',
     'LightweightThemeImageOptimizer.jsm',
-    'OpenH264Provider.jsm',
     'SpellCheckDictionaryBootstrap.js',
 ]
 
 # Don't ship unused providers on Android
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     EXTRA_JS_MODULES.addons += [
         'PluginProvider.jsm',
     ]
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -24,14 +24,14 @@ toolkit.jar:
 * content/mozapps/extensions/update.xul                         (content/update.xul)
   content/mozapps/extensions/update.js                          (content/update.js)
   content/mozapps/extensions/eula.xul                           (content/eula.xul)
   content/mozapps/extensions/eula.js                            (content/eula.js)
   content/mozapps/extensions/newaddon.xul                       (content/newaddon.xul)
   content/mozapps/extensions/newaddon.js                        (content/newaddon.js)
   content/mozapps/extensions/setting.xml                        (content/setting.xml)
   content/mozapps/extensions/pluginPrefs.xul                    (content/pluginPrefs.xul)
-  content/mozapps/extensions/openH264Prefs.xul                  (content/openH264Prefs.xul)
+  content/mozapps/extensions/gmpPrefs.xul                       (content/gmpPrefs.xul)
   content/mozapps/extensions/OpenH264-license.txt               (content/OpenH264-license.txt)
   content/mozapps/xpinstall/xpinstallConfirm.xul                (content/xpinstallConfirm.xul)
   content/mozapps/xpinstall/xpinstallConfirm.js                 (content/xpinstallConfirm.js)
   content/mozapps/xpinstall/xpinstallConfirm.css                (content/xpinstallConfirm.css)
   content/mozapps/xpinstall/xpinstallItem.xml                   (content/xpinstallItem.xml)
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -35,21 +35,21 @@ support-files =
   browser_purchase.xml
 
 [browser_addonrepository_performance.js]
 [browser_bug557956.js]
 skip-if = e10s
 [browser_bug616841.js]
 [browser_cancelCompatCheck.js]
 [browser_checkAddonCompatibility.js]
+[browser_gmpProvider.js]
 [browser_hotfix.js]
 [browser_installssl.js]
 [browser_newaddon.js]
 skip-if = e10s
-[browser_openH264.js]
 [browser_select_compatoverrides.js]
 [browser_select_confirm.js]
 [browser_select_selection.js]
 [browser_select_update.js]
 [browser_updatessl.js]
 [browser_task_next_test.js]
 
 [include:browser-common.ini]
rename from toolkit/mozapps/extensions/test/browser/browser_openH264.js
rename to toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
--- a/toolkit/mozapps/extensions/test/browser/browser_openH264.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_gmpProvider.js
@@ -1,50 +1,49 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 "use strict";
 
 Cu.import("resource://gre/modules/Promise.jsm");
 let {AddonTestUtils} = Cu.import("resource://testing-common/AddonManagerTesting.jsm", {});
-let OpenH264Scope = Cu.import("resource://gre/modules/addons/OpenH264Provider.jsm");
-
-const OPENH264_PLUGIN_ID       = "gmp-gmpopenh264";
-const OPENH264_PREF_BRANCH     = "media." + OPENH264_PLUGIN_ID + ".";
-const OPENH264_PREF_ENABLED    = OPENH264_PREF_BRANCH + "enabled";
-const OPENH264_PREF_VERSION    = OPENH264_PREF_BRANCH + "version";
-const OPENH264_PREF_LASTUPDATE = OPENH264_PREF_BRANCH + "lastUpdate";
-const OPENH264_PREF_AUTOUPDATE = OPENH264_PREF_BRANCH + "autoupdate";
-const PREF_LOGGING             = OPENH264_PREF_BRANCH + "provider.logging";
-const PREF_LOGGING_LEVEL       = PREF_LOGGING + ".level";
-const PREF_LOGGING_DUMP        = PREF_LOGGING + ".dump";
-const GMP_PREF_LASTCHECK       = "media.gmp-manager.lastCheck";
-const GMP_PREF_LOG             = "media.gmp-manager.log";
+let GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
 
 const TEST_DATE = new Date(2013, 0, 1, 12);
 
 let gManagerWindow;
 let gCategoryUtilities;
 let gIsEnUsLocale;
 
-let MockGMPAddon = Object.freeze({
-  id: OPENH264_PLUGIN_ID,
-  isOpenH264: true,
-  isInstalled: false,
-});
+let gMockAddons = [];
+let gMockEmeAddons = [];
+
+for (let plugin of GMPScope.GMP_PLUGINS) {
+  let mockAddon = Object.freeze({
+      id: plugin.id,
+      isValid: true,
+      isInstalled: false,
+  });
+  gMockAddons.push(mockAddon);
+  if (mockAddon.id.indexOf("gmp-eme-") == 0) {
+    gMockEmeAddons.push(mockAddon);
+  }
+}
 
 let gInstalledAddonId = "";
 let gInstallDeferred = null;
+let gPrefs = Services.prefs;
+let getKey = GMPScope.GMPPrefs.getPrefKey;
 
 function MockGMPInstallManager() {
 }
 
 MockGMPInstallManager.prototype = {
-  checkForAddons: () => Promise.resolve([MockGMPAddon]),
+  checkForAddons: () => Promise.resolve(gMockAddons),
 
   installAddon: addon => {
     gInstalledAddonId = addon.id;
     gInstallDeferred.resolve();
     return Promise.resolve();
   },
 };
 
@@ -82,197 +81,313 @@ function openDetailsView(aId) {
   EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
 
   let deferred = Promise.defer();
   wait_for_view_load(gManagerWindow, deferred.resolve);
   return deferred.promise;
 }
 
 add_task(function* initializeState() {
-  Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
-  Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
-  Services.prefs.setBoolPref(GMP_PREF_LOG, true);
+  gPrefs.setBoolPref(GMPScope.KEY_LOGGING_DUMP, true);
+  gPrefs.setIntPref(GMPScope.KEY_LOGGING_LEVEL, 0);
 
   gManagerWindow = yield open_manager();
   gCategoryUtilities = new CategoryUtilities(gManagerWindow);
 
-  registerCleanupFunction(() => {
+  registerCleanupFunction(Task.async(function*() {
     Services.obs.removeObserver(gOptionsObserver, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
 
-    Services.prefs.clearUserPref(OPENH264_PREF_ENABLED);
-    Services.prefs.clearUserPref(OPENH264_PREF_VERSION);
-    Services.prefs.clearUserPref(OPENH264_PREF_LASTUPDATE);
-    Services.prefs.clearUserPref(OPENH264_PREF_AUTOUPDATE);
-    Services.prefs.clearUserPref(PREF_LOGGING_DUMP);
-    Services.prefs.clearUserPref(PREF_LOGGING_LEVEL);
-    Services.prefs.clearUserPref(GMP_PREF_LOG);
-    Services.prefs.clearUserPref(GMP_PREF_LASTCHECK);
-  });
+    for (let addon of gMockAddons) {
+      gPrefs.clearUserPref(getKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id));
+      gPrefs.clearUserPref(getKey(GMPScope.KEY_PLUGIN_LAST_UPDATE, addon.id));
+      gPrefs.clearUserPref(getKey(GMPScope.KEY_PLUGIN_AUTOUPDATE, addon.id));
+      gPrefs.clearUserPref(getKey(GMPScope.KEY_PLUGIN_VERSION, addon.id));
+      gPrefs.clearUserPref(getKey(GMPScope.KEY_PLUGIN_HIDDEN, addon.id));
+    }
+    gPrefs.clearUserPref(GMPScope.KEY_LOGGING_DUMP);
+    gPrefs.clearUserPref(GMPScope.KEY_LOGGING_LEVEL);
+    gPrefs.clearUserPref(GMPScope.KEY_PROVIDER_LASTCHECK);
+    gPrefs.clearUserPref(GMPScope.KEY_EME_ENABLED);
+    yield GMPScope.GMPProvider.shutdown();
+    GMPScope.GMPProvider.startup();
+  }));
 
   let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
   gIsEnUsLocale = chrome.getSelectedLocale("global") == "en-US";
 
   Services.obs.addObserver(gOptionsObserver, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
 
-  // Start out with OpenH264 not being installed, disabled and automatic updates disabled.
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
-  Services.prefs.setIntPref (OPENH264_PREF_LASTUPDATE, 0);
-  Services.prefs.setBoolPref(OPENH264_PREF_AUTOUPDATE, false);
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, "");
+  // Start out with plugins not being installed, disabled and automatic updates
+  // disabled.
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, true);
+  for (let addon of gMockAddons) {
+    gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), false);
+    gPrefs.setIntPref(getKey(GMPScope.KEY_PLUGIN_LAST_UPDATE, addon.id), 0);
+    gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_AUTOUPDATE, addon.id), false);
+    gPrefs.setCharPref(getKey(GMPScope.KEY_PLUGIN_VERSION, addon.id), "");
+    gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_HIDDEN, addon.id), false);
+  }
+  yield GMPScope.GMPProvider.shutdown();
+  GMPScope.GMPProvider.startup();
 });
 
 add_task(function* testNotInstalled() {
   Assert.ok(gCategoryUtilities.isTypeVisible("plugin"), "Plugin tab visible.");
   yield gCategoryUtilities.openType("plugin");
 
-  let item = get_addon_element(gManagerWindow, OPENH264_PLUGIN_ID);
-  Assert.ok(item, "Got add-on element.");
-  item.parentNode.ensureElementIsVisible(item);
-  is(item.getAttribute("active"), "false");
+  for (let addon of gMockAddons) {
+    let item = get_addon_element(gManagerWindow, addon.id);
+    Assert.ok(item, "Got add-on element:" + addon.id);
+    item.parentNode.ensureElementIsVisible(item);
+    is(item.getAttribute("active"), "false");
 
-  let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning");
-  is_element_visible(el, "Warning notification is visible.");
-  el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
-  is_element_visible(el, "disabled-postfix is visible.");
-  el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
-  is_element_hidden(el, "Disable button not visible.");
-  el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
-  is_element_hidden(el, "Enable button not visible.");
+    let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning");
+    is_element_visible(el, "Warning notification is visible.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
+    is_element_visible(el, "disabled-postfix is visible.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
+    is_element_hidden(el, "Disable button not visible.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
+    is_element_hidden(el, "Enable button not visible.");
 
-  let menu = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "state-menulist");
-  is_element_visible(menu, "State menu should be visible.");
+    let menu = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "state-menulist");
+    is_element_visible(menu, "State menu should be visible.");
 
-  let neverActivate = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "never-activate-menuitem");
-  is(menu.selectedItem, neverActivate, "Plugin state should be never-activate.");
+    let neverActivate = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "never-activate-menuitem");
+    is(menu.selectedItem, neverActivate, "Plugin state should be never-activate.");
+  }
 });
 
 add_task(function* testNotInstalledDetails() {
-  yield openDetailsView(OPENH264_PLUGIN_ID);
-  let doc = gManagerWindow.document;
+  for (let addon of gMockAddons) {
+    yield openDetailsView(addon.id);
+    let doc = gManagerWindow.document;
 
-  let el = doc.getElementsByClassName("disabled-postfix")[0];
-  is_element_visible(el, "disabled-postfix is visible.");
-  el = doc.getElementById("detail-findUpdates-btn");
-  is_element_visible(el, "Find updates link is visible.");
-  el = doc.getElementById("detail-warning");
-  is_element_visible(el, "Warning notification is visible.");
-  el = doc.getElementsByTagName("setting")[0];
+    let el = doc.getElementsByClassName("disabled-postfix")[0];
+    is_element_visible(el, "disabled-postfix is visible.");
+    el = doc.getElementById("detail-findUpdates-btn");
+    is_element_visible(el, "Find updates link is visible.");
+    el = doc.getElementById("detail-warning");
+    is_element_visible(el, "Warning notification is visible.");
+    el = doc.getElementsByTagName("setting")[0];
+  }
 });
 
 add_task(function* testInstalled() {
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, true);
-  Services.prefs.setIntPref (OPENH264_PREF_LASTUPDATE, TEST_DATE.getTime());
-  Services.prefs.setBoolPref(OPENH264_PREF_AUTOUPDATE, false);
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, "1.2.3.4");
+  for (let addon of gMockAddons) {
+    gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), true);
+    gPrefs.setIntPref(getKey(GMPScope.KEY_PLUGIN_LAST_UPDATE, addon.id),
+                      TEST_DATE.getTime());
+    gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_AUTOUPDATE, addon.id), false);
+    gPrefs.setCharPref(getKey(GMPScope.KEY_PLUGIN_VERSION, addon.id), "1.2.3.4");
 
-  yield gCategoryUtilities.openType("plugin");
+    yield gCategoryUtilities.openType("plugin");
 
-  let item = get_addon_element(gManagerWindow, OPENH264_PLUGIN_ID);
-  Assert.ok(item, "Got add-on element.");
-  item.parentNode.ensureElementIsVisible(item);
-  is(item.getAttribute("active"), "true");
+    let item = get_addon_element(gManagerWindow, addon.id);
+    Assert.ok(item, "Got add-on element.");
+    item.parentNode.ensureElementIsVisible(item);
+    is(item.getAttribute("active"), "true");
 
-  let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning");
-  is_element_hidden(el, "Warning notification is hidden.");
-  el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
-  is_element_hidden(el, "disabled-postfix is hidden.");
-  el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
-  is_element_hidden(el, "Disable button not visible.");
-  el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
-  is_element_hidden(el, "Enable button not visible.");
+    let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning");
+    is_element_hidden(el, "Warning notification is hidden.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
+    is_element_hidden(el, "disabled-postfix is hidden.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
+    is_element_hidden(el, "Disable button not visible.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
+    is_element_hidden(el, "Enable button not visible.");
 
-  let menu = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "state-menulist");
-  is_element_visible(menu, "State menu should be visible.");
+    let menu = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "state-menulist");
+    is_element_visible(menu, "State menu should be visible.");
 
-  let alwaysActivate = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "always-activate-menuitem");
-  is(menu.selectedItem, alwaysActivate, "Plugin state should be always-activate.");
+    let alwaysActivate = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "always-activate-menuitem");
+    is(menu.selectedItem, alwaysActivate, "Plugin state should be always-activate.");
+  }
 });
 
 add_task(function* testInstalledDetails() {
-  yield openDetailsView(OPENH264_PLUGIN_ID);
-  let doc = gManagerWindow.document;
+  for (let addon of gMockAddons) {
+    yield openDetailsView(addon.id);
+    let doc = gManagerWindow.document;
+
+    let el = doc.getElementsByClassName("disabled-postfix")[0];
+    is_element_hidden(el, "disabled-postfix is hidden.");
+    el = doc.getElementById("detail-findUpdates-btn");
+    is_element_visible(el, "Find updates link is visible.");
+    el = doc.getElementById("detail-warning");
+    is_element_hidden(el, "Warning notification is hidden.");
+    el = doc.getElementsByTagName("setting")[0];
 
-  let el = doc.getElementsByClassName("disabled-postfix")[0];
-  is_element_hidden(el, "disabled-postfix is hidden.");
-  el = doc.getElementById("detail-findUpdates-btn");
-  is_element_visible(el, "Find updates link is visible.");
-  el = doc.getElementById("detail-warning");
-  is_element_hidden(el, "Warning notification is hidden.");
-  el = doc.getElementsByTagName("setting")[0];
+    let contextMenu = doc.getElementById("addonitem-popup");
+    let deferred = Promise.defer();
+    let listener = () => {
+      contextMenu.removeEventListener("popupshown", listener, false);
+      deferred.resolve();
+    };
+    contextMenu.addEventListener("popupshown", listener, false);
+    el = doc.getElementsByClassName("detail-view-container")[0];
+    EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
+    EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
+    yield deferred.promise;
+    let menuSep = doc.getElementById("addonitem-menuseparator");
+    is_element_hidden(menuSep, "Menu separator is hidden.");
+    contextMenu.hidePopup();
+  }
+});
+
+add_task(function* testInstalledGlobalEmeDisabled() {
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, false);
+  for (let addon of gMockEmeAddons) {
+    yield gCategoryUtilities.openType("plugin");
+
+    let item = get_addon_element(gManagerWindow, addon.id);
+    Assert.ok(item, "Got add-on element.");
+    item.parentNode.ensureElementIsVisible(item);
+    is(item.getAttribute("active"), "false");
 
-  let contextMenu = doc.getElementById("addonitem-popup");
-  let deferred = Promise.defer();
-  let listener = () => {
-    contextMenu.removeEventListener("popupshown", listener, false);
-    deferred.resolve();
-  };
-  contextMenu.addEventListener("popupshown", listener, false);
-  el = doc.getElementsByClassName("detail-view-container")[0];
-  EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
-  EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
-  yield deferred.promise;
-  let menuSep = doc.getElementById("addonitem-menuseparator");
-  is_element_hidden(menuSep, "Menu separator is hidden.");
-  contextMenu.hidePopup();
+    let el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "warning");
+    is_element_hidden(el, "Warning notification is hidden.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
+    is_element_visible(el, "disabled-postfix is visible.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
+    is_element_hidden(el, "Disable button not visible.");
+    el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
+    is_element_hidden(el, "Enable button not visible.");
+
+    let menu = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "state-menulist");
+    is_element_visible(menu, "State menu should be visible.");
+
+    let neverActivate = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "never-activate-menuitem");
+    is(menu.selectedItem, neverActivate, "Plugin state should be never-activate.");
+  }
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, true);
+});
+
+add_task(function* testInstalledGlobalEmeDisabledDetails() {
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, false);
+  for (let addon of gMockEmeAddons) {
+    yield openDetailsView(addon.id);
+    let doc = gManagerWindow.document;
+
+    let el = doc.getElementsByClassName("disabled-postfix")[0];
+    is_element_visible(el, "disabled-postfix is visible.");
+    el = doc.getElementById("detail-findUpdates-btn");
+    is_element_visible(el, "Find updates link is visible.");
+    el = doc.getElementById("detail-warning");
+    is_element_hidden(el, "Warning notification is hidden.");
+    el = doc.getElementsByTagName("setting")[0];
+
+    let contextMenu = doc.getElementById("addonitem-popup");
+    let deferred = Promise.defer();
+    let listener = () => {
+      contextMenu.removeEventListener("popupshown", listener, false);
+      deferred.resolve();
+    };
+    contextMenu.addEventListener("popupshown", listener, false);
+    el = doc.getElementsByClassName("detail-view-container")[0];
+    EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
+    EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
+    yield deferred.promise;
+    let menuSep = doc.getElementById("addonitem-menuseparator");
+    is_element_hidden(menuSep, "Menu separator is hidden.");
+    contextMenu.hidePopup();
+  }
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, true);
 });
 
 add_task(function* testPreferencesButton() {
 
   let prefValues = [
     { enabled: false, version: "" },
     { enabled: false, version: "1.2.3.4" },
     { enabled: true, version: "" },
     { enabled: true, version: "1.2.3.4" },
   ];
 
-  for (let prefs of prefValues) {
-    dump("Testing preferences button with pref settings: " + JSON.stringify(prefs) + "\n");
-    Services.prefs.setCharPref(OPENH264_PREF_VERSION, prefs.version);
-    Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, prefs.enabled);
+  for (let preferences of prefValues) {
+    dump("Testing preferences button with pref settings: " +
+         JSON.stringify(preferences) + "\n");
+    for (let addon of gMockAddons) {
+      yield close_manager(gManagerWindow);
+      gManagerWindow = yield open_manager();
+      gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+      gPrefs.setCharPref(getKey(GMPScope.KEY_PLUGIN_VERSION, addon.id),
+                         preferences.version);
+      gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id),
+                         preferences.enabled);
+
+      yield gCategoryUtilities.openType("plugin");
+      let doc = gManagerWindow.document;
+      let item = get_addon_element(gManagerWindow, addon.id);
 
+      let button = doc.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
+      EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
+      let deferred = Promise.defer();
+      wait_for_view_load(gManagerWindow, deferred.resolve);
+      yield deferred.promise;
+
+      is(gOptionsObserver.lastDisplayed, addon.id);
+    }
+  }
+});
+
+add_task(function* testUpdateButton() {
+  gPrefs.clearUserPref(GMPScope.KEY_PROVIDER_LASTCHECK);
+
+  let originalInstallManager = GMPScope.GMPInstallManager;
+  Object.defineProperty(GMPScope, "GMPInstallManager", {
+    value: MockGMPInstallManager,
+    writable: true,
+    enumerable: true,
+    configurable: true
+  });
+
+  for (let addon of gMockAddons) {
     yield gCategoryUtilities.openType("plugin");
     let doc = gManagerWindow.document;
-    let item = get_addon_element(gManagerWindow, OPENH264_PLUGIN_ID);
+    let item = get_addon_element(gManagerWindow, addon.id);
+
+    gInstalledAddonId = "";
+    gInstallDeferred = Promise.defer();
 
     let button = doc.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
     EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
     let deferred = Promise.defer();
     wait_for_view_load(gManagerWindow, deferred.resolve);
     yield deferred.promise;
 
-    is(gOptionsObserver.lastDisplayed, OPENH264_PLUGIN_ID);
-  }
-});
-
-add_task(function* testUpdateButton() {
-  Services.prefs.clearUserPref(GMP_PREF_LASTCHECK);
+    button = doc.getElementById("detail-findUpdates-btn");
+    Assert.ok(button != null, "Got detail-findUpdates-btn");
+    EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
+    yield gInstallDeferred.promise;
 
-  yield gCategoryUtilities.openType("plugin");
-  let doc = gManagerWindow.document;
-  let item = get_addon_element(gManagerWindow, OPENH264_PLUGIN_ID);
-
-  Object.defineProperty(OpenH264Scope, "GMPInstallManager", {
-    value: MockGMPInstallManager,
+    Assert.equal(gInstalledAddonId, addon.id);
+  }
+  Object.defineProperty(GMPScope, "GMPInstallManager", {
+    value: originalInstallManager,
     writable: true,
     enumerable: true,
     configurable: true
   });
-  gInstalledAddonId = "";
-  gInstallDeferred = Promise.defer();
+});
+
+add_task(function* testHidden() {
+  gPrefs.clearUserPref(GMPScope.KEY_PROVIDER_LASTCHECK);
+
+  for (let addon of gMockAddons) {
+    gPrefs.setBoolPref(getKey(GMPScope.KEY_PLUGIN_HIDDEN, addon.id), true);
+  }
 
-  let button = doc.getAnonymousElementByAttribute(item, "anonid", "preferences-btn");
-  EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
-  let deferred = Promise.defer();
-  wait_for_view_load(gManagerWindow, deferred.resolve);
-  yield deferred.promise;
+  // Hiding of plugins requires a restart of the GMP provider.
+  yield GMPScope.GMPProvider.shutdown();
+  GMPScope.GMPProvider.startup();
 
-  button = doc.getElementById("detail-findUpdates-btn");
-  Assert.ok(button != null, "Got detail-findUpdates-btn");
-  EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
-  yield gInstallDeferred.promise;
-
-  Assert.equal(gInstalledAddonId, OPENH264_PLUGIN_ID);
-  delete OpenH264Scope.GMPInstallManager;
+  for (let addon of gMockAddons) {
+    yield gCategoryUtilities.openType("plugin");
+    let doc = gManagerWindow.document;
+    let item = get_addon_element(gManagerWindow, addon.id);
+    Assert.equal(item, null);
+  }
 });
 
 add_task(function* test_cleanup() {
   yield close_manager(gManagerWindow);
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.js
@@ -97,17 +97,17 @@ registrar.registerFactory(Components.ID(
                           "Fake Plugin Host",
                           "@mozilla.org/plugin/host;1", PluginHostFactory);
 
 var gPluginIDs = [null, null, null, null, null];
 
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
-  Services.prefs.setBoolPref("media.gmp-gmpopenh264.provider.enabled", false);
+  Services.prefs.setBoolPref("media.gmp-provider.enabled", false);
 
   startupManager();
 
   run_test_1();
 }
 
 function found_plugin(aNum, aId) {
   if (gPluginIDs[aNum])
rename from toolkit/mozapps/extensions/test/xpcshell/test_openh264.js
rename to toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
--- a/toolkit/mozapps/extensions/test/xpcshell/test_openh264.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
@@ -1,279 +1,340 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-const OPENH264_PLUGIN_ID       = "gmp-gmpopenh264";
-const OPENH264_PREF_BRANCH     = "media." + OPENH264_PLUGIN_ID + ".";
-const OPENH264_PREF_ENABLED    = OPENH264_PREF_BRANCH + "enabled";
-const OPENH264_PREF_VERSION    = OPENH264_PREF_BRANCH + "version";
-const OPENH264_PREF_LASTUPDATE = OPENH264_PREF_BRANCH + "lastUpdate";
-const OPENH264_PREF_AUTOUPDATE = OPENH264_PREF_BRANCH + "autoupdate";
-const PREF_LOGGING             = OPENH264_PREF_BRANCH + "provider.logging";
-const PREF_LOGGING_LEVEL       = PREF_LOGGING + ".level";
-const PREF_LOGGING_DUMP        = PREF_LOGGING + ".dump";
-const GMP_PREF_LASTCHECK       = "media.gmp-manager.lastCheck";
-const GMP_PREF_LOG             = "media.gmp-manager.log";
-const SEC_IN_A_DAY             = 24 * 60 * 60;
+let GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
 
-let MockGMPAddon = Object.freeze({
-  id: OPENH264_PLUGIN_ID,
-  isOpenH264: true,
-  isInstalled: false,
-});
+let gMockAddons = new Map();
+let gMockEmeAddons = new Map();
+
+for (let plugin of GMPScope.GMP_PLUGINS) {
+  let mockAddon = Object.freeze({
+      id: plugin.id,
+      isValid: true,
+      isInstalled: false,
+      nameId: plugin.name,
+      descriptionId: plugin.description,
+  });
+  gMockAddons.set(mockAddon.id, mockAddon);
+  if (mockAddon.id.indexOf("gmp-eme-") == 0) {
+    gMockEmeAddons.set(mockAddon.id, mockAddon);
+  }
+}
 
 let gInstalledAddonId = "";
+let gPrefs = Services.prefs;
+let gGetKey = GMPScope.GMPPrefs.getPrefKey;
 
 function MockGMPInstallManager() {
 }
 
 MockGMPInstallManager.prototype = {
-  checkForAddons: () => Promise.resolve([MockGMPAddon]),
+  checkForAddons: () => Promise.resolve([...gMockAddons.values()]),
 
   installAddon: addon => {
     gInstalledAddonId = addon.id;
     return Promise.resolve();
   },
 };
 
 
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
   startupManager();
 
-  Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
-  Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
-  Services.prefs.setBoolPref(GMP_PREF_LOG, true);
+  gPrefs.setBoolPref(GMPScope.KEY_LOGGING_DUMP, true);
+  gPrefs.setIntPref(GMPScope.KEY_LOGGING_LEVEL, 0);
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, true);
+  for (let addon of gMockAddons.values()) {
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_HIDDEN, addon.id), false);
+  }
+  GMPScope.GMPProvider.shutdown();
+  GMPScope.GMPProvider.startup();
 
   run_next_test();
 }
 
 add_task(function* test_notInstalled() {
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, "");
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
+  for (let addon of gMockAddons.values()) {
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, addon.id), "");
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), false);
+  }
 
-  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
-  Assert.equal(addons.length, 1);
-  let addon = addons[0];
+  let addons = yield promiseAddonsByIDs([...gMockAddons.keys()]);
+  Assert.equal(addons.length, gMockAddons.size);
+
+  for (let addon of addons) {
+    Assert.ok(!addon.isInstalled);
+    Assert.equal(addon.type, "plugin");
+    Assert.equal(addon.version, "");
 
-  Assert.ok(!addon.isInstalled);
-  Assert.equal(addon.type, "plugin");
-  Assert.equal(addon.version, "");
+    let mockAddon = gMockAddons.get(addon.id);
 
-  let name = pluginsBundle.GetStringFromName("openH264_name");
-  Assert.equal(addon.name, name);
-  let description = pluginsBundle.GetStringFromName("openH264_description");
-  Assert.equal(addon.description, description);
-
-  Assert.ok(!addon.isActive);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.userDisabled);
+    Assert.notEqual(mockAddon, null);
+    let name = mockAddon.nameId;
+    try {
+      name = pluginsBundle.GetStringFromName(mockAddon.nameId);
+    } catch (ex) { } // Not all GMPs have a localized name.
+    Assert.equal(addon.name, name);
+    let description = mockAddon.descriptionId;
+    try {
+      description = pluginsBundle.GetStringFromName(mockAddon.descriptionId);
+    } catch (ex) { } // Not all GMPs have a localized description.
+    Assert.equal(addon.description, description);
 
-  Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
-  Assert.equal(addon.size, 0);
-  Assert.equal(addon.scope, AddonManager.SCOPE_APPLICATION);
-  Assert.equal(addon.pendingOperations, AddonManager.PENDING_NONE);
-  Assert.equal(addon.operationsRequiringRestart, AddonManager.PENDING_NONE);
+    Assert.ok(!addon.isActive);
+    Assert.ok(!addon.appDisabled);
+    Assert.ok(addon.userDisabled);
 
-  Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
-                                  AddonManager.PERM_CAN_ENABLE);
+    Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+    Assert.equal(addon.size, 0);
+    Assert.equal(addon.scope, AddonManager.SCOPE_APPLICATION);
+    Assert.equal(addon.pendingOperations, AddonManager.PENDING_NONE);
+    Assert.equal(addon.operationsRequiringRestart, AddonManager.PENDING_NONE);
 
-  Assert.equal(addon.updateDate, null);
+    Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
+                                    AddonManager.PERM_CAN_ENABLE);
 
-  Assert.ok(addon.isCompatible);
-  Assert.ok(addon.isPlatformCompatible);
-  Assert.ok(addon.providesUpdatesSecurely);
-  Assert.ok(!addon.foreignInstall);
+    Assert.equal(addon.updateDate, null);
+
+    Assert.ok(addon.isCompatible);
+    Assert.ok(addon.isPlatformCompatible);
+    Assert.ok(addon.providesUpdatesSecurely);
+    Assert.ok(!addon.foreignInstall);
 
-  let mimetypes = addon.pluginMimeTypes;
-  Assert.ok(mimetypes);
-  Assert.equal(mimetypes.length, 0);
-  let libraries = addon.pluginLibraries;
-  Assert.ok(libraries);
-  Assert.equal(libraries.length, 0);
-  Assert.equal(addon.pluginFullpath, "");
+    let mimetypes = addon.pluginMimeTypes;
+    Assert.ok(mimetypes);
+    Assert.equal(mimetypes.length, 0);
+    let libraries = addon.pluginLibraries;
+    Assert.ok(libraries);
+    Assert.equal(libraries.length, 0);
+    Assert.equal(addon.pluginFullpath, "");
+  }
 });
 
 add_task(function* test_installed() {
   const TEST_DATE = new Date(2013, 0, 1, 12);
   const TEST_VERSION = "1.2.3.4";
   const TEST_TIME_SEC = Math.round(TEST_DATE.getTime() / 1000);
 
-  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
-  file.append(OPENH264_PLUGIN_ID);
-  file.append(TEST_VERSION);
+  let addons = yield promiseAddonsByIDs([...gMockAddons.keys()]);
+  Assert.equal(addons.length, gMockAddons.size);
 
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
-  Services.prefs.setCharPref(OPENH264_PREF_LASTUPDATE, "" + TEST_TIME_SEC);
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, TEST_VERSION);
+  for (let addon of addons) {
+    let mockAddon = gMockAddons.get(addon.id);
+    Assert.notEqual(mockAddon, null);
 
-  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
-  Assert.equal(addons.length, 1);
-  let addon = addons[0];
+    let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    file.append(addon.id);
+    file.append(TEST_VERSION);
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_ENABLED, mockAddon.id), false);
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_LAST_UPDATE, mockAddon.id),
+                      "" + TEST_TIME_SEC);
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, mockAddon.id),
+                      TEST_VERSION);
 
-  Assert.ok(addon.isInstalled);
-  Assert.equal(addon.type, "plugin");
-  let name = pluginsBundle.GetStringFromName("openH264_name");
-  Assert.equal(addon.name, name);
-  Assert.equal(addon.version, TEST_VERSION);
+    Assert.ok(addon.isInstalled);
+    Assert.equal(addon.type, "plugin");
+    Assert.ok(!addon.isActive);
+    Assert.ok(!addon.appDisabled);
+    Assert.ok(addon.userDisabled);
 
-  Assert.ok(!addon.isActive);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.userDisabled);
+    let name = mockAddon.nameId;
+    try {
+      name = pluginsBundle.GetStringFromName(mockAddon.nameId);
+    } catch (ex) { } // Not all GMPs have a localized name.
+    Assert.equal(addon.name, name);
+    Assert.equal(addon.version, TEST_VERSION);
 
-  Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
-                                  AddonManager.PERM_CAN_ENABLE);
+    Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
+                                    AddonManager.PERM_CAN_ENABLE);
 
-  Assert.equal(addon.updateDate.getTime(), TEST_TIME_SEC * 1000);
+    Assert.equal(addon.updateDate.getTime(), TEST_TIME_SEC * 1000);
 
-  let mimetypes = addon.pluginMimeTypes;
-  Assert.ok(mimetypes);
-  Assert.equal(mimetypes.length, 0);
-  let libraries = addon.pluginLibraries;
-  Assert.ok(libraries);
-  Assert.equal(libraries.length, 1);
-  Assert.equal(libraries[0], TEST_VERSION);
-  let fullpath = addon.pluginFullpath;
-  Assert.equal(fullpath.length, 1);
-  Assert.equal(fullpath[0], file.path);
+    let mimetypes = addon.pluginMimeTypes;
+    Assert.ok(mimetypes);
+    Assert.equal(mimetypes.length, 0);
+    let libraries = addon.pluginLibraries;
+    Assert.ok(libraries);
+    Assert.equal(libraries.length, 1);
+    Assert.equal(libraries[0], TEST_VERSION);
+    let fullpath = addon.pluginFullpath;
+    Assert.equal(fullpath.length, 1);
+    Assert.equal(fullpath[0], file.path);
+  }
 });
 
 add_task(function* test_enable() {
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, true);
+  let addons = yield promiseAddonsByIDs([...gMockAddons.keys()]);
+  Assert.equal(addons.length, gMockAddons.size);
+
+  for (let addon of addons) {
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), true);
 
-  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
-  Assert.equal(addons.length, 1);
-  let addon = addons[0];
+    Assert.ok(addon.isActive);
+    Assert.ok(!addon.appDisabled);
+    Assert.ok(!addon.userDisabled);
+
+    Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
+                                    AddonManager.PERM_CAN_DISABLE);
+  }
+});
 
-  Assert.ok(addon.isActive);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(!addon.userDisabled);
+add_task(function* test_globalEmeDisabled() {
+  let addons = yield promiseAddonsByIDs([...gMockEmeAddons.keys()]);
+  Assert.equal(addons.length, gMockEmeAddons.size);
 
-  Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
-                                  AddonManager.PERM_CAN_DISABLE);
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, false);
+  GMPScope.GMPProvider.shutdown();
+  GMPScope.GMPProvider.startup();
+  for (let addon of addons) {
+    Assert.ok(!addon.isActive);
+    Assert.ok(!addon.appDisabled);
+    Assert.ok(addon.userDisabled);
+
+    Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
+                                    AddonManager.PERM_CAN_ENABLE);
+  }
+  gPrefs.setBoolPref(GMPScope.KEY_EME_ENABLED, true);
+  GMPScope.GMPProvider.shutdown();
+  GMPScope.GMPProvider.startup();
 });
 
 add_task(function* test_autoUpdatePrefPersistance() {
-  Services.prefs.clearUserPref(OPENH264_PREF_AUTOUPDATE);
-  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
-  let prefs = Services.prefs;
-  Assert.equal(addons.length, 1);
-  let addon = addons[0];
+  let addons = yield promiseAddonsByIDs([...gMockAddons.keys()]);
+  Assert.equal(addons.length, gMockAddons.size);
+
+  for (let addon of addons) {
+    let autoupdateKey = gGetKey(GMPScope.KEY_PLUGIN_AUTOUPDATE, addon.id);
+    gPrefs.clearUserPref(autoupdateKey);
 
-  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
-  Assert.ok(!prefs.getBoolPref(OPENH264_PREF_AUTOUPDATE));
+    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+    Assert.ok(!gPrefs.getBoolPref(autoupdateKey));
 
-  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
-  Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE);
-  Assert.ok(prefs.getBoolPref(OPENH264_PREF_AUTOUPDATE));
+    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
+    Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE);
+    Assert.ok(gPrefs.getBoolPref(autoupdateKey));
 
-  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
-  Assert.ok(!prefs.prefHasUserValue(OPENH264_PREF_AUTOUPDATE));
+    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+    Assert.ok(!gPrefs.prefHasUserValue(autoupdateKey));
+  }
 });
 
 add_task(function* test_pluginRegistration() {
   const TEST_VERSION = "1.2.3.4";
 
-  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
-  file.append(OPENH264_PLUGIN_ID);
-  file.append(TEST_VERSION);
+  for (let addon of gMockAddons.values()) {
+    let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    file.append(addon.id);
+    file.append(TEST_VERSION);
 
-  let addedPaths = [];
-  let removedPaths = [];
-  let clearPaths = () => { addedPaths = []; removedPaths = []; }
+    let addedPaths = [];
+    let removedPaths = [];
+    let clearPaths = () => { addedPaths = []; removedPaths = []; }
 
-  let MockGMPService = {
-    addPluginDirectory: path => addedPaths.push(path),
-    removePluginDirectory: path => removedPaths.push(path),
-  };
+    let MockGMPService = {
+      addPluginDirectory: path => addedPaths.push(path),
+      removePluginDirectory: path => removedPaths.push(path),
+    };
 
-  let OpenH264Scope = Cu.import("resource://gre/modules/addons/OpenH264Provider.jsm");
-  OpenH264Scope.gmpService = MockGMPService;
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, true);
+    GMPScope.gmpService = MockGMPService;
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), true);
 
-  // Check that the OpenH264 plugin gets registered after startup.
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, TEST_VERSION);
-  clearPaths();
-  yield promiseRestartManager();
-  Assert.notEqual(addedPaths.indexOf(file.path), -1);
-  Assert.deepEqual(removedPaths, []);
+    // Check that the plugin gets registered after startup.
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, addon.id),
+                      TEST_VERSION);
+    clearPaths();
+    yield promiseRestartManager();
+    Assert.notEqual(addedPaths.indexOf(file.path), -1);
+    Assert.deepEqual(removedPaths, []);
 
-  // Check that clearing the version doesn't trigger registration.
-  clearPaths();
-  Services.prefs.clearUserPref(OPENH264_PREF_VERSION);
-  Assert.deepEqual(addedPaths, []);
-  Assert.deepEqual(removedPaths, [file.path]);
+    // Check that clearing the version doesn't trigger registration.
+    clearPaths();
+    gPrefs.clearUserPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, addon.id));
+    Assert.deepEqual(addedPaths, []);
+    Assert.deepEqual(removedPaths, [file.path]);
 
-  // Restarting with no version set should not trigger registration.
-  clearPaths();
-  yield promiseRestartManager();
-  Assert.equal(addedPaths.indexOf(file.path), -1);
-  Assert.equal(removedPaths.indexOf(file.path), -1);
+    // Restarting with no version set should not trigger registration.
+    clearPaths();
+    yield promiseRestartManager();
+    Assert.equal(addedPaths.indexOf(file.path), -1);
+    Assert.equal(removedPaths.indexOf(file.path), -1);
 
-  // Changing the pref mid-session should cause unregistration and registration.
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, TEST_VERSION);
-  clearPaths();
-  const TEST_VERSION_2 = "5.6.7.8";
-  let file2 = Services.dirsvc.get("ProfD", Ci.nsIFile);
-  file2.append(OPENH264_PLUGIN_ID);
-  file2.append(TEST_VERSION_2);
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, TEST_VERSION_2);
-  Assert.deepEqual(addedPaths, [file2.path]);
-  Assert.deepEqual(removedPaths, [file.path]);
+    // Changing the pref mid-session should cause unregistration and registration.
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, addon.id),
+                      TEST_VERSION);
+    clearPaths();
+    const TEST_VERSION_2 = "5.6.7.8";
+    let file2 = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    file2.append(addon.id);
+    file2.append(TEST_VERSION_2);
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, addon.id),
+                      TEST_VERSION_2);
+    Assert.deepEqual(addedPaths, [file2.path]);
+    Assert.deepEqual(removedPaths, [file.path]);
 
-  // Disabling OpenH264 should cause unregistration.
-  Services.prefs.setCharPref(OPENH264_PREF_VERSION, TEST_VERSION);
-  clearPaths();
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
-  Assert.deepEqual(addedPaths, []);
-  Assert.deepEqual(removedPaths, [file.path]);
+    // Disabling the plugin should cause unregistration.
+    gPrefs.setCharPref(gGetKey(GMPScope.KEY_PLUGIN_VERSION, addon.id),
+                      TEST_VERSION);
+    clearPaths();
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), false);
+    Assert.deepEqual(addedPaths, []);
+    Assert.deepEqual(removedPaths, [file.path]);
 
-  // Restarting with OpenH264 disabled should not cause registration.
-  clearPaths();
-  yield promiseRestartManager();
-  Assert.equal(addedPaths.indexOf(file.path), -1);
-  Assert.equal(removedPaths.indexOf(file.path), -1);
+    // Restarting with the plugin disabled should not cause registration.
+    clearPaths();
+    yield promiseRestartManager();
+    Assert.equal(addedPaths.indexOf(file.path), -1);
+    Assert.equal(removedPaths.indexOf(file.path), -1);
 
-  // Re-enabling OpenH264 should cause registration.
-  clearPaths();
-  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, true);
-  Assert.deepEqual(addedPaths, [file.path]);
-  Assert.deepEqual(removedPaths, []);
+    // Re-enabling the plugin should cause registration.
+    clearPaths();
+    gPrefs.setBoolPref(gGetKey(GMPScope.KEY_PLUGIN_ENABLED, addon.id), true);
+    Assert.deepEqual(addedPaths, [file.path]);
+    Assert.deepEqual(removedPaths, []);
+    GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
+  }
 });
 
 add_task(function* test_periodicUpdate() {
-  let OpenH264Scope = Cu.import("resource://gre/modules/addons/OpenH264Provider.jsm");
-  Object.defineProperty(OpenH264Scope, "GMPInstallManager", {
+  Object.defineProperty(GMPScope, "GMPInstallManager", {
     value: MockGMPInstallManager,
     writable: true,
     enumerable: true,
     configurable: true
   });
 
-  Services.prefs.clearUserPref(OPENH264_PREF_AUTOUPDATE);
-  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
-  let prefs = Services.prefs;
-  Assert.equal(addons.length, 1);
-  let addon = addons[0];
+  let addons = yield promiseAddonsByIDs([...gMockAddons.keys()]);
+  Assert.equal(addons.length, gMockAddons.size);
+
+  for (let addon of addons) {
+    gPrefs.clearUserPref(gGetKey(GMPScope.KEY_PLUGIN_AUTOUPDATE, addon.id));
 
-  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
-  Services.prefs.setIntPref(GMP_PREF_LASTCHECK, 0);
-  let result = yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
-  Assert.strictEqual(result, false);
+    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+    gPrefs.setIntPref(GMPScope.KEY_PROVIDER_LASTCHECK, 0);
+    let result =
+      yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+    Assert.strictEqual(result, false);
 
-  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
-  Services.prefs.setIntPref(GMP_PREF_LASTCHECK, Date.now() / 1000 - 60);
-  result = yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
-  Assert.strictEqual(result, false);
+    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
+    gPrefs.setIntPref(GMPScope.KEY_PROVIDER_LASTCHECK, Date.now() / 1000 - 60);
+    result =
+      yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+    Assert.strictEqual(result, false);
 
-  Services.prefs.setIntPref(GMP_PREF_LASTCHECK, Date.now() / 1000 - 2 * SEC_IN_A_DAY);
-  gInstalledAddonId = "";
-  result = yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
-  Assert.strictEqual(result, true);
-  Assert.equal(gInstalledAddonId, OPENH264_PLUGIN_ID);
+    gPrefs.setIntPref(GMPScope.KEY_PROVIDER_LASTCHECK,
+                     Date.now() / 1000 - 2 * GMPScope.SEC_IN_A_DAY);
+    gInstalledAddonId = "";
+    result =
+      yield addon.findUpdates({}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+    Assert.strictEqual(result, true);
+    Assert.equal(gInstalledAddonId, addon.id);
+  }
+
+  GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js
@@ -62,17 +62,17 @@ registrar.registerFactory(Components.ID(
                           "@mozilla.org/plugin/host;1", PluginHostFactory);
 
 // This verifies that when the list of plugins changes the add-ons manager
 // correctly updates
 function run_test() {
   do_test_pending();
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
-  Services.prefs.setBoolPref("media.gmp-gmpopenh264.provider.enabled", false);
+  Services.prefs.setBoolPref("media.gmp-provider.enabled", false);
 
   startupManager();
   AddonManager.addAddonListener(AddonListener);
   AddonManager.addInstallListener(InstallListener);
 
   run_test_1();
 }
 
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -6,18 +6,18 @@ firefox-appdir = browser
 dupe-manifest =
 support-files =
   data/**
   xpcshell-shared.ini
 
 [test_addon_path_service.js]
 [test_asyncBlocklistLoad.js]
 [test_DeferredSave.js]
+[test_gmpProvider.js]
+run-if = appname == "firefox"
 [test_metadata_update.js]
-[test_openh264.js]
-run-if = appname == "firefox"
 [test_pluginInfoURL.js]
 [test_provider_shutdown.js]
 [test_shutdown.js]
 [test_XPIcancel.js]
 [test_XPIStates.js]
 
 [include:xpcshell-shared.ini]