Fixed theming race condition and other bugs.
This commit is contained in:
parent
7753e5345c
commit
971697043c
|
@ -19,116 +19,44 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
{
|
{
|
||||||
const PARENT_CLASS = "__NoScript_Theme__";
|
|
||||||
let patchSheet = s => {
|
|
||||||
const PARENT_SELECTOR = `.${PARENT_CLASS}`;
|
|
||||||
let rules = s.cssRules;
|
|
||||||
for (let j = 0, len = rules.length; j < len; j++) {
|
|
||||||
let rule = rules[j];
|
|
||||||
if (rule.styleSheet && patchSheet(rule.styleSheet)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (rule.conditionText !== "(prefers-color-scheme: light)") continue;
|
|
||||||
for (let r of rule.cssRules) {
|
|
||||||
let {selectorText} = r;
|
|
||||||
if (selectorText.includes("[data-theme=") || !selectorText.startsWith(PARENT_SELECTOR)) continue;
|
|
||||||
selectorText = selectorText.replace(PARENT_SELECTOR, `${PARENT_SELECTOR}[data-theme="light"]`);
|
|
||||||
s.insertRule(`${selectorText} {${r.style.cssText}}`, j);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let patchAll = () => {
|
|
||||||
for (let s of document.styleSheets) {
|
|
||||||
try {
|
|
||||||
if (patchSheet(s)) return true;
|
|
||||||
} catch (e) {
|
|
||||||
// cross-site stylesheet?
|
|
||||||
debug(e, s.href); // DEV_ONLY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!patchAll()) {
|
|
||||||
debug("Couldn't patch sheets while loading, deferring to onload"); // DEV_ONLY
|
|
||||||
let onload = e => {
|
|
||||||
if (patchAll()) {
|
|
||||||
removeEventListener(e.type, onload, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addEventListener("load", onload, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentCSS;
|
let contentCSS;
|
||||||
|
|
||||||
let root = document.documentElement;
|
|
||||||
root.classList.add(PARENT_CLASS);
|
|
||||||
|
|
||||||
const VINTAGE = "vintageTheme";
|
const VINTAGE = "vintageTheme";
|
||||||
|
|
||||||
let update = toTheme => {
|
|
||||||
if (window.localStorage) try {
|
|
||||||
localStorage.setItem("theme", toTheme);
|
|
||||||
} catch (e) {}
|
|
||||||
return root.dataset.theme = toTheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateFavIcon = isVintage => {
|
|
||||||
let favIcon = document.querySelector("link[rel=icon]");
|
|
||||||
if (!favIcon) return;
|
|
||||||
let {href} = favIcon;
|
|
||||||
const BASE = new URL("/img/", location.href);
|
|
||||||
if (!href.startsWith(BASE)) return alert("return");
|
|
||||||
const SUB = BASE + "vintage/";
|
|
||||||
let vintageIcon = href.startsWith(SUB);
|
|
||||||
if (isVintage === vintageIcon) return;
|
|
||||||
favIcon.href = isVintage ? href.replace(BASE, SUB) : href.replace(SUB, BASE);
|
|
||||||
}
|
|
||||||
|
|
||||||
let refreshVintage = isVintage => {
|
|
||||||
if (localStorage) try {
|
|
||||||
localStorage.setItem(VINTAGE, isVintage || "");
|
|
||||||
} catch (e) {}
|
|
||||||
document.documentElement.classList.toggle("vintage", isVintage === true);
|
|
||||||
if (browser.browserAction) {
|
|
||||||
browser.browserAction.setIcon({path: {64: `/img${isVintage ? "/vintage/" : "/"}ui-maybe64.png` }});
|
|
||||||
}
|
|
||||||
updateFavIcon(isVintage);
|
|
||||||
}
|
|
||||||
|
|
||||||
const THEMES = ["dark", "light", "auto"];
|
const THEMES = ["dark", "light", "auto"];
|
||||||
var Themes = {
|
|
||||||
|
globalThis.Themes = {
|
||||||
VINTAGE,
|
VINTAGE,
|
||||||
setup(theme = null) {
|
update() {},
|
||||||
|
refreshVintage() {},
|
||||||
|
async setup(theme = null) {
|
||||||
if (theme) {
|
if (theme) {
|
||||||
if (browser && browser.storage) {
|
if (browser && browser.storage) {
|
||||||
browser.storage.local.set({theme});
|
browser.storage.local.set({theme});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (localStorage) {
|
if (self.localStorage) {
|
||||||
theme = localStorage.getItem("theme");
|
theme = localStorage.getItem("theme");
|
||||||
if (!THEMES.includes(theme)) theme = null;
|
if (!THEMES.includes(theme)) theme = null;
|
||||||
}
|
}
|
||||||
if (!theme && browser && browser.storage) {
|
if (!theme && browser && browser.storage) {
|
||||||
if (document.readyState === "loading") {
|
if (self.document?.readyState === "loading") {
|
||||||
document.documentElement.style.visibility = "hidden";
|
document.documentElement.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
return browser.storage.local.get(["theme"]).then(({theme}) => {
|
return browser.storage.local.get(["theme"]).then(({theme}) => {
|
||||||
update(theme);
|
Themes.update(theme);
|
||||||
|
if (self.document) {
|
||||||
document.documentElement.style.visibility = "";
|
document.documentElement.style.visibility = "";
|
||||||
|
}
|
||||||
return theme || "auto";
|
return theme || "auto";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return update(theme);
|
return Themes.update(theme);
|
||||||
},
|
},
|
||||||
|
|
||||||
async isVintage() {
|
async isVintage() {
|
||||||
let ret;
|
let ret;
|
||||||
if (localStorage) {
|
if (self.localStorage) {
|
||||||
ret = localStorage.getItem(VINTAGE);
|
ret = localStorage.getItem(VINTAGE);
|
||||||
if (ret !== null) return !(ret === "false" || !ret);
|
if (ret !== null) return !(ret === "false" || !ret);
|
||||||
}
|
}
|
||||||
|
@ -137,13 +65,13 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
async setVintage(b) {
|
async setVintage(b) {
|
||||||
refreshVintage(b);
|
Themes.refreshVintage(b);
|
||||||
await browser.storage.local.set({[VINTAGE]: b});
|
await browser.storage.local.set({[VINTAGE]: b});
|
||||||
return b;
|
return b;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getContentCSS() {
|
async getContentCSS() {
|
||||||
contentCSS = contentCSS || (async () => {
|
contentCSS ||= (async () => {
|
||||||
const replaceAsync = async (string, regexp, replacerFunction) => {
|
const replaceAsync = async (string, regexp, replacerFunction) => {
|
||||||
regexp.lastIndex = 0;
|
regexp.lastIndex = 0;
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
@ -186,9 +114,12 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
refreshVintage(await Themes.isVintage());
|
if (self.document) {
|
||||||
|
await include("/common/themesDOM.js");
|
||||||
|
}
|
||||||
|
await Themes.setup();
|
||||||
|
Themes.refreshVintage(await Themes.isVintage());
|
||||||
})();
|
})();
|
||||||
Promise.resolve(Themes.setup());
|
|
||||||
|
|
||||||
browser.storage.onChanged.addListener((changes, area) => {
|
browser.storage.onChanged.addListener((changes, area) => {
|
||||||
if (area !== "local") return;
|
if (area !== "local") return;
|
||||||
|
@ -197,11 +128,11 @@
|
||||||
let {oldValue, newValue} = changes[key];
|
let {oldValue, newValue} = changes[key];
|
||||||
if (oldValue !== newValue) {
|
if (oldValue !== newValue) {
|
||||||
callback(newValue);
|
callback(newValue);
|
||||||
window.dispatchEvent(new CustomEvent("NoScriptThemeChanged", {detail: {[key]: newValue}}));
|
self.dispatchEvent(new CustomEvent("NoScriptThemeChanged", {detail: {[key]: newValue}}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ifChanged("theme", update);
|
ifChanged("theme", Themes.update);
|
||||||
ifChanged(VINTAGE, refreshVintage);
|
ifChanged(VINTAGE, Themes.refreshVintage);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* NoScript - a Firefox extension for whitelist driven safe JavaScript execution
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005-2024 Giorgio Maone <https://maone.net>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (self.document) {
|
||||||
|
const PARENT_CLASS = "__NoScript_Theme__";
|
||||||
|
const patchSheet = s => {
|
||||||
|
const PARENT_SELECTOR = `.${PARENT_CLASS}`;
|
||||||
|
const rules = s.cssRules;
|
||||||
|
for (let j = 0, len = rules.length; j < len; j++) {
|
||||||
|
const rule = rules[j];
|
||||||
|
if (rule.styleSheet && patchSheet(rule.styleSheet)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (rule.conditionText !== "(prefers-color-scheme: light)") continue;
|
||||||
|
for (let r of rule.cssRules) {
|
||||||
|
let {selectorText} = r;
|
||||||
|
if (selectorText.includes("[data-theme=") || !selectorText.startsWith(PARENT_SELECTOR)) continue;
|
||||||
|
selectorText = selectorText.replace(PARENT_SELECTOR, `${PARENT_SELECTOR}[data-theme="light"]`);
|
||||||
|
s.insertRule(`${selectorText} {${r.style.cssText}}`, j);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const patchAll = () => {
|
||||||
|
for (const s of document.styleSheets) {
|
||||||
|
try {
|
||||||
|
if (patchSheet(s)) return true;
|
||||||
|
} catch (e) {
|
||||||
|
// cross-site stylesheet?
|
||||||
|
debug(e, s.href); // DEV_ONLY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!patchAll()) {
|
||||||
|
debug("Couldn't patch sheets while loading, deferring to onload"); // DEV_ONLY
|
||||||
|
const onload = e => {
|
||||||
|
if (patchAll()) {
|
||||||
|
removeEventListener(e.type, onload, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addEventListener("load", onload, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.classList.add(PARENT_CLASS);
|
||||||
|
|
||||||
|
Themes.update = toTheme => {
|
||||||
|
if (window.localStorage) try {
|
||||||
|
localStorage.setItem("theme", toTheme);
|
||||||
|
} catch (e) {}
|
||||||
|
return root.dataset.theme = toTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFavIcon = isVintage => {
|
||||||
|
let favIcon = document.querySelector("link[rel=icon]");
|
||||||
|
if (!favIcon) return;
|
||||||
|
let {href} = favIcon;
|
||||||
|
const BASE = new URL("/img/", location.href);
|
||||||
|
if (!href.startsWith(BASE)) return alert("return");
|
||||||
|
const SUB = BASE + "vintage/";
|
||||||
|
let vintageIcon = href.startsWith(SUB);
|
||||||
|
if (isVintage === vintageIcon) return;
|
||||||
|
favIcon.href = isVintage ? href.replace(BASE, SUB) : href.replace(SUB, BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Themes.refreshVintage = isVintage => {
|
||||||
|
if (localStorage) try {
|
||||||
|
localStorage.setItem(VINTAGE, isVintage || "");
|
||||||
|
} catch (e) {}
|
||||||
|
document.documentElement.classList.toggle("vintage", isVintage === true);
|
||||||
|
browser?.action?.setIcon({path: {64: `/img${isVintage ? "/vintage/" : "/"}ui-maybe64.png` }});
|
||||||
|
updateFavIcon(isVintage);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue