tor-browser

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

DecoderDoctorParent.sys.mjs (9096B)


      1 /* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      8 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      9 
     10 const lazy = {};
     11 
     12 ChromeUtils.defineLazyGetter(lazy, "gNavigatorBundle", function () {
     13  return Services.strings.createBundle(
     14    "chrome://browser/locale/browser.properties"
     15  );
     16 });
     17 
     18 XPCOMUtils.defineLazyPreferenceGetter(
     19  lazy,
     20  "DEBUG_LOG",
     21  "media.decoder-doctor.testing",
     22  false
     23 );
     24 
     25 function LOG_DD(message) {
     26  if (lazy.DEBUG_LOG) {
     27    dump("[DecoderDoctorParent] " + message + "\n");
     28  }
     29 }
     30 
     31 export class DecoderDoctorParent extends JSWindowActorParent {
     32  getLabelForNotificationBox({ type, decoderDoctorReportId }) {
     33    if (type == "platform-decoder-not-found") {
     34      if (decoderDoctorReportId == "MediaWMFNeeded") {
     35        return lazy.gNavigatorBundle.GetStringFromName(
     36          "decoder.noHWAcceleration.message"
     37        );
     38      }
     39      // Although this name seems generic, this is actually for not being able
     40      // to find libavcodec on Linux.
     41      if (decoderDoctorReportId == "MediaPlatformDecoderNotFound") {
     42        return lazy.gNavigatorBundle.GetStringFromName(
     43          "decoder.noCodecsLinux.message"
     44        );
     45      }
     46    }
     47    if (type == "cannot-initialize-pulseaudio") {
     48      return lazy.gNavigatorBundle.GetStringFromName(
     49        "decoder.noPulseAudio.message"
     50      );
     51    }
     52    if (type == "unsupported-libavcodec" && AppConstants.platform == "linux") {
     53      return lazy.gNavigatorBundle.GetStringFromName(
     54        "decoder.unsupportedLibavcodec.message"
     55      );
     56    }
     57    if (type == "decode-error") {
     58      return lazy.gNavigatorBundle.GetStringFromName(
     59        "decoder.decodeError.message"
     60      );
     61    }
     62    if (type == "decode-warning") {
     63      return lazy.gNavigatorBundle.GetStringFromName(
     64        "decoder.decodeWarning.message"
     65      );
     66    }
     67    return "";
     68  }
     69 
     70  getSumoForLearnHowButton({ type, decoderDoctorReportId }) {
     71    if (
     72      type == "platform-decoder-not-found" &&
     73      decoderDoctorReportId == "MediaWMFNeeded"
     74    ) {
     75      return "fix-video-audio-problems-firefox-windows";
     76    }
     77    if (type == "cannot-initialize-pulseaudio") {
     78      return "fix-common-audio-and-video-issues";
     79    }
     80    return "";
     81  }
     82 
     83  getEndpointForReportIssueButton(type) {
     84    if (type == "decode-error" || type == "decode-warning") {
     85      return Services.prefs.getStringPref(
     86        "media.decoder-doctor.new-issue-endpoint",
     87        ""
     88      );
     89    }
     90    return "";
     91  }
     92 
     93  receiveMessage(aMessage) {
     94    // The top level browsing context's embedding element should be a xul browser element.
     95    let browser = this.browsingContext.top.embedderElement;
     96    // The xul browser is owned by a window.
     97    let window = browser?.ownerGlobal;
     98 
     99    if (!browser || !window) {
    100      // We don't have a browser or window so bail!
    101      return;
    102    }
    103 
    104    let box = browser.getTabBrowser().getNotificationBox(browser);
    105    let notificationId = "decoder-doctor-notification";
    106    if (box.getNotificationWithValue(notificationId)) {
    107      // We already have a notification showing, bail.
    108      return;
    109    }
    110 
    111    let parsedData;
    112    try {
    113      parsedData = JSON.parse(aMessage.data);
    114    } catch (ex) {
    115      console.error(
    116        "Malformed Decoder Doctor message with data: ",
    117        aMessage.data
    118      );
    119      return;
    120    }
    121    // parsedData (the result of parsing the incoming 'data' json string)
    122    // contains analysis information from Decoder Doctor:
    123    // - 'type' is the type of issue, it determines which text to show in the
    124    //   infobar.
    125    // - 'isSolved' is true when the notification actually indicates the
    126    //   resolution of that issue, to be reported as telemetry.
    127    // - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be
    128    //   used here as key for the telemetry (counting infobar displays,
    129    //   "Learn how" buttons clicks, and resolutions) and for the prefs used
    130    //   to store at-issue formats.
    131    // - 'formats' contains a comma-separated list of formats (or key systems)
    132    //   that suffer the issue. These are kept in a pref, which the backend
    133    //   uses to later find when an issue is resolved.
    134    // - 'decodeIssue' is a description of the decode error/warning.
    135    // - 'resourceURL' is the resource with the issue.
    136    let {
    137      type,
    138      isSolved,
    139      decoderDoctorReportId,
    140      formats,
    141      decodeIssue,
    142      docURL,
    143      resourceURL,
    144    } = parsedData;
    145    type = type.toLowerCase();
    146    // Error out early on invalid ReportId
    147    if (!/^\w+$/im.test(decoderDoctorReportId)) {
    148      return;
    149    }
    150    LOG_DD(
    151      `type=${type}, isSolved=${isSolved}, ` +
    152        `decoderDoctorReportId=${decoderDoctorReportId}, formats=${formats}, ` +
    153        `decodeIssue=${decodeIssue}, docURL=${docURL}, ` +
    154        `resourceURL=${resourceURL}`
    155    );
    156    let title = this.getLabelForNotificationBox({
    157      type,
    158      decoderDoctorReportId,
    159    });
    160    if (!title) {
    161      return;
    162    }
    163 
    164    // We keep the list of formats in prefs for the sake of the decoder itself,
    165    // which reads it to determine when issues get solved for these formats.
    166    // (Writing prefs from e10s content is not allowed.)
    167    let formatsPref =
    168      formats && "media.decoder-doctor." + decoderDoctorReportId + ".formats";
    169    let buttonClickedPref =
    170      "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
    171    let formatsInPref = formats && Services.prefs.getCharPref(formatsPref, "");
    172 
    173    if (!isSolved) {
    174      if (formats) {
    175        if (!formatsInPref) {
    176          Services.prefs.setCharPref(formatsPref, formats);
    177        } else {
    178          // Split existing formats into an array of strings.
    179          let existing = formatsInPref.split(",").map(x => x.trim());
    180          // Keep given formats that were not already recorded.
    181          let newbies = formats
    182            .split(",")
    183            .map(x => x.trim())
    184            .filter(x => !existing.includes(x));
    185          // And rewrite pref with the added new formats (if any).
    186          if (newbies.length) {
    187            Services.prefs.setCharPref(
    188              formatsPref,
    189              existing.concat(newbies).join(", ")
    190            );
    191          }
    192        }
    193      } else if (!decodeIssue) {
    194        console.error(
    195          "Malformed Decoder Doctor unsolved message with no formats nor decode issue"
    196        );
    197        return;
    198      }
    199 
    200      let buttons = [];
    201      let sumo = this.getSumoForLearnHowButton({ type, decoderDoctorReportId });
    202      if (sumo) {
    203        LOG_DD(`sumo=${sumo}`);
    204        buttons.push({
    205          label: lazy.gNavigatorBundle.GetStringFromName(
    206            "decoder.noCodecs.button"
    207          ),
    208          supportPage: sumo,
    209          callback() {
    210            let clickedInPref = Services.prefs.getBoolPref(
    211              buttonClickedPref,
    212              false
    213            );
    214            if (!clickedInPref) {
    215              Services.prefs.setBoolPref(buttonClickedPref, true);
    216            }
    217          },
    218        });
    219      }
    220      let endpoint = this.getEndpointForReportIssueButton(type);
    221      if (endpoint) {
    222        LOG_DD(`endpoint=${endpoint}`);
    223        buttons.push({
    224          label: lazy.gNavigatorBundle.GetStringFromName(
    225            "decoder.decodeError.button"
    226          ),
    227          accessKey: lazy.gNavigatorBundle.GetStringFromName(
    228            "decoder.decodeError.accesskey"
    229          ),
    230          callback() {
    231            let clickedInPref = Services.prefs.getBoolPref(
    232              buttonClickedPref,
    233              false
    234            );
    235            if (!clickedInPref) {
    236              Services.prefs.setBoolPref(buttonClickedPref, true);
    237            }
    238 
    239            let params = new URLSearchParams();
    240            params.append("url", docURL);
    241            params.append("label", "type-media");
    242            params.append("problem_type", "video_bug");
    243            params.append("src", "media-decode-error");
    244 
    245            let details = { "Technical Information:": decodeIssue };
    246            if (resourceURL) {
    247              details["Resource:"] = resourceURL;
    248            }
    249 
    250            params.append("details", JSON.stringify(details));
    251            window.openTrustedLinkIn(endpoint + "?" + params.toString(), "tab");
    252          },
    253        });
    254      }
    255 
    256      box.appendNotification(
    257        notificationId,
    258        {
    259          label: title,
    260          image: "", // This uses the info icon as specified below.
    261          priority: box.PRIORITY_INFO_LOW,
    262        },
    263        buttons
    264      );
    265    } else if (formatsInPref) {
    266      // Issue is solved, and prefs haven't been cleared yet, meaning it's the
    267      // first time we get this resolution -> Clear prefs and report telemetry.
    268      Services.prefs.clearUserPref(formatsPref);
    269      Services.prefs.clearUserPref(buttonClickedPref);
    270    }
    271  }
    272 }