Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / browser / base / content / contentTheme.js
blob3c46b80becae12f0fcf28b30c3977a5c3a43119f
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/. */
5 "use strict";
8   const prefersDarkQuery = window.matchMedia("(prefers-color-scheme: dark)");
10   function _isTextColorDark(r, g, b) {
11     return 0.2125 * r + 0.7154 * g + 0.0721 * b <= 110;
12   }
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-text-primary-color",
37       {
38         lwtProperty: "ntp_text",
39         processColor(rgbaChannels, element) {
40           // We only have access to the browser when we're in a chrome
41           // docshell, so for now only set the color scheme in that case, and
42           // use the `lwt-newtab-brighttext` attribute as a fallback mechanism.
43           let browserStyle =
44             element.ownerGlobal?.docShell?.chromeEventHandler.style;
46           if (!rgbaChannels) {
47             element.removeAttribute("lwt-newtab");
48             element.toggleAttribute(
49               "lwt-newtab-brighttext",
50               prefersDarkQuery.matches
51             );
52             if (browserStyle) {
53               browserStyle.colorScheme = "";
54             }
55             return null;
56           }
58           element.setAttribute("lwt-newtab", "true");
59           const { r, g, b, a } = rgbaChannels;
60           let darkMode = !_isTextColorDark(r, g, b);
61           element.toggleAttribute("lwt-newtab-brighttext", darkMode);
62           if (browserStyle) {
63             browserStyle.colorScheme = darkMode ? "dark" : "light";
64           }
66           return `rgba(${r}, ${g}, ${b}, ${a})`;
67         },
68       },
69     ],
70     [
71       "--in-content-zap-gradient",
72       {
73         lwtProperty: "zap_gradient",
74         processColor(value) {
75           return value;
76         },
77       },
78     ],
79     [
80       "--lwt-sidebar-background-color",
81       {
82         lwtProperty: "sidebar",
83         processColor(rgbaChannels) {
84           if (!rgbaChannels) {
85             return null;
86           }
87           const { r, g, b } = rgbaChannels;
88           // Drop alpha channel
89           return `rgb(${r}, ${g}, ${b})`;
90         },
91       },
92     ],
93     [
94       "--lwt-sidebar-text-color",
95       {
96         lwtProperty: "sidebar_text",
97         processColor(rgbaChannels, element) {
98           if (!rgbaChannels) {
99             element.removeAttribute("lwt-sidebar");
100             return null;
101           }
103           // TODO(emilio): Can we share this code somehow with LightWeightThemeConsumer?
104           const { r, g, b, a } = rgbaChannels;
105           element.setAttribute(
106             "lwt-sidebar",
107             _isTextColorDark(r, g, b) ? "light" : "dark"
108           );
109           return `rgba(${r}, ${g}, ${b}, ${a})`;
110         },
111       },
112     ],
113     [
114       "--lwt-sidebar-highlight-background-color",
115       {
116         lwtProperty: "sidebar_highlight",
117         processColor(rgbaChannels, element) {
118           if (!rgbaChannels) {
119             element.removeAttribute("lwt-sidebar-highlight");
120             return null;
121           }
122           element.setAttribute("lwt-sidebar-highlight", "true");
124           const { r, g, b, a } = rgbaChannels;
125           return `rgba(${r}, ${g}, ${b}, ${a})`;
126         },
127       },
128     ],
129     [
130       "--lwt-sidebar-highlight-text-color",
131       {
132         lwtProperty: "sidebar_highlight_text",
133       },
134     ],
135   ];
137   /**
138    * ContentThemeController handles theme updates sent by the frame script.
139    * To be able to use ContentThemeController, you must add your page to the whitelist
140    * in LightweightThemeChild.sys.mjs
141    */
142   const ContentThemeController = {
143     /**
144      * Listen for theming updates from the LightweightThemeChild actor, and
145      * begin listening to changes in preferred color scheme.
146      */
147     init() {
148       addEventListener("LightweightTheme:Set", this);
150       // We don't sync default theme attributes in `init()`, as we may not have
151       // a root element to attach the attribute to yet. They will be set when
152       // the first LightweightTheme:Set event is delivered during pageshow.
153       prefersDarkQuery.addEventListener("change", this);
154     },
156     /**
157      * Handle theme updates from the LightweightThemeChild actor or due to
158      * changes to the prefers-color-scheme media query.
159      * @param {Object} event object containing the theme or query update.
160      */
161     handleEvent(event) {
162       const root = document.documentElement;
164       if (event.type == "LightweightTheme:Set") {
165         let { data } = event.detail;
166         if (!data) {
167           data = {};
168         }
169         this._setProperties(root, data);
170       } else if (event.type == "change") {
171         // If a lightweight theme doesn't apply, update lwt-newtab-brighttext to
172         // reflect prefers-color-scheme.
173         if (!root.hasAttribute("lwt-newtab")) {
174           root.toggleAttribute("lwt-newtab-brighttext", event.matches);
175         }
176       }
177     },
179     /**
180      * Set a CSS variable to a given value
181      * @param {Element} elem The element where the CSS variable should be added.
182      * @param {string} variableName The CSS variable to set.
183      * @param {string} value The new value of the CSS variable.
184      */
185     _setProperty(elem, variableName, value) {
186       if (value) {
187         elem.style.setProperty(variableName, value);
188       } else {
189         elem.style.removeProperty(variableName);
190       }
191     },
193     /**
194      * Apply theme data to an element
195      * @param {Element} root The element where the properties should be applied.
196      * @param {Object} themeData The theme data.
197      */
198     _setProperties(elem, themeData) {
199       for (let [cssVarName, definition] of inContentVariableMap) {
200         const { lwtProperty, processColor } = definition;
201         let value = themeData[lwtProperty];
203         if (processColor) {
204           value = processColor(value, elem);
205         } else if (value) {
206           const { r, g, b, a } = value;
207           value = `rgba(${r}, ${g}, ${b}, ${a})`;
208         }
210         this._setProperty(elem, cssVarName, value);
211       }
212     },
213   };
214   ContentThemeController.init();