Release v2.4.0 — Erzwungene Einstellungen: about:config-Prefs bei jedem Start setzen

Neuer forcedPrefs-WebExtension-Experiment (Services.prefs) setzt beim Start
globale Prefs (Startseite aus, Kartenansicht, nicht gruppiert, absteigend,
3-Spalten-Layout, nicht auto-gelesen) sowie pro Identität die Signatur-/
Antwort-Einstellungen. Pro Setting im Options-Dialog (⚙-Tab) abschaltbar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PZHd9vRsBiH8cKYdQo1Yv
This commit is contained in:
Kendrick Bollens
2026-07-22 14:24:43 +02:00
parent 83d0d52a05
commit d244273e44
8 changed files with 260 additions and 1 deletions

View File

@@ -98,6 +98,55 @@ browser.storage.onChanged.addListener((changes, area) => {
});
updateErledigtPopup();
// ── about:config-Prefs bei jedem Start erzwingen ──
//
// Manche Thunderbird-Einstellungen (Startseite, Ansicht, Layout, pro Konto das
// Signatur-/Antwort-Verhalten) „springen zurück" und müssen sonst von Hand
// nachgezogen werden. Wir setzen sie bei JEDEM Start neu über den privilegierten
// forcedPrefs-Experiment (Services.prefs). Die Liste liegt in
// lib/forced-prefs-list.js (globale FORCED_PREFS / FORCED_IDENTITY_PREFS).
// Pro Eintrag kann der Client im Options-Dialog opt-outen (forced_prefs_optout).
const FORCED_PREFS_OPTOUT_KEY = 'forced_prefs_optout';
async function enforceForcedPrefs() {
const stored = await browser.storage.local.get(FORCED_PREFS_OPTOUT_KEY);
const disabled = new Set(stored[FORCED_PREFS_OPTOUT_KEY] || []);
// Globale Prefs
for (const p of FORCED_PREFS) {
if (disabled.has(p.id)) continue;
try {
await browser.forcedPrefs.setPref(p.name, p.type, p.value);
} catch (e) {
console.error('[forcedPrefs] konnte', p.name, 'nicht setzen:', e);
}
}
// Pro-Identität-Prefs — über alle Konten/Identitäten iterieren
const accounts = await browser.accounts.list();
for (const acc of accounts) {
for (const ident of (acc.identities || [])) {
for (const p of FORCED_IDENTITY_PREFS) {
if (disabled.has(p.id)) continue;
const name = `mail.identity.${ident.id}.${p.suffix}`;
try {
await browser.forcedPrefs.setPref(name, p.type, p.value);
} catch (e) {
console.error('[forcedPrefs] konnte', name, 'nicht setzen:', e);
}
}
}
}
}
enforceForcedPrefs(); // Top-Level = läuft einmal pro TB-Start
// Wird eine Pref im Options-Dialog wieder AKTIVIERT, sofort anwenden
// (statt bis zum nächsten Neustart zu warten).
browser.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes[FORCED_PREFS_OPTOUT_KEY]) enforceForcedPrefs();
});
// ── Signatur in neuen Compose-Fenstern automatisch aktualisieren ──
//
// Thunderbird merkt sich die Signatur einer Identität pro Sitzung. Öffnet der

61
experiments/prefs/api.js Normal file
View File

@@ -0,0 +1,61 @@
"use strict";
// Privilegierter Parent-Scope: schreibt/liest about:config-Prefs über Services.prefs.
// Der Modulpfad von ExtensionCommon wechselte mitten im TB-Lebenszyklus:
// TB 115+ -> ExtensionCommon.sys.mjs (ESM)
// TB 109-114 -> ExtensionCommon.jsm (JSM)
// Deshalb defensiv laden, damit dieselbe api.js über die Flotte funktioniert.
var { ExtensionCommon } = (() => {
try {
return ChromeUtils.importESModule(
"resource://gre/modules/ExtensionCommon.sys.mjs"
);
} catch (e) {
return ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
}
})();
// `Services` ist im privilegierten ExtensionAPI-Sandbox in TB 109+ global
// (stellt Services.prefs = nsIPrefBranch bereit); kein Import nötig.
this.forcedPrefs = class extends ExtensionCommon.ExtensionAPI {
getAPI(context) {
return {
forcedPrefs: {
async setPref(name, type, value) {
switch (type) {
case "bool":
Services.prefs.setBoolPref(name, !!value);
break;
case "int":
Services.prefs.setIntPref(name, value | 0);
break;
case "string":
// setStringPref ist unicode-sicher (im Gegensatz zu setCharPref).
Services.prefs.setStringPref(name, String(value));
break;
default:
throw new Error("forcedPrefs.setPref: unknown type " + type);
}
return true;
},
async getPref(name, type) {
if (Services.prefs.getPrefType(name) === Services.prefs.PREF_INVALID) {
return null;
}
switch (type) {
case "bool":
return Services.prefs.getBoolPref(name, false);
case "int":
return Services.prefs.getIntPref(name, 0);
case "string":
return Services.prefs.getStringPref(name, "");
default:
throw new Error("forcedPrefs.getPref: unknown type " + type);
}
},
},
};
}
};

View File

@@ -0,0 +1,29 @@
[
{
"namespace": "forcedPrefs",
"description": "Read/write about:config preferences from a privileged parent context.",
"functions": [
{
"name": "setPref",
"type": "function",
"async": true,
"description": "Force an about:config pref to a fixed value. type is one of 'bool','int','string'.",
"parameters": [
{ "name": "name", "type": "string" },
{ "name": "type", "type": "string", "enum": ["bool", "int", "string"] },
{ "name": "value", "type": "any" }
]
},
{
"name": "getPref",
"type": "function",
"async": true,
"description": "Read an about:config pref; returns null if unset.",
"parameters": [
{ "name": "name", "type": "string" },
{ "name": "type", "type": "string", "enum": ["bool", "int", "string"] }
]
}
]
}
]

52
lib/forced-prefs-list.js Normal file
View File

@@ -0,0 +1,52 @@
"use strict";
// Single Source of Truth für die about:config-Prefs, die bei jedem TB-Start
// erzwungen werden. Wird als klassisches Script in BEIDEN Kontexten geladen
// (Background + Options-Seite), definiert also nur globale Variablen — keine
// import/export-Mechanik.
//
// Jeder Eintrag:
// id - stabiler Opt-out-Key (NIE umbenennen/wiederverwenden; landet in storage)
// label - Text im Options-Dialog
// type - "bool" | "int" | "string"
// value - der bei jedem Start erzwungene Wert
// FORCED_PREFS zusätzlich: name - voller about:config-Pref-Name
// FORCED_IDENTITY_PREFS: suffix - voller Name = mail.identity.<id>.<suffix>
//
// WICHTIG: Werte an einem korrekt eingerichteten Referenz-Thunderbird
// (Doku-Seite „Thunderbird einrichten", Page 99) per about:config gegenprüfen —
// dieser Code läuft privilegiert auf allen Clients, ein Tippfehler trifft alle.
var FORCED_PREFS = [
{ id: "start_page_off", label: "Startseite beim Öffnen ausblenden",
name: "mailnews.start_page.enabled", type: "bool", value: false },
{ id: "listview_cards", label: "Kartenansicht (statt Tabelle)",
name: "mail.threadpane.listview", type: "int", value: 0 },
{ id: "no_grouping", label: "Nachrichten nicht gruppieren",
name: "mailnews.default_view_flags", type: "int", value: 0 },
{ id: "sort_descending", label: "Neue Nachrichten oben (absteigend)",
name: "mailnews.default_sort_order", type: "int", value: 2 },
{ id: "layout_3col", label: "3-Spalten-Ansicht (vertikales Layout)",
name: "mail.pane_config.dynamic", type: "int", value: 2 },
{ id: "no_auto_read", label: "Nachrichten nicht automatisch als gelesen markieren",
name: "mailnews.mark_message_read.auto", type: "bool", value: false },
];
// Pro Identität angewandt: voller Name = mail.identity.<id>.<suffix>
var FORCED_IDENTITY_PREFS = [
{ id: "reply_on_top", label: "Antwort über dem Zitat",
suffix: "reply_on_top", type: "int", value: 1 },
// sig_bottom=false -> Signatur direkt unter der Antwort, VOR dem Zitat.
// (true wäre ganz unten nach dem Zitat.)
{ id: "sig_bottom", label: "Signatur unter Antwort / vor dem Zitat",
suffix: "sig_bottom", type: "bool", value: false },
{ id: "sig_on_reply", label: "Signatur bei Antworten beifügen",
suffix: "sig_on_reply", type: "bool", value: true },
{ id: "sig_on_fwd", label: "Signatur beim Weiterleiten beifügen",
suffix: "sig_on_fwd", type: "bool", value: true },
];
if (typeof globalThis !== "undefined") {
globalThis.FORCED_PREFS = FORCED_PREFS;
globalThis.FORCED_IDENTITY_PREFS = FORCED_IDENTITY_PREFS;
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "HPS Vorlagen & Signaturen",
"version": "2.3.6",
"version": "2.4.0",
"description": "Vorlagen- und Signaturverwaltung für Hotel Park Soltau mit Git-Sync",
"browser_specific_settings": {
"gecko": {
@@ -29,11 +29,22 @@
],
"background": {
"scripts": [
"lib/forced-prefs-list.js",
"lib/gitea-sync.js",
"background.js"
],
"persistent": true
},
"experiment_apis": {
"forcedPrefs": {
"schema": "experiments/prefs/schema.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["forcedPrefs"]],
"script": "experiments/prefs/api.js"
}
}
},
"browser_action": {
"default_icon": {
"16": "icons/icon.png",

Binary file not shown.

View File

@@ -1049,6 +1049,13 @@
</div>
</div>
<!-- Erzwungene Einstellungen -->
<div class="card">
<div class="card-title">Erzwungene Einstellungen</div>
<div class="card-desc">Diese Thunderbird-Einstellungen werden bei jedem Start automatisch gesetzt (sie „springen" sonst zurück). Zum Abschalten den jeweiligen Haken entfernen — der bisherige Wert wird dabei <strong>nicht</strong> wiederhergestellt, es wird nur nicht mehr erzwungen.</div>
<div id="forced-prefs-list"></div>
</div>
<div id="sync-log"></div>
</div>
@@ -1088,6 +1095,8 @@
</div>
</div>
<!-- geteilte Pref-Liste muss VOR templates_options.js geladen werden -->
<script src="../lib/forced-prefs-list.js"></script>
<script src="templates_options.js"></script>
</body>
</html>

View File

@@ -1978,3 +1978,51 @@ window.addEventListener('load', async () => {
}
});
});
// ── Opt-out-Schalter für erzwungene Prefs ──
// FORCED_PREFS / FORCED_IDENTITY_PREFS stammen aus ../lib/forced-prefs-list.js
// (im HTML vor diesem Script geladen). Gespeichert werden die ids der
// DEAKTIVIERTEN Prefs unter 'forced_prefs_optout'; Haken = wird erzwungen.
const FORCED_PREFS_OPTOUT_KEY = 'forced_prefs_optout';
async function renderForcedPrefsToggles() {
const container = document.getElementById('forced-prefs-list');
if (!container || typeof FORCED_PREFS === 'undefined') return;
const stored = await browser.storage.local.get(FORCED_PREFS_OPTOUT_KEY);
const disabled = new Set(stored[FORCED_PREFS_OPTOUT_KEY] || []);
container.textContent = '';
const entries = [...FORCED_PREFS, ...(typeof FORCED_IDENTITY_PREFS !== 'undefined' ? FORCED_IDENTITY_PREFS : [])];
for (const p of entries) {
const row = document.createElement('label');
row.className = 'forced-pref-row';
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.gap = '8px';
row.style.padding = '4px 0';
row.style.cursor = 'pointer';
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = !disabled.has(p.id); // Haken = wird erzwungen
cb.addEventListener('change', () => setForcedPrefEnabled(p.id, cb.checked));
const span = document.createElement('span');
span.textContent = p.label;
row.append(cb, span);
container.appendChild(row);
}
}
async function setForcedPrefEnabled(id, enabled) {
const stored = await browser.storage.local.get(FORCED_PREFS_OPTOUT_KEY);
const disabled = new Set(stored[FORCED_PREFS_OPTOUT_KEY] || []);
if (enabled) disabled.delete(id);
else disabled.add(id);
await browser.storage.local.set({ [FORCED_PREFS_OPTOUT_KEY]: [...disabled] });
// background.js lauscht auf storage.onChanged und wendet Aktivierungen sofort an.
}
document.addEventListener('DOMContentLoaded', renderForcedPrefsToggles);