Einstellungen -> Konto und Synchronisation -> ganz unten...
Danke, das hatte ich noch nicht auf dem Schirm, muss ich gleich mal testen. Hast du das schon damit gemacht?
Einstellungen -> Konto und Synchronisation -> ganz unten...
Danke, das hatte ich noch nicht auf dem Schirm, muss ich gleich mal testen. Hast du das schon damit gemacht?
falls das unbemerkt geblieben sein sollte...
Ist es wirklich, kläre mich auf.![]()
aber es gibt schon ein Skript welches so gar im laufenden Betrieb das Profil sichert.
Ich habe nicht gesagt, dass es mit meinem Script nicht geht, Backup geht natürlich auch, aber ich würde es nicht bei laufendem Betrieb machen. Und Backup zurück spielen, da erübrigt sich ja wohl eine Kommentierung.
habe ich es bisher noch nicht geschafft,
ein "gutes" Restore-Skript zu zaubern.
Da musst du hier RE: BackupProfile.uc.js - div. Fragen dazu schauen. Und nur zur Kenntnis, ich arbeite nur mit PowerShell-Scripten.
Ich finde Deine Bemühungen um ein Skript zum Sichern des Profils ja echt klasse,
aber es gibt schon ein Skript
Danke, ja das kenne ich alles. Es wird niemand gezwungen mein Script zu nutzen, freie Entscheidung.![]()
Skript: ProfileBackupRestore@Fu_combo_zip2.uc.js (mit KI erstellt)
=====================================================================
Vorab ein wichtiger Hinweis, und zugleich als Empfehlung, das Script nur in dem Profil einzusetzen (starten), welches nicht selbst für ein Backup oder ein Restore vorgesehen ist. Der Grund ist der, dass für Restore der Firefox zwingend beendet sein muss, und für ein Backup es auch von Vorteil ist, um Komplikationen zu vermeiden.
==============================================================
Edit:
Ich bin immer von der Logik ausgegangen, dass es so sein muss, wegen Datenverlust, usw. Jetzt habe ich bei installierten, als auch bei portablen Profilen im laufenden Betrieb Backup getestet, läuft ohne Fehler durch. Und genau so verhält es sich bei Restore, nur einen Restart danach und alles läuft fehlerfrei.
==============================================================
Dieses Script ist eine Kombination für Backup und Restore (Profilordner zurück spielen). Vorteil hierbei ist, es werden für jedes Profil nur einmal die Pfade benötigt, die sorgfältig eingetragen werden sollten.
Vor Nutzung des Skriptes müssen zwei Ordner für die Sicherungen eingerichtet werden ( Beispiele aus dem Skript ersichtlich ). In den ersten Ordner wird dann der vollständige Profilordner hinein kopiert (kein ZIP), wobei ganz wichtig ist, den richtigen Namen des Profils in das Skript einzutragen (about:profiles aufrufen und Namen entnehmen, z.B. qtqwpmy7.default-release). Bei jedem neuen Backup wird
der Profilordner hier überschrieben. Auch portable können mit entsprechenden Angaben (Pfade) gesichert werden.
Der zweite Ordner gilt als doppelte Sicherung, hier wird auch der Profilordner hinein kopiert (als ZIP-Archiv), hat als Endung aber eine fortlaufende Nummerierung mit Datum und Zähler (wichtig wenn mehrmals gesichert wird).
Die Pfade zu diesen Ordnern sind bei jedem Profil in das Skript einzutragen.
Das Skript wird über den Button in der Navbar gestartet, es öffnet sich ein Menü (Grafik 1) über das eine Auswahl für Backup oder Restore getroffen werden kann, bei Klick darauf erscheint ein Bestätigungs-Dialog (Grafik 2), der bedient werden muss. Bei OK startet das Backup oder der Restore-Vorgang, bei Erfolg wird dann über Windows eine Benachrichtigung ausgegeben (Grafik 3). Außerdem wird eine Log-Datei im Sicherungsordner 1 angelegt, die bei jeder Aktion fortgeschrieben wird (Grafik 4).
// ==UserScript==
// @name ProfileBackupRestore@Fu_combo_zip2.uc.js
// @description Navbar: Backup (Backup1 Ordner + Archiv ZIP) + Restore aus Backup1 (überschreibt Profil) + Log in Backup1
// @version 2026.07-combo-zip2
// ==/UserScript==
(function () {
"use strict";
if (location.href !== "chrome://browser/content/browser.xhtml") return;
if (typeof CustomizableUI === "undefined") return;
if (!window.gBrowser) return;
// =========================
// Pro Profil konfigurieren
// =========================
const PROFILES = [
{
label: "Nightly2",
profilePath: "G:\\Firefox Test\\Nightly2\\Profilordner",
profileName: "Profilordner",
backup1Path: "G:\\Firefox Sicherung\\Nightly2",
archiveRootPath: "G:\\Sicherung2\\Nightly2",
iconPath: "file:///C:/FoxIcons2/Nightly.png"
},
{
label: "Beta1",
profilePath: "G:\\Firefox Test\\Beta1\\Profilordner",
profileName: "Profilordner",
backup1Path: "G:\\Firefox Sicherung\\Beta1",
archiveRootPath: "G:\\Sicherung2\\Beta1",
iconPath: "file:///C:/FoxIcons2/Beta.png"
},
{
label: "Reserve 3",
profilePath: "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\Reserve 3",
profileName: "Reserve 3",
backup1Path: "G:\\Firefox Sicherung\\Reserve 3",
archiveRootPath: "G:\\Sicherung2\\Reserve 3",
iconPath: "file:///C:/FoxIcons2/Finale.png"
}
];
// =========================
// UI IDs
// =========================
const BTN_ID = "profilebackup_restore_combo_button_fixed_zip2_noroot_withlog";
const POPUP_ID = BTN_ID + "_popup";
const btnIconPath = "file:///C:/FoxIcons2/backup.png";
// =========================
// Helpers: Logging
// =========================
function pad2(n) {
return String(n).padStart(2, "0");
}
function timeHHMM(d) {
return "[" + pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + "]";
}
function formatHeaderDateGermanLong(d) {
// Ziel: "am Mittwoch, 08. Juli 2026, 09:26:01 Uhr"
try {
return "am " + d.toLocaleString("de-DE", {
weekday: "long",
year: "numeric",
month: "long",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}) + " Uhr";
} catch (_) {
// Fallback, falls toLocaleString Optionen nicht sauber unterstützt werden
const weekdays = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
const wd = weekdays[d.getDay()] || "";
const day = pad2(d.getDate());
const monthNames = ["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"];
const mn = monthNames[d.getMonth()] || "";
return "am " + wd + ", " + day + ". " + mn + " " + d.getFullYear() + ", " +
pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds()) + " Uhr";
}
}
function getLogFile(profile) {
// Log-Datei im Ordner 1 (backup1Path)
let f = new FileUtils.File(profile.backup1Path);
f.append("ProfileBackupRestore.log");
return f;
}
function ensureLogFileExists(file) {
if (!file.exists()) {
// parent muss existieren; backup1Path sollte existieren
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
}
}
function appendTextToFile(file, text) {
ensureLogFileExists(file);
const fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
// write (0x02) | create (0x08) | append (0x10)
fos.init(file, 0x02 | 0x08 | 0x10, 0o644, 0);
const conv = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
conv.init(fos, "UTF-8", 0, 0);
conv.writeString(text);
conv.close();
try { fos.close(); } catch (_) {}
}
function logBackup(profile, zipPath) {
const d = new Date();
const logFile = getLogFile(profile);
const header = "Profil gesichert " + formatHeaderDateGermanLong(d) + "\n\n";
const lines = [
timeHHMM(d) + " Backup gestartet (Firefox-Erkennung deaktiviert).\n",
timeHHMM(d) + " Kopiere Profil nach Backup1: " + profile.backup1Path + "\\" + profile.profileName + "\n",
timeHHMM(d) + " Erzeuge Archiv ZIP: " + (zipPath || "(unbekannt)") + "\n",
timeHHMM(new Date()) + " Backup erfolgreich abgeschlossen.\n"
].join("");
appendTextToFile(logFile, header + "\n" + lines + "\n");
}
function logRestore(profile, sourceDirPath, destDirPath) {
const d = new Date();
const logFile = getLogFile(profile);
const header = "Profil in alten Zustand versetzt " + formatHeaderDateGermanLong(d) + "\n\n";
const lines = [
timeHHMM(d) + " Restore gestartet (Firefox-Erkennung deaktiviert).\n",
timeHHMM(d) + " Lösche vorhandenen Profilordner: " + destDirPath + "\n",
timeHHMM(d) + " Kopiere Sicherung nach: " + destDirPath + "\n",
timeHHMM(new Date()) + " Restore erfolgreich abgeschlossen.\n"
].join("");
// Hinweis: sourceDirPath ist enthalten, aber nach deinem Muster stehen nur die drei Zeilen.
// Falls du es zusätzlich willst, sag kurz Bescheid.
appendTextToFile(logFile, header + lines + "\n");
}
// =========================
// UI Helpers: Alert
// =========================
function showAlert(text) {
try {
let w = null;
try { w = Services.wm.getMostRecentWindow("navigator:browser"); } catch (_) {}
Services.prompt.alert(w || null, "Profilsicherung", text);
} catch (e) {
console.error("Alert Fehler:", e);
}
}
// =========================
// Date/ZIP helpers
// =========================
function pad2Str(n) {
const x = parseInt(n, 10);
return pad2(isNaN(x) ? 0 : x);
}
function getDateStrYYYYMMDD(d) {
return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
}
function escapeRegExp(s) {
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// =========================
// gesperrte Dateien ignorieren
// =========================
function shouldSkipEntryByName(name) {
if (
name === "parent.lock" ||
name === "lock" ||
name === "recovery.jsonlz4" ||
name.endsWith(".sqlite-wal") ||
name.endsWith(".sqlite-shm")
) return true;
return false;
}
// =========================
// Copy helpers (Ordnerkopie)
// =========================
function copyDirectoryContents(srcDir, destDir) {
let entries = srcDir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (shouldSkipEntryByName(entry.leafName)) continue;
const destEntry = destDir.clone();
destEntry.append(entry.leafName);
try {
if (entry.isDirectory()) {
if (!destEntry.exists()) destEntry.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(entry, destEntry);
} else {
if (destEntry.exists()) destEntry.remove(false);
entry.copyTo(destDir, entry.leafName);
}
} catch (e) {
console.warn("⚠️ Datei konnte nicht kopiert werden:", entry.path, e);
}
}
}
function clearDirectoryContents(dir) {
let entries = dir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
try { entry.remove(true); } catch (e) { console.warn("⚠️ Clear fehlgeschlagen:", entry.path, e); }
}
}
// =========================
// Backup2 ZIP-Index pro Tag (01..)
// Muster: <profileName>_YYYY-MM-DD_XX.zip
// =========================
function getNextDailyZipIndex(archiveRootFile, profileName, dateStr) {
let max = 0;
const re = new RegExp("^" + escapeRegExp(profileName) + "_" + escapeRegExp(dateStr) + "_(\\d{2})\\.zip$");
let entries = archiveRootFile.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (!entry.isFile()) continue;
const m = re.exec(entry.leafName);
if (m) {
const n = parseInt(m[1], 10);
if (!isNaN(n) && n > max) max = n;
}
}
return max + 1;
}
// =========================
// ZIP-Erstellung: Inhalt ohne Root-Ebene
// =========================
function zipDirectoryNoRoot(sourceDir, zipFile) {
// ZIP Inhalt: relativ zu sourceDir, ohne Extra Root-Ordner
const baseDirPath = sourceDir.path;
if (zipFile.exists()) zipFile.remove(true);
const zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
const PR_WRONLY = 0x02;
const PR_CREATE_FILE = 0x08;
const PR_TRUNCATE = 0x20;
zipWriter.open(zipFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
function walk(dir) {
let entries = dir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (entry.path === zipFile.path) continue;
if (shouldSkipEntryByName(entry.leafName)) continue;
let relPath = entry.path.replace(baseDirPath, "");
if (!relPath) continue;
if (relPath[0] === "\\" || relPath[0] === "/") relPath = relPath.substring(1);
const saveInZipAs = relPath.replace(/\\/g, "/");
try {
if (entry.isDirectory()) {
// Verzeichniseintrag ist optional; wir laufen rekursiv weiter
walk(entry);
} else {
zipWriter.addEntryFile(
saveInZipAs,
Ci.nsIZipWriter.COMPRESSION_FASTEST,
entry,
false
);
}
} catch (_) {
// gesperrte Dateien o.ä. ignorieren
}
}
}
walk(sourceDir);
zipWriter.close();
}
// =========================
// Backup & Restore
// =========================
function runBackup(profile) {
let backupTarget = null;
let zipFile = null;
try {
const now = new Date();
const profileDir = new FileUtils.File(profile.profilePath);
if (!profileDir.exists()) throw new Error("Profilordner existiert nicht: " + profile.profilePath);
const backupRoot = new FileUtils.File(profile.backup1Path);
if (!backupRoot.exists()) backupRoot.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
const archiveRoot = new FileUtils.File(profile.archiveRootPath);
if (!archiveRoot.exists()) archiveRoot.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
// BACKUP 1 (Ordner)
backupTarget = backupRoot.clone();
backupTarget.append(profile.profileName);
if (backupTarget.exists()) backupTarget.remove(true);
backupTarget.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(profileDir, backupTarget);
// BACKUP 2 (ZIP in archiveRootPath)
const dateStr = getDateStrYYYYMMDD(now);
const nextIdx = getNextDailyZipIndex(archiveRoot, profile.profileName, dateStr);
const idxStr = pad2Str(nextIdx);
const zipName = profile.profileName + "_" + dateStr + "_" + idxStr + ".zip";
zipFile = archiveRoot.clone();
zipFile.append(zipName);
// ZIP-Inhalt aus backupTarget (ohne Root-Ebene)
zipDirectoryNoRoot(backupTarget, zipFile);
if (!zipFile.exists()) {
throw new Error("ZIP wurde nicht erstellt: " + zipFile.path);
}
// Log schreiben
try { logBackup(profile, zipFile.path); } catch (_) {}
setTimeout(() => {
showAlert(
"✅ Backup abgeschlossen!\n\n" +
"👤 Profil: " + profile.label + "\n\n" +
"📁 Backup Ordner 1: " + backupTarget.path + "\n" +
"📦 Archiv ZIP: " + zipFile.path
);
}, 50);
} catch (e) {
console.error("Backup Fehler:", e);
try {
if (zipFile && zipFile.exists()) zipFile.remove(false);
} catch (_) {}
setTimeout(() => showAlert("❌ Backup Fehler: " + e.message), 0);
}
}
function runRestore(profile) {
let profileDir = null;
let backupRootDir = null;
let backupProfileDir = null;
try {
profileDir = new FileUtils.File(profile.profilePath);
if (!profileDir.exists()) throw new Error("Zielprofil existiert nicht: " + profile.profilePath);
backupRootDir = new FileUtils.File(profile.backup1Path);
if (!backupRootDir.exists()) throw new Error("Backup1 Ordner existiert nicht: " + profile.backup1Path);
// Backup1 enthält typischerweise <profileName>/... -> backupProfileDir ist diese Ebene
backupProfileDir = backupRootDir.clone();
backupProfileDir.append(profile.profileName);
// Fallback falls Backup1 doch schon direkt der Root ist
if (!backupProfileDir.exists()) backupProfileDir = backupRootDir;
const ok = Services.prompt.confirm(
null,
"Profilsicherung (Restore)",
"Restore jetzt starten für: " + profile.label + "\n\n" +
"Quelle: " + backupProfileDir.path + "\n" +
"Ziel: " + profileDir.path + "\n\n" +
"WICHTIG: Firefox bitte vorher vollständig beenden.\nFortfahren?"
);
if (!ok) return;
// Ziel leeren
clearDirectoryContents(profileDir);
// Zurückkopieren
copyDirectoryContents(backupProfileDir, profileDir);
// Log schreiben (wie im Muster: alte Zustand...)
try { logRestore(profile, backupProfileDir.path, profileDir.path); } catch (_) {}
setTimeout(() => {
showAlert(
"✅ Restore abgeschlossen!\n\n" +
"👤 Profil: " + profile.label + "\n\n" +
"📁 Quelle: " + backupProfileDir.path + "\n" +
"📁 Ziel: " + profileDir.path + "\n\n" +
"Hinweis: Starte Firefox danach neu."
);
}, 50);
} catch (e) {
console.error("Restore Fehler:", e);
setTimeout(() => showAlert("❌ Restore Fehler: " + e.message), 0);
}
}
// =========================
// Menü erstellen
// =========================
function createMenu(doc) {
const menupopup = doc.createXULElement("menupopup");
menupopup.setAttribute("id", POPUP_ID);
// Backup Gruppe
PROFILES.forEach((profile) => {
const menuitem = doc.createXULElement("menuitem");
menuitem.setAttribute("label", "Backup: " + profile.label);
menuitem.setAttribute("class", "menuitem-iconic");
if (profile.iconPath) menuitem.setAttribute("image", profile.iconPath);
menuitem.addEventListener("command", () => {
const ok = Services.prompt.confirm(
null,
"Profilsicherung",
"Backup jetzt starten für: " + profile.label + " ?"
);
if (ok) runBackup(profile);
});
menupopup.appendChild(menuitem);
});
const sep = doc.createXULElement("menuseparator");
menupopup.appendChild(sep);
// Restore Gruppe
PROFILES.forEach((profile) => {
const menuitem = doc.createXULElement("menuitem");
menuitem.setAttribute("label", "Restore: " + profile.label);
menuitem.setAttribute("class", "menuitem-iconic");
if (profile.iconPath) menuitem.setAttribute("image", profile.iconPath);
menuitem.addEventListener("command", () => runRestore(profile));
menupopup.appendChild(menuitem);
});
return menupopup;
}
// =========================
// Button patchen
// =========================
function patchButton(btn) {
if (!btn) return false;
const oldPopup = btn.querySelector("#" + POPUP_ID);
if (oldPopup) oldPopup.remove();
btn.setAttribute("type", "menu");
btn.setAttribute("aria-haspopup", "true");
btn.setAttribute("tooltiptext", "Backup & Restore");
btn.setAttribute("image", btnIconPath);
btn.appendChild(createMenu(btn.ownerDocument));
btn.addEventListener("click", (ev) => {
if (ev.button !== 0) return;
try {
const p = btn.querySelector("#" + POPUP_ID);
if (p) p.openPopup(btn, "after_start", 0, 0, false, false);
} catch (_) {}
}, false);
return true;
}
// =========================
// Widget erstellen
// =========================
try {
if (CustomizableUI.getWidget && CustomizableUI.getWidget(BTN_ID)) {
try { CustomizableUI.destroyWidget(BTN_ID); } catch (_) {}
}
} catch (_) {}
try {
const el = document.getElementById(BTN_ID);
if (el) el.remove();
} catch (_) {}
CustomizableUI.createWidget({
id: BTN_ID,
defaultArea: CustomizableUI.AREA_NAVBAR,
label: "Profilsicherung",
tooltiptext: "Backup & Restore (mit Log)",
onCreated: (button) => setTimeout(() => patchButton(button), 0)
});
// =========================
// CSS (wie bei dir)
// =========================
const css = `
#${BTN_ID} .toolbarbutton-icon {
width: 31px !important;
height: 31px !important;
padding: 5px !important;
}
#${POPUP_ID} {
max-width: 250px !important;
min-width: 250px !important;
}
#${POPUP_ID} menuitem.menuitem-iconic img.menu-icon {
margin-left: -28px !important;
}
#${POPUP_ID} menuitem.menuitem-iconic label.menu-text {
margin-left: 6px !important;
}
`;
const sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
const ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const uri = ios.newURI("data:text/css;charset=utf-8," + encodeURIComponent(css), null, null);
sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
})();
Alles anzeigen
Das war die Lösung. Habe meinen Profilpfad nun auch ganz angegeben und damit geht es.
Alles wird gut.![]()
Im ersten Backup Ordner erstellt das Script keine Zip Datei, kopiert nur
die Dateien rein.
Ja, Endor, das ist so gewollt, und zwar aus folgendem Grund. Schon lange nutze ich PowerShell-Scripte, um ein erstelltes Backup, um schnell wieder den Profilordner zurück zu bringen. Und da muss der Ordner genau so zur Verfügung stehen, wie er im System vorhanden ist. Ich gebe dir mal ein Beispiel aus dem Skript aus #362 für das Profil Reserve 3 profilePath: "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\Reserve 3",, dafür würde das ps1-Script dann so aussehen:
# Reserve 3 Retour.ps1
# =========================
# Einstellungen (Restore)
# =========================
# 1. Ziel-Profilordner (wird überschrieben)
$ProfilePath = "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\Reserve 3"
# 2. Quelle: gesicherter Profilordner
$BackupProfile = "G:\\Firefox Sicherung\\Reserve 3\\Reserve 3"
# 3. Pfad für Log-Datei
$LogPfad = "G:\\Firefox Sicherung\\Reserve 3"
# =============================
# Ab hier besser nichts ändern
# =============================
Add-Type -AssemblyName System.Windows.Forms
function Show-Info {
param([string]$Msg)
Write-Host ("{0} - {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Msg)
}
# Feste Log-Datei für Restore
$LogFile = Join-Path $LogPfad "Retour.txt"
# Alte Log-Datei löschen, falls vorhanden
if (Test-Path -Path $LogFile) {
Remove-Item -Path $LogFile -Force
}
Start-Sleep -Seconds 1
# neue Log-Datei anlegen
$LogFile = (New-Item $LogFile -ItemType File -Force).FullName
# Überschrift für das LogFile
Add-Content $LogFile ("Profil in alten Zustand versetzt am {0} Uhr`n" -f (Get-Date -Format "dddd, dd. MMMM yyyy, HH:mm:ss"))
Add-Content $LogFile "`n`n"
function Write-Log {
param([string]$Text)
Add-Content -Path $LogFile -Value ("[{0}] {1}" -f (Get-Date -Format "HH:mm"), $Text)
}
Show-Info "Starte Restore des Firefox-Profils (Firefox-Erkennung deaktiviert)."
Write-Log "Restore gestartet (Firefox-Erkennung deaktiviert)."
# =========================
# Restore-Teil
# =========================
# prüfen, ob Sicherungsprofil existiert
if (-not (Test-Path -LiteralPath $BackupProfile)) {
Show-Info "Sicherungsprofil nicht gefunden: $BackupProfile"
Write-Log "FEHLER: Sicherungsprofil nicht gefunden: $BackupProfile"
[System.Windows.Forms.MessageBox]::Show(
"Sicherungsprofil wurde nicht gefunden:`n$BackupProfile",
"Firefox-Restore",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
) | Out-Null
exit 1
}
# vorhandenes Ziel-Profil löschen
if (Test-Path -LiteralPath $ProfilePath) {
Show-Info "Lösche vorhandenen Profilordner: $ProfilePath"
Write-Log "Lösche vorhandenen Profilordner: $ProfilePath"
Remove-Item -LiteralPath $ProfilePath -Recurse -Force
Start-Sleep -Seconds 2
} else {
Show-Info "Zielprofil existiert noch nicht – nichts zu löschen."
Write-Log "Hinweis: Zielprofil existiert noch nicht – nichts zu löschen."
}
# Sicherungsprofil zurückkopieren
Show-Info "Kopiere Sicherung nach: $ProfilePath"
Write-Log "Kopiere Sicherung nach: $ProfilePath"
Copy-Item -LiteralPath $BackupProfile -Destination $ProfilePath -Recurse -Force
# Erfolgsmeldung
Show-Info "Restore erfolgreich abgeschlossen."
Write-Log "Restore erfolgreich abgeschlossen."
[System.Windows.Forms.MessageBox]::Show(
"Firefox-Profil erfolgreich zurückgespielt.`n`nZielprofil: $ProfilePath`nLog: $LogFile`n`n⚠️ Hinweis: Firefox-Erkennung deaktiviert - manuell schließen!",
"Firefox-Restore",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Information
) | Out-Null
Alles anzeigen
Diese ps1-Datei kann von überall aus gestartet werden. Oberstes Gebot ist, der Firefox muss beendet sein ( sollte besser auch bei der Sicherung sein). Wenn das Backup dann wieder zurück übertragen wurde wird eine Log-Datei geschrieben ( Ordner muss angegeben werden, wo sie abgelegt werden soll), die das zum Inhalt hat.
Aber wenn die Profilbezeichnung ein Leerzeichen enthält funktioniert es nicht.
zbs. Firefox 152 dann kommt nur die Meldung :
In meinem Script habe ich z.B das stehen: profilePath: "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\Reserve 3",
Name lautet "Reserve 3", und hat ein Leerzeichen, funktioniert aber ohne Fehler. Ich werde aber trotzdem noch mit anderen testen, melde mich wieder.
Edit:
Und auch hiermit profilePath: "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\ooaxewl1.Reserve-Profil 2", keine Probleme, weder mit Script aus #362 noch aus #367. Es sind im Pfad ja insgesamt 2 Leerzeichen enthalten, deswegen ja zwingend vorgeschrieben die Anführungszeichen. Heißt der Profilordner bei dir wirklich "Firefox 152"?
Eventuell hat ja noch jemand getestet, dann bitte kommentieren.
Wer dann doch lieber ein ZIP-Archiv im zweiten Sicherungsordner haben möchte, der kann dann dafür dieses Script nutzen. Ansonsten hat sich gegenüber dem Script aus #362 nichts geändert.
// wurde mit KI erstellt
// ==UserScript==
// @name ProfileBackup@Fu_ZIP.uc.js
// @description Firefox Profil sichern: Backup (Ordner) + Archiv (ZIP pro Tag/01..), gesperrte Dateien ignoriert
// @version 2026.07-zip-nsizipwriter
// ==/UserScript==
(function () {
"use strict";
if (location.href !== "chrome://browser/content/browser.xhtml") return;
if (typeof CustomizableUI === "undefined") return;
if (!window.gBrowser) return;
// =========================
// Pro Profil konfigurieren
// =========================
const PROFILES = [
{
label: "Reserve 3",
profilePath: "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\Reserve 3",
profileName: "Reserve 3",
backup1Path: "G:\\Firefox Sicherung\\Reserve 3",
archiveRootPath: "G:\\Sicherung2\\Reserve 3",
iconPath: "file:///C:/FoxIcons2/Finale.png"
},
{
label: "Nightly2",
profilePath: "G:\\Firefox Test\\Nightly2\\Profilordner",
profileName: "Profilordner",
backup1Path: "G:\\Firefox Sicherung\\Nightly2",
archiveRootPath: "G:\\Sicherung2\\Nightly2",
iconPath: "file:///C:/FoxIcons2/Nightly.png"
},
{
label: "Beta1",
profilePath: "G:\\Firefox Test\\Beta1\\Profilordner",
profileName: "Profilordner",
backup1Path: "G:\\Firefox Sicherung\\Beta1",
archiveRootPath: "G:\\Sicherung2\\Beta1",
iconPath: "file:///C:/FoxIcons2/Beta.png"
}
];
// =========================
// UI IDs
// =========================
const BTN_ID = "profilebackup_menu_button_icons_final";
const POPUP_ID = BTN_ID + "_popup";
// =========================
// Button-Icon
// =========================
const btnIconPath = "file:///C:/FoxIcons2/backup.png";
// =========================
// UI Helpers
// =========================
function showAlert(text) {
try {
let w = null;
try { w = Services.wm.getMostRecentWindow("navigator:browser"); } catch (_) {}
Services.prompt.alert(w || null, "Profilsicherung", text);
} catch (e) {
console.error("Alert Fehler:", e);
}
}
function pad2(n) {
return String(n).padStart(2, "0");
}
function getDateStrYYYYMMDD(d) {
return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
}
function escapeRegExp(s) {
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// =========================
// gesperrte Dateien + WAL/SHM ignorieren
// =========================
function shouldSkipEntry(entry) {
const name = entry.leafName;
if (name === "parent.lock") return true;
if (name === "lock") return true;
if (name === "recovery.jsonlz4") return true;
if (name.endsWith(".sqlite-wal")) return true;
if (name.endsWith(".sqlite-shm")) return true;
return false;
}
// =========================
// Backup 1: Ordnerkopie (wie bisher)
// =========================
function copyDirectoryContents(srcDir, destDir) {
let entries = srcDir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (shouldSkipEntry(entry)) continue;
const newFile = destDir.clone();
newFile.append(entry.leafName);
try {
if (entry.isDirectory()) {
if (!newFile.exists()) newFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(entry, newFile);
} else {
entry.copyTo(destDir, entry.leafName);
}
} catch (e) {
console.warn("⚠️ Datei konnte nicht kopiert werden (gesperrt?): " + entry.path, e);
}
}
}
// =========================
// ZIP-Name-Zähler pro Tag (01..)
// =========================
function getNextDailyZipIndex(archiveRootFile, profileName, dateStr) {
let max = 0;
const re = new RegExp(
"^" + escapeRegExp(profileName) + "_" + escapeRegExp(dateStr) + "_(\\d{2})\\.zip$"
);
let entries = archiveRootFile.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (!entry.isFile()) continue;
const m = re.exec(entry.leafName);
if (m) {
const n = parseInt(m[1], 10);
if (!isNaN(n) && n > max) max = n;
}
}
return max + 1;
}
// =========================
// ZIP-Erstellung mit nsIZipWriter
// Packt den Inhalt des Profilordners relativ ein (ohne zusätzlichen Root-Ordner)
// =========================
function zipProfileFolder(profileDir, zipFile, baseDirPathForRel) {
// Basis: Wir brauchen relativ zu baseDirPathForRel
// baseDirPathForRel = profileDir.path
const zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
const PR_WRONLY = 0x02;
const PR_CREATE_FILE = 0x08;
const PR_TRUNCATE = 0x20;
if (zipFile.exists()) {
zipFile.remove(true);
}
zipWriter.open(zipFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
// Rekursiv: Dateien hinzufügen
function addRecursive(currentDir) {
let entries = currentDir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
if (shouldSkipEntry(entry)) continue;
if (entry.path === zipFile.path) continue;
if (entry.isDirectory()) {
addRecursive(entry);
} else {
// Relativer Pfad im ZIP
let rel = entry.path.replace(baseDirPathForRel, "");
rel = rel.replace(/^([\\\/])/, ""); // führenden Slash entfernen
// ZIP verlangt forward slashes
rel = rel.replace(/\\/g, "/");
try {
zipWriter.addEntryFile(
rel,
Ci.nsIZipWriter.COMPRESSION_FASTEST,
entry,
false
);
} catch (e) {
// gesperrt → ignorieren (wie im Vorlage-Script)
}
}
}
}
addRecursive(profileDir);
zipWriter.close();
}
// =========================
// runBackup
// =========================
function runBackup(profile) {
let backupTarget = null;
let archiveRootFile = null;
try {
const now = new Date();
const profileDir = new FileUtils.File(profile.profilePath);
if (!profileDir.exists()) throw new Error("Profilordner existiert nicht: " + profile.profilePath);
const backupRoot = new FileUtils.File(profile.backup1Path);
if (!backupRoot.exists()) backupRoot.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
archiveRootFile = new FileUtils.File(profile.archiveRootPath);
if (!archiveRootFile.exists()) archiveRootFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
// -------------------------
// BACKUP 1 (Ordner)
// -------------------------
backupTarget = backupRoot.clone();
backupTarget.append(profile.profileName);
if (backupTarget.exists()) backupTarget.remove(true);
backupTarget.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(profileDir, backupTarget);
// -------------------------
// BACKUP 2 (ZIP, täglich + 01..)
// -------------------------
const dateStr = getDateStrYYYYMMDD(now);
const nextIdx = getNextDailyZipIndex(archiveRootFile, profile.profileName, dateStr);
const idxStr = pad2(nextIdx);
const zipFileName = profile.profileName + "_" + dateStr + "_" + idxStr + ".zip";
const zipFile = archiveRootFile.clone();
zipFile.append(zipFileName);
zipProfileFolder(profileDir, zipFile, profileDir.path);
if (!zipFile.exists()) {
throw new Error("ZIP wurde nicht erstellt: " + zipFile.path);
}
setTimeout(() => {
showAlert(
"✅ Backup abgeschlossen!\n\n" +
"👤 Profil: " + profile.label + "\n\n" +
"📁 Backup Ordner 1: " + backupTarget.path + "\n" +
"📦 Archiv ZIP: " + zipFile.path
);
}, 50);
} catch (e) {
console.error("Backup Fehler:", e);
try {
// optional: staging o.ä. gäbe es hier nicht; also best-effort cleanup nicht nötig
} catch (_) {}
setTimeout(() => showAlert("❌ Fehler: " + e.message), 0);
}
}
// =========================
// Menü erstellen
// =========================
function createMenu(doc) {
const menupopup = doc.createXULElement("menupopup");
menupopup.setAttribute("id", POPUP_ID);
PROFILES.forEach((profile) => {
const menuitem = doc.createXULElement("menuitem");
menuitem.setAttribute("label", "Backup: " + profile.label);
menuitem.setAttribute("class", "menuitem-iconic");
if (profile.iconPath) {
menuitem.setAttribute("image", profile.iconPath);
}
menuitem.addEventListener("command", () => {
let ok = false;
try {
ok = Services.prompt.confirm(
null,
"Profilsicherung",
"Backup jetzt starten für: " + profile.label + " ?"
);
} catch (_) {
ok = true; // Fallback
}
if (ok) runBackup(profile);
});
menupopup.appendChild(menuitem);
});
return menupopup;
}
// =========================
// Button patchen (öffnet Popup)
// =========================
function patchButton(btn) {
if (!btn) return false;
const oldPopup = btn.querySelector("#" + POPUP_ID);
if (oldPopup) oldPopup.remove();
btn.setAttribute("type", "menu");
btn.setAttribute("aria-haspopup", "true");
btn.setAttribute("tooltiptext", "Profile auswählen (Backup starten)");
btn.setAttribute("image", btnIconPath);
btn.appendChild(createMenu(btn.ownerDocument));
btn.addEventListener("click", (ev) => {
if (ev.button !== 0) return;
try {
const p = btn.querySelector("#" + POPUP_ID);
if (p) p.openPopup(btn, "after_start", 0, 0, false, false);
} catch (_) {}
}, false);
return true;
}
// =========================
// Alte Instanz entfernen + Widget erstellen
// =========================
try {
if (CustomizableUI.getWidget && CustomizableUI.getWidget(BTN_ID)) {
try { CustomizableUI.destroyWidget(BTN_ID); } catch (_) {}
}
} catch (_) {}
try {
const el = document.getElementById(BTN_ID);
if (el) el.remove();
} catch (_) {}
CustomizableUI.createWidget({
id: BTN_ID,
defaultArea: CustomizableUI.AREA_NAVBAR,
label: "Profilsicherung",
tooltiptext: "Profile auswählen (Backup starten)",
onCreated: (button) => setTimeout(() => patchButton(button), 0)
});
// =========================
// CSS Styles (Button & Popup) – wie bei dir
// =========================
const css = `
#${BTN_ID} .toolbarbutton-icon {
width: 31px !important;
height: 31px !important;
padding: 5px !important;
}
#${POPUP_ID} {
max-width: 250px !important;
min-width: 250px !important;
}
#${POPUP_ID} menuitem.menuitem-iconic img.menu-icon {
margin-left: -28px !important;
}
#${POPUP_ID} menuitem.menuitem-iconic label.menu-text {
margin-left: 6px !important;
}
`;
const sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
const ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const uri = ios.newURI("data:text/css;charset=utf-8," + encodeURIComponent(css), null, null);
sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
})();
Alles anzeigen
Script in #362 getauscht, Button wird nun auch in Version 152 und 153 gezeigt.
Fehler in der Konsole, Schaltfläche wird nicht angezeigt, funktioniert nicht, v152, v153beta9
Das kann durchaus richtig sein, denn ich habe es nur in der 154 aufgebaut und getestet. Sorry , das werde ich nacharbeiten, stelle es dann hier wieder rein.
Skript ProfileBackup@Fu.uc.js
=============================
// wurde mit KI erstellt
Vor Nutzung des Skriptes müssen zwei Ordner für die Sicherungen eingerichtet werden ( Beispiele aus dem Skript ersichtlich ). In den ersten Ordner wird dann der vollständige Profilordner hinein kopiert (kein ZIP), wobei ganz wichtig ist, den richtigen Namen des Profils in das Skript einzutragen (about:profiles aufrufen und Namen entnehmen, z.B. qtqwpmy7.default-release). Bei jedem neuen Backup wird
der Profilordner hier überschrieben. Auch portable können mit entsprechenden Angaben (Pfade) gesichert werden.
Der zweite Ordner gilt als doppelte Sicherung, hier wird auch der Profilordner hinein kopiert, hat als Endung aber eine fortlaufende Nummerierung mit Datum und Uhrzeit (wichtig wenn mehrmals gesichert wird).
Die Pfade zu diesen Ordnern sind bei jedem Profil in das Skript einzutragen.
Das Skript wird über den Button in der Navbar gestartet, es öffnet sich ein Menü (Grafik 1) über das eine Auswahl für ein Backup getroffen werden kann, bei Klick darauf erscheint ein Bestätigungs-Dialog (Grafik 2), der bedient werden muss. Bei OK startet das Backup, bei Erfolg wird dann über Windows eine Benachrichtigung ausgegeben (Grafik 3).
// ==UserScript==
// @name ProfileBackup@Fu.uc.js
// @description Firefox Profil sichern (Backup + Archiv als Ordner, gesperrte Dateien ignoriert)
// @version 2026.07-icons-css-ordner-fixed
// ==/UserScript==
(function () {
"use strict";
if (location.href !== "chrome://browser/content/browser.xhtml") return;
if (typeof CustomizableUI === "undefined") return;
if (!window.gBrowser) return;
// =========================
// Pro Profil konfigurieren
// =========================
const PROFILES = [
{
label: "Reserve 3",
profilePath: "C:\\Users\\Old Man\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\Reserve 3",
profileName: "Reserve 3",
backup1Path: "G:\\Firefox Sicherung\\Reserve 3",
archiveRootPath: "G:\\Sicherung2\\Reserve 3",
iconPath: "file:///C:/FoxIcons2/Finale.png"
},
{
label: "Nightly2",
profilePath: "G:\\Firefox Test\\Nightly2\\Profilordner",
profileName: "Profilordner",
backup1Path: "G:\\Firefox Sicherung\\Nightly2",
archiveRootPath: "G:\\Sicherung2\\Nightly2",
iconPath: "file:///C:/FoxIcons2/Nightly.png"
},
{
label: "Beta1",
profilePath: "G:\\Firefox Test\\Beta1\\Profilordner",
profileName: "Profilordner",
backup1Path: "G:\\Firefox Sicherung\\Beta1",
archiveRootPath: "G:\\Sicherung2\\Beta1",
iconPath: "file:///C:/FoxIcons2/Beta.png"
}
// Weitere Profile hier ergänzen:
// {
// label: "Stable",
// profilePath: "D:\\Firefox\\Stable\\Profilordner",
// profileName: "Stable_Profil",
// backup1Path: "D:\\Firefox Sicherung\\Stable",
// archiveRootPath: "D:\\Sicherung2\\Stable",
// iconPath: "file:///C:/FoxIcons2/002.png"
// }
];
// =========================
// UI IDs
// =========================
const BTN_ID = "profilebackup_menu_button_icons_final";
const POPUP_ID = BTN_ID + "_popup";
// =========================
// Button-Icon
// =========================
const btnIconPath = "file:///C:/FoxIcons2/backup.png";
// =========================
// Helpers
// =========================
function showAlert(text) {
try {
let w = null;
try {
w = Services.wm.getMostRecentWindow("navigator:browser");
} catch (_) {}
Services.prompt.alert(w || null, "Profilsicherung", text);
} catch (e) {
console.error("Alert Fehler:", e);
}
}
function pad(n) {
return String(n).padStart(2, "0");
}
// =========================
// Backup-Logik
// =========================
function getArchiveFolder(root, profileName) {
const d = new Date();
const timestamp =
pad(d.getMonth() + 1) +
pad(d.getDate()) + "_" +
pad(d.getHours()) +
pad(d.getMinutes());
const archiveTarget = root.clone();
archiveTarget.append(profileName + "_" + timestamp);
return archiveTarget;
}
function copyDirectoryContents(srcDir, destDir) {
let entries = srcDir.directoryEntries;
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
// gesperrte / WAL / shm ignorieren
if (
entry.leafName === "parent.lock" ||
entry.leafName === "lock" ||
entry.leafName === "recovery.jsonlz4" ||
entry.leafName.endsWith(".sqlite-wal") ||
entry.leafName.endsWith(".sqlite-shm")
) {
continue;
}
const newFile = destDir.clone();
newFile.append(entry.leafName);
try {
if (entry.isDirectory()) {
if (!newFile.exists()) newFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(entry, newFile);
} else {
entry.copyTo(destDir, entry.leafName);
}
} catch (e) {
console.warn("⚠️ Datei konnte nicht kopiert werden (gesperrt?): " + entry.path, e);
}
}
}
function runBackup(profile) {
try {
const profileDir = new FileUtils.File(profile.profilePath);
if (!profileDir.exists()) {
throw new Error("Profilordner existiert nicht: " + profile.profilePath);
}
const backupRoot = new FileUtils.File(profile.backup1Path);
if (!backupRoot.exists()) backupRoot.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
const archiveRoot = new FileUtils.File(profile.archiveRootPath);
if (!archiveRoot.exists()) archiveRoot.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
// BACKUP 1: immer gleicher Zielordner
const backupTarget = backupRoot.clone();
backupTarget.append(profile.profileName);
if (backupTarget.exists()) backupTarget.remove(true);
backupTarget.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(profileDir, backupTarget);
// BACKUP 2: Zeitstempel-Ordner im Archivroot
const archiveTarget = getArchiveFolder(archiveRoot, profile.profileName);
archiveTarget.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
copyDirectoryContents(profileDir, archiveTarget);
// Alert nachträglich (robuster)
setTimeout(() => {
showAlert(
"✅ Backup abgeschlossen!\n\n" +
"👤 Profil: " + profile.label + "\n\n" +
"📁 Backup Ordner 1: " + backupTarget.path + "\n" +
"📁 Archiv Ordner 2: " + archiveTarget.path
);
}, 50);
} catch (e) {
console.error("Backup Fehler:", e);
setTimeout(() => showAlert("❌ Fehler: " + e.message), 0);
}
}
// =========================
// Menü bauen
// =========================
function createMenu(doc) {
const menupopup = doc.createXULElement("menupopup");
menupopup.setAttribute("id", POPUP_ID);
PROFILES.forEach((profile) => {
const menuitem = doc.createXULElement("menuitem");
menuitem.setAttribute("label", "Backup: " + profile.label);
menuitem.setAttribute("class", "menuitem-iconic");
if (profile.iconPath) {
menuitem.setAttribute("image", profile.iconPath);
}
menuitem.addEventListener("command", () => {
let ok = false;
try {
ok = Services.prompt.confirm(
null,
"Profilsicherung",
"Backup jetzt starten für: " + profile.label + " ?"
);
} catch (_) {
ok = true; // Fallback
}
if (ok) runBackup(profile);
});
menupopup.appendChild(menuitem);
});
return menupopup;
}
// =========================
// Button patchen
// =========================
function patchButton(btn) {
if (!btn) return false;
const oldPopup = btn.querySelector("#" + POPUP_ID);
if (oldPopup) oldPopup.remove();
btn.setAttribute("type", "menu");
btn.setAttribute("aria-haspopup", "true");
btn.setAttribute("tooltiptext", "Profile auswählen (Backup starten)");
btn.setAttribute("image", btnIconPath);
btn.appendChild(createMenu(btn.ownerDocument));
btn.addEventListener("click", (ev) => {
if (ev.button !== 0) return;
try {
const p = btn.querySelector("#" + POPUP_ID);
if (p) p.openPopup(btn, "after_start", 0, 0, false, false);
} catch (_) {}
}, false);
return true;
}
// =========================
// Alte Instanz entfernen + Widget erstellen
// =========================
try {
if (CustomizableUI.getWidget && CustomizableUI.getWidget(BTN_ID)) {
try { CustomizableUI.destroyWidget(BTN_ID); } catch (_) {}
}
} catch (_) {}
try {
const el = document.getElementById(BTN_ID);
if (el) el.remove();
} catch (_) {}
CustomizableUI.createWidget({
id: BTN_ID,
defaultArea: CustomizableUI.AREA_NAVBAR,
label: "Profilsicherung",
tooltiptext: "Profile auswählen (Backup starten)",
onCreated: (button) => setTimeout(() => patchButton(button), 0)
});
// =========================
// CSS Styles (Button & Popup)
// =========================
const css = `
#${BTN_ID} .toolbarbutton-icon {
width: 31px !important;
height: 31px !important;
padding: 5px !important;
}
#${POPUP_ID} {
max-width: 250px !important;
min-width: 250px !important;
}
#${POPUP_ID} menuitem.menuitem-iconic img.menu-icon {
margin-left: -28px !important;
}
#${POPUP_ID} menuitem.menuitem-iconic label.menu-text {
margin-left: 6px !important;
}
`;
const sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
const ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
const uri = ios.newURI("data:text/css;charset=utf-8," + encodeURIComponent(css), null, null);
sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
})();
Alles anzeigen
Das Script TabMoveOnly.uc.js funktionierte auch nicht mehr (jetzt erst festgestellt). Hier dann die bereinigte Version zur Verfügung.
// ==UserScript==
// @name TabMoveOnly.uc.js
// @namespace local
// @description Nur das Verschieben von Tabs per Drag & Drop
// @include main
// @compatibility Firefox152+
// @version 2026-07-05
// ==/UserScript==
"use strict";
TabMoveOnly();
function TabMoveOnly() {
if (!window.gBrowser) return;
const TAB_DROP_TYPE = window.TAB_DROP_TYPE || "application/x-moz-tabbrowser-tab";
// =========================================================
// FEINEINSTELLUNGEN FÜR DIE MARKER
// =========================================================
// Links: Marker horizontal verschieben in px
const LEFT_MARKER_SHIFT = 0;
// Rechts: Marker horizontal verschieben in px
const RIGHT_MARKER_SHIFT = 0;
// Marker vertikal leicht nach oben/unten korrigieren in px
const MARKER_TOP = -1;
// =========================================================
// MARKER-CSS
// =========================================================
const css = `
.tabbrowser-tab[dnd-marker]{
position: relative !important;
}
.tab-dnd-marker{
position: absolute !important;
pointer-events: none !important;
z-index: 1000 !important;
content: "" !important;
width: 14px !important;
height: 18px !important;
border-radius: 50% !important;
background:
radial-gradient(circle at center,
#00ffff 0 35%,
#1dacd6 36% 60%,
rgba(255,255,255,0) 61%) !important;
box-shadow: 0 0 3px rgba(255, 90, 82, 0.35) !important;
}
`;
// =========================================================
// CSS EINBINDEN
// =========================================================
const sss = Cc["@mozilla.org/content/style-sheet-service;1"]
.getService(Ci.nsIStyleSheetService);
const uri = Services.io.newURI(
"data:text/css;charset=UTF-8," + encodeURIComponent(css)
);
if (!sss.sheetRegistered(uri, sss.USER_SHEET)) {
sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
}
const tabContainer = gBrowser.tabContainer;
// Listener nur einmal aktivieren
let listenersActive = false;
// Reserviert für Gruppen-/Drop-Zustand
let lastGroupStart = null;
let lastGroupEnd = null;
// =========================================================
// MARKER-VERWALTUNG
// =========================================================
function clearDndMarkers() {
for (const tab of gBrowser.tabs) {
tab.removeAttribute("dnd-marker");
tab.style.removeProperty("--tab-dnd-marker-top");
if (tab._dndMarkerEl) {
tab._dndMarkerEl.remove();
tab._dndMarkerEl = null;
}
}
}
function setDndMarker(tab, side) {
clearDndMarkers();
if (!tab || !side) return;
// Nur zur Erkennung / CSS-Selektoren
tab.setAttribute("dnd-marker", side);
// Vertikale Feinkorrektur als Variable direkt am Tab
tab.style.setProperty("--tab-dnd-marker-top", `${MARKER_TOP}px`);
// Echtes Marker-Element, damit dein CSS sicher greift
const marker = tab.ownerDocument.createXULElement("box");
marker.className = `tab-dnd-marker ${side}`;
// Horizontale Feineinstellung pro Seite
if (side === "left") {
marker.style.setProperty("left", `${1 + LEFT_MARKER_SHIFT}px`, "important");
} else if (side === "right") {
marker.style.setProperty("right", `${1 + RIGHT_MARKER_SHIFT}px`, "important");
}
tab.appendChild(marker);
tab._dndMarkerEl = marker;
}
// =========================================================
// HILFSFUNKTIONEN FÜR TAB / DROP
// =========================================================
function getTabFromEventTarget(event, ignoreTabSides = false) {
let { target } = event;
if (!target) return null;
if (target.nodeType !== Node.ELEMENT_NODE) {
target = target.parentElement;
}
let tab = target?.closest("tab") || target?.closest("tab-group");
const selectedTab = gBrowser.selectedTab;
// Falls der Cursor nur im Randbereich ist, nimm den aktuell ausgewählten Tab
if (tab && ignoreTabSides) {
const { width, height } = tab.getBoundingClientRect();
if (
event.screenX < tab.screenX + width * 0.25 ||
event.screenX > tab.screenX + width * 0.75 ||
((event.screenY < tab.screenY + height * 0.25 ||
event.screenY > tab.screenY + height * 0.75) &&
gBrowser.tabContainer.verticalMode)
) {
return selectedTab;
}
}
return tab;
}
// Firefox-interne Drop-Index-Berechnung für Multirow / normales Tab-Layout
gBrowser.tabContainer._getDropIndex = function (event) {
let tabToDropAt = getTabFromEventTarget(event, false);
if (tabToDropAt?.localName == "tab-group") {
tabToDropAt = tabToDropAt.previousSibling;
if (!tabToDropAt) {
tabToDropAt = gBrowser.visibleTabs[0];
}
}
if (!tabToDropAt) {
tabToDropAt = gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1];
}
if (!tabToDropAt) return null;
const tabPos = gBrowser.tabContainer.getIndexOfItem(tabToDropAt);
const rect = tabToDropAt.getBoundingClientRect();
const ltr = window.getComputedStyle(this).direction == "ltr";
if (ltr) {
return event.clientX < rect.x + rect.width / 2 ? tabPos : tabPos + 1;
}
return event.clientX > rect.x + rect.width / 2 ? tabPos : tabPos + 1;
};
// Prüft, ob der Drag wirklich ein Tab-Drag ist
function orig_getDropEffectForTabDrag(event) {
const dt = event.dataTransfer;
let isMovingTabs = dt.mozItemCount > 0;
for (let i = 0; i < dt.mozItemCount; i++) {
const types = dt.mozTypesAt(0);
if (types[0] != TAB_DROP_TYPE) {
isMovingTabs = false;
break;
}
}
if (isMovingTabs) {
const sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (
XULElement.isInstance(sourceNode) &&
sourceNode.localName == "tab" &&
sourceNode.documentGlobal.isChromeWindow &&
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
sourceNode.documentGlobal.gBrowser.tabContainer == sourceNode.container
) {
if (
PrivateBrowsingUtils.isWindowPrivate(window) !=
PrivateBrowsingUtils.isWindowPrivate(sourceNode.documentGlobal)
) {
return "none";
}
if (window.gMultiProcessBrowser != sourceNode.documentGlobal.gMultiProcessBrowser) return "none";
if (window.gFissionBrowser != sourceNode.documentGlobal.gFissionBrowser) return "none";
return dt.dropEffect == "copy" ? "copy" : "move";
}
}
if (Services.droppedLinkHandler.canDropLink(event, true)) return "link";
return "none";
}
function getMarkerInfo(event) {
let tabToDropAt = getTabFromEventTarget(event, false);
if (tabToDropAt?.localName == "tab-group") {
tabToDropAt = tabToDropAt.previousSibling;
if (!tabToDropAt) {
tabToDropAt = gBrowser.visibleTabs[0];
}
}
if (!tabToDropAt) {
const lastTab = gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1];
return { tab: lastTab, side: "right" };
}
const rect = tabToDropAt.getBoundingClientRect();
const ltr = window.getComputedStyle(gBrowser.tabContainer).direction == "ltr";
const side = ltr
? (event.clientX < rect.x + rect.width / 2 ? "left" : "right")
: (event.clientX > rect.x + rect.width / 2 ? "left" : "right");
return { tab: tabToDropAt, side };
}
// =========================================================
// DRAG OVER
// =========================================================
function performTabDragOver(event) {
const effects = orig_getDropEffectForTabDrag(event);
// Nur echte Tab-Drags zulassen
if (effects == "none" || effects == "link") {
clearDndMarkers();
return;
}
event.preventDefault();
event.stopPropagation();
const tab = getTabFromEventTarget(event, true);
// Kleine Starthilfe für Gruppen-Merker
if (tab && tab.nodeName == "tab-group" && !lastGroupStart) {
const first = tab.querySelector("tab:first-of-type");
const last = tab.querySelector("tab:last-of-type");
if (first && last) {
lastGroupStart = first._tPos;
lastGroupEnd = last._tPos;
}
}
const newIndex = gBrowser.tabContainer._getDropIndex(event);
if (newIndex == null) {
clearDndMarkers();
return;
}
const markerInfo = getMarkerInfo(event);
setDndMarker(markerInfo.tab, markerInfo.side);
}
// =========================================================
// DROP
// =========================================================
function performTabDropEvent(event) {
clearDndMarkers();
const dt = event.dataTransfer;
const dropEffect = dt.dropEffect;
let draggedTab;
if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) {
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (!draggedTab) return;
}
if (draggedTab && dropEffect != "copy" && draggedTab.container == gBrowser.tabContainer) {
let newIndex = gBrowser.tabContainer._getDropIndex(event);
if (newIndex == null) return;
const selectedTabs = gBrowser.selectedTabs.length > 1 ? gBrowser.selectedTabs : [draggedTab];
let tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex);
const tab = getTabFromEventTarget(event, false);
const tabgroup = tab?.closest("tab-group");
if (!tab) {
newIndex = gBrowser.tabs.length;
tabToMoveAt = null;
}
if (tabgroup && !tab.previousSibling) {
newIndex = 0;
selectedTabs.forEach(t => {
gBrowser.moveTabTo(t, { tabIndex: newIndex++, forceUngrouped: true });
});
} else if (
!tab ||
(!tabgroup && !tabToMoveAt?.group) ||
(tabgroup && tabToMoveAt?.group)
) {
selectedTabs.forEach(t => {
gBrowser.moveTabBefore(t, tabToMoveAt);
});
} else {
tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex - 1);
selectedTabs.forEach(t => {
gBrowser.moveTabAfter(t, tabToMoveAt);
});
}
lastGroupStart = null;
lastGroupEnd = null;
}
}
// =========================================================
// EINMALIGE INITIALISIERUNG
// =========================================================
tabContainer.addEventListener("dragstart", () => {
if (listenersActive) return;
tabContainer.on_dragover = e => performTabDragOver(e);
tabContainer._onDragOver = e => performTabDragOver(e);
tabContainer.ondrop = e => performTabDropEvent(e);
tabContainer.addEventListener("dragend", clearDndMarkers, true);
tabContainer.addEventListener("drop", clearDndMarkers, true);
listenersActive = true;
});
}
Alles anzeigen
Ich denke, mit der Möglichkeit per ESC und dem Linksklick außerhalb des Panels, sind doch nun zwei gute Möglichkeiten
geschaffen worden, damit das Panel auch wieder geschlossen werden kann.
Aber Mira_Belle , es wird niemand gezwungen meinen Lösungsvorschlag umzusetzen, es war mein guter Wille eine Lösung zu suchen, weil Hilfe erwartet wurde.
Mh, das Ganze hat aber auch den Nachteil, dass keine Links angeklickt werden können!
Sorry, darauf habe ich nicht geachtet (weil ich auch nicht damit arbeite). Dann hier der neue Absatz // Panel erstellen, da können dann auch Links geöffnet werden, Panel schließt nur bei Klick außerhalb.
// Panel erstellen
function createPanel() {
let doc = document;
let panel = doc.createXULElement('panel');
panel.id = "wetterfuchs-panel";
panel.setAttribute('noautohide', "false");
panel.setAttribute('type', "arrow");
panel.setAttribute('position', "after_end");
panel.addEventListener('popuphiding', () => {
clearPanel();
});
// Klick außerhalb: Panel schließen
function closePanel(event) {
if (panel.state !== "open")
return;
const button = document.getElementById(id);
// Klick auf den Button -> Menü/Panel nicht sofort schließen
if (button && button.contains(event.target))
return;
// Klick innerhalb des Panels -> Links funktionieren lassen
if (panel.contains(event.target))
return;
panel.hidePopup();
}
document.addEventListener("mousedown", closePanel, true);
let vbox = doc.createXULElement('vbox');
vbox.setAttribute('flex', '1');
panel.appendChild(vbox);
let browser = doc.createXULElement('browser');
browser.id = "wetterfuchs-iframe";
browser.setAttribute('type', 'content');
browser.setAttribute('flex', '1');
browser.setAttribute('remote', 'true');
browser.setAttribute('maychangeremoteness', 'true');
browser.setAttribute('disableglobalhistory', 'true');
browser.setAttribute('src', wfthrobber);
vbox.appendChild(browser);
// Mittelklick auf den Browser öffnet die aktuelle Seite in einem neuen Tab
browser.addEventListener("auxclick", event => {
if (event.button === 1) {
event.preventDefault();
openUrlFromPanel();
}
});
doc.getElementById('mainPopupSet').appendChild(panel);
return panel;
}
Alles anzeigen
Oder im Script die Funktion // Panel erstellen durch diesen Abschnitt ersetzen.
// Panel erstellen
function createPanel() {
let doc = document;
let panel = doc.createXULElement('panel');
panel.id = "wetterfuchs-panel";
panel.setAttribute('noautohide', "false");
panel.setAttribute('type', "arrow");
panel.setAttribute('position', "after_end");
// Panel bei Klick außerhalb schließen
panel.addEventListener('popuphiding', clearPanel);
// Klick ins Panel: bei Linksklick schließen
panel.addEventListener('mousedown', (event) => {
if (event.button === 0) panel.hidePopup();
else if (event.button === 1) openUrlFromPanel();
});
// Klick außerhalb: Panel schließen
document.addEventListener('click', (event) => {
if (panel.state === "open" && !panel.contains(event.target) && event.target.id !== id) {
panel.hidePopup();
}
});
let vbox = doc.createXULElement('vbox');
vbox.setAttribute('flex', '1');
panel.appendChild(vbox);
let browser = doc.createXULElement('browser');
browser.id = "wetterfuchs-iframe";
browser.setAttribute('type', 'content');
browser.setAttribute('flex', '1');
browser.setAttribute('remote', 'true');
browser.setAttribute('maychangeremoteness', 'true');
browser.setAttribute('disableglobalhistory', 'true');
browser.setAttribute('src', wfthrobber);
vbox.appendChild(browser);
doc.getElementById('mainPopupSet').appendChild(panel);
return panel;
}
Alles anzeigen
Vor einiger Zeit hatte Freund Büssen mich gebeten sein Script zum öffnen einer analogen Uhr wieder funktionstüchtig zu machen. Es lag genau das Problem an, dass die Uhr nur nach einem Neustart wieder geschlossen wurde. Mit KI konnte ich es dann wieder so lauffähig machen, dass bei einem Klick irgendwo in das Fenster die Uhr wieder geschlossen wird. Das ist der Abschnitt, der in das Script eingearbeitet wurde, eventuell hilft es weiter.
// Close listener installieren wenn Panel offen ist
panel.addEventListener('popupshown', event => {
if (this._closeListener)
return;
const btn = document.getElementById('uhr-toolbarbutton');
this._closeListener = (ev) => {
try {
// Panel / Button Klick nicht zum Schließen nutzen
if (ev.target && panel.contains && panel.contains(ev.target))
return;
if (btn && ev.target && btn.contains && btn.contains(ev.target))
return;
// irgendwo im Browserfenster => schließen
if (panel.state === "open")
panel.hidePopup();
} catch (e) {}
};
// Capture: zuverlässiger auch in neuen FF-Versionen
window.addEventListener('mousedown', this._closeListener, true);
window.addEventListener('touchstart', this._closeListener, true);
});
Alles anzeigen
würde die Variante auch den vorhandenen angepinnten Tab öffnen, oder würde ein neuer Tab geöffnent werden?
Ich arbeite zwar nicht mit angepinnten Tabs, aber Variante 2 würde einen neuen Tab öffnen.
Funktioniert, Dankeschön.
Bitteschön. ![]()
Eine zweite Möglichkeit wäre das gewesen:
was muss korrigiert werden?
Trage das bitte ein zum Aufruf:
Genau deswegen habe ich dann noch mal angefangen zu basteln...
![]()
Das war die einzige Möglichkeit, die ich gefunden habe, um die Hoverfarbe links im Navi-Bereich so zu gestalten.
Das ist auch nicht mehr so einfach, musste auch nach Möglichkeiten suchen. Eine Variante hatte ich hier schon gezeigt, bin jetzt auch dabei geblieben.
/* =========================================
Sidebar Buttons (alle)
========================================= */
/* Inaktive Buttons */
:root {
--button-background-color-ghost: #e0e0e0 !important;
}
/* Firefox Buttons / HG rechts */
moz-page-nav-button {
--page-nav-button-border-radius: 0 60px 60px 0 !important;
/* Farben */
--page-nav-button-background-color-selected: #228b22 !important;
--page-nav-button-background-color-hover: #26f7fd !important;
--page-nav-button-background-color-active: greenyellow !important;
/* Textfarbe */
--page-nav-button-text-color: black !important;
--page-nav-button-text-color-hover: #480607 !important;
--page-nav-button-text-color-active: blue !important;
}
moz-page-nav-button:hover {
background: #9dff00 !important;
background-repeat: no-repeat !important;
background-position: 12px 10px !important;
}
/* Buttons / HG links */
moz-page-nav-button {
border: 2px solid #3b2f2f !important;
border-radius: 60px !important;
background-color: #ffffe4 !important;
width: 260px !important;
margin-left: 20px !important;
}
/* aktiver Button */
button[aria-selected="true"] {
appearance: none !important;
border: 2px solid white !important;
}
a.moz-page-nav-link,
button[aria-selected="false"] {
appearance: none !important;
border: 2px solid white !important;
}
/* Textfarbe im aktiven Button */
button[selected] {
color: yellow !important;
}
/* blauer Rand links im aktiven Button ausgeblendet */
@media not (prefers-contrast) {
button::before {
content: "";
display: none !important;
}
}
}
/* Button Einstellungen ENDE */
Alles anzeigen