Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
"use strict";
const {
DevToolsServer,
const {
createStringGrip,
const {
getActorIdForInternalSourceId,
const {
WebConsoleUtils,
loader.lazyRequireGetter(
this,
["getStyleSheetText"],
true
);
const {
TYPES: { CSS_MESSAGE },
const { MESSAGE_CATEGORY } = require("resource://devtools/shared/constants.js");
class CSSMessageWatcher extends nsIConsoleListenerWatcher {
/**
* Start watching for all CSS messages related to a given Target Actor.
* This will notify about existing messages, but also the one created in future.
*
* @param TargetActor targetActor
* The target actor from which we should observe messages
* @param Object options
* Dictionary object with following attributes:
* - onAvailable: mandatory function
* This will be called for each resource.
*/
async watch(targetActor, { onAvailable }) {
super.watch(targetActor, { onAvailable });
// Calling ensureCSSErrorReportingEnabled will make the server parse the stylesheets to
// retrieve the warnings if the docShell wasn't already watching for CSS messages.
await this.#ensureCSSErrorReportingEnabled(targetActor);
}
/**
* Returns true if the message is considered a CSS message, and as a result, should
* be sent to the client.
*
* @param {nsIConsoleMessage|nsIScriptError} message
*/
shouldHandleMessage(targetActor, message) {
// The listener we use can be called either with a nsIConsoleMessage or as nsIScriptError.
// In this file, we want to ignore anything but nsIScriptError.
if (
// We only care about CSS Parser nsIScriptError
!(message instanceof Ci.nsIScriptError) ||
message.category !== MESSAGE_CATEGORY.CSS_PARSER
) {
return false;
}
// Filter specific to CONTENT PROCESS targets
// Process targets listen for everything but messages from private windows.
if (this.isProcessTarget(targetActor)) {
return !message.isFromPrivateWindow;
}
if (!message.innerWindowID) {
return false;
}
const ids = targetActor.windows.map(window =>
WebConsoleUtils.getInnerWindowId(window)
);
return ids.includes(message.innerWindowID);
}
/**
* Prepare an nsIScriptError to be sent to the client.
*
* @param nsIScriptError error
* The page error we need to send to the client.
* @return object
* The object you can send to the remote client.
*/
buildResource(targetActor, error) {
const stack = this.prepareStackForRemote(targetActor, error.stack);
let lineText = error.sourceLine;
if (
lineText &&
lineText.length > DevToolsServer.LONG_STRING_INITIAL_LENGTH
) {
lineText = lineText.substr(0, DevToolsServer.LONG_STRING_INITIAL_LENGTH);
}
const notesArray = this.prepareNotesForRemote(targetActor, error.notes);
// If there is no location information in the error but we have a stack,
// fill in the location with the first frame on the stack.
let { sourceName, sourceId, lineNumber, columnNumber } = error;
if (!sourceName && !sourceId && !lineNumber && !columnNumber && stack) {
sourceName = stack[0].filename;
sourceId = stack[0].sourceId;
lineNumber = stack[0].lineNumber;
columnNumber = stack[0].columnNumber;
}
const pageError = {
errorMessage: createStringGrip(targetActor, error.errorMessage),
sourceName,
sourceId: getActorIdForInternalSourceId(targetActor, sourceId),
lineText,
lineNumber,
columnNumber,
category: error.category,
innerWindowID: error.innerWindowID,
timeStamp: error.microSecondTimeStamp / 1000,
warning: !!(error.flags & error.warningFlag),
error: !(error.flags & (error.warningFlag | error.infoFlag)),
info: !!(error.flags & error.infoFlag),
private: error.isFromPrivateWindow,
stacktrace: stack,
notes: notesArray,
chromeContext: error.isFromChromeContext,
isForwardedFromContentProcess: error.isForwardedFromContentProcess,
};
return {
pageError,
resourceType: CSS_MESSAGE,
cssSelectors: error.cssSelectors,
};
}
/**
* Ensure that CSS error reporting is enabled for the provided target actor.
*
* @param {TargetActor} targetActor
* The target actor for which CSS Error Reporting should be enabled.
* @return {Promise} Promise that resolves when cssErrorReportingEnabled was
* set in all the docShells owned by the provided target, and existing
* stylesheets have been re-parsed if needed.
*/
async #ensureCSSErrorReportingEnabled(targetActor) {
const docShells = targetActor.docShells;
if (!docShells) {
// If the target actor does not expose a docShells getter (ie is not an
// instance of WindowGlobalTargetActor), nothing to do here.
return;
}
const promises = docShells.map(async docShell => {
if (docShell.cssErrorReportingEnabled) {
// CSS Error Reporting already enabled here, nothing to do.
return;
}
try {
docShell.cssErrorReportingEnabled = true;
} catch (e) {
return;
}
// After enabling CSS Error Reporting, reparse existing stylesheets to
// detect potential CSS errors.
// Ensure docShell.document is available.
docShell.QueryInterface(Ci.nsIWebNavigation);
// We don't really want to reparse UA sheets and such, but want to do
// Shadow DOM / XBL.
const sheets = InspectorUtils.getAllStyleSheets(
docShell.document,
/* documentOnly = */ true
);
for (const sheet of sheets) {
if (InspectorUtils.hasRulesModifiedByCSSOM(sheet)) {
continue;
}
try {
// Reparse the sheet so that we see the existing errors.
const text = await getStyleSheetText(sheet);
InspectorUtils.parseStyleSheet(sheet, text, /* aUpdate = */ false);
} catch (e) {
console.error("Error while parsing stylesheet");
}
}
});
await Promise.all(promises);
}
}
module.exports = CSSMessageWatcher;