diff --git a/browser/components/backup/Archive.worker.mjs b/browser/components/backup/Archive.worker.mjs index 20d0c389735a..5d516d6afe30 100644 --- a/browser/components/backup/Archive.worker.mjs +++ b/browser/components/backup/Archive.worker.mjs @@ -89,10 +89,10 @@ class ArchiveWorker { * Arguments that are described in more detail below. * @param {string} params.archivePath * The path on the file system to write the single-file archive. - * @param {string} params.templateURI - * A URI pointing to the HTML template that will be used for the viewable - * part of the document. The inlined MIME message will be appended after - * the contents of this template. + * @param {string} params.markup + * The HTML markup to insert into the archive file before the HTML + * comment block. This is the markup that will be rendered if the HTML + * file is opened in a web browser. * @param {object} params.backupMetadata * The metadata associated with this backup. This is a copy of the metadata * object that is contained within the compressed backups' manifest. @@ -106,7 +106,7 @@ class ArchiveWorker { */ async constructArchive({ archivePath, - templateURI, + markup, backupMetadata, compressedBackupSnapshotPath, encryptionArgs, @@ -120,15 +120,6 @@ class ArchiveWorker { ); } - // We can get at the template content by using a sync XHR, which is fine to - // to do in a Worker. - let templateXhr = new XMLHttpRequest(); - // Using a synchronous XHR in a worker is fine. - templateXhr.open("GET", templateURI, false); - templateXhr.responseType = "text"; - templateXhr.send(null); - let template = templateXhr.responseText; - let boundary = this.#generateBoundary(); let jsonBlock; @@ -156,7 +147,7 @@ class ArchiveWorker { // // This isn't supposed to be some kind of generalized MIME message // generator, so we're happy to construct it by hand here. - await IOUtils.writeUTF8(archivePath, template); + await IOUtils.writeUTF8(archivePath, markup); await IOUtils.writeUTF8( archivePath, ` @@ -310,7 +301,8 @@ ${ArchiveUtils.INLINE_MIME_END_MARKER} let textDecoder = new TextDecoder(); let decodedHeader = textDecoder.decode(headerBuffer); - const EXPECTED_HEADER = /^\n\n/; + const EXPECTED_HEADER = + /^[\r\n]+[\r\n]+/; let headerMatches = decodedHeader.match(EXPECTED_HEADER); if (!headerMatches) { throw new Error("Corrupt archive header"); diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs index 13e96c3a1192..95270a3d7566 100644 --- a/browser/components/backup/BackupService.sys.mjs +++ b/browser/components/backup/BackupService.sys.mjs @@ -68,6 +68,13 @@ ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () { ); }); +ChromeUtils.defineLazyGetter(lazy, "gDOMLocalization", function () { + return new DOMLocalization([ + "branding/brand.ftl", + "preview/backupSettings.ftl", + ]); +}); + ChromeUtils.defineLazyGetter(lazy, "defaultParentDirPath", function () { return Services.dirsvc.get("Docs", Ci.nsIFile).path; }); @@ -1171,6 +1178,76 @@ export class BackupService extends EventTarget { } } + /** + * Given a URI to an HTML template for the single-file backup archive, + * produces the static markup that will then be used as the beginning of that + * single-file backup archive. + * + * @param {string} templateURI + * A URI pointing at a template for the HTML content for the page. This is + * what is visible if the file is loaded in a web browser. + * @param {boolean} isEncrypted + * True if the template should indicate that the backup is encrypted. + * @param {object} backupMetadata + * The metadata for the backup, which is also stored in the backup manifest + * of the compressed backup snapshot. + * @returns {Promise} + */ + async renderTemplate(templateURI, isEncrypted, backupMetadata) { + let templateResponse = await fetch(templateURI); + let templateString = await templateResponse.text(); + let templateDOM = new DOMParser().parseFromString( + templateString, + "text/html" + ); + + // Set the lang attribute on the element + templateDOM.documentElement.setAttribute( + "lang", + Services.locale.appLocaleAsBCP47 + ); + + // TODO: insert download link (bug 1903117) + let supportLinkHref = + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "recover-from-backup"; + let supportLink = templateDOM.querySelector("#support-link"); + supportLink.href = supportLinkHref; + + let encStateNode = templateDOM.querySelector("#encryption-state"); + lazy.gDOMLocalization.setAttributes( + encStateNode, + isEncrypted + ? "backup-file-encryption-state-encrypted" + : "backup-file-encryption-state-not-encrypted" + ); + + let lastBackedUpNode = templateDOM.querySelector("#last-backed-up"); + lazy.gDOMLocalization.setArgs(lastBackedUpNode, { + // It's very unlikely that backupMetadata.date isn't a valid Date string, + // but if it _is_, then Fluent will cause us to crash in debug builds. + // We fallback to the current date if all else fails. + date: new Date(backupMetadata.date).getTime() || new Date().getTime(), + }); + + let creationDeviceNode = templateDOM.querySelector("#creation-device"); + lazy.gDOMLocalization.setArgs(creationDeviceNode, { + machineName: backupMetadata.machineName, + }); + + try { + await lazy.gDOMLocalization.translateFragment( + templateDOM.documentElement + ); + } catch (_) { + // This shouldn't happen, but we don't want a missing locale string to + // cause backup creation to fail. + } + + let serializer = new XMLSerializer(); + return serializer.serializeToString(templateDOM); + } + /** * Creates a portable, potentially encrypted single-file archive containing * a compressed backup snapshot. The single-file archive is a specially @@ -1203,6 +1280,12 @@ export class BackupService extends EventTarget { backupMetadata, options = {} ) { + let markup = await this.renderTemplate( + templateURI, + !!encState, + backupMetadata + ); + let worker = new lazy.BasePromiseWorker( "resource:///modules/backup/Archive.worker.mjs", { type: "module" } @@ -1225,7 +1308,7 @@ export class BackupService extends EventTarget { await worker.post("constructArchive", [ { archivePath, - templateURI, + markup, backupMetadata, compressedBackupSnapshotPath, encryptionArgs, diff --git a/browser/components/backup/content/archive.template.html b/browser/components/backup/content/archive.template.html index 05209f662c58..15b71269bd7d 100644 --- a/browser/components/backup/content/archive.template.html +++ b/browser/components/backup/content/archive.template.html @@ -15,13 +15,36 @@ #endif - + - {{title}} + -

This is placeholder content for now.

+

+

+ +

+ +
+
+

+
    +
  1. +
  2. +
  3. +
+
+
+
    +
  1. +
  2. +
+
diff --git a/browser/components/backup/tests/xpcshell/data/test_archive.template.html b/browser/components/backup/tests/xpcshell/data/test_archive.template.html index 8a8ec7607eda..f48fc39315c0 100644 --- a/browser/components/backup/tests/xpcshell/data/test_archive.template.html +++ b/browser/components/backup/tests/xpcshell/data/test_archive.template.html @@ -1,12 +1,37 @@ - + - This is a test title + +

+

+ +

+ +
+
+

+
    +
  1. +
  2. +
  3. +
+
+
+
    +
  1. +
  2. +
+
+

This is some text.

I'm also including some unicode characters: 🐶🐶🐶🐶🐶🐶🐶🐶🐶🐶🐶🐶

Here are some Arabic glyphs: أهلاً بك!

diff --git a/browser/locales-preview/backupSettings.ftl b/browser/locales-preview/backupSettings.ftl index e83dd4a8781a..4f00fd832c5a 100644 --- a/browser/locales-preview/backupSettings.ftl +++ b/browser/locales-preview/backupSettings.ftl @@ -84,4 +84,33 @@ disable-backup-encryption-support-link = What will be backed up? disable-backup-encryption-cancel-button = Cancel disable-backup-encryption-confirm-button = Remove password +## These strings are inserted into the generated single-file backup archive. +## The single-file backup archive is a specially-crafted, static HTML file +## that is placed within a user specified directory (the Documents folder by +## default) within a folder labelled with the "backup-folder-name" string. + +backup-file-header = { -brand-short-name } is ready to be restored +backup-file-title = Restore { -brand-short-name } +backup-file-intro = Get back to browsing and recover all your bookmarks, history, and other data. Learn more + +# Variables: +# $date (string) - Date to be formatted based on locale +backup-file-last-backed-up = Last backed up: { DATETIME($date, timeStyle: "short") }, { DATETIME($date, dateStyle: "short") } + +backup-file-encryption-state-encrypted = Encrypted +backup-file-encryption-state-not-encrypted = Not encrypted + +# Variables: +# $machineName (String) - Name of the machine that the backup was created on. +backup-file-creation-device = Created on { $machineName } + +backup-file-how-to-restore-header = How to restore your data: +backup-file-moz-browser-restore-step-1 = Go to Settings > Backup +backup-file-moz-browser-restore-step-2 = Under “Restore”, click “Choose backup file” +backup-file-moz-browser-restore-step-3 = Restart { -brand-short-name } when asked + +backup-file-other-browser-restore-step-1 = Download and install { -brand-short-name }: +backup-file-download-moz-browser-button = Download { -brand-short-name } +backup-file-other-browser-restore-step-2 = Open { -brand-short-name } and restore your backup + ##