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/. */
7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
8 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
12 ChromeUtils.defineLazyGetter(lazy, "gNavigatorBundle", function () {
13 return Services.strings.createBundle(
14 "chrome://browser/locale/browser.properties"
18 XPCOMUtils.defineLazyPreferenceGetter(
21 "media.decoder-doctor.testing",
25 function LOG_DD(message) {
27 dump("[DecoderDoctorParent] " + message + "\n");
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"
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"
47 if (type == "cannot-initialize-pulseaudio") {
48 return lazy.gNavigatorBundle.GetStringFromName(
49 "decoder.noPulseAudio.message"
52 if (type == "unsupported-libavcodec" && AppConstants.platform == "linux") {
53 return lazy.gNavigatorBundle.GetStringFromName(
54 "decoder.unsupportedLibavcodec.message"
57 if (type == "decode-error") {
58 return lazy.gNavigatorBundle.GetStringFromName(
59 "decoder.decodeError.message"
62 if (type == "decode-warning") {
63 return lazy.gNavigatorBundle.GetStringFromName(
64 "decoder.decodeWarning.message"
70 getSumoForLearnHowButton({ type, decoderDoctorReportId }) {
72 type == "platform-decoder-not-found" &&
73 decoderDoctorReportId == "MediaWMFNeeded"
75 return "fix-video-audio-problems-firefox-windows";
77 if (type == "cannot-initialize-pulseaudio") {
78 return "fix-common-audio-and-video-issues";
83 getEndpointForReportIssueButton(type) {
84 if (type == "decode-error" || type == "decode-warning") {
85 return Services.prefs.getStringPref(
86 "media.decoder-doctor.new-issue-endpoint",
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;
99 if (!browser || !window) {
100 // We don't have a browser or window so bail!
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.
113 parsedData = JSON.parse(aMessage.data);
116 "Malformed Decoder Doctor message with data: ",
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
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.
139 decoderDoctorReportId,
145 type = type.toLowerCase();
146 // Error out early on invalid ReportId
147 if (!/^\w+$/im.test(decoderDoctorReportId)) {
151 `type=${type}, isSolved=${isSolved}, ` +
152 `decoderDoctorReportId=${decoderDoctorReportId}, formats=${formats}, ` +
153 `decodeIssue=${decodeIssue}, docURL=${docURL}, ` +
154 `resourceURL=${resourceURL}`
156 let title = this.getLabelForNotificationBox({
158 decoderDoctorReportId,
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.)
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, "");
175 if (!formatsInPref) {
176 Services.prefs.setCharPref(formatsPref, formats);
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
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(
189 existing.concat(newbies).join(", ")
193 } else if (!decodeIssue) {
195 "Malformed Decoder Doctor unsolved message with no formats nor decode issue"
201 let sumo = this.getSumoForLearnHowButton({ type, decoderDoctorReportId });
203 LOG_DD(`sumo=${sumo}`);
205 label: lazy.gNavigatorBundle.GetStringFromName(
206 "decoder.noCodecs.button"
210 let clickedInPref = Services.prefs.getBoolPref(
214 if (!clickedInPref) {
215 Services.prefs.setBoolPref(buttonClickedPref, true);
220 let endpoint = this.getEndpointForReportIssueButton(type);
222 LOG_DD(`endpoint=${endpoint}`);
224 label: lazy.gNavigatorBundle.GetStringFromName(
225 "decoder.decodeError.button"
227 accessKey: lazy.gNavigatorBundle.GetStringFromName(
228 "decoder.decodeError.accesskey"
231 let clickedInPref = Services.prefs.getBoolPref(
235 if (!clickedInPref) {
236 Services.prefs.setBoolPref(buttonClickedPref, true);
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");
245 let details = { "Technical Information:": decodeIssue };
247 details["Resource:"] = resourceURL;
250 params.append("details", JSON.stringify(details));
251 window.openTrustedLinkIn(endpoint + "?" + params.toString(), "tab");
256 box.appendNotification(
260 image: "", // This uses the info icon as specified below.
261 priority: box.PRIORITY_INFO_LOW,
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);