css-messages.js (6454B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const nsIConsoleListenerWatcher = require("resource://devtools/server/actors/resources/utils/nsi-console-listener-watcher.js"); 8 const { 9 createStringGrip, 10 } = require("resource://devtools/server/actors/object/utils.js"); 11 const { 12 getActorIdForInternalSourceId, 13 } = require("resource://devtools/server/actors/utils/dbg-source.js"); 14 const { 15 WebConsoleUtils, 16 } = require("resource://devtools/server/actors/webconsole/utils.js"); 17 18 loader.lazyRequireGetter( 19 this, 20 ["getStyleSheetText"], 21 "resource://devtools/server/actors/utils/stylesheet-utils.js", 22 true 23 ); 24 25 const { MESSAGE_CATEGORY } = require("resource://devtools/shared/constants.js"); 26 27 class CSSMessageWatcher extends nsIConsoleListenerWatcher { 28 /** 29 * Start watching for all CSS messages related to a given Target Actor. 30 * This will notify about existing messages, but also the one created in future. 31 * 32 * @param TargetActor targetActor 33 * The target actor from which we should observe messages 34 * @param Object options 35 * Dictionary object with following attributes: 36 * - onAvailable: mandatory function 37 * This will be called for each resource. 38 */ 39 async watch(targetActor, { onAvailable }) { 40 super.watch(targetActor, { onAvailable }); 41 42 // Calling ensureCSSErrorReportingEnabled will make the server parse the stylesheets to 43 // retrieve the warnings if the docShell wasn't already watching for CSS messages. 44 await this.#ensureCSSErrorReportingEnabled(targetActor); 45 } 46 47 /** 48 * Returns true if the message is considered a CSS message, and as a result, should 49 * be sent to the client. 50 * 51 * @param {nsIConsoleMessage|nsIScriptError} message 52 */ 53 shouldHandleMessage(targetActor, message) { 54 // The listener we use can be called either with a nsIConsoleMessage or as nsIScriptError. 55 // In this file, we want to ignore anything but nsIScriptError. 56 if ( 57 // We only care about CSS Parser nsIScriptError 58 !(message instanceof Ci.nsIScriptError) || 59 message.category !== MESSAGE_CATEGORY.CSS_PARSER 60 ) { 61 return false; 62 } 63 64 // Filter specific to CONTENT PROCESS targets 65 // Process targets listen for everything but messages from private windows. 66 if (this.isProcessTarget(targetActor)) { 67 return !message.isFromPrivateWindow; 68 } 69 70 if (!message.innerWindowID) { 71 return false; 72 } 73 74 const ids = targetActor.windows.map(window => 75 WebConsoleUtils.getInnerWindowId(window) 76 ); 77 return ids.includes(message.innerWindowID); 78 } 79 80 /** 81 * Prepare an nsIScriptError to be sent to the client. 82 * 83 * @param nsIScriptError error 84 * The page error we need to send to the client. 85 * @return object 86 * The object you can send to the remote client. 87 */ 88 buildResource(targetActor, error) { 89 const stack = this.prepareStackForRemote(targetActor, error.stack); 90 const notesArray = this.prepareNotesForRemote(targetActor, error.notes); 91 92 // If there is no location information in the error but we have a stack, 93 // fill in the location with the first frame on the stack. 94 let { sourceName, sourceId, lineNumber, columnNumber } = error; 95 if (!sourceName && !sourceId && !lineNumber && !columnNumber && stack) { 96 sourceName = stack[0].filename; 97 sourceId = stack[0].sourceId; 98 lineNumber = stack[0].lineNumber; 99 columnNumber = stack[0].columnNumber; 100 } 101 102 const pageError = { 103 errorMessage: createStringGrip(targetActor, error.errorMessage), 104 sourceName, 105 sourceId: getActorIdForInternalSourceId(targetActor, sourceId), 106 lineNumber, 107 columnNumber, 108 category: error.category, 109 innerWindowID: error.innerWindowID, 110 timeStamp: error.microSecondTimeStamp / 1000, 111 warning: !!(error.flags & error.warningFlag), 112 error: !(error.flags & (error.warningFlag | error.infoFlag)), 113 info: !!(error.flags & error.infoFlag), 114 private: error.isFromPrivateWindow, 115 stacktrace: stack, 116 notes: notesArray, 117 chromeContext: error.isFromChromeContext, 118 isForwardedFromContentProcess: error.isForwardedFromContentProcess, 119 }; 120 121 return { 122 pageError, 123 cssSelectors: error.cssSelectors, 124 }; 125 } 126 127 /** 128 * Ensure that CSS error reporting is enabled for the provided target actor. 129 * 130 * @param {TargetActor} targetActor 131 * The target actor for which CSS Error Reporting should be enabled. 132 * @return {Promise} Promise that resolves when cssErrorReportingEnabled was 133 * set in all the docShells owned by the provided target, and existing 134 * stylesheets have been re-parsed if needed. 135 */ 136 async #ensureCSSErrorReportingEnabled(targetActor) { 137 const docShells = targetActor.docShells; 138 if (!docShells) { 139 // If the target actor does not expose a docShells getter (ie is not an 140 // instance of WindowGlobalTargetActor), nothing to do here. 141 return; 142 } 143 144 const promises = docShells.map(async docShell => { 145 if (docShell.cssErrorReportingEnabled) { 146 // CSS Error Reporting already enabled here, nothing to do. 147 return; 148 } 149 150 try { 151 docShell.cssErrorReportingEnabled = true; 152 } catch (e) { 153 return; 154 } 155 156 // After enabling CSS Error Reporting, reparse existing stylesheets to 157 // detect potential CSS errors. 158 159 // Ensure docShell.document is available. 160 docShell.QueryInterface(Ci.nsIWebNavigation); 161 // We don't really want to reparse UA sheets and such, but want to do 162 // Shadow DOM / XBL. 163 const sheets = InspectorUtils.getAllStyleSheets( 164 docShell.document, 165 /* documentOnly = */ true 166 ); 167 for (const sheet of sheets) { 168 if (InspectorUtils.hasRulesModifiedByCSSOM(sheet)) { 169 continue; 170 } 171 172 try { 173 // Reparse the sheet so that we see the existing errors. 174 const text = await getStyleSheetText(sheet); 175 InspectorUtils.parseStyleSheet(sheet, text, /* aUpdate = */ false); 176 } catch (e) { 177 console.error("Error while parsing stylesheet"); 178 } 179 } 180 }); 181 182 await Promise.all(promises); 183 } 184 } 185 module.exports = CSSMessageWatcher;