commit 10657554bc2492b3a866be5c3883b33cd04d0964
parent d4aa022380da7e3c96a25035d4dd3182a209ad02
Author: Duncan McIntosh <dmcintosh@mozilla.com>
Date: Wed, 29 Oct 2025 19:37:55 +0000
Bug 1992808 - Part 3: Differentiate between a backup scheduled before Firefox started and one scheduled after Firefox starts. r=cdupuis
Differential Revision: https://phabricator.services.mozilla.com/D270270
Diffstat:
3 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs
@@ -4059,16 +4059,24 @@ export class BackupService extends EventTarget {
now - lastBackupDate > lazy.minimumTimeBetweenBackupsSeconds
) {
lazy.logConsole.debug(
- "Last backup exceeded minimum time between backups. Queing a " +
+ "Last backup exceeded minimum time between backups. Queueing a " +
"backup via idleDispatch."
);
+
// Just because the user hasn't sent us events in a while doesn't mean
// that the browser itself isn't busy. It might be, for example, playing
// video or doing a complex calculation that the user is actively
// waiting to complete, and we don't want to draw resources from that.
// Instead, we'll use ChromeUtils.idleDispatch to wait until the event
// loop in the parent process isn't so busy with higher priority things.
- this.createBackupOnIdleDispatch();
+ let expectedBackupTime =
+ lastBackupDate + lazy.minimumTimeBetweenBackupsSeconds;
+ this.createBackupOnIdleDispatch({
+ reason:
+ expectedBackupTime < this._startupTimeUnixSeconds
+ ? "missed"
+ : "idle",
+ });
} else {
lazy.logConsole.debug(
"Last backup was too recent. Not creating one for now."
@@ -4078,6 +4086,16 @@ export class BackupService extends EventTarget {
}
/**
+ * Gets the time that Firefox started as milliseconds since the Unix epoch.
+ *
+ * This is in a getter to make it easier for tests to stub it out.
+ */
+ get _startupTimeUnixSeconds() {
+ let startupTimeMs = Services.startup.getStartupInfo().process.getTime();
+ return Math.floor(startupTimeMs / 1000);
+ }
+
+ /**
* Calls BackupService.createBackup at the next moment when the event queue
* is not busy with higher priority events. This is intentionally broken out
* into its own method to make it easier to stub out in tests.
diff --git a/browser/components/backup/metrics.yaml b/browser/components/backup/metrics.yaml
@@ -650,10 +650,9 @@ browser.backup:
The reason that the backup was started. Can be one of the following:
- "manual" if the user selected "Backup Now";
- "idle" if the user was idle and sufficiently long passed since
- the last backup (not implemented yet, will be in Part 3);
+ the last backup;
- "missed" if the user was idle, sufficiently long has passed since
- the last backup, and this backup was scheduled to happen before
- Firefox opened (not implemented yet, will be in Part 3);
+ the last backup, and this backup was scheduled to happen before;
- "user deleted some data" if cookies or other user data was
deleted;
- "encryption" if the password was added, changed, or removed;
diff --git a/browser/components/backup/tests/xpcshell/test_BackupService_scheduler.js b/browser/components/backup/tests/xpcshell/test_BackupService_scheduler.js
@@ -42,6 +42,11 @@ add_setup(() => {
MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME
);
});
+
+ // Set the startup time to zero so we only need to worry about it in its
+ // test. (Approximately, make sure Firefox starts 'long before' each test
+ // runs.)
+ sinon.stub(BackupService.prototype, "_startupTimeUnixSeconds").get(() => 0);
});
/**
@@ -183,6 +188,11 @@ add_task(async function test_BackupService_idle_no_backup_exists() {
bs.createBackupOnIdleDispatch.calledOnce,
"BackupService.createBackupOnIdleDispatch was called."
);
+ Assert.equal(
+ bs.createBackupOnIdleDispatch.firstCall.args[0].reason,
+ "idle",
+ "Recorded that the backup was caused by being idle."
+ );
sandbox.restore();
});
@@ -251,6 +261,11 @@ add_task(async function test_BackupService_idle_expired_backup() {
bs.createBackupOnIdleDispatch.calledOnce,
"BackupService.createBackupOnIdleDispatch was called."
);
+ Assert.equal(
+ bs.createBackupOnIdleDispatch.firstCall.args[0].reason,
+ "idle",
+ "Recorded that the backup was caused by being idle."
+ );
sandbox.restore();
});
@@ -287,6 +302,11 @@ add_task(async function test_BackupService_idle_time_travel() {
"BackupService.createBackupOnIdleDispatch was called."
);
Assert.equal(
+ bs.createBackupOnIdleDispatch.firstCall.args[0].reason,
+ "idle",
+ "Recorded that the backup was caused by being idle."
+ );
+ Assert.equal(
bs.state.lastBackupDate,
null,
"Should have cleared the last backup date."
@@ -294,3 +314,50 @@ add_task(async function test_BackupService_idle_time_travel() {
sandbox.restore();
});
+
+/**
+ * Tests that calling onIdle when a backup has occurred after the threshold
+ * yet before Firefox started indicates to telemetry that the backup was
+ * missed.
+ */
+add_task(async function test_BackupService_idle_expired_backup() {
+ // Let's calculate a Date that's twenty five seconds ago.
+ let twentyFiveSecondsAgo = Date.now() - 25000;
+ let lastBackupPrefValue = Math.floor(twentyFiveSecondsAgo / 1000);
+
+ Services.prefs.setIntPref(
+ LAST_BACKUP_TIMESTAMP_PREF_NAME,
+ lastBackupPrefValue
+ );
+
+ let bs = new BackupService();
+ let sandbox = sinon.createSandbox();
+
+ // This needs to be greater than
+ // LAST_BACKUP_TIMESTAMP_PREF_NAME + MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME
+ // and less than the current time.
+ let twentySecondsAgo = Math.floor(twentyFiveSecondsAgo + 21);
+ sandbox.stub(bs, "_startupTimeUnixSeconds").get(() => twentySecondsAgo);
+
+ bs.initBackupScheduler();
+ Assert.equal(
+ bs.state.lastBackupDate,
+ lastBackupPrefValue,
+ "State should have cached lastBackupDate"
+ );
+
+ sandbox.stub(bs, "createBackupOnIdleDispatch");
+
+ bs.onIdle();
+ Assert.ok(
+ bs.createBackupOnIdleDispatch.calledOnce,
+ "BackupService.createBackupOnIdleDispatch was called."
+ );
+ Assert.equal(
+ bs.createBackupOnIdleDispatch.firstCall.args[0].reason,
+ "missed",
+ "Recorded that the backup was caused by missing the deadline."
+ );
+
+ sandbox.restore();
+});