tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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;