theme-switching.js (5380B)
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 (function () { 7 const { require } = ChromeUtils.importESModule( 8 "resource://devtools/shared/loader/Loader.sys.mjs" 9 ); 10 const { 11 gDevTools, 12 } = require("resource://devtools/client/framework/devtools.js"); 13 const { 14 appendStyleSheet, 15 } = require("resource://devtools/client/shared/stylesheet-utils.js"); 16 17 const { 18 getTheme, 19 getAutoTheme, 20 getThemePrefValue, 21 addThemeObserver, 22 removeThemeObserver, 23 } = require("resource://devtools/client/shared/theme.js"); 24 25 const documentElement = document.documentElement; 26 27 let os; 28 const platform = navigator.platform; 29 if (platform.startsWith("Win")) { 30 os = "win"; 31 } else if (platform.startsWith("Mac")) { 32 os = "mac"; 33 } else { 34 os = "linux"; 35 } 36 37 documentElement.setAttribute("platform", os); 38 39 // no-theme attributes allows to just est the platform attribute 40 // to have per-platform CSS working correctly. 41 if (documentElement.getAttribute("no-theme") === "true") { 42 return; 43 } 44 45 const devtoolsStyleSheets = new WeakMap(); 46 let gOldTheme = ""; 47 48 /* 49 * Notify the window that a theme switch finished so tests can check the DOM 50 */ 51 function notifyWindow() { 52 window.dispatchEvent(new CustomEvent("theme-switch-complete", {})); 53 } 54 55 /* 56 * Apply all the sheets from `newTheme` and remove all of the sheets 57 * from `oldTheme` 58 */ 59 function switchTheme(newTheme) { 60 // handle forced colors attribute even if newTheme === gOldTheme, as the user can switch from light to auto, 61 // and we'd get a newTheme of "light" here (see getTheme). 62 handleForcedColorsActiveAttribute(); 63 64 if (newTheme === gOldTheme) { 65 return; 66 } 67 const oldTheme = gOldTheme; 68 gOldTheme = newTheme; 69 70 const oldThemeDef = gDevTools.getThemeDefinition(oldTheme); 71 let newThemeDef = gDevTools.getThemeDefinition(newTheme); 72 73 // The theme might not be available anymore (e.g. uninstalled) 74 // Use the default one. 75 if (!newThemeDef) { 76 newThemeDef = gDevTools.getThemeDefinition(getAutoTheme()); 77 } 78 79 // Store the sheets in a WeakMap for access later when the theme gets 80 // unapplied. It's hard to query for processing instructions so this 81 // is an easy way to access them later without storing a property on 82 // the window 83 devtoolsStyleSheets.set(newThemeDef, []); 84 85 const loadEvents = []; 86 for (const url of newThemeDef.stylesheets) { 87 const { styleSheet, loadPromise } = appendStyleSheet(document, url); 88 devtoolsStyleSheets.get(newThemeDef).push(styleSheet); 89 loadEvents.push(loadPromise); 90 } 91 92 Promise.all(loadEvents).then(() => { 93 // Unload all stylesheets and classes from the old theme. 94 if (oldThemeDef) { 95 for (const name of oldThemeDef.classList) { 96 documentElement.classList.remove(name); 97 } 98 99 for (const sheet of devtoolsStyleSheets.get(oldThemeDef) || []) { 100 sheet.remove(); 101 } 102 103 if (oldThemeDef.onUnapply) { 104 oldThemeDef.onUnapply(window, newTheme); 105 } 106 } 107 108 // Load all stylesheets and classes from the new theme. 109 for (const name of newThemeDef.classList) { 110 documentElement.classList.add(name); 111 } 112 113 if (newThemeDef.onApply) { 114 newThemeDef.onApply(window, oldTheme); 115 } 116 117 // Final notification for further theme-switching related logic. 118 gDevTools.emit("theme-switched", window, newTheme, oldTheme); 119 notifyWindow(); 120 }, console.error); 121 } 122 123 function handleThemeChange() { 124 switchTheme(getTheme()); 125 } 126 127 const FORCED_COLORS_ACTIVE_ATTRIBUTE = "forced-colors-active"; 128 const FORCE_COLORS_ACTIVE_MEDIA_QUERY_STRING = "(forced-colors: active)"; 129 /** 130 * Sets or remove the "forced-colors-active" attribute. The toolbox onlys support high contrast mode 131 * if the user didn't explicitly set a light/dark theme 132 */ 133 function handleForcedColorsActiveAttribute() { 134 const themePrefValue = getThemePrefValue(); 135 const forcedColorsActive = 136 Services.prefs.getBoolPref( 137 "devtools.high-contrast-mode-support", 138 false 139 ) && 140 themePrefValue == "auto" && 141 window.matchMedia(FORCE_COLORS_ACTIVE_MEDIA_QUERY_STRING).matches; 142 if (forcedColorsActive) { 143 documentElement.setAttribute(FORCED_COLORS_ACTIVE_ATTRIBUTE, ""); 144 } else { 145 documentElement.removeAttribute(FORCED_COLORS_ACTIVE_ATTRIBUTE); 146 } 147 } 148 149 // Add/Remove forced-colors-active attribute when the user toggles high contrast mode. 150 const mql = window.matchMedia(FORCE_COLORS_ACTIVE_MEDIA_QUERY_STRING); 151 mql.addEventListener("change", handleForcedColorsActiveAttribute); 152 153 // Check if the current document or the embedder of the document enforces a 154 // theme. 155 const forcedTheme = 156 documentElement.getAttribute("force-theme") || 157 window.top.document.documentElement.getAttribute("force-theme"); 158 159 if (forcedTheme) { 160 switchTheme(forcedTheme); 161 } else { 162 switchTheme(getTheme()); 163 164 addThemeObserver(handleThemeChange); 165 window.addEventListener( 166 "unload", 167 function () { 168 removeThemeObserver(handleThemeChange); 169 }, 170 { once: true } 171 ); 172 } 173 })();