tor-browser

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

stylesheet-utils.js (4830B)


      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 { fetch } = require("resource://devtools/shared/DevToolsUtils.js");
      8 
      9 /**
     10 * For imported stylesheets, `ownerNode` is null.
     11 *
     12 * To resolve the ownerNode for an imported stylesheet, loop on `parentStylesheet`
     13 * until we reach the topmost stylesheet, which should have a valid ownerNode.
     14 *
     15 * Constructable stylesheets do not have an owner node and this method will
     16 * return null.
     17 *
     18 * @param {StyleSheet}
     19 *        The stylesheet for which we want to retrieve the ownerNode.
     20 * @return {DOMNode|null} The ownerNode or null for constructable stylesheets.
     21 */
     22 function getStyleSheetOwnerNode(sheet) {
     23  // If this is not an imported stylesheet and we have an ownerNode available
     24  // bail out immediately.
     25  if (sheet.ownerNode) {
     26    return sheet.ownerNode;
     27  }
     28 
     29  let parentStyleSheet = sheet;
     30  while (
     31    parentStyleSheet.parentStyleSheet &&
     32    parentStyleSheet !== parentStyleSheet.parentStyleSheet
     33  ) {
     34    parentStyleSheet = parentStyleSheet.parentStyleSheet;
     35  }
     36 
     37  return parentStyleSheet.ownerNode;
     38 }
     39 
     40 exports.getStyleSheetOwnerNode = getStyleSheetOwnerNode;
     41 
     42 /**
     43 * Get the text of a stylesheet.
     44 *
     45 * TODO: A call site in window-global.js expects this method to return a promise
     46 * so it is mandatory to keep it as an async function even if we are not using
     47 * await explicitly. Bug 1810572.
     48 *
     49 * @param {StyleSheet}
     50 *        The stylesheet for which we want to retrieve the text.
     51 * @returns {Promise}
     52 */
     53 async function getStyleSheetText(styleSheet) {
     54  if (!styleSheet.href) {
     55    if (styleSheet.ownerNode) {
     56      // this is an inline <style> sheet
     57      return styleSheet.ownerNode.textContent;
     58    }
     59    // Constructed stylesheet.
     60    // TODO(bug 1769933, bug 1809108): Maybe preserve authored text?
     61    return "";
     62  }
     63 
     64  return fetchStyleSheetText(styleSheet);
     65 }
     66 
     67 exports.getStyleSheetText = getStyleSheetText;
     68 
     69 /**
     70 * Retrieve the content of a given stylesheet
     71 *
     72 * @param {StyleSheet} styleSheet
     73 * @returns {string}
     74 */
     75 async function fetchStyleSheetText(styleSheet) {
     76  const href = styleSheet.href;
     77 
     78  const options = {
     79    loadFromCache: true,
     80    policy: Ci.nsIContentPolicy.TYPE_INTERNAL_STYLESHEET,
     81    charset: getCSSCharset(styleSheet),
     82    headers: {
     83      // https://searchfox.org/mozilla-central/rev/68b1b0041a78abd06f19202558ccc4922e5ba759/netwerk/protocol/http/nsHttpHandler.cpp#124
     84      accept: "text/css,*/*;q=0.1",
     85    },
     86  };
     87 
     88  // Bug 1282660 - We use the system principal to load the default internal
     89  // stylesheets instead of the content principal since such stylesheets
     90  // require system principal to load. At meanwhile, we strip the loadGroup
     91  // for preventing the assertion of the userContextId mismatching.
     92 
     93  // chrome|file|resource|moz-extension protocols rely on the system principal.
     94  const excludedProtocolsRe = /^(chrome|file|resource|moz-extension):\/\//;
     95  if (!excludedProtocolsRe.test(href)) {
     96    // Stylesheets using other protocols should use the content principal.
     97    const ownerNode = getStyleSheetOwnerNode(styleSheet);
     98    if (ownerNode) {
     99      // eslint-disable-next-line mozilla/use-ownerGlobal
    100      options.window = ownerNode.ownerDocument.defaultView;
    101      options.principal = ownerNode.ownerDocument.nodePrincipal;
    102    }
    103  }
    104 
    105  let result;
    106 
    107  try {
    108    result = await fetch(href, options);
    109    if (result.contentType !== "text/css") {
    110      console.warn(
    111        `stylesheets: fetch from cache returned non-css content-type ` +
    112          `${result.contentType} for ${href}, trying without cache.`
    113      );
    114      options.loadFromCache = false;
    115      result = await fetch(href, options);
    116    }
    117  } catch (e) {
    118    // The list of excluded protocols can be missing some protocols, try to use the
    119    // system principal if the first fetch failed.
    120    console.error(
    121      `stylesheets: fetch failed for ${href},` +
    122        ` using system principal instead.`
    123    );
    124    options.window = undefined;
    125    options.principal = undefined;
    126    result = await fetch(href, options);
    127  }
    128 
    129  return result.content;
    130 }
    131 
    132 /**
    133 * Get charset of a given stylesheet
    134 *
    135 * @param {StyleSheet} styleSheet
    136 * @returns {string}
    137 */
    138 function getCSSCharset(styleSheet) {
    139  if (styleSheet) {
    140    // charset attribute of <link> or <style> element, if it exists
    141    if (styleSheet.ownerNode?.getAttribute) {
    142      const linkCharset = styleSheet.ownerNode.getAttribute("charset");
    143      if (linkCharset != null) {
    144        return linkCharset;
    145      }
    146    }
    147 
    148    // charset of referring document.
    149    if (styleSheet.ownerNode?.ownerDocument.characterSet) {
    150      return styleSheet.ownerNode.ownerDocument.characterSet;
    151    }
    152  }
    153 
    154  return "UTF-8";
    155 }