Bug 1156566 - Don't add invalid GMPs to GMPService in GMPProvider. r=spohl, a=lizzard
authorChris Pearce <cpearce@mozilla.com>
Fri, 15 May 2015 11:50:40 +1200
changeset 274669 a7f3541d133bf220ecbcfda808ec3af6c1394ee4
parent 274668 d00b220688b3eab30236cfe9af8b536fe34d235b
child 274670 dce1bb4fe6d8b2eb207e28580fec7b2af83a11c6
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersspohl, lizzard
bugs1156566
milestone40.0a2
Bug 1156566 - Don't add invalid GMPs to GMPService in GMPProvider. r=spohl, a=lizzard
toolkit/modules/GMPUtils.jsm
toolkit/mozapps/extensions/internal/GMPProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
--- a/toolkit/modules/GMPUtils.jsm
+++ b/toolkit/modules/GMPUtils.jsm
@@ -84,18 +84,18 @@ this.GMPPrefs = {
   KEY_UPDATE_LAST_CHECK:        "media.gmp-manager.lastCheck",
   KEY_SECONDS_BETWEEN_CHECKS:   "media.gmp-manager.secondsBetweenChecks",
   KEY_APP_DISTRIBUTION:         "distribution.id",
   KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
   KEY_BUILDID:                  "media.gmp-manager.buildID",
   KEY_CERTS_BRANCH:             "media.gmp-manager.certs.",
   KEY_PROVIDER_ENABLED:         "media.gmp-provider.enabled",
   KEY_LOG_BASE:                 "media.gmp.log.",
-  KEY_LOGGING_LEVEL:            this.KEY_LOG_BASE + "level",
-  KEY_LOGGING_DUMP:             this.KEY_LOG_BASE + "dump",
+  KEY_LOGGING_LEVEL:            "media.gmp.log.level",
+  KEY_LOGGING_DUMP:             "media.gmp.log.dump",
 
   /**
    * 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.
    */
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -442,16 +442,53 @@ GMPWrapper.prototype = {
                        this.onPrefVersionChanged, this);
     if (this._plugin.isEME) {
       Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
                          this.onPrefEMEGlobalEnabledChanged, this);
       messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
     }
     return this._updateTask;
   },
+
+  _arePluginFilesOnDisk: function () {
+    let fileExists = function(aGmpPath, aFileName) {
+      let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+      let path = OS.Path.join(aGmpPath, aFileName);
+      f.initWithPath(path);
+      return f.exists();
+    };
+
+    // Determine the name of the GMP dynamic library; it differs on every
+    // platform. Note: we can't use Services.appInfo.OS here, as that's
+    // "XPCShell" in our tests.
+    let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
+    let isOSX = ("nsILocalFileMac" in Ci);
+    let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
+
+    let libName = "";
+    let id = this._plugin.id;
+    if (isWindows) {
+      libName = id.substring(4) + ".dll";
+    } else if (isOSX) {
+      libName = "lib" + id.substring(4) + ".dylib";
+    } else if (isLinux) {
+      libName = id.substring(4) + ".so";
+    } else {
+      this._info.error("_arePluginFilesOnDisk - unsupported platform.");
+      return false;
+    }
+
+    return fileExists(this.gmpPath, libName) &&
+           fileExists(this.gmpPath, id.substring(4) + ".info");
+  },
+
+  validate: function() {
+    return !this.isInstalled ||
+           this._arePluginFilesOnDisk();
+  },
 };
 
 let GMPProvider = {
   get name() { return "GMPProvider"; },
 
   _plugins: null,
 
   startup: function() {
@@ -467,16 +504,22 @@ let GMPProvider = {
     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);
 
       if (gmpPath && isEnabled) {
+        if (!wrapper.validate()) {
+          this._log.info("startup - gmp " + plugin.id +
+                         " missing lib and/or info files, uninstalling");
+          wrapper.uninstallPlugin();
+          continue;
+        }
         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);
         }
       }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
@@ -4,16 +4,19 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 let GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
 
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+
 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,
@@ -211,40 +214,102 @@ add_task(function* test_autoUpdatePrefPe
     Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE);
     Assert.ok(gPrefs.getBoolPref(autoupdateKey));
 
     addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
     Assert.ok(!gPrefs.prefHasUserValue(autoupdateKey));
   }
 });
 
+function createMockPluginFilesIfNeeded(aFile, aPluginId) {
+  function createFile(aFileName) {
+    let f = aFile.clone();
+    f.append(aFileName);
+    if (!f.exists()) {
+      f.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+    }
+  };
+
+  // Note: we can't use Services.appInfo.OS, as that's "XPCShell" in our tests.
+  let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
+  let isOSX = ("nsILocalFileMac" in Components.interfaces);
+  let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Components.classes);
+
+  let libName = "";
+  if (isWindows) {
+    libName = aPluginId.substring(4) + ".dll";
+  } else if (isOSX) {
+    libName = "lib" + aPluginId.substring(4) + ".dylib";
+  } else if (isLinux) {
+    libName = aPluginId.substring(4) + ".so";
+  } else {
+    // FAIL!
+    return;
+  }
+  createFile(libName);
+  createFile(aPluginId.substring(4) + ".info");
+}
+
+// Array.includes is only in 41, so polyfill for 40/39 uplift.
+if (![].includes) {
+  Array.prototype.includes = function(element) {
+    var O = Object(this);
+    return O.indexOf(element) != -1;
+  }
+}
+
 add_task(function* test_pluginRegistration() {
   const TEST_VERSION = "1.2.3.4";
 
+  let profD = do_get_profile();
   for (let addon of gMockAddons.values()) {
-    let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    let file = profD.clone();
     file.append(addon.id);
     file.append(TEST_VERSION);
 
     let addedPaths = [];
     let removedPaths = [];
     let clearPaths = () => { addedPaths = []; removedPaths = []; }
 
     let MockGMPService = {
-      addPluginDirectory: path => addedPaths.push(path),
-      removePluginDirectory: path => removedPaths.push(path),
-      removeAndDeletePluginDirectory: path => removedPaths.push(path),
+      addPluginDirectory: path => {
+        if (!addedPaths.includes(path)) {
+          addedPaths.push(path);
+        }
+      },
+      removePluginDirectory: path => {
+        if (!removedPaths.includes(path)) {
+          removedPaths.push(path);
+        }
+      },
+      removeAndDeletePluginDirectory: path => {
+        if (!removedPaths.includes(path)) {
+          removedPaths.push(path);
+        }
+      },
     };
 
     GMPScope.gmpService = MockGMPService;
     gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true);
 
-    // Check that the plugin gets registered after startup.
+    // Test that plugin registration fails if the plugin dynamic library and
+    // info files are not present.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
-                      TEST_VERSION);
+                       TEST_VERSION);
+    clearPaths();
+    yield promiseRestartManager();
+    Assert.equal(addedPaths.indexOf(file.path), -1);
+    Assert.deepEqual(removedPaths, [file.path]);
+
+    // Create dummy GMP library/info files, and test that plugin registration
+    // succeeds during startup, now that we've added GMP info/lib files.
+    createMockPluginFilesIfNeeded(file, addon.id);
+
+    gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.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();
     gPrefs.clearUserPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id));
@@ -254,30 +319,30 @@ add_task(function* test_pluginRegistrati
     // 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.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
-                      TEST_VERSION);
+                       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.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
                       TEST_VERSION_2);
     Assert.deepEqual(addedPaths, [file2.path]);
     Assert.deepEqual(removedPaths, [file.path]);
 
     // Disabling the plugin should cause unregistration.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
-                      TEST_VERSION);
+                       TEST_VERSION);
     clearPaths();
     gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), false);
     Assert.deepEqual(addedPaths, []);
     Assert.deepEqual(removedPaths, [file.path]);
 
     // Restarting with the plugin disabled should not cause registration.
     clearPaths();
     yield promiseRestartManager();