diff options
Diffstat (limited to 'browser/actors/DecoderDoctorParent.sys.mjs')
-rw-r--r-- | browser/actors/DecoderDoctorParent.sys.mjs | 272 |
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); + } + } +} |