Bug 1895943 - Implement file picker and save location for backups. r=backup-reviewers,mconley,fluent-reviewers,flod,firefox-desktop-core-reviewers
1. Allows for selecting a custom path or the default (Documents) path for saving backups. The selection is passed from the "turn-on-scheduled-backups" dialog to "BackupService". 2. After pressing the "Choose" button in the dialog, a filepicker will appear so that a folder can be selected. 3. Once the dialog is confirmed, the absolute path is saved to a pref called "browser.backup.location" and saved in the service state. Other changes: - Added the Documents folder as the default save location - Added an onUpdate function for "browser.backup.location" that passes the updated BackupService state to registered widgets (backup settings section, dialogs) - Added Storybook entries and tests for the newly updated input and filepicker Figma: https://www.figma.com/design/vNbX4c0ws0L1qr0mxpKvsW/Fx-Backup?node-id=147-4568&t=tILUMKfg8c6Ed1Ul-0 (turn on backup dialog) Differential Revision: https://phabricator.services.mozilla.com/D210850
This commit is contained in:
@@ -442,6 +442,7 @@ let JSWINDOWACTORS = {
|
||||
events: {
|
||||
"BackupUI:InitWidget": { wantUntrusted: true },
|
||||
"BackupUI:ToggleScheduledBackups": { wantUntrusted: true },
|
||||
"BackupUI:ShowFilepicker": { wantUntrusted: true },
|
||||
},
|
||||
},
|
||||
matches: ["about:preferences*", "about:settings*"],
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as DefaultBackupResources from "resource:///modules/backup/BackupResour
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const BACKUP_DIR_PREF_NAME = "browser.backup.location";
|
||||
const SCHEDULED_BACKUPS_ENABLED_PREF_NAME = "browser.backup.scheduled.enabled";
|
||||
const lazy = {};
|
||||
|
||||
@@ -37,6 +38,13 @@ ChromeUtils.defineLazyGetter(lazy, "ZipWriter", () =>
|
||||
Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter", "open")
|
||||
);
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () {
|
||||
return new Localization(
|
||||
["branding/brand.ftl", "preview/backupSettings.ftl"],
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"scheduledBackupsPref",
|
||||
@@ -50,6 +58,20 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
}
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"backupDirPref",
|
||||
BACKUP_DIR_PREF_NAME,
|
||||
Services.dirsvc.get("Docs", Ci.nsIFile)
|
||||
.path /* Default directory is Documents */,
|
||||
async function onUpdateLocationDirPath(_pref, _prevVal, newVal) {
|
||||
let bs = BackupService.get();
|
||||
if (bs) {
|
||||
await bs.onUpdateLocationDirPath(newVal);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* The BackupService class orchestrates the scheduling and creation of profile
|
||||
* backups. It also does most of the heavy lifting for the restoration of a
|
||||
@@ -71,6 +93,13 @@ export class BackupService extends EventTarget {
|
||||
*/
|
||||
#resources = new Map();
|
||||
|
||||
/**
|
||||
* The name of the backup folder. Should be localized.
|
||||
*
|
||||
* @see BACKUP_DIR_NAME
|
||||
*/
|
||||
static #backupFolderName = null;
|
||||
|
||||
/**
|
||||
* Set to true if a backup is currently in progress. Causes stateUpdate()
|
||||
* to be called.
|
||||
@@ -112,7 +141,12 @@ export class BackupService extends EventTarget {
|
||||
* @type {object}
|
||||
*/
|
||||
#_state = {
|
||||
backupFilePath: "Documents", // TODO: make save location configurable (bug 1895943)
|
||||
backupDirPath: lazy.backupDirPref,
|
||||
defaultParent: {
|
||||
path: BackupService.DEFAULT_PARENT_DIR_PATH,
|
||||
fileName: PathUtils.filename(BackupService.DEFAULT_PARENT_DIR_PATH),
|
||||
iconURL: this.getIconFromFilePath(BackupService.DEFAULT_PARENT_DIR_PATH),
|
||||
},
|
||||
backupInProgress: false,
|
||||
scheduledBackupsEnabled: lazy.scheduledBackupsPref,
|
||||
encryptionEnabled: false,
|
||||
@@ -152,6 +186,29 @@ export class BackupService extends EventTarget {
|
||||
*/
|
||||
#encState = undefined;
|
||||
|
||||
/**
|
||||
* The path of the default parent directory for saving backups.
|
||||
* The current default is the Documents directory.
|
||||
*
|
||||
* @returns {string} The path of the default parent directory
|
||||
*/
|
||||
static get DEFAULT_PARENT_DIR_PATH() {
|
||||
return Services.dirsvc.get("Docs", Ci.nsIFile).path;
|
||||
}
|
||||
|
||||
/**
|
||||
* The localized name for the user's backup folder.
|
||||
*
|
||||
* @returns {string} The localized backup folder name
|
||||
*/
|
||||
static get BACKUP_DIR_NAME() {
|
||||
if (!BackupService.#backupFolderName) {
|
||||
BackupService.#backupFolderName =
|
||||
lazy.gFluentStrings.formatValueSync("backup-folder-name");
|
||||
}
|
||||
return BackupService.#backupFolderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the folder within the profile folder where this service reads
|
||||
* and writes state to.
|
||||
@@ -924,6 +981,65 @@ export class BackupService extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent directory of the backups folder. Calling this function will update
|
||||
* browser.backup.location.
|
||||
*
|
||||
* @param {string} parentDirPath directory path
|
||||
*/
|
||||
setParentDirPath(parentDirPath) {
|
||||
try {
|
||||
if (!parentDirPath || !PathUtils.filename(parentDirPath)) {
|
||||
throw new Error("Parent directory path is invalid.");
|
||||
}
|
||||
// Recreate the backups path with the new parent directory.
|
||||
let fullPath = PathUtils.join(
|
||||
parentDirPath,
|
||||
BackupService.BACKUP_DIR_NAME
|
||||
);
|
||||
Services.prefs.setStringPref(BACKUP_DIR_PREF_NAME, fullPath);
|
||||
} catch (e) {
|
||||
lazy.logConsole.error(
|
||||
`Failed to set parent directory ${parentDirPath}. ${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates backupDirPath in the backup service state. Should be called every time the value
|
||||
* for browser.backup.location changes.
|
||||
*
|
||||
* @param {string} newDirPath the new directory path for storing backups
|
||||
*/
|
||||
async onUpdateLocationDirPath(newDirPath) {
|
||||
lazy.logConsole.debug(`Updating backup location to ${newDirPath}`);
|
||||
|
||||
this.#_state.backupDirPath = newDirPath;
|
||||
this.stateUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the moz-icon URL of a file. To get the moz-icon URL, the
|
||||
* file path is convered to a fileURI. If there is a problem retreiving
|
||||
* the moz-icon due to an invalid file path, return null instead.
|
||||
*
|
||||
* @param {string} path Path of the file to read its icon from.
|
||||
* @returns {string|null} The moz-icon URL of the specified file, or
|
||||
* null if the icon cannot be retreived.
|
||||
*/
|
||||
getIconFromFilePath(path) {
|
||||
if (!path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
let fileURI = PathUtils.toFileURI(path);
|
||||
return `moz-icon:${fileURI}?size=16`;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets browser.backup.scheduled.enabled to true or false.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ export class BackupUIChild extends JSWindowActorChild {
|
||||
* @param {Event} event
|
||||
* The custom event that the widget fired.
|
||||
*/
|
||||
handleEvent(event) {
|
||||
async handleEvent(event) {
|
||||
/**
|
||||
* BackupUI:InitWidget sends a message to the parent to request the BackupService state
|
||||
* which will result in a `backupServiceState` property of the widget to be set when that
|
||||
@@ -31,6 +31,31 @@ export class BackupUIChild extends JSWindowActorChild {
|
||||
this.sendAsyncMessage("RequestState");
|
||||
} else if (event.type == "BackupUI:ToggleScheduledBackups") {
|
||||
this.sendAsyncMessage("ToggleScheduledBackups", event.detail);
|
||||
} else if (event.type == "BackupUI:ShowFilepicker") {
|
||||
let targetNodeName = event.target.nodeName;
|
||||
let { path, filename, iconURL } = await this.sendQuery("ShowFilepicker", {
|
||||
win: event.detail?.win,
|
||||
});
|
||||
|
||||
let widgets = ChromeUtils.nondeterministicGetWeakSetKeys(
|
||||
this.#inittedWidgets
|
||||
);
|
||||
|
||||
for (let widget of widgets) {
|
||||
if (widget.isConnected && widget.nodeName == targetNodeName) {
|
||||
widget.dispatchEvent(
|
||||
new CustomEvent("BackupUI:SelectNewFilepickerPath", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
path,
|
||||
filename,
|
||||
iconURL,
|
||||
},
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +66,49 @@ export class BackupUIParent extends JSWindowActorParent {
|
||||
* @param {ReceiveMessageArgument} message
|
||||
* The message received from the BackupUIChild.
|
||||
*/
|
||||
receiveMessage(message) {
|
||||
async receiveMessage(message) {
|
||||
if (message.name == "RequestState") {
|
||||
this.sendState();
|
||||
} else if (message.name == "ToggleScheduledBackups") {
|
||||
this.#bs.setScheduledBackups(message.data?.isScheduledBackupsEnabled);
|
||||
let { isScheduledBackupsEnabled, parentDirPath } = message.data;
|
||||
|
||||
if (isScheduledBackupsEnabled && parentDirPath) {
|
||||
this.#bs.setParentDirPath(parentDirPath);
|
||||
}
|
||||
|
||||
this.#bs.setScheduledBackups(isScheduledBackupsEnabled);
|
||||
|
||||
return true;
|
||||
|
||||
/**
|
||||
* TODO: (Bug 1900125) we should create a backup at the specified dir path once we turn on
|
||||
* scheduled backups. The backup folder in the chosen directory should contain
|
||||
* the archive file, which we create using BackupService.createArchive implemented in
|
||||
* Bug 1897498.
|
||||
*/
|
||||
} else if (message.name == "ShowFilepicker") {
|
||||
let { win } = message.data;
|
||||
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(win, "", Ci.nsIFilePicker.modeGetFolder);
|
||||
let result = await new Promise(resolve => fp.open(resolve));
|
||||
|
||||
if (result === Ci.nsIFilePicker.returnCancel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = fp.file.path;
|
||||
let iconURL = this.#bs.getIconFromFilePath(path);
|
||||
let filename = PathUtils.filename(path);
|
||||
|
||||
return {
|
||||
path,
|
||||
filename,
|
||||
iconURL,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,8 +36,9 @@ export default class BackupSettings extends MozLitElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.backupServiceState = {
|
||||
backupFilePath: "Documents", // TODO: make save location configurable (bug 1895943)
|
||||
backupDirPath: "",
|
||||
backupInProgress: false,
|
||||
defaultParent: {},
|
||||
scheduledBackupsEnabled: false,
|
||||
};
|
||||
}
|
||||
@@ -66,6 +67,7 @@ export default class BackupSettings extends MozLitElement {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
...event.detail,
|
||||
isScheduledBackupsEnabled: true,
|
||||
},
|
||||
})
|
||||
@@ -108,9 +110,12 @@ export default class BackupSettings extends MozLitElement {
|
||||
}
|
||||
|
||||
turnOnScheduledBackupsDialogTemplate() {
|
||||
let { fileName, path, iconURL } = this.backupServiceState.defaultParent;
|
||||
return html`<dialog id="turn-on-scheduled-backups-dialog">
|
||||
<turn-on-scheduled-backups
|
||||
.backupFilePath=${this.backupServiceState.backupFilePath}
|
||||
defaultlabel=${fileName}
|
||||
defaultpath=${path}
|
||||
defaulticonurl=${iconURL}
|
||||
></turn-on-scheduled-backups>
|
||||
</dialog>`;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@ const Template = ({ backupServiceState }) => html`
|
||||
export const BackingUpNotInProgress = Template.bind({});
|
||||
BackingUpNotInProgress.args = {
|
||||
backupServiceState: {
|
||||
backupFilePath: "Documents",
|
||||
backupDirPath: "/Some/User/Documents",
|
||||
backupInProgress: false,
|
||||
defaultParent: {
|
||||
path: "/Some/User/Documents",
|
||||
fileName: "Documents",
|
||||
},
|
||||
scheduledBackupsEnabled: false,
|
||||
},
|
||||
};
|
||||
@@ -31,8 +35,12 @@ BackingUpNotInProgress.args = {
|
||||
export const BackingUpInProgress = Template.bind({});
|
||||
BackingUpInProgress.args = {
|
||||
backupServiceState: {
|
||||
backupFilePath: "Documents",
|
||||
backupDirPath: "/Some/User/Documents",
|
||||
backupInProgress: true,
|
||||
defaultParent: {
|
||||
path: "/Some/User/Documents",
|
||||
fileName: "Documents",
|
||||
},
|
||||
scheduledBackupsEnabled: false,
|
||||
},
|
||||
};
|
||||
@@ -40,8 +48,12 @@ BackingUpInProgress.args = {
|
||||
export const ScheduledBackupsEnabled = Template.bind({});
|
||||
ScheduledBackupsEnabled.args = {
|
||||
backupServiceState: {
|
||||
backupFilePath: "Documents",
|
||||
backupDirPath: "/Some/User/Documents",
|
||||
backupInProgress: false,
|
||||
defaultParent: {
|
||||
path: "/Some/User/Documents",
|
||||
fileName: "Documents",
|
||||
},
|
||||
scheduledBackupsEnabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -53,9 +53,22 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#backup-location-filepicker-input {
|
||||
margin: 0;
|
||||
.backup-location-filepicker-input {
|
||||
flex: 1;
|
||||
margin-block: var(--space-xsmall);
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: var(--space-xxlarge);
|
||||
background-repeat: no-repeat;
|
||||
background-size: var(--icon-size-default);
|
||||
background-position: var(--space-small) 50%;
|
||||
|
||||
/* For the placeholder icon */
|
||||
fill: currentColor;
|
||||
-moz-context-properties: fill;
|
||||
|
||||
&:dir(rtl) {
|
||||
background-position: calc(100% - var(--space-small)) 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,15 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
||||
* scheduled backups.
|
||||
*/
|
||||
export default class TurnOnScheduledBackups extends MozLitElement {
|
||||
#placeholderIconURL = "chrome://global/skin/icons/page-portrait.svg";
|
||||
|
||||
static properties = {
|
||||
backupFilePath: { type: String },
|
||||
defaultIconURL: { type: String, reflect: true },
|
||||
defaultLabel: { type: String, reflect: true },
|
||||
defaultPath: { type: String, reflect: true },
|
||||
_newIconURL: { type: String },
|
||||
_newLabel: { type: String },
|
||||
_newPath: { type: String },
|
||||
showPasswordOptions: { type: Boolean, reflect: true },
|
||||
};
|
||||
|
||||
@@ -19,15 +26,22 @@ export default class TurnOnScheduledBackups extends MozLitElement {
|
||||
return {
|
||||
cancelButtonEl: "#backup-turn-on-scheduled-cancel-button",
|
||||
confirmButtonEl: "#backup-turn-on-scheduled-confirm-button",
|
||||
filePathButtonEl: "#backup-location-filepicker-button",
|
||||
filePathInputCustomEl: "#backup-location-filepicker-input-custom",
|
||||
filePathInputDefaultEl: "#backup-location-filepicker-input-default",
|
||||
passwordOptionsCheckboxEl: "#sensitive-data-checkbox-input",
|
||||
passwordOptionsExpandedEl: "#passwords",
|
||||
recommendedFolderInputEl: "#backup-location-filepicker-input",
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.backupFilePath = null;
|
||||
this.defaultIconURL = "";
|
||||
this.defaultLabel = "";
|
||||
this.defaultPath = "";
|
||||
this._newIconURL = "";
|
||||
this._newLabel = "";
|
||||
this._newPath = "";
|
||||
this.showPasswordOptions = false;
|
||||
}
|
||||
|
||||
@@ -40,10 +54,28 @@ export default class TurnOnScheduledBackups extends MozLitElement {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("BackupUI:InitWidget", { bubbles: true })
|
||||
);
|
||||
|
||||
this.addEventListener("BackupUI:SelectNewFilepickerPath", this);
|
||||
}
|
||||
|
||||
handleChooseLocation() {
|
||||
// TODO: show file picker (bug 1895943)
|
||||
handleEvent(event) {
|
||||
if (event.type == "BackupUI:SelectNewFilepickerPath") {
|
||||
let { path, filename, iconURL } = event.detail;
|
||||
this._newPath = path;
|
||||
this._newLabel = filename;
|
||||
this._newIconURL = iconURL;
|
||||
}
|
||||
}
|
||||
|
||||
async handleChooseLocation() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("BackupUI:ShowFilepicker", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
win: window.browsingContext,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
@@ -53,14 +85,12 @@ export default class TurnOnScheduledBackups extends MozLitElement {
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
this.showPasswordOptions = false;
|
||||
this.passwordOptionsCheckboxEl.checked = false;
|
||||
this.resetChanges();
|
||||
}
|
||||
|
||||
handleConfirm() {
|
||||
/**
|
||||
* TODO:
|
||||
* We should pass save location to BackupUIParent here (bug 1895943).
|
||||
* If encryption is enabled via this dialog, ensure a password is set and pass it to BackupUIParent (bug 1895981).
|
||||
* Before confirmation, verify passwords match and FxA format rules (bug 1896772).
|
||||
*/
|
||||
@@ -68,16 +98,62 @@ export default class TurnOnScheduledBackups extends MozLitElement {
|
||||
new CustomEvent("turnOnScheduledBackups", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
parentDirPath: this._newPath || this.defaultPath,
|
||||
},
|
||||
})
|
||||
);
|
||||
this.showPasswordOptions = false;
|
||||
this.passwordOptionsCheckboxEl.checked = false;
|
||||
this.resetChanges();
|
||||
}
|
||||
|
||||
handleTogglePasswordOptions() {
|
||||
this.showPasswordOptions = this.passwordOptionsCheckboxEl?.checked;
|
||||
}
|
||||
|
||||
resetChanges() {
|
||||
this._newPath = "";
|
||||
this._newIconURL = "";
|
||||
this._newLabel = "";
|
||||
this.showPasswordOptions = false;
|
||||
this.passwordOptionsCheckboxEl.checked = false;
|
||||
}
|
||||
|
||||
defaultFilePathInputTemplate() {
|
||||
let filename = this.defaultLabel;
|
||||
let iconURL = this.defaultIconURL || this.#placeholderIconURL;
|
||||
|
||||
return html`
|
||||
<input
|
||||
id="backup-location-filepicker-input-default"
|
||||
class="backup-location-filepicker-input"
|
||||
type="text"
|
||||
readonly
|
||||
data-l10n-id="turn-on-scheduled-backups-location-default-folder"
|
||||
data-l10n-args=${JSON.stringify({
|
||||
recommendedFolder: filename,
|
||||
})}
|
||||
data-l10n-attrs="value"
|
||||
style=${`background-image: url(${iconURL})`}
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
customFilePathInputTemplate() {
|
||||
let filename = this._newLabel;
|
||||
let iconURL = this._newIconURL || this.#placeholderIconURL;
|
||||
|
||||
return html`
|
||||
<input
|
||||
id="backup-location-filepicker-input-custom"
|
||||
class="backup-location-filepicker-input"
|
||||
type="text"
|
||||
readonly
|
||||
value=${filename}
|
||||
style=${`background-image: url(${iconURL})`}
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
allOptionsTemplate() {
|
||||
return html`
|
||||
<fieldset id="all-controls">
|
||||
@@ -87,18 +163,10 @@ export default class TurnOnScheduledBackups extends MozLitElement {
|
||||
for="backup-location-filepicker-input"
|
||||
data-l10n-id="turn-on-scheduled-backups-location-label"
|
||||
></label>
|
||||
<!-- TODO: show folder icon (bug 1895943) -->
|
||||
<div id="backup-location-filepicker">
|
||||
<input
|
||||
id="backup-location-filepicker-input"
|
||||
type="text"
|
||||
readonly
|
||||
data-l10n-id="turn-on-scheduled-backups-location-default-folder"
|
||||
data-l10n-args=${JSON.stringify({
|
||||
recommendedFolder: this.backupFilePath,
|
||||
})}
|
||||
data-l10n-attrs="value"
|
||||
/>
|
||||
${!this._newPath
|
||||
? this.defaultFilePathInputTemplate()
|
||||
: this.customFilePathInputTemplate()}
|
||||
<moz-button
|
||||
id="backup-location-filepicker-button"
|
||||
@click=${this.handleChooseLocation}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { html } from "lit.all.mjs";
|
||||
import { html, ifDefined } from "lit.all.mjs";
|
||||
import "chrome://global/content/elements/moz-card.mjs";
|
||||
import "./turn-on-scheduled-backups.mjs";
|
||||
|
||||
@@ -16,15 +16,26 @@ export default {
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
const Template = ({ backupFilePath }) => html`
|
||||
const Template = ({ defaultPath, _newPath, defaultLabel, _newLabel }) => html`
|
||||
<moz-card style="width: 27.8rem;">
|
||||
<turn-on-scheduled-backups
|
||||
.backupFilePath=${backupFilePath}
|
||||
defaultPath=${defaultPath}
|
||||
_newPath=${ifDefined(_newPath)}
|
||||
defaultLabel=${defaultLabel}
|
||||
_newLabel=${ifDefined(_newLabel)}
|
||||
></turn-on-scheduled-backups>
|
||||
</moz-card>
|
||||
`;
|
||||
|
||||
export const RecommendedFolder = Template.bind({});
|
||||
RecommendedFolder.args = {
|
||||
backupFilePath: "Documents",
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
defaultPath: "/Some/User/Documents",
|
||||
defaultLabel: "Documents",
|
||||
};
|
||||
|
||||
export const CustomLocation = Template.bind({});
|
||||
CustomLocation.args = {
|
||||
...Default.args,
|
||||
_newPath: "/Some/Test/Custom/Dir",
|
||||
_newLabel: "Dir",
|
||||
};
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { BackupService } = ChromeUtils.importESModule(
|
||||
"resource:///modules/backup/BackupService.sys.mjs"
|
||||
);
|
||||
|
||||
const { MockFilePicker } = SpecialPowers;
|
||||
|
||||
add_setup(async () => {
|
||||
MockFilePicker.init(window.browsingContext);
|
||||
registerCleanupFunction(() => {
|
||||
MockFilePicker.cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the section for controlling backup in about:preferences is
|
||||
* visible, but can also be hidden via a pref.
|
||||
@@ -87,6 +100,119 @@ add_task(async function test_turn_on_scheduled_backups_confirm() {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the turn on scheduled backups dialog displays the default input field
|
||||
* and a filepicker to choose a custom backup file path, updates the input field to show
|
||||
* that path, and sets browser.backup.location to the path from the settings page.
|
||||
*/
|
||||
add_task(async function test_turn_on_custom_location_filepicker() {
|
||||
await BrowserTestUtils.withNewTab("about:preferences", async browser => {
|
||||
const mockCustomParentDir = await IOUtils.createUniqueDirectory(
|
||||
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;
|
||||
|
||||
// After setting up mocks, start testing components
|
||||
let settings = browser.contentDocument.querySelector("backup-settings");
|
||||
let turnOnButton = settings.scheduledBackupsButtonEl;
|
||||
|
||||
Assert.ok(
|
||||
turnOnButton,
|
||||
"Button to turn on scheduled backups should be found"
|
||||
);
|
||||
|
||||
await settings.updateComplete;
|
||||
let turnOnScheduledBackups = settings.turnOnScheduledBackupsEl;
|
||||
|
||||
Assert.ok(
|
||||
turnOnScheduledBackups,
|
||||
"turn-on-scheduled-backups should be found"
|
||||
);
|
||||
|
||||
// First verify the default input value and dir path button
|
||||
let filePathInputDefault = turnOnScheduledBackups.filePathInputDefaultEl;
|
||||
let filePathButton = turnOnScheduledBackups.filePathButtonEl;
|
||||
const documentsPath = BackupService.DEFAULT_PARENT_DIR_PATH;
|
||||
|
||||
Assert.ok(
|
||||
filePathInputDefault,
|
||||
"Default input for choosing a file path should be found"
|
||||
);
|
||||
Assert.equal(
|
||||
filePathInputDefault.value,
|
||||
`${PathUtils.filename(documentsPath)} (recommended)`,
|
||||
"Default input displays the expected text"
|
||||
);
|
||||
Assert.ok(
|
||||
filePathButton,
|
||||
"Button for choosing a file path should be found"
|
||||
);
|
||||
|
||||
// Next, verify the filepicker and updated dialog
|
||||
let inputUpdatePromise = BrowserTestUtils.waitForCondition(
|
||||
() => turnOnScheduledBackups.filePathInputCustomEl
|
||||
);
|
||||
|
||||
filePathButton.click();
|
||||
|
||||
await filePickerShownPromise;
|
||||
await turnOnScheduledBackups.updateComplete;
|
||||
|
||||
info("Waiting for file path input to update");
|
||||
await inputUpdatePromise;
|
||||
Assert.ok("Input should have been updated");
|
||||
|
||||
let filePathInputCustom = turnOnScheduledBackups.filePathInputCustomEl;
|
||||
Assert.equal(
|
||||
filePathInputCustom.value,
|
||||
PathUtils.filename(mockCustomParentDir),
|
||||
"Input should display file path from filepicker"
|
||||
);
|
||||
|
||||
// Now close the dialog by confirming choices and verify that backup settings are saved
|
||||
let confirmButton = turnOnScheduledBackups.confirmButtonEl;
|
||||
Assert.ok(confirmButton, "Confirm button should be found");
|
||||
|
||||
let confirmButtonPromise = BrowserTestUtils.waitForEvent(
|
||||
window,
|
||||
"turnOnScheduledBackups"
|
||||
);
|
||||
|
||||
confirmButton.click();
|
||||
|
||||
await confirmButtonPromise;
|
||||
await settings.updateComplete;
|
||||
|
||||
// Backup folder should be joined with the updated path
|
||||
let locationPrefVal = Services.prefs.getStringPref(
|
||||
"browser.backup.location"
|
||||
);
|
||||
Assert.equal(
|
||||
locationPrefVal,
|
||||
PathUtils.join(mockCustomParentDir, BackupService.BACKUP_DIR_NAME),
|
||||
"Backup location pref should be updated"
|
||||
);
|
||||
|
||||
await IOUtils.remove(mockCustomParentDir, {
|
||||
ignoreAbsent: true,
|
||||
recursive: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the turn off scheduled backups dialog can set
|
||||
* browser.backup.scheduled.enabled to false from the settings page.
|
||||
|
||||
@@ -38,8 +38,12 @@
|
||||
*/
|
||||
add_task(async function test_turnOnScheduledBackupsDialog() {
|
||||
let settings = document.getElementById("test-backup-settings");
|
||||
const testDefaultName = "test-default-path";
|
||||
settings.backupServiceState = {
|
||||
scheduledBackupsEnabled: false,
|
||||
defaultParent: {
|
||||
path: PathUtils.join(PathUtils.tempDir, testDefaultName),
|
||||
fileName: testDefaultName,
|
||||
}
|
||||
}
|
||||
|
||||
await settings.updateComplete;
|
||||
@@ -73,7 +77,12 @@
|
||||
*/
|
||||
add_task(async function test_turnOffScheduledBackupsDialog() {
|
||||
let settings = document.getElementById("test-backup-settings");
|
||||
const testDefaultName = "test-default-path";
|
||||
settings.backupServiceState = {
|
||||
defaultParent: {
|
||||
path: PathUtils.join(PathUtils.tempDir, testDefaultName),
|
||||
fileName: testDefaultName,
|
||||
},
|
||||
scheduledBackupsEnabled: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
src="chrome://browser/content/backup/turn-on-scheduled-backups.mjs"
|
||||
type="module"
|
||||
></script>
|
||||
<link rel="localization" href="preview/backupSettings.ftl"/>
|
||||
<link rel="localization" href="branding/brand.ftl"/>
|
||||
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
<script>
|
||||
|
||||
@@ -84,6 +86,41 @@
|
||||
|
||||
ok(turnOnScheduledBackups.passwordOptionsExpandedEl, "Passwords expanded options should be found");
|
||||
})
|
||||
|
||||
/**
|
||||
* Tests that the dialog displays a default save location for backups and updates to a custom one
|
||||
* if there is one selected.
|
||||
*/
|
||||
add_task(async function test_inputs() {
|
||||
let turnOnScheduledBackups = document.getElementById("test-turn-on-scheduled-backups");
|
||||
let inputDefault = turnOnScheduledBackups.filePathInputDefaultEl;
|
||||
ok(inputDefault, "Default input should be found");
|
||||
|
||||
let promise = BrowserTestUtils.waitForCondition(() => inputDefault.value);
|
||||
|
||||
/* Normally we would pass in the default attributes, but for this test, we will
|
||||
* hardcode them since file paths vary across different platforms.
|
||||
*/
|
||||
const defaultPathFilename = "testdefaultpath";
|
||||
let defaultPath = PathUtils.join(PathUtils.tempDir, defaultPathFilename);
|
||||
turnOnScheduledBackups.defaultPath = defaultPath;
|
||||
turnOnScheduledBackups.defaultLabel = defaultPathFilename;
|
||||
await turnOnScheduledBackups.updateComplete;
|
||||
await promise;
|
||||
|
||||
is(inputDefault.value, `${defaultPathFilename} (recommended)`, "Default input should not be empty and should contain part of the default path");
|
||||
|
||||
// Now pretend a custom file path was selected
|
||||
const newPathFilename = "testnewpath";
|
||||
let newPath = PathUtils.join(PathUtils.tempDir, newPathFilename);
|
||||
turnOnScheduledBackups._newPath = newPath;
|
||||
turnOnScheduledBackups._newLabel = newPathFilename;
|
||||
await turnOnScheduledBackups.updateComplete;
|
||||
|
||||
let inputCustom = turnOnScheduledBackups.filePathInputCustomEl;
|
||||
ok(inputCustom, "Input should be updated");
|
||||
is(inputCustom.value, newPathFilename, "Input value should be set to the new path");
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This string is used to name the folder that users will save backups to.
|
||||
# "Restore" is an action and intended for prompting users to select this folder
|
||||
# when following backup restoration steps.
|
||||
backup-folder-name = Restore { -brand-product-name }
|
||||
|
||||
settings-data-backup-header = Backup
|
||||
settings-data-backup-toggle = Manage backup
|
||||
|
||||
|
||||
Reference in New Issue
Block a user