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"use strict"; 8 9var EXPORTED_SYMBOLS = ["DecoderDoctorParent"]; 10 11const { AppConstants } = ChromeUtils.import( 12 "resource://gre/modules/AppConstants.jsm" 13); 14const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 15const { XPCOMUtils } = ChromeUtils.import( 16 "resource://gre/modules/XPCOMUtils.jsm" 17); 18 19XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() { 20 return Services.strings.createBundle( 21 "chrome://browser/locale/browser.properties" 22 ); 23}); 24 25XPCOMUtils.defineLazyPreferenceGetter( 26 this, 27 "DEBUG_LOG", 28 "media.decoder-doctor.testing", 29 false 30); 31 32function LOG_DD(message) { 33 if (DEBUG_LOG) { 34 dump("[DecoderDoctorParent] " + message + "\n"); 35 } 36} 37 38class DecoderDoctorParent extends JSWindowActorParent { 39 getLabelForNotificationBox({ type, decoderDoctorReportId }) { 40 if (type == "platform-decoder-not-found") { 41 if (decoderDoctorReportId == "MediaWMFNeeded") { 42 return gNavigatorBundle.GetStringFromName( 43 "decoder.noHWAcceleration.message" 44 ); 45 } 46 // Although this name seems generic, this is actually for not being able 47 // to find libavcodec on Linux. 48 if (decoderDoctorReportId == "MediaPlatformDecoderNotFound") { 49 return gNavigatorBundle.GetStringFromName( 50 "decoder.noCodecsLinux.message" 51 ); 52 } 53 } 54 if (type == "cannot-initialize-pulseaudio") { 55 return gNavigatorBundle.GetStringFromName("decoder.noPulseAudio.message"); 56 } 57 if (type == "unsupported-libavcodec" && AppConstants.platform == "linux") { 58 return gNavigatorBundle.GetStringFromName( 59 "decoder.unsupportedLibavcodec.message" 60 ); 61 } 62 if (type == "decode-error") { 63 return gNavigatorBundle.GetStringFromName("decoder.decodeError.message"); 64 } 65 if (type == "decode-warning") { 66 return gNavigatorBundle.GetStringFromName( 67 "decoder.decodeWarning.message" 68 ); 69 } 70 return ""; 71 } 72 73 getSumoForLearnHowButton({ type, decoderDoctorReportId }) { 74 if ( 75 type == "platform-decoder-not-found" && 76 decoderDoctorReportId == "MediaWMFNeeded" 77 ) { 78 return "fix-video-audio-problems-firefox-windows"; 79 } 80 if (type == "cannot-initialize-pulseaudio") { 81 return "fix-common-audio-and-video-issues"; 82 } 83 return ""; 84 } 85 86 getEndpointForReportIssueButton(type) { 87 if (type == "decode-error" || type == "decode-warning") { 88 return Services.prefs.getStringPref( 89 "media.decoder-doctor.new-issue-endpoint", 90 "" 91 ); 92 } 93 return ""; 94 } 95 96 receiveMessage(aMessage) { 97 // The top level browsing context's embedding element should be a xul browser element. 98 let browser = this.browsingContext.top.embedderElement; 99 // The xul browser is owned by a window. 100 let window = browser?.ownerGlobal; 101 102 if (!browser || !window) { 103 // We don't have a browser or window so bail! 104 return; 105 } 106 107 let box = browser.getTabBrowser().getNotificationBox(browser); 108 let notificationId = "decoder-doctor-notification"; 109 if (box.getNotificationWithValue(notificationId)) { 110 // We already have a notification showing, bail. 111 return; 112 } 113 114 let parsedData; 115 try { 116 parsedData = JSON.parse(aMessage.data); 117 } catch (ex) { 118 Cu.reportError( 119 "Malformed Decoder Doctor message with data: " + aMessage.data 120 ); 121 return; 122 } 123 // parsedData (the result of parsing the incoming 'data' json string) 124 // contains analysis information from Decoder Doctor: 125 // - 'type' is the type of issue, it determines which text to show in the 126 // infobar. 127 // - 'isSolved' is true when the notification actually indicates the 128 // resolution of that issue, to be reported as telemetry. 129 // - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be 130 // used here as key for the telemetry (counting infobar displays, 131 // "Learn how" buttons clicks, and resolutions) and for the prefs used 132 // to store at-issue formats. 133 // - 'formats' contains a comma-separated list of formats (or key systems) 134 // that suffer the issue. These are kept in a pref, which the backend 135 // uses to later find when an issue is resolved. 136 // - 'decodeIssue' is a description of the decode error/warning. 137 // - 'resourceURL' is the resource with the issue. 138 let { 139 type, 140 isSolved, 141 decoderDoctorReportId, 142 formats, 143 decodeIssue, 144 docURL, 145 resourceURL, 146 } = parsedData; 147 type = type.toLowerCase(); 148 // Error out early on invalid ReportId 149 if (!/^\w+$/im.test(decoderDoctorReportId)) { 150 return; 151 } 152 LOG_DD( 153 `type=${type}, isSolved=${isSolved}, ` + 154 `decoderDoctorReportId=${decoderDoctorReportId}, formats=${formats}, ` + 155 `decodeIssue=${decodeIssue}, docURL=${docURL}, ` + 156 `resourceURL=${resourceURL}` 157 ); 158 let title = this.getLabelForNotificationBox({ 159 type, 160 decoderDoctorReportId, 161 }); 162 if (!title) { 163 return; 164 } 165 166 // We keep the list of formats in prefs for the sake of the decoder itself, 167 // which reads it to determine when issues get solved for these formats. 168 // (Writing prefs from e10s content is not allowed.) 169 let formatsPref = 170 formats && "media.decoder-doctor." + decoderDoctorReportId + ".formats"; 171 let buttonClickedPref = 172 "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked"; 173 let formatsInPref = formats && Services.prefs.getCharPref(formatsPref, ""); 174 175 if (!isSolved) { 176 if (formats) { 177 if (!formatsInPref) { 178 Services.prefs.setCharPref(formatsPref, formats); 179 } else { 180 // Split existing formats into an array of strings. 181 let existing = formatsInPref.split(",").map(x => x.trim()); 182 // Keep given formats that were not already recorded. 183 let newbies = formats 184 .split(",") 185 .map(x => x.trim()) 186 .filter(x => !existing.includes(x)); 187 // And rewrite pref with the added new formats (if any). 188 if (newbies.length) { 189 Services.prefs.setCharPref( 190 formatsPref, 191 existing.concat(newbies).join(", ") 192 ); 193 } 194 } 195 } else if (!decodeIssue) { 196 Cu.reportError( 197 "Malformed Decoder Doctor unsolved message with no formats nor decode issue" 198 ); 199 return; 200 } 201 202 let buttons = []; 203 let sumo = this.getSumoForLearnHowButton({ type, decoderDoctorReportId }); 204 if (sumo) { 205 LOG_DD(`sumo=${sumo}`); 206 buttons.push({ 207 label: gNavigatorBundle.GetStringFromName("decoder.noCodecs.button"), 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: gNavigatorBundle.GetStringFromName( 225 "decoder.decodeError.button" 226 ), 227 accessKey: 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 title, 258 notificationId, 259 "", // This uses the info icon as specified below. 260 box.PRIORITY_INFO_LOW, 261 buttons 262 ); 263 } else if (formatsInPref) { 264 // Issue is solved, and prefs haven't been cleared yet, meaning it's the 265 // first time we get this resolution -> Clear prefs and report telemetry. 266 Services.prefs.clearUserPref(formatsPref); 267 Services.prefs.clearUserPref(buttonClickedPref); 268 } 269 } 270} 271