Files
BetterSEQTA-Plus/src/seqta/utils/themeImagePagePatch.js
T

171 lines
4.8 KiB
JavaScript

/**
* PAGE context only — theme CustomCSS and decorative image variables must live
* in page-origin stylesheets. Extension-injected #custom-theme cannot load
* page blob: URLs referenced via var() on Firefox.
*/
(function () {
if (window.__bsplusThemePagePatched) return;
window.__bsplusThemePagePatched = true;
var LOG = "[BetterSEQTA+] theme page:";
var BRIDGE_ID = "bsplus-theme-image-bridge";
var PAYLOAD_ID = "bsplus-theme-image-payload";
var IMAGES_STYLE_ID = "bsplus-theme-images";
var THEME_STYLE_ID = "custom-theme";
var PREVIEW_STYLE_ID = "custom-theme-preview";
var urlCache = {};
var headObserver = null;
function log(event, detail) {
if (!document.documentElement.hasAttribute("data-bsplus-verbose-log")) return;
console.info(LOG, event, detail);
}
function base64ToBlob(base64, mime) {
var bytes = atob(base64);
var ab = new ArrayBuffer(bytes.length);
var view = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) view[i] = bytes.charCodeAt(i);
return new Blob([ab], { type: mime || "image/png" });
}
function releaseCachedUrls() {
for (var key in urlCache) {
if (!Object.prototype.hasOwnProperty.call(urlCache, key)) continue;
try {
URL.revokeObjectURL(urlCache[key]);
} catch (e) {}
}
urlCache = {};
}
function styleEl(id) {
var el = document.getElementById(id);
if (!el) {
el = document.createElement("style");
el.id = id;
document.head.appendChild(el);
}
return el;
}
function keepThemeStyleLast() {
var themeStyle = document.getElementById(THEME_STYLE_ID);
if (themeStyle && document.head.contains(themeStyle) && document.head.lastElementChild !== themeStyle) {
document.head.appendChild(themeStyle);
}
}
function watchHead() {
if (headObserver) return;
headObserver = new MutationObserver(keepThemeStyleLast);
headObserver.observe(document.head, { childList: true });
}
function setStyleText(id, text, watchThemeOrder) {
if (!text) {
document.getElementById(id)?.remove();
return;
}
styleEl(id).textContent = text;
if (watchThemeOrder) {
watchHead();
keepThemeStyleLast();
}
}
function clearAll() {
releaseCachedUrls();
setStyleText(IMAGES_STYLE_ID, "");
document.getElementById(THEME_STYLE_ID)?.remove();
document.getElementById(PREVIEW_STYLE_ID)?.remove();
headObserver?.disconnect();
headObserver = null;
log("cleared");
}
function applyThemeImages(images) {
releaseCachedUrls();
if (!images?.length) {
setStyleText(IMAGES_STYLE_ID, "");
return;
}
var lines = [":root {"];
for (var i = 0; i < images.length; i++) {
var img = images[i];
if (!img?.variableName || !img.data) continue;
try {
var url = URL.createObjectURL(base64ToBlob(img.data, img.mime));
urlCache[img.variableName] = url;
lines.push(' --' + img.variableName + ': url("' + url + '");');
} catch (e) {
console.warn(LOG, "skip image", img.variableName, e);
}
}
lines.push("}");
styleEl(IMAGES_STYLE_ID).textContent = lines.join("\n");
log("images applied", { count: images.length });
}
function processPayload() {
var raw = document.getElementById(PAYLOAD_ID)?.value;
if (!raw) {
clearAll();
return;
}
try {
var payload = JSON.parse(raw);
if (!payload || payload.clear) {
clearAll();
return;
}
if (payload.images !== undefined) applyThemeImages(payload.images);
if (payload.customCss !== undefined) {
setStyleText(THEME_STYLE_ID, payload.customCss || "", true);
log("custom css applied");
}
if (payload.previewCss !== undefined) {
setStyleText(PREVIEW_STYLE_ID, payload.previewCss || "");
}
if (payload.clearPreview) {
setStyleText(PREVIEW_STYLE_ID, "");
}
} catch (e) {
console.warn(LOG, "invalid payload", e);
}
}
function ensureBridge() {
if (!document.getElementById(PAYLOAD_ID)) {
var payload = document.createElement("textarea");
payload.id = PAYLOAD_ID;
payload.hidden = true;
payload.setAttribute("aria-hidden", "true");
payload.tabIndex = -1;
document.documentElement.appendChild(payload);
}
if (!document.getElementById(BRIDGE_ID)) {
var bridge = document.createElement("div");
bridge.id = BRIDGE_ID;
bridge.hidden = true;
bridge.setAttribute("aria-hidden", "true");
document.documentElement.appendChild(bridge);
}
}
ensureBridge();
new MutationObserver(processPayload).observe(document.getElementById(BRIDGE_ID), {
attributes: true,
attributeFilter: ["data-rev"],
});
processPayload();
log("patch active");
})();