summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base/content/browser-media.js
diff options
context:
space:
mode:
Diffstat (limited to 'application/basilisk/base/content/browser-media.js')
-rw-r--r--application/basilisk/base/content/browser-media.js354
1 files changed, 354 insertions, 0 deletions
diff --git a/application/basilisk/base/content/browser-media.js b/application/basilisk/base/content/browser-media.js
new file mode 100644
index 000000000..bd5c5b227
--- /dev/null
+++ b/application/basilisk/base/content/browser-media.js
@@ -0,0 +1,354 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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/. */
+
+var gEMEHandler = {
+ get uiEnabled() {
+#ifdef MOZ_EME
+ let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
+ // Force-disable on WinXP:
+ if (navigator.platform.toLowerCase().startsWith("win")) {
+ emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
+ }
+ return emeUIEnabled;
+#else
+ return false;
+#endif
+ },
+ ensureEMEEnabled: function(browser, keySystem) {
+ Services.prefs.setBoolPref("media.eme.enabled", true);
+ if (keySystem) {
+ if (keySystem.startsWith("com.adobe") &&
+ Services.prefs.getPrefType("media.gmp-eme-adobe.enabled") &&
+ !Services.prefs.getBoolPref("media.gmp-eme-adobe.enabled")) {
+ Services.prefs.setBoolPref("media.gmp-eme-adobe.enabled", true);
+ } else if (keySystem == "com.widevine.alpha" &&
+ Services.prefs.getPrefType("media.gmp-widevinecdm.enabled") &&
+ !Services.prefs.getBoolPref("media.gmp-widevinecdm.enabled")) {
+ Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", true);
+ }
+ }
+ browser.reload();
+ },
+ isKeySystemVisible: function(keySystem) {
+ if (!keySystem) {
+ return false;
+ }
+ if (keySystem.startsWith("com.adobe") &&
+ Services.prefs.getPrefType("media.gmp-eme-adobe.visible")) {
+ return Services.prefs.getBoolPref("media.gmp-eme-adobe.visible");
+ }
+ if (keySystem == "com.widevine.alpha" &&
+ Services.prefs.getPrefType("media.gmp-widevinecdm.visible")) {
+ return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
+ }
+ return true;
+ },
+ getEMEDisabledFragment: function(msgId) {
+ let mainMessage = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.message");
+ let [prefix, suffix] = mainMessage.split(/%(?:\$\d)?S/).map(s => document.createTextNode(s));
+ let text = gNavigatorBundle.getString("emeNotifications.drmContentDisabled.learnMoreLabel");
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ let link = document.createElement("label");
+ link.className = "text-link";
+ link.setAttribute("href", baseURL + "drm-content");
+ link.textContent = text;
+
+ let fragment = document.createDocumentFragment();
+ [prefix, link, suffix].forEach(n => fragment.appendChild(n));
+ return fragment;
+ },
+ getMessageWithBrandName: function(notificationId) {
+ let msgId = "emeNotifications." + notificationId + ".message";
+ return gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
+ },
+ receiveMessage: function({target: browser, data: data}) {
+ let parsedData;
+ try {
+ parsedData = JSON.parse(data);
+ } catch (ex) {
+ Cu.reportError("Malformed EME video message with data: " + data);
+ return;
+ }
+ let {status: status, keySystem: keySystem} = parsedData;
+ // Don't need to show if disabled or keysystem not visible.
+ if (!this.uiEnabled || !this.isKeySystemVisible(keySystem)) {
+ return;
+ }
+
+ let notificationId;
+ let buttonCallback;
+ // Notification message can be either a string or a DOM fragment.
+ let notificationMessage;
+ switch (status) {
+ case "available":
+ case "cdm-created":
+ // Only show the chain icon for proprietary CDMs. Clearkey is not one.
+ if (keySystem != "org.w3.clearkey") {
+ this.showPopupNotificationForSuccess(browser, keySystem);
+ }
+ // ... and bail!
+ return;
+
+ case "api-disabled":
+ case "cdm-disabled":
+ notificationId = "drmContentDisabled";
+ buttonCallback = gEMEHandler.ensureEMEEnabled.bind(gEMEHandler, browser, keySystem)
+ notificationMessage = this.getEMEDisabledFragment();
+ break;
+
+ case "cdm-insufficient-version":
+ notificationId = "drmContentCDMInsufficientVersion";
+ notificationMessage = this.getMessageWithBrandName(notificationId);
+ break;
+
+ case "cdm-not-installed":
+ notificationId = "drmContentCDMInstalling";
+ notificationMessage = this.getMessageWithBrandName(notificationId);
+ break;
+
+ case "cdm-not-supported":
+ // Not to pop up user-level notification because they cannot do anything
+ // about it.
+ return;
+ default:
+ Cu.reportError(new Error("Unknown message ('" + status + "') dealing with EME key request: " + data));
+ return;
+ }
+
+ // Now actually create the notification
+
+ let box = gBrowser.getNotificationBox(browser);
+ if (box.getNotificationWithValue(notificationId)) {
+ return;
+ }
+
+ let buttons = [];
+ if (buttonCallback) {
+ let msgPrefix = "emeNotifications." + notificationId + ".";
+ let btnLabelId = msgPrefix + "button.label";
+ let btnAccessKeyId = msgPrefix + "button.accesskey";
+ buttons.push({
+ label: gNavigatorBundle.getString(btnLabelId),
+ accessKey: gNavigatorBundle.getString(btnAccessKeyId),
+ callback: buttonCallback
+ });
+ }
+
+ let iconURL = "chrome://browser/skin/drm-icon.svg#chains-black";
+
+ box.appendNotification(notificationMessage, notificationId, iconURL,
+ box.PRIORITY_WARNING_MEDIUM, buttons);
+ },
+ showPopupNotificationForSuccess: function(browser, keySystem) {
+ // We're playing EME content! Remove any "we can't play because..." messages.
+ var box = gBrowser.getNotificationBox(browser);
+ ["drmContentDisabled",
+ "drmContentCDMInstalling"
+ ].forEach(function (value) {
+ var notification = box.getNotificationWithValue(value);
+ if (notification)
+ box.removeNotification(notification);
+ });
+
+ // Don't bother creating it if it's already there:
+ if (PopupNotifications.getNotification("drmContentPlaying", browser)) {
+ return;
+ }
+
+ let msgPrefix = "emeNotifications.drmContentPlaying.";
+ let msgId = msgPrefix + "message2";
+ let btnLabelId = msgPrefix + "button.label";
+ let btnAccessKeyId = msgPrefix + "button.accesskey";
+
+ let message = gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
+ let anchorId = "eme-notification-icon";
+ let firstPlayPref = "browser.eme.ui.firstContentShown";
+ if (!Services.prefs.getPrefType(firstPlayPref) ||
+ !Services.prefs.getBoolPref(firstPlayPref)) {
+ document.getElementById(anchorId).setAttribute("firstplay", "true");
+ Services.prefs.setBoolPref(firstPlayPref, true);
+ } else {
+ document.getElementById(anchorId).removeAttribute("firstplay");
+ }
+
+ let mainAction = {
+ label: gNavigatorBundle.getString(btnLabelId),
+ accessKey: gNavigatorBundle.getString(btnAccessKeyId),
+ callback: function() { openPreferences("paneContent"); },
+ dismiss: true
+ };
+ let options = {
+ dismissed: true,
+ eventCallback: aTopic => aTopic == "swapping",
+ learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content",
+ };
+ PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
+};
+
+XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
+ return document.getElementById("bundle_brand").getString("brandShortName");
+});
+
+const TELEMETRY_DDSTAT_SHOWN = 0;
+const TELEMETRY_DDSTAT_SHOWN_FIRST = 1;
+const TELEMETRY_DDSTAT_CLICKED = 2;
+const TELEMETRY_DDSTAT_CLICKED_FIRST = 3;
+const TELEMETRY_DDSTAT_SOLVED = 4;
+
+let gDecoderDoctorHandler = {
+ getLabelForNotificationBox(type) {
+ if (type == "adobe-cdm-not-found" &&
+ AppConstants.platform == "win") {
+ return gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+ if (type == "adobe-cdm-not-activated" &&
+ AppConstants.platform == "win") {
+ return gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+ if (type == "platform-decoder-not-found") {
+ if (AppConstants.platform == "win") {
+ return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+ }
+ if (AppConstants.platform == "linux") {
+ return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+ }
+ }
+ if (type == "cannot-initialize-pulseaudio") {
+ return gNavigatorBundle.getString("decoder.noPulseAudio.message");
+ }
+ if (type == "unsupported-libavcodec" &&
+ AppConstants.platform == "linux") {
+ return gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
+ }
+ return "";
+ },
+
+ getSumoForLearnHowButton(type) {
+ if (AppConstants.platform == "win") {
+ return "fix-video-audio-problems-firefox-windows";
+ }
+ if (type == "cannot-initialize-pulseaudio") {
+ return "fix-common-audio-and-video-issues";
+ }
+ return "";
+ },
+
+ receiveMessage({target: browser, data: data}) {
+ let box = gBrowser.getNotificationBox(browser);
+ let notificationId = "decoder-doctor-notification";
+ if (box.getNotificationWithValue(notificationId)) {
+ return;
+ }
+
+ let parsedData;
+ try {
+ parsedData = JSON.parse(data);
+ } catch (ex) {
+ Cu.reportError("Malformed Decoder Doctor message with data: " + data);
+ return;
+ }
+ // parsedData (the result of parsing the incoming 'data' json string)
+ // contains analysis information from Decoder Doctor:
+ // - 'type' is the type of issue, it determines which text to show in the
+ // infobar.
+ // - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be
+ // used here as key for the telemetry (counting infobar displays,
+ // "Learn how" buttons clicks, and resolutions) and for the prefs used
+ // to store at-issue formats.
+ // - 'formats' contains a comma-separated list of formats (or key systems)
+ // that suffer the issue. These are kept in a pref, which the backend
+ // uses to later find when an issue is resolved.
+ // - 'isSolved' is true when the notification actually indicates the
+ // resolution of that issue, to be reported as telemetry.
+ let {type, isSolved, decoderDoctorReportId, formats} = parsedData;
+ type = type.toLowerCase();
+ // Error out early on invalid ReportId
+ if (!(/^\w+$/mi).test(decoderDoctorReportId)) {
+ return
+ }
+ let title = gDecoderDoctorHandler.getLabelForNotificationBox(type);
+ if (!title) {
+ return;
+ }
+
+ // We keep the list of formats in prefs for the sake of the decoder itself,
+ // which reads it to determine when issues get solved for these formats.
+ // (Writing prefs from e10s content is now allowed.)
+ let formatsPref = "media.decoder-doctor." + decoderDoctorReportId + ".formats";
+ let buttonClickedPref = "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
+ let histogram =
+ Services.telemetry.getKeyedHistogramById("DECODER_DOCTOR_INFOBAR_STATS");
+
+ let formatsInPref = Services.prefs.getPrefType(formatsPref) &&
+ Services.prefs.getCharPref(formatsPref);
+
+ if (!isSolved) {
+ if (!formats) {
+ Cu.reportError("Malformed Decoder Doctor unsolved message with no formats");
+ return;
+ }
+ if (!formatsInPref) {
+ Services.prefs.setCharPref(formatsPref, formats);
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN_FIRST);
+ } else {
+ // Split existing formats into an array of strings.
+ let existing = formatsInPref.split(",").map(String.trim);
+ // Keep given formats that were not already recorded.
+ let newbies = formats.split(",").map(String.trim)
+ .filter(x => !existing.includes(x));
+ // And rewrite pref with the added new formats (if any).
+ if (newbies.length) {
+ Services.prefs.setCharPref(formatsPref,
+ existing.concat(newbies).join(", "));
+ }
+ }
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN);
+
+ let buttons = [];
+ let sumo = gDecoderDoctorHandler.getSumoForLearnHowButton(type);
+ if (sumo) {
+ buttons.push({
+ label: gNavigatorBundle.getString("decoder.noCodecs.button"),
+ accessKey: gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+ callback() {
+ let clickedInPref = Services.prefs.getPrefType(buttonClickedPref) &&
+ Services.prefs.getBoolPref(buttonClickedPref);
+ if (!clickedInPref) {
+ Services.prefs.setBoolPref(buttonClickedPref, true);
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED_FIRST);
+ }
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED);
+
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ openUILinkIn(baseURL + sumo, "tab");
+ }
+ });
+ }
+
+ box.appendNotification(
+ title,
+ notificationId,
+ "", // This uses the info icon as specified below.
+ box.PRIORITY_INFO_LOW,
+ buttons
+ );
+ } else if (formatsInPref) {
+ // Issue is solved, and prefs haven't been cleared yet, meaning it's the
+ // first time we get this resolution -> Clear prefs and report telemetry.
+ Services.prefs.clearUserPref(formatsPref);
+ Services.prefs.clearUserPref(buttonClickedPref);
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SOLVED);
+ }
+ },
+}
+
+window.getGroupMessageManager("browsers").addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
+window.getGroupMessageManager("browsers").addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+window.addEventListener("unload", function() {
+ window.getGroupMessageManager("browsers").removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+ window.getGroupMessageManager("browsers").removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
+}, false);