/* Dark Mode Theme Manager * Handles OS dark mode detection and manual theme toggle */ var ThemeManager = window.ThemeManager || {}; ThemeManager.getPrefBranch = function() { if (!this.prefBranch) { this.prefBranch = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("browser.theme."); } return this.prefBranch; }; ThemeManager.getGlobalPrefBranch = function() { if (!this.globalPrefBranch) { this.globalPrefBranch = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch(""); } return this.globalPrefBranch; }; ThemeManager.init = function() { this.getPrefBranch(); // Check stored preference first, then OS preference. this.applyTheme(); // Listen for preference and system theme changes. this.setupPrefObserver(); this.setupMediaQueryListener(); }; ThemeManager.setupPrefObserver = function() { if (!this.prefObserverRegistered) { this.getPrefBranch().addObserver("", this, false); this.prefObserverRegistered = true; } }; ThemeManager.observe = function(subject, topic, data) { // browser.theme.mode changes are intentionally applied on next startup // so the preferences UI setting is restart-required. if (topic === "nsPref:changed" && data === "mode") { return; } }; ThemeManager.shouldFollowSystem = function() { try { return this.getPrefBranch().getCharPref("mode") === "system"; } catch (e) { return true; } }; ThemeManager.readWindowsThemeValue = function(rootKey, valueName) { var regKey = Components.classes["@mozilla.org/windows-registry-key;1"] .createInstance(Components.interfaces.nsIWindowsRegKey); var winReg = Components.interfaces.nsIWindowsRegKey; var personalizePath = "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; try { regKey.open(rootKey, personalizePath, winReg.ACCESS_READ); if (regKey.hasValue(valueName)) { return regKey.readIntValue(valueName); } } catch (e) { // Ignore and return null below. } try { regKey.close(); } catch (e2) { } return null; }; ThemeManager.isSystemDarkModePreferred = function() { // Prefer Windows registry signal when available. try { var winReg = Components.interfaces.nsIWindowsRegKey; var appsUseLight = this.readWindowsThemeValue(winReg.ROOT_KEY_CURRENT_USER, "AppsUseLightTheme"); var systemUseLight = this.readWindowsThemeValue(winReg.ROOT_KEY_CURRENT_USER, "SystemUsesLightTheme"); // Some setups expose values under HKLM only. if (appsUseLight === null) { appsUseLight = this.readWindowsThemeValue(winReg.ROOT_KEY_LOCAL_MACHINE, "AppsUseLightTheme"); } if (systemUseLight === null) { systemUseLight = this.readWindowsThemeValue(winReg.ROOT_KEY_LOCAL_MACHINE, "SystemUsesLightTheme"); } // Treat either key requesting dark as dark mode. if (appsUseLight !== null || systemUseLight !== null) { return appsUseLight === 0 || systemUseLight === 0; } } catch (e) { // Fall through to standards-based media query. } // Fallback for non-Windows or unavailable registry signal. if (window.matchMedia) { var mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); if (mediaQuery.media !== "not all") { return mediaQuery.matches; } } return false; }; ThemeManager.setupMediaQueryListener = function() { // Listen for OS dark mode changes if supported if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").media !== "not all") { var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); darkModeQuery.addListener(function(e) { // Apply if following system mode (or no explicit pref). if (this.shouldFollowSystem() || !this.hasUserPreference()) { this.setTheme(e.matches ? "dark" : "light"); } }.bind(this)); } }; ThemeManager.hasUserPreference = function() { try { var pref = this.getPrefBranch().getCharPref("mode"); return pref !== ""; } catch (e) { return false; } }; ThemeManager.applyTheme = function() { var theme = "light"; // Check user preference first try { var userPref = this.getPrefBranch().getCharPref("mode"); if (userPref === "dark" || userPref === "light") { theme = userPref; } else if (userPref === "system") { theme = this.isSystemDarkModePreferred() ? "dark" : "light"; } } catch (e) { // Preference doesn't exist, check OS. theme = this.isSystemDarkModePreferred() ? "dark" : "light"; } this.setTheme(theme); }; ThemeManager.setTheme = function(theme) { var docElement = document.documentElement; if (theme === "dark") { docElement.setAttribute("data-theme", "dark"); } else { docElement.removeAttribute("data-theme"); } try { var globalPrefs = this.getGlobalPrefBranch(); var mode = "system"; var prefersColorScheme = 3; try { mode = this.getPrefBranch().getCharPref("mode"); } catch (e2) { } if (mode === "dark") { prefersColorScheme = 2; } else if (mode === "light") { prefersColorScheme = 1; } globalPrefs.setIntPref("browser.display.prefers_color_scheme", prefersColorScheme); globalPrefs.setIntPref("ui.color_scheme", theme === "dark" ? 2 : 1); globalPrefs.setBoolPref("mozilla.widget.disable-native-theme", false); } catch (e) { // Ignore pref write failures and keep the chrome theme applied. } }; // Public method to toggle theme ThemeManager.toggleDarkMode = function() { var docElement = document.documentElement; var isDark = docElement.getAttribute("data-theme") === "dark"; try { this.getPrefBranch().setCharPref("mode", !isDark ? "dark" : "light"); } catch (e) { console.error("Failed to set theme preference:", e); } this.setTheme(!isDark ? "dark" : "light"); }; // Get current theme mode ThemeManager.getTheme = function() { var docElement = document.documentElement; return docElement.getAttribute("data-theme") === "dark" ? "dark" : "light"; }; window.ThemeManager = ThemeManager; this.ThemeManager = ThemeManager; if (typeof top != "undefined") { top.ThemeManager = ThemeManager; } window.toggleDarkMode = function() { return ThemeManager.toggleDarkMode(); }; this.toggleDarkMode = window.toggleDarkMode; if (typeof top != "undefined") { top.toggleDarkMode = window.toggleDarkMode; } // Initialize when document is ready if (document.readyState === "interactive" || document.readyState === "complete") { ThemeManager.init(); } else { document.addEventListener("DOMContentLoaded", function() { ThemeManager.init(); }); }