From 5e9215b2cf552a122e9355ddd84a818d2775cbed Mon Sep 17 00:00:00 2001 From: rope Date: Mon, 31 Aug 2026 19:26:50 +0100 Subject: [PATCH] zen: drop Services from the autoconfig script, add status pref The autoconfig sandbox has no Services global, so the watcher threw before registering anything. Go through Components.classes only, resolve ProfD per pass, and record the result in fred.zenTheme.status. Co-Authored-By: Claude Opus 5 --- apps/zen.nix | 79 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/apps/zen.nix b/apps/zen.nix index 1cdb812..6170191 100644 --- a/apps/zen.nix +++ b/apps/zen.nix @@ -9,48 +9,79 @@ let # /chrome/zen-theme.css as a style-sheet-service USER_SHEET here # and re-register it under a cache-busting URI whenever its mtime moves — # that is what makes a `theme-apply` switch land in a running Zen - # (settings/theming.nix writes that file on every switch). A separate file, - # not userChrome.css: the legacy loader would hold its own copy of the same - # sheet and the two would race on cascade order. + # (settings/theming.nix writes that file on every switch). + # + # userChrome.css can't stay as a safety net: nsIStyleSheetService user + # sheets are ordered BEFORE the profile's userChrome.css, so a leftover copy + # would outrank every sheet we register and pin Zen to whichever wallpaper + # was live at startup — the exact bug this fixes. + # + # Everything here goes through Components.classes. The autoconfig sandbox is + # not a normal system-principal scope: `Services` is not a global in it, and + # reaching for it throws before a single sheet is registered. + # + # Diagnosing a silent failure: about:config -> fred.zenTheme.status holds + # "ok ", "missing " or "error " from the last pass. themeHotReload = '' try { - const { classes: kCc, interfaces: kCi } = Components; + const kCc = Components.classes; + const kCi = Components.interfaces; const sss = kCc["@mozilla.org/content/style-sheet-service;1"] .getService(kCi.nsIStyleSheetService); + const io = kCc["@mozilla.org/network/io-service;1"] + .getService(kCi.nsIIOService); + const dirs = kCc["@mozilla.org/file/directory_service;1"] + .getService(kCi.nsIProperties); + const prefs = kCc["@mozilla.org/preferences-service;1"] + .getService(kCi.nsIPrefBranch); + + const note = (msg) => { + try { prefs.setStringPref("fred.zenTheme.status", msg); } catch (e) {} + }; - const file = Services.dirsvc.get("ProfD", kCi.nsIFile); - file.append("chrome"); - file.append("zen-theme.css"); - const base = Services.io.newFileURI(file).spec; let live = null; const apply = () => { - if (!file.exists()) return; - // install(1) stamps the copy with the current time, so mtime alone - // distinguishes one wallpaper's sheet from the next. - const stamp = String(file.lastModifiedTime); - if (stamp === live) return; - // A fresh query string dodges the CSS loader's per-URI cache; without - // it a re-register would hand back the sheet parsed at startup. - sss.loadAndRegisterSheet(Services.io.newURI(base + "?" + stamp), - sss.USER_SHEET); - if (live !== null) { - const old = Services.io.newURI(base + "?" + live); - if (sss.sheetRegistered(old, sss.USER_SHEET)) - sss.unregisterSheet(old, sss.USER_SHEET); + try { + // Resolved per pass, not once: autoconfig runs early enough that + // ProfD is not guaranteed yet, and a throw here would kill the timer + // for the whole session. + const file = dirs.get("ProfD", kCi.nsIFile); + file.append("chrome"); + file.append("zen-theme.css"); + if (!file.exists()) { note("missing " + file.path); return; } + + // install(1) stamps the copy with the current time, so mtime alone + // distinguishes one wallpaper's sheet from the next. + const stamp = String(file.lastModifiedTime); + if (stamp === live) return; + + // A fresh query string dodges the CSS loader's per-URI cache; without + // it a re-register would hand back the sheet parsed at startup. + const base = io.newFileURI(file).spec; + sss.loadAndRegisterSheet(io.newURI(base + "?" + stamp), sss.USER_SHEET); + if (live !== null) { + const old = io.newURI(base + "?" + live); + if (sss.sheetRegistered(old, sss.USER_SHEET)) + sss.unregisterSheet(old, sss.USER_SHEET); + } + live = stamp; + note("ok " + stamp); + } catch (e) { + note("error " + e); } - live = stamp; }; const timer = kCc["@mozilla.org/timer;1"].createInstance(kCi.nsITimer); // The observer service holds the only strong reference that keeps the // timer (and this closure) off the GC's list for the session. - Services.obs.addObserver({ timer, observe() {} }, "quit-application"); + kCc["@mozilla.org/observer-service;1"].getService(kCi.nsIObserverService) + .addObserver({ timer, observe() {} }, "quit-application"); timer.initWithCallback({ notify: apply }, 2000, kCi.nsITimer.TYPE_REPEATING_SLACK); apply(); } catch (e) { - Components.utils.reportError(e); + Components.utils.reportError("zen-theme: " + e); } ''; in