Dactyloidae/browser/base/content/themeManager.js

232 lines
6.9 KiB
JavaScript

/* 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();
});
}