summaryrefslogtreecommitdiffstats
path: root/browser/actors/DecoderDoctorParent.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/actors/DecoderDoctorParent.sys.mjs272
1 files changed, 272 insertions, 0 deletions
diff --git a/browser/actors/DecoderDoctorParent.sys.mjs b/browser/actors/DecoderDoctorParent.sys.mjs
new file mode 100644
index 0000000000..f0c789ec57
--- /dev/null
+++ b/browser/actors/DecoderDoctorParent.sys.mjs
@@ -0,0 +1,272 @@
+/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyGetter(lazy, "gNavigatorBundle", function () {
+ return Services.strings.createBundle(
+ "chrome://browser/locale/browser.properties"
+ );
+});
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "DEBUG_LOG",
+ "media.decoder-doctor.testing",
+ false
+);
+
+function LOG_DD(message) {
+ if (lazy.DEBUG_LOG) {
+ dump("[DecoderDoctorParent] " + message + "\n");
+ }
+}
+
+export class DecoderDoctorParent extends JSWindowActorParent {
+ getLabelForNotificationBox({ type, decoderDoctorReportId }) {
+ if (type == "platform-decoder-not-found") {
+ if (decoderDoctorReportId == "MediaWMFNeeded") {
+ return lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.noHWAcceleration.message"
+ );
+ }
+ // Although this name seems generic, this is actually for not being able
+ // to find libavcodec on Linux.
+ if (decoderDoctorReportId == "MediaPlatformDecoderNotFound") {
+ return lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.noCodecsLinux.message"
+ );
+ }
+ }
+ if (type == "cannot-initialize-pulseaudio") {
+ return lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.noPulseAudio.message"
+ );
+ }
+ if (type == "unsupported-libavcodec" && AppConstants.platform == "linux") {
+ return lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.unsupportedLibavcodec.message"
+ );
+ }
+ if (type == "decode-error") {
+ return lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.decodeError.message"
+ );
+ }
+ if (type == "decode-warning") {
+ return lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.decodeWarning.message"
+ );
+ }
+ return "";
+ }
+
+ getSumoForLearnHowButton({ type, decoderDoctorReportId }) {
+ if (
+ type == "platform-decoder-not-found" &&
+ decoderDoctorReportId == "MediaWMFNeeded"
+ ) {
+ return "fix-video-audio-problems-firefox-windows";
+ }
+ if (type == "cannot-initialize-pulseaudio") {
+ return "fix-common-audio-and-video-issues";
+ }
+ return "";
+ }
+
+ getEndpointForReportIssueButton(type) {
+ if (type == "decode-error" || type == "decode-warning") {
+ return Services.prefs.getStringPref(
+ "media.decoder-doctor.new-issue-endpoint",
+ ""
+ );
+ }
+ return "";
+ }
+
+ receiveMessage(aMessage) {
+ // The top level browsing context's embedding element should be a xul browser element.
+ let browser = this.browsingContext.top.embedderElement;
+ // The xul browser is owned by a window.
+ let window = browser?.ownerGlobal;
+
+ if (!browser || !window) {
+ // We don't have a browser or window so bail!
+ return;
+ }
+
+ let box = browser.getTabBrowser().getNotificationBox(browser);
+ let notificationId = "decoder-doctor-notification";
+ if (box.getNotificationWithValue(notificationId)) {
+ // We already have a notification showing, bail.
+ return;
+ }
+
+ let parsedData;
+ try {
+ parsedData = JSON.parse(aMessage.data);
+ } catch (ex) {
+ console.error(
+ "Malformed Decoder Doctor message with data: ",
+ aMessage.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.
+ // - 'isSolved' is true when the notification actually indicates the
+ // resolution of that issue, to be reported as telemetry.
+ // - '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.
+ // - 'decodeIssue' is a description of the decode error/warning.
+ // - 'resourceURL' is the resource with the issue.
+ let {
+ type,
+ isSolved,
+ decoderDoctorReportId,
+ formats,
+ decodeIssue,
+ docURL,
+ resourceURL,
+ } = parsedData;
+ type = type.toLowerCase();
+ // Error out early on invalid ReportId
+ if (!/^\w+$/im.test(decoderDoctorReportId)) {
+ return;
+ }
+ LOG_DD(
+ `type=${type}, isSolved=${isSolved}, ` +
+ `decoderDoctorReportId=${decoderDoctorReportId}, formats=${formats}, ` +
+ `decodeIssue=${decodeIssue}, docURL=${docURL}, ` +
+ `resourceURL=${resourceURL}`
+ );
+ let title = this.getLabelForNotificationBox({
+ type,
+ decoderDoctorReportId,
+ });
+ 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 not allowed.)
+ let formatsPref =
+ formats && "media.decoder-doctor." + decoderDoctorReportId + ".formats";
+ let buttonClickedPref =
+ "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
+ let formatsInPref = formats && Services.prefs.getCharPref(formatsPref, "");
+
+ if (!isSolved) {
+ if (formats) {
+ if (!formatsInPref) {
+ Services.prefs.setCharPref(formatsPref, formats);
+ } else {
+ // Split existing formats into an array of strings.
+ let existing = formatsInPref.split(",").map(x => x.trim());
+ // Keep given formats that were not already recorded.
+ let newbies = formats
+ .split(",")
+ .map(x => x.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(", ")
+ );
+ }
+ }
+ } else if (!decodeIssue) {
+ console.error(
+ "Malformed Decoder Doctor unsolved message with no formats nor decode issue"
+ );
+ return;
+ }
+
+ let buttons = [];
+ let sumo = this.getSumoForLearnHowButton({ type, decoderDoctorReportId });
+ if (sumo) {
+ LOG_DD(`sumo=${sumo}`);
+ buttons.push({
+ label: lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.noCodecs.button"
+ ),
+ supportPage: sumo,
+ callback() {
+ let clickedInPref = Services.prefs.getBoolPref(
+ buttonClickedPref,
+ false
+ );
+ if (!clickedInPref) {
+ Services.prefs.setBoolPref(buttonClickedPref, true);
+ }
+ },
+ });
+ }
+ let endpoint = this.getEndpointForReportIssueButton(type);
+ if (endpoint) {
+ LOG_DD(`endpoint=${endpoint}`);
+ buttons.push({
+ label: lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.decodeError.button"
+ ),
+ accessKey: lazy.gNavigatorBundle.GetStringFromName(
+ "decoder.decodeError.accesskey"
+ ),
+ callback() {
+ let clickedInPref = Services.prefs.getBoolPref(
+ buttonClickedPref,
+ false
+ );
+ if (!clickedInPref) {
+ Services.prefs.setBoolPref(buttonClickedPref, true);
+ }
+
+ let params = new URLSearchParams();
+ params.append("url", docURL);
+ params.append("label", "type-media");
+ params.append("problem_type", "video_bug");
+ params.append("src", "media-decode-error");
+
+ let details = { "Technical Information:": decodeIssue };
+ if (resourceURL) {
+ details["Resource:"] = resourceURL;
+ }
+
+ params.append("details", JSON.stringify(details));
+ window.openTrustedLinkIn(endpoint + "?" + params.toString(), "tab");
+ },
+ });
+ }
+
+ box.appendNotification(
+ notificationId,
+ {
+ label: title,
+ image: "", // This uses the info icon as specified below.
+ priority: 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);
+ }
+ }
+}