934 lines
25 KiB
JavaScript
934 lines
25 KiB
JavaScript
/* 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/. */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
|
|
GMPInstallManager: "resource://gre/modules/GMPInstallManager.sys.mjs",
|
|
Log: "resource://gre/modules/Log.sys.mjs",
|
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
});
|
|
|
|
import {
|
|
GMPPrefs,
|
|
GMPUtils,
|
|
OPEN_H264_ID,
|
|
WIDEVINE_L1_ID,
|
|
WIDEVINE_L3_ID,
|
|
} from "resource://gre/modules/GMPUtils.sys.mjs";
|
|
|
|
const SEC_IN_A_DAY = 24 * 60 * 60;
|
|
// How long to wait after a user enabled EME before attempting to download CDMs.
|
|
const GMP_CHECK_DELAY = 10 * 1000; // milliseconds
|
|
|
|
const XHTML = "http://www.w3.org/1999/xhtml";
|
|
|
|
const NS_GRE_DIR = "GreD";
|
|
const CLEARKEY_PLUGIN_ID = "gmp-clearkey";
|
|
const CLEARKEY_VERSION = "0.1";
|
|
|
|
const FIRST_CONTENT_PROCESS_TOPIC = "ipc:first-content-process-created";
|
|
|
|
const GMP_LICENSE_INFO = "plugins-gmp-license-info";
|
|
const GMP_PRIVACY_INFO = "plugins-gmp-privacy-info";
|
|
const GMP_LEARN_MORE = "learn_more_label";
|
|
|
|
const GMP_PLUGINS = [
|
|
{
|
|
id: OPEN_H264_ID,
|
|
name: "plugins-openh264-name",
|
|
description: "plugins-openh264-description",
|
|
level: "",
|
|
libName: "gmpopenh264",
|
|
// The following licenseURL is part of an awful hack to include the OpenH264
|
|
// license without having bug 624602 fixed yet, and intentionally ignores
|
|
// localisation.
|
|
licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt",
|
|
homepageURL: "https://www.openh264.org/",
|
|
},
|
|
{
|
|
id: WIDEVINE_L1_ID,
|
|
name: "plugins-widevine-name",
|
|
description: "plugins-widevine-description",
|
|
level: "L1",
|
|
libName: "Google.Widevine.CDM",
|
|
licenseURL: "https://www.google.com/policies/privacy/",
|
|
homepageURL: "https://www.widevine.com/",
|
|
isEME: true,
|
|
},
|
|
{
|
|
id: WIDEVINE_L3_ID,
|
|
name: "plugins-widevine-name",
|
|
description: "plugins-widevine-description",
|
|
level: "L3",
|
|
libName: "widevinecdm",
|
|
licenseURL: "https://www.google.com/policies/privacy/",
|
|
homepageURL: "https://www.widevine.com/",
|
|
isEME: true,
|
|
},
|
|
];
|
|
|
|
ChromeUtils.defineLazyGetter(
|
|
lazy,
|
|
"addonsBundle",
|
|
() => new Localization(["toolkit/about/aboutAddons.ftl"], true)
|
|
);
|
|
ChromeUtils.defineLazyGetter(lazy, "gmpService", () =>
|
|
Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(
|
|
Ci.mozIGeckoMediaPluginChromeService
|
|
)
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"gmpProviderEnabled",
|
|
GMPPrefs.KEY_PROVIDER_ENABLED
|
|
);
|
|
|
|
var gLogger;
|
|
var gLogAppenderDump = null;
|
|
|
|
function configureLogging() {
|
|
if (!gLogger) {
|
|
gLogger = lazy.Log.repository.getLogger("Toolkit.GMP");
|
|
gLogger.addAppender(
|
|
new lazy.Log.ConsoleAppender(new lazy.Log.BasicFormatter())
|
|
);
|
|
}
|
|
gLogger.level = GMPPrefs.getInt(
|
|
GMPPrefs.KEY_LOGGING_LEVEL,
|
|
lazy.Log.Level.Warn
|
|
);
|
|
|
|
let logDumping = GMPPrefs.getBool(GMPPrefs.KEY_LOGGING_DUMP, false);
|
|
if (logDumping != !!gLogAppenderDump) {
|
|
if (logDumping) {
|
|
gLogAppenderDump = new lazy.Log.DumpAppender(
|
|
new lazy.Log.BasicFormatter()
|
|
);
|
|
gLogger.addAppender(gLogAppenderDump);
|
|
} else {
|
|
gLogger.removeAppender(gLogAppenderDump);
|
|
gLogAppenderDump = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The GMPWrapper provides the info for the various GMP plugins to public
|
|
* callers through the API.
|
|
*/
|
|
function GMPWrapper(aPluginInfo, aRawPluginInfo) {
|
|
this._plugin = aPluginInfo;
|
|
this._rawPlugin = aRawPluginInfo;
|
|
this._log = lazy.Log.repository.getLoggerWithMessagePrefix(
|
|
"Toolkit.GMP",
|
|
"GMPWrapper(" + this._plugin.id + ") "
|
|
);
|
|
Services.prefs.addObserver(
|
|
GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED, this._plugin.id),
|
|
this,
|
|
true
|
|
);
|
|
Services.prefs.addObserver(
|
|
GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION, this._plugin.id),
|
|
this,
|
|
true
|
|
);
|
|
if (this._plugin.isEME) {
|
|
Services.prefs.addObserver(GMPPrefs.KEY_EME_ENABLED, this, true);
|
|
Services.obs.addObserver(this, "EMEVideo:CDMMissing");
|
|
}
|
|
}
|
|
|
|
GMPWrapper.prototype = {
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIObserver",
|
|
"nsISupportsWeakReference",
|
|
]),
|
|
|
|
// An active task that checks for plugin updates and installs them.
|
|
_updateTask: null,
|
|
_gmpPath: null,
|
|
_isUpdateCheckPending: false,
|
|
|
|
set gmpPath(aPath) {
|
|
this._gmpPath = aPath;
|
|
},
|
|
get gmpPath() {
|
|
if (!this._gmpPath && this.isInstalled) {
|
|
this._gmpPath = PathUtils.join(
|
|
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
|
|
this._plugin.id,
|
|
GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, null, this._plugin.id)
|
|
);
|
|
}
|
|
return this._gmpPath;
|
|
},
|
|
|
|
get id() {
|
|
return this._plugin.id;
|
|
},
|
|
get libName() {
|
|
return this._plugin.libName;
|
|
},
|
|
get type() {
|
|
return "plugin";
|
|
},
|
|
get isGMPlugin() {
|
|
return true;
|
|
},
|
|
get name() {
|
|
return this._plugin.name;
|
|
},
|
|
get creator() {
|
|
return null;
|
|
},
|
|
get homepageURL() {
|
|
return this._plugin.homepageURL;
|
|
},
|
|
|
|
get description() {
|
|
return this._plugin.description;
|
|
},
|
|
get fullDescription() {
|
|
return null;
|
|
},
|
|
|
|
getFullDescription(doc) {
|
|
let plugin = this._rawPlugin;
|
|
|
|
let frag = doc.createDocumentFragment();
|
|
for (let [urlProp, labelId] of [
|
|
["learnMoreURL", GMP_LEARN_MORE],
|
|
[
|
|
"licenseURL",
|
|
this.id == WIDEVINE_L1_ID || this.id == WIDEVINE_L3_ID
|
|
? GMP_PRIVACY_INFO
|
|
: GMP_LICENSE_INFO,
|
|
],
|
|
]) {
|
|
if (plugin[urlProp]) {
|
|
let a = doc.createElementNS(XHTML, "a");
|
|
a.href = plugin[urlProp];
|
|
a.target = "_blank";
|
|
a.textContent = lazy.addonsBundle.formatValueSync(labelId);
|
|
|
|
if (frag.childElementCount) {
|
|
frag.append(
|
|
doc.createElementNS(XHTML, "br"),
|
|
doc.createElementNS(XHTML, "br")
|
|
);
|
|
}
|
|
frag.append(a);
|
|
}
|
|
}
|
|
|
|
return frag;
|
|
},
|
|
|
|
get version() {
|
|
return GMPPrefs.getString(
|
|
GMPPrefs.KEY_PLUGIN_VERSION,
|
|
null,
|
|
this._plugin.id
|
|
);
|
|
},
|
|
|
|
get isActive() {
|
|
return (
|
|
!this.appDisabled &&
|
|
!this.userDisabled &&
|
|
!GMPUtils.isPluginHidden(this._plugin)
|
|
);
|
|
},
|
|
get appDisabled() {
|
|
if (
|
|
this._plugin.isEME &&
|
|
!GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)
|
|
) {
|
|
// If "media.eme.enabled" is false, all EME plugins are disabled.
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
get userDisabled() {
|
|
return !GMPPrefs.getBool(
|
|
GMPPrefs.KEY_PLUGIN_ENABLED,
|
|
true,
|
|
this._plugin.id
|
|
);
|
|
},
|
|
set userDisabled(aVal) {
|
|
GMPPrefs.setBool(
|
|
GMPPrefs.KEY_PLUGIN_ENABLED,
|
|
aVal === false,
|
|
this._plugin.id
|
|
);
|
|
},
|
|
|
|
async enable() {
|
|
this.userDisabled = false;
|
|
},
|
|
async disable() {
|
|
this.userDisabled = true;
|
|
},
|
|
|
|
get blocklistState() {
|
|
return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
|
|
},
|
|
get size() {
|
|
return 0;
|
|
},
|
|
get scope() {
|
|
return lazy.AddonManager.SCOPE_APPLICATION;
|
|
},
|
|
get pendingOperations() {
|
|
return lazy.AddonManager.PENDING_NONE;
|
|
},
|
|
|
|
get operationsRequiringRestart() {
|
|
return lazy.AddonManager.OP_NEEDS_RESTART_NONE;
|
|
},
|
|
|
|
get permissions() {
|
|
let permissions = 0;
|
|
if (!this.appDisabled) {
|
|
permissions |= lazy.AddonManager.PERM_CAN_UPGRADE;
|
|
permissions |= this.userDisabled
|
|
? lazy.AddonManager.PERM_CAN_ENABLE
|
|
: lazy.AddonManager.PERM_CAN_DISABLE;
|
|
}
|
|
return permissions;
|
|
},
|
|
|
|
get updateDate() {
|
|
let time = Number(
|
|
GMPPrefs.getInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, 0, this._plugin.id)
|
|
);
|
|
if (this.isInstalled) {
|
|
return new Date(time * 1000);
|
|
}
|
|
return null;
|
|
},
|
|
|
|
get isCompatible() {
|
|
return true;
|
|
},
|
|
|
|
get isPlatformCompatible() {
|
|
return true;
|
|
},
|
|
|
|
get providesUpdatesSecurely() {
|
|
return true;
|
|
},
|
|
|
|
get foreignInstall() {
|
|
return false;
|
|
},
|
|
|
|
get installTelemetryInfo() {
|
|
return { source: "gmp-plugin" };
|
|
},
|
|
|
|
isCompatibleWith() {
|
|
return true;
|
|
},
|
|
|
|
get applyBackgroundUpdates() {
|
|
if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
|
|
return lazy.AddonManager.AUTOUPDATE_DEFAULT;
|
|
}
|
|
|
|
return GMPPrefs.getBool(
|
|
GMPPrefs.KEY_PLUGIN_AUTOUPDATE,
|
|
true,
|
|
this._plugin.id
|
|
)
|
|
? lazy.AddonManager.AUTOUPDATE_ENABLE
|
|
: lazy.AddonManager.AUTOUPDATE_DISABLE;
|
|
},
|
|
|
|
set applyBackgroundUpdates(aVal) {
|
|
if (aVal == lazy.AddonManager.AUTOUPDATE_DEFAULT) {
|
|
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
|
|
} else if (aVal == lazy.AddonManager.AUTOUPDATE_ENABLE) {
|
|
GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
|
|
} else if (aVal == lazy.AddonManager.AUTOUPDATE_DISABLE) {
|
|
GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called by the addon manager to update GMP addons. For example this will be
|
|
* used if a user manually checks for GMP plugin updates by using the
|
|
* menu in about:addons.
|
|
*
|
|
* This function is not used if MediaKeySystemAccess is requested and
|
|
* Widevine is not yet installed, or if the user toggles prefs to enable EME.
|
|
* For the function used in those cases see `checkForUpdates`.
|
|
*/
|
|
findUpdates(aListener, aReason) {
|
|
this._log.trace(
|
|
"findUpdates() - " + this._plugin.id + " - reason=" + aReason
|
|
);
|
|
|
|
// In the case of GMP addons we do not wish to implement AddonInstall, as
|
|
// we don't want to display information as in a normal addon install such
|
|
// as a download progress bar. As such, we short circuit our
|
|
// listeners by indicating that no updates exist (though some may).
|
|
lazy.AddonManagerPrivate.callNoUpdateListeners(this, aListener);
|
|
|
|
if (aReason === lazy.AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
|
|
if (!lazy.AddonManager.shouldAutoUpdate(this)) {
|
|
this._log.trace(
|
|
"findUpdates() - " + this._plugin.id + " - no autoupdate"
|
|
);
|
|
return Promise.resolve(false);
|
|
}
|
|
|
|
let secSinceLastCheck =
|
|
Date.now() / 1000 -
|
|
Services.prefs.getIntPref(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
|
|
if (secSinceLastCheck <= SEC_IN_A_DAY) {
|
|
this._log.trace(
|
|
"findUpdates() - " +
|
|
this._plugin.id +
|
|
" - last check was less then a day ago"
|
|
);
|
|
return Promise.resolve(false);
|
|
}
|
|
} else if (aReason !== lazy.AddonManager.UPDATE_WHEN_USER_REQUESTED) {
|
|
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() - " + this._plugin.id + " - update task already running"
|
|
);
|
|
return this._updateTask;
|
|
}
|
|
|
|
this._updateTask = (async () => {
|
|
this._log.trace("findUpdates() - updateTask");
|
|
try {
|
|
let installManager = new lazy.GMPInstallManager();
|
|
let res = await installManager.checkForAddons();
|
|
let update = res.addons.find(addon => addon.id === this._plugin.id);
|
|
if (update && update.isValid && !update.isInstalled) {
|
|
this._log.trace(
|
|
"findUpdates() - found update for " +
|
|
this._plugin.id +
|
|
", installing"
|
|
);
|
|
await installManager.installAddon(update);
|
|
} else {
|
|
this._log.trace("findUpdates() - no updates for " + this._plugin.id);
|
|
}
|
|
this._log.info(
|
|
"findUpdates() - updateTask succeeded for " + this._plugin.id
|
|
);
|
|
} catch (e) {
|
|
this._log.error(
|
|
"findUpdates() - updateTask for " + this._plugin.id + " threw",
|
|
e
|
|
);
|
|
throw e;
|
|
} finally {
|
|
this._updateTask = null;
|
|
}
|
|
return true;
|
|
})();
|
|
|
|
return this._updateTask;
|
|
},
|
|
|
|
get pluginLibraries() {
|
|
if (this.isInstalled) {
|
|
let path = this.version;
|
|
return [path];
|
|
}
|
|
return [];
|
|
},
|
|
get pluginFullpath() {
|
|
if (this.isInstalled) {
|
|
let path = PathUtils.join(
|
|
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
|
|
this._plugin.id,
|
|
this.version
|
|
);
|
|
return [path];
|
|
}
|
|
return [];
|
|
},
|
|
|
|
get isInstalled() {
|
|
return this.version && !!this.version.length;
|
|
},
|
|
|
|
_handleEnabledChanged() {
|
|
this._log.info(
|
|
"_handleEnabledChanged() id=" +
|
|
this._plugin.id +
|
|
" isActive=" +
|
|
this.isActive
|
|
);
|
|
|
|
lazy.AddonManagerPrivate.callAddonListeners(
|
|
this.isActive ? "onEnabling" : "onDisabling",
|
|
this,
|
|
false
|
|
);
|
|
if (this._gmpPath) {
|
|
if (this.isActive) {
|
|
this._log.info(
|
|
"onPrefEnabledChanged() - adding gmp directory " + this._gmpPath
|
|
);
|
|
lazy.gmpService.addPluginDirectory(this._gmpPath);
|
|
} else {
|
|
this._log.info(
|
|
"onPrefEnabledChanged() - removing gmp directory " + this._gmpPath
|
|
);
|
|
lazy.gmpService.removePluginDirectory(this._gmpPath);
|
|
}
|
|
}
|
|
lazy.AddonManagerPrivate.callAddonListeners(
|
|
this.isActive ? "onEnabled" : "onDisabled",
|
|
this
|
|
);
|
|
},
|
|
|
|
onPrefEMEGlobalEnabledChanged() {
|
|
this._log.info(
|
|
"onPrefEMEGlobalEnabledChanged() id=" +
|
|
this._plugin.id +
|
|
" appDisabled=" +
|
|
this.appDisabled +
|
|
" isActive=" +
|
|
this.isActive +
|
|
" hidden=" +
|
|
GMPUtils.isPluginHidden(this._plugin)
|
|
);
|
|
|
|
lazy.AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, [
|
|
"appDisabled",
|
|
]);
|
|
// If EME or the GMP itself are disabled, uninstall the GMP.
|
|
// Otherwise, check for updates, so we download and install the GMP.
|
|
if (this.appDisabled) {
|
|
this.uninstallPlugin();
|
|
} else if (!GMPUtils.isPluginHidden(this._plugin)) {
|
|
lazy.AddonManagerPrivate.callInstallListeners(
|
|
"onExternalInstall",
|
|
null,
|
|
this,
|
|
null,
|
|
false
|
|
);
|
|
lazy.AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
|
|
lazy.AddonManagerPrivate.callAddonListeners("onInstalled", this);
|
|
this.checkForUpdates(GMP_CHECK_DELAY);
|
|
}
|
|
if (!this.userDisabled) {
|
|
this._handleEnabledChanged();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* This is called if prefs are changed to enable EME, or if Widevine
|
|
* MediaKeySystemAccess is requested but the Widevine CDM is not installed.
|
|
*
|
|
* For the function used by the addon manager see `findUpdates`.
|
|
*/
|
|
checkForUpdates(delay) {
|
|
if (this._isUpdateCheckPending) {
|
|
return;
|
|
}
|
|
this._isUpdateCheckPending = true;
|
|
GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
|
|
// Delay this in case the user changes his mind and doesn't want to
|
|
// enable EME after all.
|
|
lazy.setTimeout(() => {
|
|
if (!this.appDisabled) {
|
|
let gmpInstallManager = new lazy.GMPInstallManager();
|
|
// We don't really care about the results, if someone is interested
|
|
// they can check the log.
|
|
gmpInstallManager.simpleCheckAndInstall().catch(() => {});
|
|
}
|
|
this._isUpdateCheckPending = false;
|
|
}, delay);
|
|
},
|
|
|
|
onPrefEnabledChanged() {
|
|
if (!this._plugin.isEME || !this.appDisabled) {
|
|
this._handleEnabledChanged();
|
|
}
|
|
},
|
|
|
|
onPrefVersionChanged() {
|
|
lazy.AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
|
|
if (this._gmpPath) {
|
|
this._log.info(
|
|
"onPrefVersionChanged() - unregistering gmp directory " + this._gmpPath
|
|
);
|
|
lazy.gmpService.removeAndDeletePluginDirectory(
|
|
this._gmpPath,
|
|
true /* can defer */
|
|
);
|
|
}
|
|
lazy.AddonManagerPrivate.callAddonListeners("onUninstalled", this);
|
|
|
|
lazy.AddonManagerPrivate.callInstallListeners(
|
|
"onExternalInstall",
|
|
null,
|
|
this,
|
|
null,
|
|
false
|
|
);
|
|
lazy.AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
|
|
this._gmpPath = null;
|
|
if (this.isInstalled) {
|
|
this._gmpPath = PathUtils.join(
|
|
Services.dirsvc.get("ProfD", Ci.nsIFile).path,
|
|
this._plugin.id,
|
|
GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, null, this._plugin.id)
|
|
);
|
|
}
|
|
if (this._gmpPath && this.isActive) {
|
|
this._log.info(
|
|
"onPrefVersionChanged() - registering gmp directory " + this._gmpPath
|
|
);
|
|
lazy.gmpService.addPluginDirectory(this._gmpPath);
|
|
}
|
|
lazy.AddonManagerPrivate.callAddonListeners("onInstalled", this);
|
|
},
|
|
|
|
observe(subject, topic, data) {
|
|
if (topic == "nsPref:changed") {
|
|
let pref = data;
|
|
if (
|
|
pref ==
|
|
GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED, this._plugin.id)
|
|
) {
|
|
this.onPrefEnabledChanged();
|
|
} else if (
|
|
pref ==
|
|
GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION, this._plugin.id)
|
|
) {
|
|
this.onPrefVersionChanged();
|
|
} else if (pref == GMPPrefs.KEY_EME_ENABLED) {
|
|
this.onPrefEMEGlobalEnabledChanged();
|
|
}
|
|
} else if (topic == "EMEVideo:CDMMissing") {
|
|
this.checkForUpdates(0);
|
|
}
|
|
},
|
|
|
|
uninstallPlugin() {
|
|
lazy.AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
|
|
if (this.gmpPath) {
|
|
this._log.info(
|
|
"uninstallPlugin() - unregistering gmp directory " + this.gmpPath
|
|
);
|
|
lazy.gmpService.removeAndDeletePluginDirectory(this.gmpPath);
|
|
}
|
|
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
|
|
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_HASHVALUE, this.id);
|
|
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_ABI, this.id);
|
|
GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, this.id);
|
|
lazy.AddonManagerPrivate.callAddonListeners("onUninstalled", this);
|
|
},
|
|
|
|
shutdown() {
|
|
Services.prefs.removeObserver(
|
|
GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED, this._plugin.id),
|
|
this
|
|
);
|
|
Services.prefs.removeObserver(
|
|
GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION, this._plugin.id),
|
|
this
|
|
);
|
|
if (this._plugin.isEME) {
|
|
Services.prefs.removeObserver(GMPPrefs.KEY_EME_ENABLED, this);
|
|
Services.obs.removeObserver(this, "EMEVideo:CDMMissing");
|
|
}
|
|
return this._updateTask;
|
|
},
|
|
|
|
_arePluginFilesOnDisk() {
|
|
let fileExists = function (aGmpPath, aFileName) {
|
|
let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
|
let path = PathUtils.join(aGmpPath, aFileName);
|
|
f.initWithPath(path);
|
|
return f.exists();
|
|
};
|
|
|
|
let libName =
|
|
AppConstants.DLL_PREFIX + this._plugin.libName + AppConstants.DLL_SUFFIX;
|
|
let infoName;
|
|
if (
|
|
this._plugin.id == WIDEVINE_L1_ID ||
|
|
this._plugin.id == WIDEVINE_L3_ID
|
|
) {
|
|
infoName = "manifest.json";
|
|
} else {
|
|
infoName = this._plugin.id.substring(4) + ".info";
|
|
}
|
|
|
|
return (
|
|
fileExists(this.gmpPath, libName) && fileExists(this.gmpPath, infoName)
|
|
);
|
|
},
|
|
|
|
validate() {
|
|
if (!this.isInstalled) {
|
|
// Not installed -> Valid.
|
|
return {
|
|
installed: false,
|
|
valid: true,
|
|
};
|
|
}
|
|
|
|
let expectedABI = GMPUtils._expectedABI(this._plugin);
|
|
let abi = GMPPrefs.getString(
|
|
GMPPrefs.KEY_PLUGIN_ABI,
|
|
expectedABI,
|
|
this._plugin.id
|
|
);
|
|
if (abi != expectedABI) {
|
|
// ABI doesn't match. Possibly this is a profile migrated across platforms
|
|
// or from 32 -> 64 bit.
|
|
return {
|
|
installed: true,
|
|
mismatchedABI: true,
|
|
valid: false,
|
|
};
|
|
}
|
|
|
|
// Installed -> Check if files are missing.
|
|
let filesOnDisk = this._arePluginFilesOnDisk();
|
|
return {
|
|
installed: true,
|
|
valid: filesOnDisk,
|
|
};
|
|
},
|
|
};
|
|
|
|
var GMPProvider = {
|
|
get name() {
|
|
return "GMPProvider";
|
|
},
|
|
|
|
_plugins: null,
|
|
|
|
startup() {
|
|
configureLogging();
|
|
this._log = lazy.Log.repository.getLoggerWithMessagePrefix(
|
|
"Toolkit.GMP",
|
|
"GMPProvider."
|
|
);
|
|
this.buildPluginList();
|
|
this.ensureProperCDMInstallState();
|
|
|
|
Services.prefs.addObserver(GMPPrefs.KEY_LOG_BASE, configureLogging);
|
|
|
|
for (let plugin of this._plugins.values()) {
|
|
let wrapper = plugin.wrapper;
|
|
let gmpPath = wrapper.gmpPath;
|
|
let isEnabled = wrapper.isActive;
|
|
this._log.trace(
|
|
"startup - enabled=" + isEnabled + ", gmpPath=" + gmpPath
|
|
);
|
|
|
|
if (gmpPath && isEnabled) {
|
|
let validation = wrapper.validate();
|
|
if (validation.mismatchedABI) {
|
|
this._log.info(
|
|
"startup - gmp " + plugin.id + " mismatched ABI, uninstalling"
|
|
);
|
|
wrapper.uninstallPlugin();
|
|
continue;
|
|
}
|
|
if (!validation.valid) {
|
|
this._log.info(
|
|
"startup - gmp " + plugin.id + " invalid, uninstalling"
|
|
);
|
|
wrapper.uninstallPlugin();
|
|
continue;
|
|
}
|
|
this._log.info("startup - adding gmp directory " + gmpPath);
|
|
try {
|
|
lazy.gmpService.addPluginDirectory(gmpPath);
|
|
} catch (e) {
|
|
if (e.name != "NS_ERROR_NOT_AVAILABLE") {
|
|
throw e;
|
|
}
|
|
this._log.warn(
|
|
"startup - adding gmp directory failed with " +
|
|
e.name +
|
|
" - sandboxing not available?",
|
|
e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
let greDir = Services.dirsvc.get(NS_GRE_DIR, Ci.nsIFile);
|
|
let path = greDir.path;
|
|
if (
|
|
GMPUtils._isWindowsOnARM64() &&
|
|
GMPPrefs.getBool(
|
|
GMPPrefs.KEY_PLUGIN_ALLOW_X64_ON_ARM64,
|
|
true,
|
|
CLEARKEY_PLUGIN_ID
|
|
)
|
|
) {
|
|
path = PathUtils.join(path, "i686");
|
|
}
|
|
let clearkeyPath = PathUtils.join(
|
|
path,
|
|
CLEARKEY_PLUGIN_ID,
|
|
CLEARKEY_VERSION
|
|
);
|
|
this._log.info("startup - adding clearkey CDM directory " + clearkeyPath);
|
|
lazy.gmpService.addPluginDirectory(clearkeyPath);
|
|
} catch (e) {
|
|
this._log.warn("startup - adding clearkey CDM failed", e);
|
|
}
|
|
},
|
|
|
|
shutdown() {
|
|
this._log.trace("shutdown");
|
|
Services.prefs.removeObserver(GMPPrefs.KEY_LOG_BASE, configureLogging);
|
|
|
|
let shutdownTask = (async () => {
|
|
this._log.trace("shutdown - shutdownTask");
|
|
let shutdownSucceeded = true;
|
|
|
|
for (let plugin of this._plugins.values()) {
|
|
try {
|
|
await plugin.wrapper.shutdown();
|
|
} catch (e) {
|
|
shutdownSucceeded = false;
|
|
}
|
|
}
|
|
|
|
this._plugins = null;
|
|
|
|
if (!shutdownSucceeded) {
|
|
throw new Error("Shutdown failed");
|
|
}
|
|
})();
|
|
|
|
return shutdownTask;
|
|
},
|
|
|
|
async getAddonByID(aId) {
|
|
if (!this.isEnabled) {
|
|
return null;
|
|
}
|
|
|
|
let plugin = this._plugins.get(aId);
|
|
if (plugin && !GMPUtils.isPluginHidden(plugin)) {
|
|
return plugin.wrapper;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
async getAddonsByTypes(aTypes) {
|
|
if (!this.isEnabled || (aTypes && !aTypes.includes("plugin"))) {
|
|
return [];
|
|
}
|
|
|
|
let results = Array.from(this._plugins.values())
|
|
.filter(p => !GMPUtils.isPluginHidden(p))
|
|
.map(p => p.wrapper);
|
|
|
|
return results;
|
|
},
|
|
|
|
get isEnabled() {
|
|
return lazy.gmpProviderEnabled;
|
|
},
|
|
|
|
buildPluginList() {
|
|
this._plugins = new Map();
|
|
for (let aPlugin of GMP_PLUGINS) {
|
|
let plugin = {
|
|
id: aPlugin.id,
|
|
name: lazy.addonsBundle.formatValueSync(aPlugin.name),
|
|
description: lazy.addonsBundle.formatValueSync(aPlugin.description),
|
|
libName: aPlugin.libName,
|
|
homepageURL: aPlugin.homepageURL,
|
|
optionsURL: aPlugin.optionsURL,
|
|
wrapper: null,
|
|
isEME: aPlugin.isEME,
|
|
};
|
|
plugin.wrapper = new GMPWrapper(plugin, aPlugin);
|
|
this._plugins.set(plugin.id, plugin);
|
|
}
|
|
},
|
|
|
|
ensureProperCDMInstallState() {
|
|
if (!GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)) {
|
|
for (let plugin of this._plugins.values()) {
|
|
if (plugin.isEME && plugin.wrapper.isInstalled) {
|
|
lazy.gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
|
|
plugin.wrapper.uninstallPlugin();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
observe(subject, topic) {
|
|
if (topic == FIRST_CONTENT_PROCESS_TOPIC) {
|
|
lazy.AddonManagerPrivate.registerProvider(GMPProvider, ["plugin"]);
|
|
Services.obs.notifyObservers(null, "gmp-provider-registered");
|
|
|
|
Services.obs.removeObserver(this, FIRST_CONTENT_PROCESS_TOPIC);
|
|
}
|
|
},
|
|
|
|
addObserver() {
|
|
Services.obs.addObserver(this, FIRST_CONTENT_PROCESS_TOPIC);
|
|
},
|
|
};
|
|
|
|
GMPProvider.addObserver();
|
|
|
|
// For test use only.
|
|
export const GMPTestUtils = {
|
|
/**
|
|
* Used to override the GMP service with a mock.
|
|
*
|
|
* @param {object} mockService
|
|
* The mocked gmpService object.
|
|
* @param {function} callback
|
|
* Method called with the overridden gmpService. The override
|
|
* is undone after the callback returns.
|
|
*/
|
|
async overrideGmpService(mockService, callback) {
|
|
let originalGmpService = lazy.gmpService;
|
|
lazy.gmpService = mockService;
|
|
try {
|
|
return await callback();
|
|
} finally {
|
|
lazy.gmpService = originalGmpService;
|
|
}
|
|
},
|
|
};
|