tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

EncryptedMediaParent.sys.mjs (9467B)


      1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 const lazy = {};
      7 
      8 ChromeUtils.defineLazyGetter(lazy, "gBrandBundle", function () {
      9  return Services.strings.createBundle(
     10    "chrome://branding/locale/brand.properties"
     11  );
     12 });
     13 
     14 ChromeUtils.defineLazyGetter(lazy, "gNavigatorBundle", function () {
     15  return Services.strings.createBundle(
     16    "chrome://browser/locale/browser.properties"
     17  );
     18 });
     19 
     20 ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () {
     21  return new Localization(["branding/brand.ftl", "browser/browser.ftl"], true);
     22 });
     23 
     24 export class EncryptedMediaParent extends JSWindowActorParent {
     25  isUiEnabled() {
     26    return Services.prefs.getBoolPref("browser.eme.ui.enabled");
     27  }
     28 
     29  ensureEMEEnabled(aBrowser, aKeySystem) {
     30    Services.prefs.setBoolPref("media.eme.enabled", true);
     31    if (
     32      aKeySystem &&
     33      aKeySystem == "com.widevine.alpha" &&
     34      Services.prefs.getPrefType("media.gmp-widevinecdm.enabled") &&
     35      !Services.prefs.getBoolPref("media.gmp-widevinecdm.enabled")
     36    ) {
     37      Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", true);
     38    }
     39    aBrowser.reload();
     40  }
     41 
     42  isKeySystemVisible(aKeySystem) {
     43    if (!aKeySystem) {
     44      return false;
     45    }
     46    if (
     47      aKeySystem == "com.widevine.alpha" &&
     48      Services.prefs.getPrefType("media.gmp-widevinecdm.visible")
     49    ) {
     50      return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
     51    }
     52    return true;
     53  }
     54 
     55  getMessageWithBrandName(aNotificationId) {
     56    let msgId = "emeNotifications." + aNotificationId + ".message";
     57    return lazy.gNavigatorBundle.formatStringFromName(msgId, [
     58      lazy.gBrandBundle.GetStringFromName("brandShortName"),
     59    ]);
     60  }
     61 
     62  async receiveMessage(aMessage) {
     63    if (!this.handledMessages) {
     64      this.handledMessages = new Set();
     65    }
     66    // The top level browsing context's embedding element should be a xul browser element.
     67    let browser = this.browsingContext.top.embedderElement;
     68 
     69    if (!browser) {
     70      // We don't have a browser so bail!
     71      return;
     72    }
     73 
     74    let parsedData;
     75    try {
     76      parsedData = JSON.parse(aMessage.data);
     77    } catch (ex) {
     78      console.error("Malformed EME video message with data: ", aMessage.data);
     79      return;
     80    }
     81    let { status, keySystem } = parsedData;
     82    if (this.handledMessages.has(status)) {
     83      return;
     84    }
     85 
     86    // First, see if we need to do updates. We don't need to do anything for
     87    // hidden keysystems:
     88    if (!this.isKeySystemVisible(keySystem)) {
     89      return;
     90    }
     91    if (status == "cdm-not-installed") {
     92      Services.obs.notifyObservers(browser, "EMEVideo:CDMMissing");
     93    }
     94 
     95    // Don't need to show UI if disabled.
     96    if (!this.isUiEnabled()) {
     97      return;
     98    }
     99 
    100    let notificationId;
    101    let buttonCallback;
    102    let supportPage;
    103    // Notification message can be either a string or a DOM fragment.
    104    let notificationMessage;
    105    switch (status) {
    106      case "available":
    107      case "cdm-created":
    108        // Only show the chain icon for proprietary CDMs. Clearkey is not one.
    109        if (keySystem != "org.w3.clearkey") {
    110          this.showPopupNotificationForSuccess(browser, keySystem);
    111        }
    112        this.reportEMEDecryptionProbe();
    113        // ... and bail!
    114        return;
    115 
    116      case "api-disabled":
    117      case "cdm-disabled":
    118        this.handledMessages.add(status);
    119        notificationId = "drmContentDisabled";
    120        buttonCallback = () => {
    121          this.ensureEMEEnabled(browser, keySystem);
    122        };
    123        notificationMessage = lazy.gNavigatorBundle.GetStringFromName(
    124          "emeNotifications.drmContentDisabled.message2"
    125        );
    126        supportPage = "drm-content";
    127        break;
    128 
    129      case "cdm-not-installed":
    130        this.handledMessages.add(status);
    131        notificationId = "drmContentCDMInstalling";
    132        notificationMessage = this.getMessageWithBrandName(notificationId);
    133        break;
    134 
    135      case "cdm-not-supported":
    136        // Not to pop up user-level notification because they cannot do anything
    137        // about it.
    138        return;
    139      default:
    140        console.error(
    141          new Error(
    142            "Unknown message ('" +
    143              status +
    144              "') dealing with EME key request: " +
    145              aMessage.data
    146          )
    147        );
    148        return;
    149    }
    150 
    151    // Now actually create the notification
    152 
    153    let notificationBox = browser.getTabBrowser().getNotificationBox(browser);
    154    if (notificationBox.getNotificationWithValue(notificationId)) {
    155      this.handledMessages.delete(status);
    156      return;
    157    }
    158 
    159    let buttons = [];
    160    if (supportPage) {
    161      buttons.push({ supportPage });
    162    }
    163    if (buttonCallback) {
    164      let msgPrefix = "emeNotifications." + notificationId + ".";
    165      let manageLabelId = msgPrefix + "button.label";
    166      let manageAccessKeyId = msgPrefix + "button.accesskey";
    167      buttons.push({
    168        label: lazy.gNavigatorBundle.GetStringFromName(manageLabelId),
    169        accessKey: lazy.gNavigatorBundle.GetStringFromName(manageAccessKeyId),
    170        callback: buttonCallback,
    171      });
    172    }
    173 
    174    let iconURL = "chrome://browser/skin/drm-icon.svg";
    175    await notificationBox.appendNotification(
    176      notificationId,
    177      {
    178        label: notificationMessage,
    179        image: iconURL,
    180        priority: notificationBox.PRIORITY_INFO_HIGH,
    181      },
    182      buttons
    183    );
    184    this.handledMessages.delete(status);
    185  }
    186 
    187  async showPopupNotificationForSuccess(aBrowser) {
    188    // We're playing EME content! Remove any "we can't play because..." messages.
    189    let notificationBox = aBrowser.getTabBrowser().getNotificationBox(aBrowser);
    190    ["drmContentDisabled", "drmContentCDMInstalling"].forEach(function (value) {
    191      let notification = notificationBox.getNotificationWithValue(value);
    192      if (notification) {
    193        notificationBox.removeNotification(notification);
    194      }
    195    });
    196 
    197    // Don't bother creating it if it's already there:
    198    if (
    199      aBrowser.ownerGlobal.PopupNotifications.getNotification(
    200        "drmContentPlaying",
    201        aBrowser
    202      )
    203    ) {
    204      return;
    205    }
    206 
    207    let msgId = "eme-notifications-drm-content-playing";
    208    let manageLabelId = "eme-notifications-drm-content-playing-manage";
    209    let manageAccessKeyId =
    210      "eme-notifications-drm-content-playing-manage-accesskey";
    211    let dismissLabelId = "eme-notifications-drm-content-playing-dismiss";
    212    let dismissAccessKeyId =
    213      "eme-notifications-drm-content-playing-dismiss-accesskey";
    214 
    215    let [
    216      message,
    217      manageLabel,
    218      manageAccessKey,
    219      dismissLabel,
    220      dismissAccessKey,
    221    ] = await lazy.gFluentStrings.formatValues([
    222      msgId,
    223      manageLabelId,
    224      manageAccessKeyId,
    225      dismissLabelId,
    226      dismissAccessKeyId,
    227    ]);
    228 
    229    let anchorId = "eme-notification-icon";
    230    let firstPlayPref = "browser.eme.ui.firstContentShown";
    231    let document = aBrowser.ownerDocument;
    232    if (
    233      !Services.prefs.getPrefType(firstPlayPref) ||
    234      !Services.prefs.getBoolPref(firstPlayPref)
    235    ) {
    236      document.getElementById(anchorId).setAttribute("firstplay", "true");
    237      Services.prefs.setBoolPref(firstPlayPref, true);
    238    } else {
    239      document.getElementById(anchorId).removeAttribute("firstplay");
    240    }
    241 
    242    let mainAction = {
    243      label: manageLabel,
    244      accessKey: manageAccessKey,
    245      callback() {
    246        aBrowser.ownerGlobal.openPreferences("general-drm");
    247      },
    248      dismiss: true,
    249    };
    250 
    251    let secondaryActions = [
    252      {
    253        label: dismissLabel,
    254        accessKey: dismissAccessKey,
    255        callback: () => {},
    256        dismiss: true,
    257      },
    258    ];
    259 
    260    let options = {
    261      dismissed: true,
    262      eventCallback: aTopic => aTopic == "swapping",
    263      learnMoreURL:
    264        Services.urlFormatter.formatURLPref("app.support.baseURL") +
    265        "drm-content",
    266      hideClose: true,
    267    };
    268    aBrowser.ownerGlobal.PopupNotifications.show(
    269      aBrowser,
    270      "drmContentPlaying",
    271      message,
    272      anchorId,
    273      mainAction,
    274      secondaryActions,
    275      options
    276    );
    277  }
    278 
    279  async reportEMEDecryptionProbe() {
    280    let hasHardwareDecryption = false;
    281    let hasSoftwareClearlead = false;
    282    let hasHardwareClearlead = false;
    283    let hasWMF = false;
    284 
    285    // Get CDM capabilities from the GMP process.
    286    let infos = [];
    287    let cdmInfo = await ChromeUtils.getGMPContentDecryptionModuleInformation();
    288    infos.push(...cdmInfo);
    289 
    290    // Get CDM capabilities from the MFCDM process, if exists.
    291    if (ChromeUtils.getWMFContentDecryptionModuleInformation !== undefined) {
    292      hasWMF = true;
    293      cdmInfo = await ChromeUtils.getWMFContentDecryptionModuleInformation();
    294      infos.push(...cdmInfo);
    295    }
    296 
    297    for (let info of infos) {
    298      if (info.isHardwareDecryption) {
    299        hasHardwareDecryption = true;
    300      }
    301      if (info.clearlead) {
    302        if (info.isHardwareDecryption) {
    303          hasHardwareClearlead = true;
    304        } else {
    305          hasSoftwareClearlead = true;
    306        }
    307      }
    308    }
    309    Glean.mediadrm.decryption.has_hardware_decryption.set(
    310      hasHardwareDecryption
    311    );
    312    Glean.mediadrm.decryption.has_hardware_clearlead.set(hasHardwareClearlead);
    313    Glean.mediadrm.decryption.has_software_clearlead.set(hasSoftwareClearlead);
    314    Glean.mediadrm.decryption.has_wmf.set(hasWMF);
    315  }
    316 }