contentTheme.js (5894B)
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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 { 8 const prefersDarkQuery = window.matchMedia("(prefers-color-scheme: dark)"); 9 10 function _isTextColorDark(r, g, b) { 11 return 0.2125 * r + 0.7154 * g + 0.0721 * b <= 110; 12 } 13 14 const inContentVariableMap = [ 15 [ 16 "--newtab-background-color", 17 { 18 lwtProperty: "ntp_background", 19 processColor(rgbaChannels) { 20 if (!rgbaChannels) { 21 return null; 22 } 23 const { r, g, b } = rgbaChannels; 24 // Drop alpha channel 25 return `rgb(${r}, ${g}, ${b})`; 26 }, 27 }, 28 ], 29 [ 30 "--newtab-background-color-secondary", 31 { 32 lwtProperty: "ntp_card_background", 33 }, 34 ], 35 [ 36 "--newtab-background-card", 37 { 38 lwtProperty: "ntp_card_background", 39 }, 40 ], 41 [ 42 "--newtab-text-primary-color", 43 { 44 lwtProperty: "ntp_text", 45 processColor(rgbaChannels, element) { 46 element.toggleAttribute("lwt-newtab", !!rgbaChannels); 47 if (!rgbaChannels) { 48 element.toggleAttribute( 49 "lwt-newtab-brighttext", 50 prefersDarkQuery.matches 51 ); 52 return null; 53 } 54 55 const { r, g, b, a } = rgbaChannels; 56 let darkMode = !_isTextColorDark(r, g, b); 57 element.toggleAttribute("lwt-newtab-brighttext", darkMode); 58 return `rgba(${r}, ${g}, ${b}, ${a})`; 59 }, 60 }, 61 ], 62 [ 63 "--in-content-zap-gradient", 64 { 65 lwtProperty: "zap_gradient", 66 processColor(value) { 67 return value; 68 }, 69 }, 70 ], 71 [ 72 "--sidebar-background-color", 73 { 74 lwtProperty: "sidebar", 75 processColor(rgbaChannels) { 76 if (!rgbaChannels) { 77 return null; 78 } 79 const { r, g, b } = rgbaChannels; 80 // Drop alpha channel 81 return `rgb(${r}, ${g}, ${b})`; 82 }, 83 }, 84 ], 85 [ 86 "--sidebar-text-color", 87 { 88 lwtProperty: "sidebar_text", 89 processColor(rgbaChannels, element) { 90 if (!rgbaChannels) { 91 element.removeAttribute("lwt-sidebar"); 92 return null; 93 } 94 95 // TODO(emilio): Can we share this code somehow with LightWeightThemeConsumer? 96 const { r, g, b, a } = rgbaChannels; 97 element.setAttribute( 98 "lwt-sidebar", 99 _isTextColorDark(r, g, b) ? "light" : "dark" 100 ); 101 return `rgba(${r}, ${g}, ${b}, ${a})`; 102 }, 103 }, 104 ], 105 [ 106 "--lwt-sidebar-highlight-background-color", 107 { 108 lwtProperty: "sidebar_highlight", 109 processColor(rgbaChannels, element) { 110 element.toggleAttribute("lwt-sidebar-highlight", !!rgbaChannels); 111 if (!rgbaChannels) { 112 return null; 113 } 114 115 const { r, g, b, a } = rgbaChannels; 116 return `rgba(${r}, ${g}, ${b}, ${a})`; 117 }, 118 }, 119 ], 120 [ 121 "--lwt-sidebar-highlight-text-color", 122 { 123 lwtProperty: "sidebar_highlight_text", 124 }, 125 ], 126 ]; 127 128 /** 129 * ContentThemeController handles theme updates sent by the frame script. 130 * To be able to use ContentThemeController, you must add your page to the whitelist 131 * in LightweightThemeChild.sys.mjs 132 */ 133 const ContentThemeController = { 134 /** 135 * Listen for theming updates from the LightweightThemeChild actor, and 136 * begin listening to changes in preferred color scheme. 137 */ 138 init() { 139 addEventListener("LightweightTheme:Set", this); 140 141 // We don't sync default theme attributes in `init()`, as we may not have 142 // a root element to attach the attribute to yet. They will be set when 143 // the first LightweightTheme:Set event is delivered during pageshow. 144 prefersDarkQuery.addEventListener("change", this); 145 }, 146 147 /** 148 * Handle theme updates from the LightweightThemeChild actor or due to 149 * changes to the prefers-color-scheme media query. 150 * 151 * @param {object} event object containing the theme or query update. 152 */ 153 handleEvent(event) { 154 if (event.type == "LightweightTheme:Set") { 155 this._setProperties(event.detail.data || {}); 156 } else if (event.type == "change") { 157 const root = document.documentElement; 158 // If a lightweight theme doesn't apply, update lwt-newtab-brighttext to 159 // reflect prefers-color-scheme. 160 if (!root.hasAttribute("lwt-newtab")) { 161 root.toggleAttribute("lwt-newtab-brighttext", event.matches); 162 } 163 } 164 }, 165 166 /** 167 * Set a CSS variable to a given value 168 * 169 * @param {Element} elem The element where the CSS variable should be added. 170 * @param {string} variableName The CSS variable to set. 171 * @param {string} value The new value of the CSS variable. 172 */ 173 _setProperty(elem, variableName, value) { 174 if (value) { 175 elem.style.setProperty(variableName, value); 176 } else { 177 elem.style.removeProperty(variableName); 178 } 179 }, 180 181 /** 182 * Apply theme data to an element 183 * 184 * @param {object} themeData The theme data. 185 */ 186 _setProperties(themeData) { 187 const root = document.documentElement; 188 for (let [cssVarName, definition] of inContentVariableMap) { 189 const { lwtProperty, processColor } = definition; 190 let value = themeData[lwtProperty]; 191 192 if (processColor) { 193 value = processColor(value, root); 194 } else if (value) { 195 const { r, g, b, a } = value; 196 value = `rgba(${r}, ${g}, ${b}, ${a})`; 197 } 198 199 this._setProperty(root, cssVarName, value); 200 } 201 }, 202 }; 203 ContentThemeController.init(); 204 }