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 }