diff options
Diffstat (limited to 'browser/actors/EncryptedMediaParent.sys.mjs')
-rw-r--r-- | browser/actors/EncryptedMediaParent.sys.mjs | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/browser/actors/EncryptedMediaParent.sys.mjs b/browser/actors/EncryptedMediaParent.sys.mjs new file mode 100644 index 0000000000..46a76ccb28 --- /dev/null +++ b/browser/actors/EncryptedMediaParent.sys.mjs @@ -0,0 +1,267 @@ +/* vim: set ts=2 sw=2 sts=2 et tw=80: */ +/* 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"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "gBrandBundle", function() { + return Services.strings.createBundle( + "chrome://branding/locale/brand.properties" + ); +}); + +XPCOMUtils.defineLazyGetter(lazy, "gNavigatorBundle", function() { + return Services.strings.createBundle( + "chrome://browser/locale/browser.properties" + ); +}); + +XPCOMUtils.defineLazyGetter(lazy, "gFluentStrings", function() { + return new Localization(["branding/brand.ftl", "browser/browser.ftl"], true); +}); + +export class EncryptedMediaParent extends JSWindowActorParent { + isUiEnabled() { + return Services.prefs.getBoolPref("browser.eme.ui.enabled"); + } + + ensureEMEEnabled(aBrowser, aKeySystem) { + Services.prefs.setBoolPref("media.eme.enabled", true); + if ( + aKeySystem && + aKeySystem == "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); + } + aBrowser.reload(); + } + + isKeySystemVisible(aKeySystem) { + if (!aKeySystem) { + return false; + } + if ( + aKeySystem == "com.widevine.alpha" && + Services.prefs.getPrefType("media.gmp-widevinecdm.visible") + ) { + return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible"); + } + return true; + } + + getMessageWithBrandName(aNotificationId) { + let msgId = "emeNotifications." + aNotificationId + ".message"; + return lazy.gNavigatorBundle.formatStringFromName(msgId, [ + lazy.gBrandBundle.GetStringFromName("brandShortName"), + ]); + } + + receiveMessage(aMessage) { + // The top level browsing context's embedding element should be a xul browser element. + let browser = this.browsingContext.top.embedderElement; + + if (!browser) { + // We don't have a browser so bail! + return; + } + + let parsedData; + try { + parsedData = JSON.parse(aMessage.data); + } catch (ex) { + console.error("Malformed EME video message with data: ", aMessage.data); + return; + } + let { status, keySystem } = parsedData; + + // First, see if we need to do updates. We don't need to do anything for + // hidden keysystems: + if (!this.isKeySystemVisible(keySystem)) { + return; + } + if (status == "cdm-not-installed") { + Services.obs.notifyObservers(browser, "EMEVideo:CDMMissing"); + } + + // Don't need to show UI if disabled. + if (!this.isUiEnabled()) { + return; + } + + let notificationId; + let buttonCallback; + let supportPage; + // 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 = () => { + this.ensureEMEEnabled(browser, keySystem); + }; + notificationMessage = lazy.gNavigatorBundle.GetStringFromName( + "emeNotifications.drmContentDisabled.message2" + ); + supportPage = "drm-content"; + 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: + console.error( + new Error( + "Unknown message ('" + + status + + "') dealing with EME key request: " + + aMessage.data + ) + ); + return; + } + + // Now actually create the notification + + let notificationBox = browser.getTabBrowser().getNotificationBox(browser); + if (notificationBox.getNotificationWithValue(notificationId)) { + return; + } + + let buttons = []; + if (supportPage) { + buttons.push({ supportPage }); + } + if (buttonCallback) { + let msgPrefix = "emeNotifications." + notificationId + "."; + let manageLabelId = msgPrefix + "button.label"; + let manageAccessKeyId = msgPrefix + "button.accesskey"; + buttons.push({ + label: lazy.gNavigatorBundle.GetStringFromName(manageLabelId), + accessKey: lazy.gNavigatorBundle.GetStringFromName(manageAccessKeyId), + callback: buttonCallback, + }); + } + + let iconURL = "chrome://browser/skin/drm-icon.svg"; + notificationBox.appendNotification( + notificationId, + { + label: notificationMessage, + image: iconURL, + priority: notificationBox.PRIORITY_INFO_HIGH, + }, + buttons + ); + } + + async showPopupNotificationForSuccess(aBrowser) { + // We're playing EME content! Remove any "we can't play because..." messages. + let notificationBox = aBrowser.getTabBrowser().getNotificationBox(aBrowser); + ["drmContentDisabled", "drmContentCDMInstalling"].forEach(function(value) { + let notification = notificationBox.getNotificationWithValue(value); + if (notification) { + notificationBox.removeNotification(notification); + } + }); + + // Don't bother creating it if it's already there: + if ( + aBrowser.ownerGlobal.PopupNotifications.getNotification( + "drmContentPlaying", + aBrowser + ) + ) { + return; + } + + let msgPrefix = "eme-notifications-drm-content-playing"; + let msgId = msgPrefix; + let manageLabelId = msgPrefix + "-manage"; + let manageAccessKeyId = msgPrefix + "-manage-accesskey"; + let dismissLabelId = msgPrefix + "-dismiss"; + let dismissAccessKeyId = msgPrefix + "-dismiss-accesskey"; + + let [ + message, + manageLabel, + manageAccessKey, + dismissLabel, + dismissAccessKey, + ] = await lazy.gFluentStrings.formatValues([ + msgId, + manageLabelId, + manageAccessKeyId, + dismissLabelId, + dismissAccessKeyId, + ]); + + let anchorId = "eme-notification-icon"; + let firstPlayPref = "browser.eme.ui.firstContentShown"; + let document = aBrowser.ownerDocument; + 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: manageLabel, + accessKey: manageAccessKey, + callback() { + aBrowser.ownerGlobal.openPreferences("general-drm"); + }, + dismiss: true, + }; + + let secondaryActions = [ + { + label: dismissLabel, + accessKey: dismissAccessKey, + callback: () => {}, + dismiss: true, + }, + ]; + + let options = { + dismissed: true, + eventCallback: aTopic => aTopic == "swapping", + learnMoreURL: + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "drm-content", + hideClose: true, + }; + aBrowser.ownerGlobal.PopupNotifications.show( + aBrowser, + "drmContentPlaying", + message, + anchorId, + mainAction, + secondaryActions, + options + ); + } +} |