commit 45f78eb8a4ce94ff423616cb3f0b286e16391541
parent ed88a0583f7e9b59b52398d664d8019677a298b3
Author: Jason Prickett <jprickett@mozilla.com>
Date: Thu, 16 Oct 2025 18:29:28 +0000
Bug 1993550 - Add utm params to restore component support links when used in about welcome r=omc-reviewers,sthompson,mviar
Differential Revision: https://phabricator.services.mozilla.com/D268752
Diffstat:
3 files changed, 253 insertions(+), 16 deletions(-)
diff --git a/browser/components/backup/content/restore-from-backup.mjs b/browser/components/backup/content/restore-from-backup.mjs
@@ -2,7 +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/. */
-import { html, styleMap } from "chrome://global/content/vendor/lit.all.mjs";
+import {
+ html,
+ ifDefined,
+ styleMap,
+} from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
import { ERRORS } from "chrome://browser/content/backup/backup-constants.mjs";
import { getErrorL10nId } from "chrome://browser/content/backup/backup-errors.mjs";
@@ -55,7 +59,7 @@ export default class RestoreFromBackup extends MozLitElement {
scheduledBackupsEnabled: false,
lastBackupDate: null,
lastBackupFileName: "",
- supportBaseLink: "",
+ supportBaseLink: "https://support.mozilla.org/",
backupInProgress: false,
recoveryInProgress: false,
recoveryErrorCode: ERRORS.NONE,
@@ -208,6 +212,64 @@ export default class RestoreFromBackup extends MozLitElement {
}
}
+ /**
+ * Constructs a support URL with UTM parameters for use
+ * when embedded in about:welcome
+ *
+ * @param {string} supportPage - The support page slug
+ * @returns {string} The full support URL including UTM params
+ */
+
+ getSupportURLWithUTM(supportPage) {
+ let supportURL = new URL(
+ supportPage,
+ this.backupServiceState.supportBaseLink
+ );
+ supportURL.searchParams.set("utm_medium", "firefox-desktop");
+ supportURL.searchParams.set("utm_source", "npo");
+ supportURL.searchParams.set("utm_campaign", "fx-backup-restore");
+ supportURL.searchParams.set("utm_content", "restore-error");
+ return supportURL.href;
+ }
+
+ /**
+ * Returns a support link anchor element, either with UTM params for use in
+ * about:welcome, or falling back to moz-support-link otherwise
+ *
+ * @param {object} options - Link configuration options
+ * @param {string} options.id - The element id
+ * @param {string} options.l10nId - The fluent l10n id
+ * @param {string} options.l10nName - The fluent l10n name
+ * @param {string} options.supportPage - The support page slug
+ * @returns {TemplateResult} The link template
+ */
+
+ getSupportLinkAnchor({
+ id,
+ l10nId,
+ l10nName,
+ supportPage = "firefox-backup",
+ }) {
+ if (this.aboutWelcomeEmbedded) {
+ return html`<a
+ id=${id}
+ target="_blank"
+ href=${this.getSupportURLWithUTM(supportPage)}
+ data-l10n-id=${ifDefined(l10nId)}
+ data-l10n-name=${ifDefined(l10nName)}
+ ></a>`;
+ }
+
+ return html`<a
+ id=${id}
+ slot="support-link"
+ is="moz-support-link"
+ support-page=${supportPage}
+ data-l10n-id=${ifDefined(l10nId)}
+ data-l10n-name=${ifDefined(l10nName)}
+ ></a>`;
+ }
+
applyContentCustomizations() {
if (this.aboutWelcomeEmbedded) {
this.style.setProperty("--label-font-weight", "600");
@@ -241,13 +303,10 @@ export default class RestoreFromBackup extends MozLitElement {
</div>
${!this.backupServiceState?.backupFileInfo
- ? html`<a
- id="restore-from-backup-no-backup-file-link"
- slot="support-link"
- is="moz-support-link"
- support-page="firefox-backup"
- data-l10n-id="restore-from-backup-no-backup-file-link"
- ></a>`
+ ? this.getSupportLinkAnchor({
+ id: "restore-from-backup-no-backup-file-link",
+ l10nId: "restore-from-backup-no-backup-file-link",
+ })
: null}
${this.backupServiceState?.backupFileInfo
? html`<p
@@ -331,13 +390,10 @@ export default class RestoreFromBackup extends MozLitElement {
class="field-error"
data-l10n-id="backup-service-error-incorrect-password"
>
- <a
- id="backup-incorrect-password-support-link"
- slot="support-link"
- is="moz-support-link"
- support-page="firefox-backup"
- data-l10n-name="incorrect-password-support-link"
- ></a>
+ ${this.getSupportLinkAnchor({
+ id: "backup-incorrect-password-support-link",
+ l10nName: "incorrect-password-support-link",
+ })}
</span>
`
: html`<label
diff --git a/browser/components/backup/tests/browser/browser_settings_restore_from_backup.js b/browser/components/backup/tests/browser/browser_settings_restore_from_backup.js
@@ -3,6 +3,10 @@
"use strict";
+const { ERRORS } = ChromeUtils.importESModule(
+ "chrome://browser/content/backup/backup-constants.mjs"
+);
+
let TEST_PROFILE_PATH;
add_setup(async () => {
@@ -460,3 +464,88 @@ add_task(async function test_restore_backup_file_info_display() {
);
});
});
+
+/**
+ * Helper function to test that a support link has the appropriate attributes
+ *
+ * @param {Element} link - The support link element to test
+ * @param {string} linkName - The name of the link to test
+ */
+
+function assertNonEmbeddedSupportLink(link, linkName) {
+ Assert.ok(link, `${linkName} should be present`);
+ Assert.equal(
+ link.getAttribute("is"),
+ "moz-support-link",
+ `${linkName} should use moz-support-link when not embedded`
+ );
+ Assert.equal(
+ link.getAttribute("support-page"),
+ "firefox-backup",
+ `${linkName} should have support-page attribute`
+ );
+ Assert.ok(
+ !link.href.includes("utm_source"),
+ `${linkName} should not have UTM params when not embedded`
+ );
+}
+
+/**
+ * Tests that support links use moz-support-link when aboutWelcomeEmbedded is falsy
+ */
+add_task(async function test_support_links_non_embedded() {
+ await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => {
+ let settings = browser.contentDocument.querySelector("backup-settings");
+ await settings.updateComplete;
+
+ settings.restoreFromBackupButtonEl.click();
+ await settings.updateComplete;
+
+ let restoreFromBackup = settings.restoreFromBackupEl;
+ Assert.ok(restoreFromBackup, "restore-from-backup should be found");
+
+ Assert.ok(
+ !restoreFromBackup.aboutWelcomeEmbedded,
+ "aboutWelcomeEmbedded should be falsy"
+ );
+
+ // Test the 'no backup file' link
+ let noBackupFileLink = restoreFromBackup.shadowRoot.querySelector(
+ "#restore-from-backup-no-backup-file-link"
+ );
+ assertNonEmbeddedSupportLink(noBackupFileLink, "'No backup file' link");
+
+ // Test the description link
+ restoreFromBackup.backupServiceState = {
+ ...restoreFromBackup.backupServiceState,
+ backupFileInfo: {
+ date: new Date(),
+ deviceName: "test-device",
+ isEncrypted: false,
+ },
+ };
+ await restoreFromBackup.updateComplete;
+
+ let descriptionLink = restoreFromBackup.shadowRoot.querySelector(
+ "#restore-from-backup-learn-more-link"
+ );
+ assertNonEmbeddedSupportLink(descriptionLink, "Description link");
+
+ // Test the incorrect password link
+ restoreFromBackup.backupServiceState = {
+ ...restoreFromBackup.backupServiceState,
+ backupFileInfo: {
+ date: new Date(),
+ deviceName: "test-device",
+ isEncrypted: true,
+ },
+ recoveryErrorCode: ERRORS.UNAUTHORIZED,
+ };
+ await restoreFromBackup.updateComplete;
+
+ let passwordErrorLink = restoreFromBackup.shadowRoot.querySelector(
+ "#backup-incorrect-password-support-link"
+ );
+ assertNonEmbeddedSupportLink(passwordErrorLink, "Password error link");
+ });
+});
diff --git a/browser/components/backup/tests/chrome/test_restore_from_backup.html b/browser/components/backup/tests/chrome/test_restore_from_backup.html
@@ -262,6 +262,98 @@
}
restoreFromBackup.remove();
});
+
+ /**
+ * Helper function to test that a support link has correct attributes
+ * and UTM params when used with aboutWelcomeEmbedded
+ *
+ * @param {Element} link - The support link element to test
+ * @param {string} linkName - The name of the link to test
+ */
+
+ function assertEmbeddedSupportLink(link, linkName) {
+ ok(link, `${linkName} should be present`);
+ ok(
+ !link.hasAttribute("is"),
+ `${linkName} should not have 'is' attribute`
+ );
+ ok(
+ !link.hasAttribute("support-page"),
+ `${linkName} should not have support-page attribute`
+ );
+ ok(
+ link.hasAttribute("href"),
+ `${linkName} should have href attribute`
+ );
+
+ let url = new URL(link.getAttribute("href"));
+
+ is(
+ url.searchParams.get("utm_medium"),
+ "firefox-desktop",
+ `${linkName} should have correct utm_medium`
+ );
+ is(
+ url.searchParams.get("utm_source"),
+ "npo",
+ `${linkName} should have correct utm_source`
+ );
+ is(
+ url.searchParams.get("utm_campaign"),
+ "fx-backup-restore",
+ `${linkName} should have correct utm_campaign`
+ );
+ is(
+ url.searchParams.get("utm_content"),
+ "restore-error",
+ `${linkName} should have correct utm_content`
+ );
+ is(
+ link.getAttribute("target"),
+ "_blank",
+ `${linkName} should have target='_blank'`
+ );
+ }
+
+ /**
+ * Tests that support links have UTM parameters when aboutWelcomeEmbedded is true
+ */
+ add_task(async function test_support_links_with_utm_params() {
+ let content = document.getElementById("content");
+ let restoreFromBackup = document.createElement("restore-from-backup");
+ content.appendChild(restoreFromBackup);
+
+ // Set up the support base link for testing, otherwise links will be broken
+ restoreFromBackup.backupServiceState = {
+ ...restoreFromBackup.backupServiceState,
+ supportBaseLink: "https://support.mozilla.org/",
+ };
+ restoreFromBackup.aboutWelcomeEmbedded = true;
+ await restoreFromBackup.updateComplete;
+
+ // Test the "no backup file" link
+ let noBackupFileLink = restoreFromBackup.shadowRoot.querySelector(
+ "#restore-from-backup-no-backup-file-link"
+ );
+ assertEmbeddedSupportLink(noBackupFileLink, "No backup file link");
+
+ // Test the incorrect password support link
+ restoreFromBackup.backupServiceState = {
+ ...restoreFromBackup.backupServiceState,
+ backupFileInfo: {
+ date: new Date(),
+ isEncrypted: true,
+ },
+ recoveryErrorCode: ERRORS.UNAUTHORIZED,
+ };
+ await restoreFromBackup.updateComplete;
+ let passwordErrorLink = restoreFromBackup.shadowRoot.querySelector(
+ "#backup-incorrect-password-support-link"
+ );
+ assertEmbeddedSupportLink(passwordErrorLink, "Password error link");
+
+ restoreFromBackup.remove();
+ });
</script>
</head>
<body>