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 }