mirror of
https://github.com/zen-browser/desktop.git
synced 2026-06-07 20:24:19 +00:00
feat: Start making automatic backups for session files, p=#11861
* feat: Start making automatic backups for session files, b=no-bug, c=common * feat: Allow new links to open in unsynced windows, b=no-bug, c=no-component
This commit is contained in:
@@ -10,3 +10,6 @@
|
||||
|
||||
- name: zen.window-sync.prefer-unsynced-windows
|
||||
value: false
|
||||
|
||||
- name: zen.window-sync.open-link-in-new-unsynced-window
|
||||
value: true
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
diff --git a/browser/modules/URILoadingHelper.sys.mjs b/browser/modules/URILoadingHelper.sys.mjs
|
||||
index 9175fa820ad6bb75cd125fbfda2bf07d6dba4c90..f673cfd15289401ae425256016bf51303063e0b2 100644
|
||||
index 9175fa820ad6bb75cd125fbfda2bf07d6dba4c90..62a1a62f304ec8c83e859196861025347e79ba46 100644
|
||||
--- a/browser/modules/URILoadingHelper.sys.mjs
|
||||
+++ b/browser/modules/URILoadingHelper.sys.mjs
|
||||
@@ -542,7 +542,7 @@ export const URILoadingHelper = {
|
||||
@@ -225,6 +225,7 @@ function openInWindow(url, params, sourceWindow) {
|
||||
features,
|
||||
sa
|
||||
);
|
||||
+ win._zenStartupSyncFlag = Services.prefs.getBoolPref('zen.window-sync.open-link-in-new-unsynced-window') ? 'unsynced' : 'synced';
|
||||
}
|
||||
|
||||
function openInCurrentTab(targetBrowser, url, uriObj, params) {
|
||||
@@ -542,7 +543,7 @@ export const URILoadingHelper = {
|
||||
// page. If a load request bounces off for the currently selected tab,
|
||||
// we'll open a new tab instead.
|
||||
let tab = w.gBrowser.getTabForBrowser(targetBrowser);
|
||||
@@ -11,7 +19,7 @@ index 9175fa820ad6bb75cd125fbfda2bf07d6dba4c90..f673cfd15289401ae425256016bf5130
|
||||
where = "tab";
|
||||
targetBrowser = null;
|
||||
} else if (
|
||||
@@ -972,7 +972,7 @@ export const URILoadingHelper = {
|
||||
@@ -972,7 +973,7 @@ export const URILoadingHelper = {
|
||||
ignoreQueryString || replaceQueryString,
|
||||
ignoreFragmentWhenComparing
|
||||
);
|
||||
@@ -20,7 +28,7 @@ index 9175fa820ad6bb75cd125fbfda2bf07d6dba4c90..f673cfd15289401ae425256016bf5130
|
||||
for (let i = 0; i < browsers.length; i++) {
|
||||
let browser = browsers[i];
|
||||
let browserCompare = cleanURL(
|
||||
@@ -1018,7 +1018,7 @@ export const URILoadingHelper = {
|
||||
@@ -1018,7 +1019,7 @@ export const URILoadingHelper = {
|
||||
}
|
||||
|
||||
if (!doAdopt) {
|
||||
|
||||
@@ -9,143 +9,148 @@
|
||||
*
|
||||
* FOR ANY WEBSITE THAT WOULD NEED TO USE THE ACCENT COLOR, ETC
|
||||
*/
|
||||
{
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
'resource://gre/modules/AppConstants.sys.mjs'
|
||||
);
|
||||
|
||||
const kZenThemePrefsList = [
|
||||
'zen.theme.accent-color',
|
||||
'zen.theme.border-radius',
|
||||
'zen.theme.content-element-separation',
|
||||
];
|
||||
const kZenMaxElementSeparation = 12;
|
||||
|
||||
/**
|
||||
* ZenThemeModifier controls the application of theme data to the browser,
|
||||
* for example, it injects the accent color to the document. This is used
|
||||
* because we need a way to apply the accent color without having to worry about
|
||||
* shadow roots not inheriting the accent color.
|
||||
*
|
||||
* note: It must be a Firefox builtin page with access to the browser's configuration
|
||||
* and services.
|
||||
*/
|
||||
var ZenThemeModifier = {
|
||||
_inMainBrowserWindow: false,
|
||||
const kZenThemePrefsList = [
|
||||
'zen.theme.accent-color',
|
||||
'zen.theme.border-radius',
|
||||
'zen.theme.content-element-separation',
|
||||
];
|
||||
const kZenMaxElementSeparation = 12;
|
||||
|
||||
/**
|
||||
* Listen for theming updates from the LightweightThemeChild actor, and
|
||||
* begin listening to changes in preferred color scheme.
|
||||
* ZenThemeModifier controls the application of theme data to the browser,
|
||||
* for example, it injects the accent color to the document. This is used
|
||||
* because we need a way to apply the accent color without having to worry about
|
||||
* shadow roots not inheriting the accent color.
|
||||
*
|
||||
* note: It must be a Firefox builtin page with access to the browser's configuration
|
||||
* and services.
|
||||
*/
|
||||
init() {
|
||||
this._inMainBrowserWindow = window.location.href == 'chrome://browser/content/browser.xhtml';
|
||||
this.listenForEvents();
|
||||
this.updateAllThemeBasics();
|
||||
},
|
||||
window.ZenThemeModifier = {
|
||||
_inMainBrowserWindow: false,
|
||||
|
||||
listenForEvents() {
|
||||
var handleEvent = this.handleEvent.bind(this);
|
||||
// Listen for changes in the accent color and border radius
|
||||
for (let pref of kZenThemePrefsList) {
|
||||
Services.prefs.addObserver(pref, handleEvent);
|
||||
}
|
||||
/**
|
||||
* Listen for theming updates from the LightweightThemeChild actor, and
|
||||
* begin listening to changes in preferred color scheme.
|
||||
*/
|
||||
init() {
|
||||
this._inMainBrowserWindow = window.location.href == 'chrome://browser/content/browser.xhtml';
|
||||
this.listenForEvents();
|
||||
this.updateAllThemeBasics();
|
||||
},
|
||||
|
||||
// Add fullscreen listener to update the theme when going in and out of fullscreen
|
||||
const eventsForSeparation = [
|
||||
'ZenViewSplitter:SplitViewDeactivated',
|
||||
'ZenViewSplitter:SplitViewActivated',
|
||||
'fullscreen',
|
||||
'ZenCompactMode:Toggled',
|
||||
];
|
||||
const separationHandler = this.updateElementSeparation.bind(this);
|
||||
for (let eventName of eventsForSeparation) {
|
||||
window.addEventListener(eventName, separationHandler);
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
'unload',
|
||||
() => {
|
||||
for (let pref of kZenThemePrefsList) {
|
||||
Services.prefs.removeObserver(pref, handleEvent);
|
||||
}
|
||||
for (let eventName of eventsForSeparation) {
|
||||
window.removeEventListener(eventName, separationHandler);
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
},
|
||||
|
||||
handleEvent() {
|
||||
// note: even might be undefined, but we shoudnt use it!
|
||||
this.updateAllThemeBasics();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update all theme basics, like the accent color.
|
||||
*/
|
||||
async updateAllThemeBasics() {
|
||||
this.updateAccentColor();
|
||||
this.updateBorderRadius();
|
||||
this.updateElementSeparation();
|
||||
},
|
||||
|
||||
updateBorderRadius() {
|
||||
const borderRadius = Services.prefs.getIntPref('zen.theme.border-radius', -1);
|
||||
|
||||
// -1 is the default value, will use platform-native values
|
||||
// otherwise, use the custom value
|
||||
if (borderRadius == -1) {
|
||||
if (AppConstants.platform == 'macosx') {
|
||||
const targetRadius = window.matchMedia('(-moz-mac-tahoe-theme)').matches ? 15 : 10;
|
||||
document.documentElement.style.setProperty('--zen-border-radius', targetRadius + 'px');
|
||||
} else if (AppConstants.platform == 'linux') {
|
||||
// Linux uses GTK CSD titlebar radius, default to 8px
|
||||
document.documentElement.style.setProperty(
|
||||
'--zen-border-radius',
|
||||
'env(-moz-gtk-csd-titlebar-radius, 8px)'
|
||||
);
|
||||
} else {
|
||||
// Windows defaults to 8px
|
||||
document.documentElement.style.setProperty('--zen-border-radius', '8px');
|
||||
listenForEvents() {
|
||||
var handleEvent = this.handleEvent.bind(this);
|
||||
// Listen for changes in the accent color and border radius
|
||||
for (let pref of kZenThemePrefsList) {
|
||||
Services.prefs.addObserver(pref, handleEvent);
|
||||
}
|
||||
} else {
|
||||
// Use the overridden value
|
||||
document.documentElement.style.setProperty('--zen-border-radius', borderRadius + 'px');
|
||||
}
|
||||
},
|
||||
|
||||
updateElementSeparation() {
|
||||
const kMinElementSeparation = 0.1; // in px
|
||||
let separation = this.elementSeparation;
|
||||
if (
|
||||
document.documentElement.hasAttribute('inFullscreen') &&
|
||||
window.gZenCompactModeManager?.preference &&
|
||||
!document.getElementById('tabbrowser-tabbox')?.hasAttribute('zen-split-view') &&
|
||||
Services.prefs.getBoolPref('zen.view.borderless-fullscreen', true)
|
||||
) {
|
||||
separation = 0;
|
||||
}
|
||||
// In order to still use it on fullscreen, even if it's 0px, add .1px (almost invisible)
|
||||
separation = Math.max(kMinElementSeparation, separation);
|
||||
document.documentElement.style.setProperty('--zen-element-separation', separation + 'px');
|
||||
if (separation == kMinElementSeparation) {
|
||||
document.documentElement.setAttribute('zen-no-padding', true);
|
||||
} else {
|
||||
document.documentElement.removeAttribute('zen-no-padding');
|
||||
}
|
||||
},
|
||||
// Add fullscreen listener to update the theme when going in and out of fullscreen
|
||||
const eventsForSeparation = [
|
||||
'ZenViewSplitter:SplitViewDeactivated',
|
||||
'ZenViewSplitter:SplitViewActivated',
|
||||
'fullscreen',
|
||||
'ZenCompactMode:Toggled',
|
||||
];
|
||||
const separationHandler = this.updateElementSeparation.bind(this);
|
||||
for (let eventName of eventsForSeparation) {
|
||||
window.addEventListener(eventName, separationHandler);
|
||||
}
|
||||
|
||||
get elementSeparation() {
|
||||
return Math.min(
|
||||
Services.prefs.getIntPref('zen.theme.content-element-separation'),
|
||||
kZenMaxElementSeparation
|
||||
);
|
||||
},
|
||||
window.addEventListener(
|
||||
'unload',
|
||||
() => {
|
||||
for (let pref of kZenThemePrefsList) {
|
||||
Services.prefs.removeObserver(pref, handleEvent);
|
||||
}
|
||||
for (let eventName of eventsForSeparation) {
|
||||
window.removeEventListener(eventName, separationHandler);
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the accent color.
|
||||
*/
|
||||
updateAccentColor() {
|
||||
const accentColor = Services.prefs.getStringPref('zen.theme.accent-color');
|
||||
document.documentElement.style.setProperty('--zen-primary-color', accentColor);
|
||||
},
|
||||
};
|
||||
handleEvent() {
|
||||
// note: even might be undefined, but we shoudnt use it!
|
||||
this.updateAllThemeBasics();
|
||||
},
|
||||
|
||||
if (typeof Services !== 'undefined') ZenThemeModifier.init();
|
||||
/**
|
||||
* Update all theme basics, like the accent color.
|
||||
*/
|
||||
async updateAllThemeBasics() {
|
||||
this.updateAccentColor();
|
||||
this.updateBorderRadius();
|
||||
this.updateElementSeparation();
|
||||
},
|
||||
|
||||
updateBorderRadius() {
|
||||
const borderRadius = Services.prefs.getIntPref('zen.theme.border-radius', -1);
|
||||
|
||||
// -1 is the default value, will use platform-native values
|
||||
// otherwise, use the custom value
|
||||
if (borderRadius == -1) {
|
||||
if (AppConstants.platform == 'macosx') {
|
||||
const targetRadius = window.matchMedia('(-moz-mac-tahoe-theme)').matches ? 15 : 10;
|
||||
document.documentElement.style.setProperty('--zen-border-radius', targetRadius + 'px');
|
||||
} else if (AppConstants.platform == 'linux') {
|
||||
// Linux uses GTK CSD titlebar radius, default to 8px
|
||||
document.documentElement.style.setProperty(
|
||||
'--zen-border-radius',
|
||||
'env(-moz-gtk-csd-titlebar-radius, 8px)'
|
||||
);
|
||||
} else {
|
||||
// Windows defaults to 8px
|
||||
document.documentElement.style.setProperty('--zen-border-radius', '8px');
|
||||
}
|
||||
} else {
|
||||
// Use the overridden value
|
||||
document.documentElement.style.setProperty('--zen-border-radius', borderRadius + 'px');
|
||||
}
|
||||
},
|
||||
|
||||
updateElementSeparation() {
|
||||
const kMinElementSeparation = 0.1; // in px
|
||||
let separation = this.elementSeparation;
|
||||
if (
|
||||
document.documentElement.hasAttribute('inFullscreen') &&
|
||||
window.gZenCompactModeManager?.preference &&
|
||||
!document.getElementById('tabbrowser-tabbox')?.hasAttribute('zen-split-view') &&
|
||||
Services.prefs.getBoolPref('zen.view.borderless-fullscreen', true)
|
||||
) {
|
||||
separation = 0;
|
||||
}
|
||||
// In order to still use it on fullscreen, even if it's 0px, add .1px (almost invisible)
|
||||
separation = Math.max(kMinElementSeparation, separation);
|
||||
document.documentElement.style.setProperty('--zen-element-separation', separation + 'px');
|
||||
if (separation == kMinElementSeparation) {
|
||||
document.documentElement.setAttribute('zen-no-padding', true);
|
||||
} else {
|
||||
document.documentElement.removeAttribute('zen-no-padding');
|
||||
}
|
||||
},
|
||||
|
||||
get elementSeparation() {
|
||||
return Math.min(
|
||||
Services.prefs.getIntPref('zen.theme.content-element-separation'),
|
||||
kZenMaxElementSeparation
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the accent color.
|
||||
*/
|
||||
updateAccentColor() {
|
||||
const accentColor = Services.prefs.getStringPref('zen.theme.accent-color');
|
||||
document.documentElement.style.setProperty('--zen-primary-color', accentColor);
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof Services !== 'undefined') ZenThemeModifier.init();
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ category browser-before-ui-startup resource:///modules/zen/ZenSessionManager.sys
|
||||
category browser-before-ui-startup resource:///modules/zen/ZenWindowSync.sys.mjs ZenWindowSync.init
|
||||
|
||||
# App shutdown consumers
|
||||
category browser-quit-application-granted resource:///modules/zen/ZenSessionManager.sys.mjs ZenSessionStore.uninit
|
||||
category browser-quit-application-granted resource:///modules/zen/ZenWindowSync.sys.mjs ZenWindowSync.uninit
|
||||
|
||||
@@ -15,9 +15,16 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
SessionSaver: 'resource:///modules/sessionstore/SessionSaver.sys.mjs',
|
||||
setTimeout: 'resource://gre/modules/Timer.sys.mjs',
|
||||
gWindowSyncEnabled: 'resource:///modules/zen/ZenWindowSync.sys.mjs',
|
||||
DeferredTask: 'resource://gre/modules/DeferredTask.sys.mjs',
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(lazy, 'gShouldLog', 'zen.session-store.log', true);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
'gMaxSessionBackups',
|
||||
'zen.session-store.max-backups',
|
||||
10
|
||||
);
|
||||
|
||||
// Note that changing this hidden pref will make the previous session file
|
||||
// unused, causing a new session file to be created on next write.
|
||||
@@ -30,6 +37,10 @@ const MIGRATION_PREF = 'zen.ui.migration.session-manager-restore';
|
||||
// 'browser.startup.page' preference value to resume the previous session.
|
||||
const BROWSER_STARTUP_RESUME_SESSION = 3;
|
||||
|
||||
// The amount of time (in milliseconds) to wait for our backup regeneration
|
||||
// debouncer to kick off a regeneration.
|
||||
const REGENERATION_DEBOUNCE_RATE_MS = 20 * 60 * 1000; // 20 minutes
|
||||
|
||||
/**
|
||||
* Class representing the sidebar object stored in the session file.
|
||||
* This object holds all the data related to tabs, groups, folders
|
||||
@@ -58,13 +69,17 @@ export class nsZenSessionManager {
|
||||
* @type {nsZenSidebarObject}
|
||||
*/
|
||||
#sidebarObject = new nsZenSidebarObject();
|
||||
/**
|
||||
* A deferred task to create backups of the session file.
|
||||
*/
|
||||
#deferredBackupTask = null;
|
||||
|
||||
// Called from SessionComponents.manifest on app-startup
|
||||
init() {
|
||||
let profileDir = Services.dirsvc.get('ProfD', Ci.nsIFile).path;
|
||||
let backupFile = null;
|
||||
if (SHOULD_BACKUP_FILE) {
|
||||
backupFile = PathUtils.join(profileDir, 'zen-sessions-backup', FILE_NAME);
|
||||
backupFile = PathUtils.join(this.#backupFolderPath, FILE_NAME);
|
||||
}
|
||||
let filePath = PathUtils.join(profileDir, FILE_NAME);
|
||||
this.#file = new JSONFile({
|
||||
@@ -72,6 +87,15 @@ export class nsZenSessionManager {
|
||||
compression: SHOULD_COMPRESS_FILE ? 'lz4' : undefined,
|
||||
backupFile,
|
||||
});
|
||||
this.#deferredBackupTask = new lazy.DeferredTask(async () => {
|
||||
await this.#createBackupsIfNeeded();
|
||||
}, REGENERATION_DEBOUNCE_RATE_MS);
|
||||
}
|
||||
|
||||
uninit() {
|
||||
this.#file = null;
|
||||
this.#deferredBackupTask?.disarm();
|
||||
this.#deferredBackupTask = null;
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
@@ -80,6 +104,11 @@ export class nsZenSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
get #backupFolderPath() {
|
||||
let profileDir = Services.dirsvc.get('ProfD', Ci.nsIFile).path;
|
||||
return PathUtils.join(profileDir, 'zen-sessions-backup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spaces data from the Places database for migration.
|
||||
* This is only called once during the first run after updating
|
||||
@@ -244,9 +273,66 @@ export class nsZenSessionManager {
|
||||
// quitting the app.
|
||||
this.#file.data = this.#sidebar;
|
||||
this.#file.saveSoon();
|
||||
this.#debounceRegeneration();
|
||||
this.log(`Saving Zen session data with ${this.#sidebar.tabs?.length || 0} tabs`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the last known backup should be deleted and a new one
|
||||
* created. This uses the #deferredBackupTask to debounce clusters of
|
||||
* events that might cause such a regeneration to occur.
|
||||
*/
|
||||
#debounceRegeneration() {
|
||||
this.#deferredBackupTask.disarm();
|
||||
this.#deferredBackupTask.arm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates backups of the session file if needed. We only keep
|
||||
* a limited number of backups to avoid using too much disk space.
|
||||
* The way we are doing this is by replacing the file for today's
|
||||
* date if it already exists, otherwise we create a new one.
|
||||
* We then delete the oldest backups if we exceed the maximum
|
||||
* number of backups allowed.
|
||||
*
|
||||
* We run the next backup creation after a delay or when idling,
|
||||
* to avoid blocking the main thread during session saves.
|
||||
*/
|
||||
async #createBackupsIfNeeded() {
|
||||
if (!SHOULD_BACKUP_FILE) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const today = new Date();
|
||||
const backupFolder = this.#backupFolderPath;
|
||||
await IOUtils.makeDirectory(backupFolder, {
|
||||
ignoreExisting: true,
|
||||
createAncestors: true,
|
||||
});
|
||||
const todayFileName = `zen-sessions-${today.getFullYear()}-${(today.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}.json${
|
||||
SHOULD_COMPRESS_FILE ? 'lz4' : ''
|
||||
}`;
|
||||
const todayFilePath = PathUtils.join(backupFolder, todayFileName);
|
||||
const sessionFilePath = this.#file.path;
|
||||
this.log(`Backing up session file to ${todayFileName}`);
|
||||
await IOUtils.copy(sessionFilePath, todayFilePath, { noOverwrite: false });
|
||||
// Now we need to check if we have exceeded the maximum
|
||||
// number of backups allowed, and delete the oldest ones
|
||||
// if needed.
|
||||
let files = await IOUtils.getChildren(backupFolder);
|
||||
files = files.filter((file) => file.startsWith('zen-sessions-')).sort();
|
||||
for (let i = 0; i < files.length - lazy.gMaxSessionBackups; i++) {
|
||||
const fileToDelete = PathUtils.join(backupFolder, files[i].name);
|
||||
this.log(`Deleting old backup file ${files[i].name}`);
|
||||
await IOUtils.remove(fileToDelete);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('ZenSessionManager: Failed to create session file backups', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the session data for a closed window if it meets the criteria.
|
||||
* See SessionStoreInternal.maybeSaveClosedWindow for more details.
|
||||
|
||||
Reference in New Issue
Block a user