tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit e567fc04567b9f5115f0fb7f75d4bf34a3d1ceac
parent a69b0adb399ce162b471b284e3010758f3d146b6
Author: Harsheet <hsohaney@mozilla.com>
Date:   Sun, 16 Nov 2025 21:14:13 +0000

Bug 1999786 - Save a persistent state between screens for the backup spotlight. r=cdupuis,omc-reviewers,emcminn,sthompson

Differential Revision: https://phabricator.services.mozilla.com/D272543

Diffstat:
Mbrowser/components/DesktopActorRegistry.sys.mjs | 4++++
Mbrowser/components/backup/BackupService.sys.mjs | 18++++++++++++++++++
Mbrowser/components/backup/actors/BackupUIChild.sys.mjs | 4++++
Mbrowser/components/backup/content/turn-on-scheduled-backups.mjs | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mbrowser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js | 192++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
5 files changed, 261 insertions(+), 26 deletions(-)

diff --git a/browser/components/DesktopActorRegistry.sys.mjs b/browser/components/DesktopActorRegistry.sys.mjs @@ -239,6 +239,10 @@ let JSWINDOWACTORS = { "BackupUI:ShowBackupLocation": { wantUntrusted: true }, "BackupUI:EditBackupLocation": { wantUntrusted: true }, "BackupUI:ErrorBarDismissed": { wantUntrusted: true }, + "BackupUI:SetEmbeddedComponentPersistentData": { wantUntrusted: true }, + "BackupUI:FlushEmbeddedComponentPersistentData": { + wantUntrusted: true, + }, }, }, includeChrome: true, diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -775,6 +775,19 @@ export class BackupService extends EventTarget { } /** + * Sets the persisted options between screens for embedded components. + * This is specifically used in the Spotlight onboarding experience. + * + * This data is flushed upon creating a backup or exiting the backup flow. + * + * @param {object} data - data to persist between screens. + */ + setEmbeddedComponentPersistentData(data) { + this.#_state.embeddedComponentPersistentData = { ...data }; + this.stateUpdate(); + } + + /** * An object holding the current state of the BackupService instance, for * the purposes of representing it in the user interface. Ideally, this would * be named #state instead of #_state, but sphinx-js seems to be fairly @@ -807,6 +820,8 @@ export class BackupService extends EventTarget { backupErrorCode: lazy.backupErrorCode, archiveEnabledStatus: this.archiveEnabledStatus.enabled, restoreEnabledStatus: this.restoreEnabledStatus.enabled, + /** Utilized by the spotlight to persist information between screens */ + embeddedComponentPersistentData: {}, }; /** @@ -3591,6 +3606,9 @@ export class BackupService extends EventTarget { if (shouldEnableScheduledBackups) { // reset the error states when reenabling backup Services.prefs.setIntPref(BACKUP_ERROR_CODE_PREF_NAME, ERRORS.NONE); + + // flush the embedded component's persistent data + this.setEmbeddedComponentPersistentData({}); } else { // set user-disabled pref if backup is being disabled Services.prefs.setBoolPref( diff --git a/browser/components/backup/actors/BackupUIChild.sys.mjs b/browser/components/backup/actors/BackupUIChild.sys.mjs @@ -140,6 +140,10 @@ export class BackupUIChild extends JSWindowActorChild { this.sendAsyncMessage("EditBackupLocation"); } else if (event.type == "BackupUI:ErrorBarDismissed") { this.sendAsyncMessage("ErrorBarDismissed"); + } else if (event.type == "BackupUI:SetEmbeddedComponentPersistentData") { + this.sendAsyncMessage("SetEmbeddedComponentPersistentData", event.detail); + } else if (event.type == "BackupUI:FlushEmbeddedComponentPersistentData") { + this.sendAsyncMessage("FlushEmbeddedComponentPersistentData"); } } diff --git a/browser/components/backup/content/turn-on-scheduled-backups.mjs b/browser/components/backup/content/turn-on-scheduled-backups.mjs @@ -151,6 +151,20 @@ export default class TurnOnScheduledBackups extends MozLitElement { this._newPath = path; this._newLabel = filename; this._newIconURL = iconURL; + + if (this.embeddedFxBackupOptIn) { + // Let's set a persistent path + this.dispatchEvent( + new CustomEvent("BackupUI:SetEmbeddedComponentPersistentData", { + bubbles: true, + detail: { + path, + label: filename, + iconURL, + }, + }) + ); + } } else if (event.type == "ValidPasswordsDetected") { let { password } = event.detail; this._passwordsMatch = true; @@ -199,17 +213,22 @@ export default class TurnOnScheduledBackups extends MozLitElement { detail.password = this._inputPassValue; } - if ( - this.embeddedFxBackupOptIn && - this.backupIsEncrypted && - !detail.password - ) { - // We're in the embedded component and we haven't set a password yet when - // one is expected, let's not do a confirm action yet! - this.dispatchEvent( - new CustomEvent("SpotlightOnboardingAdvanceScreens", { bubbles: true }) - ); - return; + if (this.embeddedFxBackupOptIn && this.backupIsEncrypted) { + if (!detail.password) { + // We're in the embedded component and we haven't set a password yet + // when one is expected, let's not do a confirm action yet! + this.dispatchEvent( + new CustomEvent("SpotlightOnboardingAdvanceScreens", { + bubbles: true, + }) + ); + return; + } + + // The persistent data will take precedence over the default path + detail.parentDirPath = + this.backupServiceState?.embeddedComponentPersistentData?.path || + detail.parentDirPath; } this.dispatchEvent( @@ -258,6 +277,17 @@ export default class TurnOnScheduledBackups extends MozLitElement { const passwordElement = this.passwordOptionsExpandedEl; passwordElement.reset(); } + + if ( + this.embeddedFxBackupOptIn && + this.backupServiceState?.embeddedComponentPersistentData + ) { + this.dispatchEvent( + new CustomEvent("BackupUI:FlushEmbeddedComponentPersistentData", { + bubbles: true, + }) + ); + } } defaultFilePathInputTemplate() { @@ -285,9 +315,19 @@ export default class TurnOnScheduledBackups extends MozLitElement { `; } + /** + * Note: We also consider the embeddedComponentPersistentData since we might be in the + * Spotlight where we need this persistent data between screens. This state property should + * not be set if we are not in the Spotlight. + */ customFilePathInputTemplate() { - let filename = this._newLabel; - let iconURL = this._newIconURL || this.#placeholderIconURL; + let filename = + this._newLabel || + this.backupServiceState?.embeddedComponentPersistentData?.label; + let iconURL = + this._newIconURL || + this.backupServiceState?.embeddedComponentPersistentData?.iconURL || + this.#placeholderIconURL; return html` <input @@ -322,7 +362,8 @@ export default class TurnOnScheduledBackups extends MozLitElement { "turn-on-scheduled-backups-location-label"} ></label> <div id="backup-location-filepicker"> - ${!this._newPath + ${!this._newPath && + !this.backupServiceState?.embeddedComponentPersistentData?.path ? this.defaultFilePathInputTemplate() : this.customFilePathInputTemplate()} <moz-button diff --git a/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js b/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js @@ -9,6 +9,22 @@ const { ERRORS } = ChromeUtils.importESModule( const SCHEDULED_BACKUPS_ENABLED_PREF = "browser.backup.scheduled.enabled"; +async function setup_mockFilePicker(mockParentDir) { + const dummyFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + + dummyFile.initWithPath(mockParentDir); + let filePickerShownPromise = new Promise(resolve => { + MockFilePicker.showCallback = () => { + Assert.ok(true, "Filepicker shown"); + MockFilePicker.setFiles([dummyFile]); + resolve(); + }; + }); + MockFilePicker.returnValue = MockFilePicker.returnOK; + + return { filePickerShownPromise }; +} + add_setup(async () => { MockFilePicker.init(window.browsingContext); registerCleanupFunction(() => { @@ -96,19 +112,9 @@ add_task(async function test_turn_on_custom_location_filepicker() { PathUtils.tempDir, "settings-custom-dir-test" ); - const dummyFile = Cc["@mozilla.org/file/local;1"].createInstance( - Ci.nsIFile - ); - dummyFile.initWithPath(mockCustomParentDir); - let filePickerShownPromise = new Promise(resolve => { - MockFilePicker.showCallback = () => { - Assert.ok(true, "Filepicker shown"); - MockFilePicker.setFiles([dummyFile]); - resolve(); - }; - }); - MockFilePicker.returnValue = MockFilePicker.returnOK; + let { filePickerShownPromise } = + await setup_mockFilePicker(mockCustomParentDir); // After setting up mocks, start testing components /** @type {import("../../content/backup-settings.mjs").default} */ @@ -549,3 +555,165 @@ add_task(async function test_default_location_selected() { await SpecialPowers.popPrefEnv(); }); + +/** + * Tests that the persistent data for embedded components is set when a user picks a file + * and is flushed once backup is enabled. + */ +add_task(async function test_embedded_component_persistent_data_filepicker() { + await SpecialPowers.pushPrefEnv({ + set: [[SCHEDULED_BACKUPS_ENABLED_PREF, false]], + }); + + await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + const mockCustomParentDir = await IOUtils.createUniqueDirectory( + PathUtils.tempDir, + "our-dummy-folder" + ); + let { filePickerShownPromise } = + await setup_mockFilePicker(mockCustomParentDir); + + let settings = browser.contentDocument.querySelector("backup-settings"); + let turnOnButton = settings.scheduledBackupsButtonEl; + + Assert.ok( + turnOnButton, + "Button to turn on scheduled backups should be found" + ); + + turnOnButton.click(); + + await settings.updateComplete; + let turnOnScheduledBackups = settings.turnOnScheduledBackupsEl; + + // for the purposes of this test, we will act like we are an embedded component + turnOnScheduledBackups.embeddedFxBackupOptIn = true; + + // First verify the default input value and dir path button + let filePathButton = turnOnScheduledBackups.filePathButtonEl; + let stateUpdatePromise = BrowserTestUtils.waitForEvent( + window, + "BackupUI:StateWasUpdated" + ); + Assert.ok( + filePathButton, + "Button for choosing a file path should be found" + ); + filePathButton.click(); + + await filePickerShownPromise; + await stateUpdatePromise; + await turnOnScheduledBackups.updateComplete; + + Assert.equal( + settings.backupServiceState.embeddedComponentPersistentData.path, + mockCustomParentDir, + "Our persistent path should be set correctly" + ); + + // Let's create a backup + let confirmButton = turnOnScheduledBackups.confirmButtonEl; + let promise = BrowserTestUtils.waitForEvent( + window, + "BackupUI:EnableScheduledBackups" + ); + + Assert.ok(confirmButton, "Confirm button should be found"); + + confirmButton.click(); + + await promise; + await settings.updateComplete; + }); + + await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + let settings = browser.contentDocument.querySelector("backup-settings"); + await settings.updateComplete; + + Assert.deepEqual( + settings.backupServiceState.embeddedComponentPersistentData, + {}, + "Our persistent path should be flushed" + ); + }); + + await SpecialPowers.popPrefEnv(); +}); + +/** + * Tests that the persistent data for embedded components is flushed if the dialog is cancelled + */ +add_task( + async function test_embedded_component_persistent_data_filepicker_cancelled() { + await SpecialPowers.pushPrefEnv({ + set: [[SCHEDULED_BACKUPS_ENABLED_PREF, false]], + }); + + await BrowserTestUtils.withNewTab( + "about:preferences#sync", + async browser => { + const mockCustomParentDir = await IOUtils.createUniqueDirectory( + PathUtils.tempDir, + "our-dummy-folder" + ); + let { filePickerShownPromise } = + await setup_mockFilePicker(mockCustomParentDir); + + let settings = browser.contentDocument.querySelector("backup-settings"); + let turnOnButton = settings.scheduledBackupsButtonEl; + + Assert.ok( + turnOnButton, + "Button to turn on scheduled backups should be found" + ); + + turnOnButton.click(); + + await settings.updateComplete; + let turnOnScheduledBackups = settings.turnOnScheduledBackupsEl; + + // for the purposes of this test, we will act like we are an embedded component + turnOnScheduledBackups.embeddedFxBackupOptIn = true; + + // First verify the default input value and dir path button + let filePathButton = turnOnScheduledBackups.filePathButtonEl; + const waitForStateUpdate = () => + BrowserTestUtils.waitForEvent(window, "BackupUI:StateWasUpdated"); + + let stateUpdatePromise = waitForStateUpdate(); + + Assert.ok( + filePathButton, + "Button for choosing a file path should be found" + ); + filePathButton.click(); + + await filePickerShownPromise; + await stateUpdatePromise; + await turnOnScheduledBackups.updateComplete; + + Assert.equal( + settings.backupServiceState.embeddedComponentPersistentData.path, + mockCustomParentDir, + "Our persistent path should be set correctly" + ); + + stateUpdatePromise = waitForStateUpdate(); + + let dialog = settings.turnOnScheduledBackupsDialogEl; + let closedPromise = BrowserTestUtils.waitForEvent(dialog, "close"); + dialog.close(); + await closedPromise; + await stateUpdatePromise; + + Assert.deepEqual( + settings.backupServiceState.embeddedComponentPersistentData, + {}, + "Our persistent path should be flushed" + ); + } + ); + + await SpecialPowers.popPrefEnv(); + } +);