Bug 1009909 - Firefox desktop: Integrate the openh264 media plugin in the add-ons manager. r=unfocused
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Wed, 16 Jul 2014 15:49:30 +0200
changeset 216509 889755c1797ba912b8c43bdda6177be3b31ee1b0
parent 216270 4024d8019701d7704807d451172a65008b0702cf
child 216510 5e3d604b364bb93eb993c70c5ac763bfcb927e03
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersunfocused
bugs1009909
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1009909 - Firefox desktop: Integrate the openh264 media plugin in the add-ons manager. r=unfocused
browser/app/profile/firefox.js
dom/locales/en-US/chrome/plugins.properties
toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/content/openH264Prefs.xul
toolkit/mozapps/extensions/extensions.manifest
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_openH264.js
toolkit/mozapps/extensions/test/xpcshell/test_duplicateplugins.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
@@ -1596,8 +1596,11 @@ 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%");
 pref("experiments.manifest.certs.1.commonName", "*.cdn.mozilla.net");
 pref("experiments.manifest.certs.1.issuerName", "CN=Cybertrust Public SureServer SV CA,O=Cybertrust Inc");
 // Whether experiments are supported by the current application profile.
 pref("experiments.supported", true);
+
+// Enable the OpenH264 plugin support in the addon manager.
+pref("media.openh264.providerEnabled", true);
--- a/dom/locales/en-US/chrome/plugins.properties
+++ b/dom/locales/en-US/chrome/plugins.properties
@@ -14,8 +14,11 @@ file_label=File:
 path_label=Path:
 version_label=Version:
 state_label=State:
 state_enabled=Enabled
 state_disabled=Disabled
 mimetype_label=MIME Type
 description_label=Description
 suffixes_label=Suffixes
+
+openH264_name=OpenH264 Video Codec provided by Cisco Systems, Inc.
+openH264_description=Play back web video and use video chats.
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
@@ -51,16 +51,18 @@ notification.upgrade=%1$S will be update
 #LOCALIZATION NOTE (notification.downloadError) %1$S is the add-on name.
 notification.downloadError=There was an error downloading %1$S.
 notification.downloadError.retry=Try again
 notification.downloadError.retry.tooltip=Try downloading this add-on again
 #LOCALIZATION NOTE (notification.installError) %1$S is the add-on name.
 notification.installError=There was an error installing %1$S.
 notification.installError.retry=Try again
 notification.installError.retry.tooltip=Try downloading and installing this add-on again
+#LOCALIZATION NOTE (notification.openH264Pending) OpenH264 will be automatically installed later.
+notification.openH264Pending=Will be installed shortly.
 
 #LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions
 contributionAmount2=Suggested Contribution: %S
 
 installDownloading=Downloading
 installDownloaded=Downloaded
 installDownloadFailed=Error downloading
 installVerifying=Verifying
@@ -92,16 +94,18 @@ details.notification.enable=%1$S will be
 #LOCALIZATION NOTE (details.notification.disable) %1$S is the add-on name, %2$S is brand name
 details.notification.disable=%1$S will be disabled after you restart %2$S.
 #LOCALIZATION NOTE (details.notification.install) %1$S is the add-on name, %2$S is brand name
 details.notification.install=%1$S will be installed after you restart %2$S.
 #LOCALIZATION NOTE (details.notification.uninstall) %1$S is the add-on name, %2$S is brand name
 details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
 #LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
 details.notification.upgrade=%1$S will be updated after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.openH264Pending) OpenH264 will be automatically installed later.
+details.notification.openH264Pending=Will be installed shortly.
 
 # LOCALIZATION NOTE (details.experiment.time.daysRemaining):
 # Semicolon-separated list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is the number of days from now that the experiment will remain active (detail view).
 details.experiment.time.daysRemaining=#1 day remaining;#1 days remaining
 #LOCALIZATION NOTE (details.experiment.time.endsToday) The experiment will end in less than a day (detail view).
 details.experiment.time.endsToday=Less than a day remaining
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -3083,16 +3083,20 @@ 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-plugin@cisco.com" && !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");
       }
     }
 
     let menulist = document.getElementById("detail-state-menulist");
     let addonType = AddonManager.addonTypes[this._addon.type];
     if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -1289,16 +1289,19 @@
               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 == "openh264-plugin@cisco.com" && !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) ||
                                         this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO;
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/openH264Prefs.xul
@@ -0,0 +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
+     have a preferences button in the list view. -->
--- a/toolkit/mozapps/extensions/extensions.manifest
+++ b/toolkit/mozapps/extensions/extensions.manifest
@@ -9,9 +9,10 @@ category update-timer addonManager @mozi
 component {7beb3ba8-6ec3-41b4-b67c-da89b8518922} amContentHandler.js
 contract @mozilla.org/uriloader/content-handler;1?type=application/x-xpinstall {7beb3ba8-6ec3-41b4-b67c-da89b8518922}
 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
 category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
+category addon-provider-module OpenH264Provider resource://gre/modules/addons/OpenH264Provider.jsm
 #endif
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/internal/OpenH264Provider.jsm
@@ -0,0 +1,234 @@
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+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");
+
+const URI_EXTENSION_STRINGS    = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME         = "type.%ID%.name";
+
+const OPENH264_PLUGIN_ID       = "openh264-plugin@cisco.com";
+const OPENH264_PREF_BRANCH     = "media.openh264.";
+const OPENH264_PREF_ENABLED    = "enabled";
+const OPENH264_PREF_PATH       = "path";
+const OPENH264_PREF_VERSION    = "version";
+const OPENH264_PREF_LASTUPDATE = "lastUpdate";
+const OPENH264_PREF_AUTOUPDATE = "autoupdate";
+const OPENH264_PREF_PROVIDERENABLED = "providerEnabled";
+const OPENH264_HOMEPAGE_URL    = "http://www.openh264.org/";
+const OPENH264_OPTIONS_URL     = "chrome://mozapps/content/extensions/openH264Prefs.xul";
+
+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));
+
+/**
+ * The OpenH264Wrapper provides the info for the OpenH264 GMP plugin to public callers through the API.
+ */
+
+let OpenH264Wrapper = Object.freeze({
+  optionsType: AddonManager.OPTIONS_TYPE_INLINE,
+  optionsURL: OPENH264_OPTIONS_URL,
+
+  get id() { return OPENH264_PLUGIN_ID; },
+  get type() { return "plugin"; },
+  get name() { return pluginsBundle.GetStringFromName("openH264_name"); },
+  get creator() { return null; },
+  get homepageURL() { return OPENH264_HOMEPAGE_URL; },
+
+  get description() { return pluginsBundle.GetStringFromName("openH264_description"); },
+
+  get version() {
+    if (this.isInstalled) {
+      return prefs.get(OPENH264_PREF_VERSION, "");
+    }
+    return "";
+  },
+
+  get isActive() { return !this.userDisabled; },
+  get appDisabled() { return false; },
+
+  get userDisabled() { return !prefs.get(OPENH264_PREF_ENABLED, false); },
+  set userDisabled(aVal) { prefs.set(OPENH264_PREF_ENABLED, aVal === false); },
+
+  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 },
+
+  get permissions() {
+    let permissions = AddonManager.PERM_CAN_UPGRADE;
+    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));
+    if (time !== NaN && this.isInstalled) {
+      return new Date(time)
+    }
+    return null;
+  },
+
+  get isCompatible() {
+    return true;
+  },
+
+  get isPlatformCompatible() {
+    return true;
+  },
+
+  get providesUpdatesSecurely() {
+    return true;
+  },
+
+  get foreignInstall() {
+    return false;
+  },
+
+  isCompatibleWith: function(aAppVersion, aPlatformVersion) {
+    return true;
+  },
+
+  get applyBackgroundUpdates() {
+    if (!prefs.isSet(OPENH264_PREF_AUTOUPDATE)) {
+      return AddonManager.AUTOUPDATE_DEFAULT;
+    }
+
+    return prefs.get(OPENH264_PREF_AUTOUPDATE, true) ?
+      AddonManager.AUTOUPDATE_ENABLE : AddonManager.AUTOUPDATE_DISABLE;
+  },
+
+  set applyBackgroundUpdates(aVal) {
+    if (aVal == AddonManager.AUTOUPDATE_DEFAULT) {
+      prefs.reset(OPENH264_PREF_AUTOUPDATE);
+    } else if (aVal == AddonManager.AUTOUPDATE_ENABLE) {
+      prefs.set(OPENH264_PREF_AUTOUPDATE, true);
+    } else if (aVal == AddonManager.AUTOUPDATE_DISABLE) {
+      prefs.set(OPENH264_PREF_AUTOUPDATE, false);
+    }
+  },
+
+  findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
+    // TODO: Hook up to openh264 update for AddonManager.UPDATE_WHEN_USER_REQUESTED (pending bug 1035225)
+
+    if ("onNoCompatibilityUpdateAvailable" in aListener)
+      aListener.onNoCompatibilityUpdateAvailable(this);
+    if ("onNoUpdateAvailable" in aListener)
+      aListener.onNoUpdateAvailable(this);
+    if ("onUpdateFinished" in aListener)
+      aListener.onUpdateFinished(this);
+  },
+
+  get pluginMimeTypes() { return []; },
+  get pluginLibraries() {
+    let path = prefs.get(OPENH264_PREF_PATH, null);
+    return path && path.length ? [OS.Path.basename(path)] : [];
+  },
+  get pluginFullpath() {
+    let path = prefs.get(OPENH264_PREF_PATH, null);
+    return path && path.length ? [path] : [];
+  },
+
+  get isInstalled() {
+    let path = prefs.get(OPENH264_PREF_PATH, "");
+    return path.length > 0;
+  },
+});
+
+let OpenH264Provider = {
+  startup: function() {
+    Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
+    prefs.observe(OPENH264_PREF_ENABLED, this.onPrefEnabledChanged, this);
+    prefs.observe(OPENH264_PREF_PATH, this.onPrefPathChanged, this);
+  },
+
+  shutdown: function() {
+    Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
+    prefs.ignore(OPENH264_PREF_ENABLED, this.onPrefEnabledChanged, this);
+    prefs.ignore(OPENH264_PREF_PATH, this.onPrefPathChanged, this);
+  },
+
+  onPrefEnabledChanged: function() {
+    let wrapper = OpenH264Wrapper;
+
+    AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
+                                           "onEnabling" : "onDisabling",
+                                           wrapper, false);
+    AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
+                                           "onEnabled" : "onDisabled",
+                                           wrapper);
+  },
+
+  onPrefPathChanged: function() {
+    let wrapper = OpenH264Wrapper;
+
+    AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
+    if (this.gmpPath) {
+      gmpService.removePluginDirectory(this.gmpPath);
+    }
+    AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+
+    AddonManagerPrivate.callInstallListeners("onExternalInstall", null, wrapper, null, false);
+    this.gmpPath = prefs.get(OPENH264_PREF_PATH, null);
+    if (this.gmpPath) {
+      gmpService.addPluginDirectory(this.gmpPath);
+    }
+    AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
+  },
+
+  buildWrapper: function() {
+    let description = pluginsBundle.GetStringFromName("openH264_description");
+    return new OpenH264Wrapper(OPENH264_PLUGIN_ID,
+                               OPENH264_PLUGIN_NAME,
+                               description);
+  },
+
+  getAddonByID: function(aId, aCallback) {
+    if (this.isEnabled && aId == OPENH264_PLUGIN_ID) {
+      aCallback(OpenH264Wrapper);
+    } else {
+      aCallback(null);
+    }
+  },
+
+  getAddonsByTypes: function(aTypes, aCallback) {
+    if (!this.isEnabled || aTypes && aTypes.indexOf("plugin") < 0) {
+      aCallback([]);
+      return;
+    }
+
+    aCallback([OpenH264Wrapper]);
+  },
+
+  get isEnabled() {
+    return prefs.get(OPENH264_PREF_PROVIDERENABLED, false);
+  },
+};
+
+AddonManagerPrivate.registerProvider(OpenH264Provider, [
+  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
@@ -8,16 +8,17 @@ JS_MODULES_PATH = 'modules/addons'
 
 EXTRA_JS_MODULES += [
     'AddonLogging.jsm',
     'AddonRepository.jsm',
     'AddonRepository_SQLiteMigrator.jsm',
     'AddonUpdateChecker.jsm',
     'Content.js',
     'LightweightThemeImageOptimizer.jsm',
+    'OpenH264Provider.jsm',
     'PluginProvider.jsm',
     'SpellCheckDictionaryBootstrap.js',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'XPIProvider.jsm',
     'XPIProviderUtils.js',
 ]
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -24,12 +24,13 @@ 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/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
@@ -36,16 +36,17 @@ support-files =
 [browser_addonrepository_performance.js]
 [browser_bug557956.js]
 [browser_bug616841.js]
 [browser_cancelCompatCheck.js]
 [browser_checkAddonCompatibility.js]
 [browser_hotfix.js]
 [browser_installssl.js]
 [browser_newaddon.js]
+[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]
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_openH264.js
@@ -0,0 +1,182 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let {AddonTestUtils} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
+
+const OPENH264_PLUGIN_ID       = "openh264-plugin@cisco.com";
+const OPENH264_PREF_BRANCH     = "media.openh264.";
+const OPENH264_PREF_ENABLED    = OPENH264_PREF_BRANCH + "enabled";
+const OPENH264_PREF_PATH       = OPENH264_PREF_BRANCH + "path";
+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 TEST_DATE = new Date(2013, 0, 1, 12);
+
+let gManagerWindow;
+let gCategoryUtilities;
+let gIsEnUsLocale;
+
+let gOptionsObserver = {
+  lastDisplayed: null,
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == AddonManager.OPTIONS_NOTIFICATION_DISPLAYED) {
+      this.lastDisplayed = aData;
+    }
+  }
+};
+
+function getInstallItem() {
+  let doc = gManagerWindow.document;
+  let list = doc.getElementById("addon-list");
+
+  let node = list.firstChild;
+  while (node) {
+    if (node.getAttribute("status") == "installing") {
+      return node;
+    }
+    node = node.nextSibling;
+  }
+
+  return null;
+}
+
+function openDetailsView(aId) {
+  let item = get_addon_element(gManagerWindow, aId);
+  Assert.ok(item, "Should have got add-on element.");
+  is_element_visible(item, "Add-on element should be visible.");
+
+  EventUtils.synthesizeMouseAtCenter(item, { clickCount: 1 }, gManagerWindow);
+  EventUtils.synthesizeMouseAtCenter(item, { clickCount: 2 }, gManagerWindow);
+
+  let deferred = Promise.defer();
+  wait_for_view_load(gManagerWindow, deferred.resolve);
+  return deferred.promise;
+}
+
+add_task(function* initializeState() {
+  gManagerWindow = yield open_manager();
+  gCategoryUtilities = new CategoryUtilities(gManagerWindow);
+
+  registerCleanupFunction(() => {
+    Services.obs.removeObserver(gOptionsObserver, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
+
+    Services.prefs.clearUserPref(OPENH264_PREF_ENABLED);
+    Services.prefs.clearUserPref(OPENH264_PREF_PATH);
+    Services.prefs.clearUserPref(OPENH264_PREF_VERSION);
+    Services.prefs.clearUserPref(OPENH264_PREF_LASTUPDATE);
+    Services.prefs.clearUserPref(OPENH264_PREF_AUTOUPDATE);
+  });
+
+  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.setCharPref(OPENH264_PREF_VERSION, "");
+  Services.prefs.setCharPref(OPENH264_PREF_LASTUPDATE, "");
+  Services.prefs.setBoolPref(OPENH264_PREF_AUTOUPDATE, false);
+  Services.prefs.setCharPref(OPENH264_PREF_PATH, "");
+});
+
+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");
+
+  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 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;
+
+  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.setBoolPref(OPENH264_PREF_VERSION, "1.2.3.4");
+  Services.prefs.setBoolPref(OPENH264_PREF_LASTUPDATE, "" + TEST_DATE.getTime());
+  Services.prefs.setBoolPref(OPENH264_PREF_AUTOUPDATE, false);
+  Services.prefs.setCharPref(OPENH264_PREF_PATH, "foo/bar");
+
+  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 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 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;
+
+  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];
+});
+
+add_task(function* testPreferencesButton() {
+  yield gCategoryUtilities.openType("plugin");
+  let doc = gManagerWindow.document;
+  let item = get_addon_element(gManagerWindow, OPENH264_PLUGIN_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, OPENH264_PLUGIN_ID);
+});
+
+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,16 +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.openh264.providerEnabled", false);
 
   startupManager();
 
   run_test_1();
 }
 
 function found_plugin(aNum, aId) {
   if (gPluginIDs[aNum])
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_openh264.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const Ci = Components.interfaces;
+
+const OPENH264_PLUGIN_ID       = "openh264-plugin@cisco.com";
+const OPENH264_PREF_BRANCH     = "media.openh264.";
+const OPENH264_PREF_ENABLED    = OPENH264_PREF_BRANCH + "enabled";
+const OPENH264_PREF_PATH       = OPENH264_PREF_BRANCH + "path";
+const OPENH264_PREF_VERSION    = OPENH264_PREF_BRANCH + "version";
+const OPENH264_PREF_LASTUPDATE = OPENH264_PREF_BRANCH + "lastUpdate";
+const OPENH264_PREF_AUTOUPDATE = OPENH264_PREF_BRANCH + "autoupdate";
+
+XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
+  () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
+
+let gProfileDir = null;
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+  startupManager();
+  run_next_test();
+}
+
+add_task(function* test_notInstalled() {
+  Services.prefs.setCharPref(OPENH264_PREF_PATH, "");
+  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
+
+  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
+  Assert.equal(addons.length, 1);
+  let addon = addons[0];
+
+  Assert.ok(!addon.isInstalled);
+  Assert.equal(addon.type, "plugin");
+  Assert.equal(addon.version, "");
+
+  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.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.permissions, AddonManager.PERM_CAN_UPGRADE |
+                                  AddonManager.PERM_CAN_ENABLE);
+
+  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, "");
+});
+
+add_task(function* test_installed() {
+  const TEST_DATE = new Date(2013, 0, 1, 12);
+  const TEST_VERSION = "1.2.3.4";
+
+  let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  file.append("openh264");
+  file.append("testDir");
+
+  Services.prefs.setBoolPref(OPENH264_PREF_ENABLED, false);
+  Services.prefs.setCharPref(OPENH264_PREF_LASTUPDATE, "" + TEST_DATE.getTime());
+  Services.prefs.setCharPref(OPENH264_PREF_VERSION, TEST_VERSION);
+  Services.prefs.setCharPref(OPENH264_PREF_PATH, file.path);
+
+  let addons = yield promiseAddonsByIDs([OPENH264_PLUGIN_ID]);
+  Assert.equal(addons.length, 1);
+  let addon = addons[0];
+
+  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.isActive);
+  Assert.ok(!addon.appDisabled);
+  Assert.ok(addon.userDisabled);
+
+  Assert.equal(addon.permissions, AddonManager.PERM_CAN_UPGRADE |
+                                  AddonManager.PERM_CAN_ENABLE);
+
+  Assert.equal(addon.updateDate.getTime(), TEST_DATE.getTime());
+
+  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], "testDir");
+  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([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);
+});
+
+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];
+
+  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
+  Assert.ok(!prefs.getBoolPref(OPENH264_PREF_AUTOUPDATE));
+
+  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
+  Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE);
+  Assert.ok(prefs.getBoolPref(OPENH264_PREF_AUTOUPDATE));
+
+  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+  Assert.ok(!prefs.prefHasUserValue(OPENH264_PREF_AUTOUPDATE));
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginchange.js
@@ -62,16 +62,18 @@ 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.openh264.providerEnabled", false);
+
   startupManager();
   AddonManager.addAddonListener(AddonListener);
   AddonManager.addInstallListener(InstallListener);
 
   run_test_1();
 }
 
 function end_test() {
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -9,8 +9,9 @@ support-files =
 
 [include:xpcshell-shared.ini]
 
 [test_addon_path_service.js]
 [test_asyncBlocklistLoad.js]
 [test_DeferredSave.js]
 [test_XPIcancel.js]
 [test_metadata_update.js]
+[test_openh264.js]