ToolbarIconColor.sys.mjs (4474B)
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 /* Module is used to set or remove the "brighttext" attribute based on 6 * calculating a luminance value from the current toolbar color. 7 * This causes items like icons on the toolbar to contrast in brightness 8 * enough to be visible, depending on the current theme/coloring of the browser 9 * window. Calculated luminance values are cached in `state.toolbarLuminanceCache`. */ 10 11 // Track individual windowstates using WeakMap 12 const _windowStateMap = new WeakMap(); 13 14 export const ToolbarIconColor = { 15 init(window) { 16 if (_windowStateMap.has(window)) { 17 return; 18 } 19 20 const state = { 21 active: false, 22 fullscreen: false, 23 customtitlebar: false, 24 toolbarLuminanceCache: new Map(), 25 }; 26 27 _windowStateMap.set(window, state); 28 29 window.addEventListener("nativethemechange", this); 30 window.addEventListener("activate", this); 31 window.addEventListener("deactivate", this); 32 window.addEventListener("toolbarvisibilitychange", this); 33 window.addEventListener("windowlwthemeupdate", this); 34 35 // If the window isn't active now, we assume that it has never been active 36 // before and will soon become active such that inferFromText will be 37 // called from the initial activate event. 38 if (Services.focus.activeWindow == window) { 39 this.inferFromText("activate", window); 40 } 41 }, 42 43 uninit(window) { 44 const state = _windowStateMap.get(window); 45 if (!state) { 46 return; 47 } 48 49 window.removeEventListener("nativethemechange", this); 50 window.removeEventListener("activate", this); 51 window.removeEventListener("deactivate", this); 52 window.removeEventListener("toolbarvisibilitychange", this); 53 window.removeEventListener("windowlwthemeupdate", this); 54 55 _windowStateMap.delete(window); 56 }, 57 58 handleEvent(event) { 59 const window = event.target.ownerGlobal; 60 switch (event.type) { 61 case "activate": 62 case "deactivate": 63 case "nativethemechange": 64 case "windowlwthemeupdate": 65 this.inferFromText(event.type, window); 66 break; 67 case "toolbarvisibilitychange": 68 this.inferFromText(event.type, window, event.visible); 69 break; 70 } 71 }, 72 73 inferFromText(reason, window, reasonValue) { 74 const state = _windowStateMap.get(window); 75 76 if (!state) { 77 return; 78 } 79 80 switch (reason) { 81 case "activate": // falls through 82 case "deactivate": 83 state.active = reason === "activate"; 84 break; 85 case "fullscreen": 86 state.fullscreen = reasonValue; 87 break; 88 case "nativethemechange": 89 case "windowlwthemeupdate": 90 // theme change, we'll need to recalculate all color values 91 state.toolbarLuminanceCache.clear(); 92 break; 93 case "toolbarvisibilitychange": 94 // toolbar changes dont require reset of the cached color values 95 break; 96 case "customtitlebar": 97 state.customtitlebar = reasonValue; 98 break; 99 } 100 101 let toolbarSelector = ".browser-toolbar:not([collapsed])"; 102 if (Services.appinfo.nativeMenubar) { 103 toolbarSelector += ":not([type=menubar])"; 104 } 105 106 // The getComputedStyle calls and setting the brighttext are separated in 107 // two loops to avoid flushing layout and making it dirty repeatedly. 108 let cachedLuminances = state.toolbarLuminanceCache; 109 let luminances = new Map(); 110 for (let toolbar of window.document.querySelectorAll(toolbarSelector)) { 111 // toolbars *should* all have ids, but guard anyway to avoid blowing up 112 let cacheKey = toolbar.id && toolbar.id + JSON.stringify(state); 113 // lookup cached luminance value for this toolbar in this window state 114 let luminance = cacheKey && cachedLuminances.get(cacheKey); 115 if (isNaN(luminance)) { 116 let { r, g, b } = InspectorUtils.colorToRGBA( 117 window.getComputedStyle(toolbar).color 118 ); 119 luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b; 120 if (cacheKey) { 121 cachedLuminances.set(cacheKey, luminance); 122 } 123 } 124 luminances.set(toolbar, luminance); 125 } 126 127 const luminanceThreshold = 127; // In between 0 and 255 128 for (let [toolbar, luminance] of luminances) { 129 toolbar.toggleAttribute("brighttext", luminance > luminanceThreshold); 130 } 131 }, 132 };