tor-browser

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

commit 8d12d7d653a05372836f671c38faf127ab772218
parent d79ae7ad4ede83346a710a0125ce40e16bc2dff8
Author: mimi <nsauermann@mozilla.com>
Date:   Fri, 17 Oct 2025 21:52:40 +0000

Bug 1994252 - [Fx Backup Messaging] Update about:welcome backup restore screen temp gating pref and add AW screen test coverage r=omc-reviewers,jprickett

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

Diffstat:
Mbrowser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx | 74+++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mbrowser/components/aboutwelcome/content/aboutwelcome.bundle.js | 39+++++++++++++++++++++++++++++++--------
Mbrowser/components/aboutwelcome/modules/AboutWelcomeDefaults.sys.mjs | 53+++++++++++------------------------------------------
Mbrowser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_languageSwitcher.js | 7++-----
Mbrowser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_mr.js | 255++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mbrowser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx | 42------------------------------------------
Mbrowser/components/aboutwelcome/tests/unit/MultiStageAboutWelcome.test.jsx | 85++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mbrowser/components/asrouter/modules/OnboardingMessageProvider.sys.mjs | 8++++----
Mbrowser/components/asrouter/tests/browser/browser_feature_callout_panel.js | 8++++++++
9 files changed, 422 insertions(+), 149 deletions(-)

diff --git a/browser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx b/browser/components/aboutwelcome/content-src/components/MultiStageAboutWelcome.jsx @@ -387,26 +387,22 @@ const renderSingleSecondaryCTAButton = ({ className += " split-button-container"; } - const isDisabled = React.useCallback( - disabledValue => { - if (disabledValue === "hasActiveMultiSelect") { - if (!activeMultiSelect) { - return true; - } + const computeDisabled = disabledValue => { + if (disabledValue === "hasActiveMultiSelect") { + if (!activeMultiSelect) { + return true; + } - for (const key in activeMultiSelect) { - if (activeMultiSelect[key]?.length > 0) { - return false; - } + for (const key in activeMultiSelect) { + if (activeMultiSelect[key]?.length > 0) { + return false; } - - return true; } - return disabledValue; - }, - [activeMultiSelect] - ); + return true; + } + return disabledValue; + }; if (isTextLink) { buttonStyling += " text-link"; @@ -438,7 +434,7 @@ const renderSingleSecondaryCTAButton = ({ id={buttonId} className={buttonStyling} value={targetElement} - disabled={isDisabled(button?.disabled)} + disabled={computeDisabled(button?.disabled)} onClick={shimmedHandleAction} /> </Localized> @@ -461,14 +457,46 @@ export const SecondaryCTA = props => { return null; } - if (Array.isArray(buttonData)) { - if (buttonData.length === 0) { - return null; - } + const buttons = React.useMemo( + () => (Array.isArray(buttonData) ? buttonData : [buttonData]), + [buttonData] + ); + const [visibleButtons, setVisibleButtons] = React.useState([]); + + React.useEffect(() => { + (async () => { + const filteredButtons = []; + for (const button of buttons) { + // No targeting, show by default for backwards compatibility + if (!button?.targeting) { + filteredButtons.push(button); + continue; + } + + try { + const shouldShowButton = await window.AWEvaluateAttributeTargeting( + button.targeting + ); + if (shouldShowButton) { + filteredButtons.push(button); + } + } catch (e) { + console.error("SecondaryCTA targeting failed:", button.targeting, e); + } + } + setVisibleButtons(filteredButtons); + })(); + }, [buttons]); + + if (!visibleButtons.length) { + return null; + } + + if (Array.isArray(buttonData)) { return ( <div className="secondary-buttons-top-container"> - {buttonData.map((button, index) => + {visibleButtons.map((button, index) => renderSingleSecondaryCTAButton({ content, button, @@ -486,7 +514,7 @@ export const SecondaryCTA = props => { return renderSingleSecondaryCTAButton({ content, - button: buttonData, + button: visibleButtons[0], targetElement, position, handleAction: props.handleAction, diff --git a/browser/components/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/aboutwelcome/content/aboutwelcome.bundle.js @@ -470,7 +470,7 @@ const renderSingleSecondaryCTAButton = ({ if (isSplitButton) { className += " split-button-container"; } - const isDisabled = react__WEBPACK_IMPORTED_MODULE_0___default().useCallback(disabledValue => { + const computeDisabled = disabledValue => { if (disabledValue === "hasActiveMultiSelect") { if (!activeMultiSelect) { return true; @@ -483,7 +483,7 @@ const renderSingleSecondaryCTAButton = ({ return true; } return disabledValue; - }, [activeMultiSelect]); + }; if (isTextLink) { buttonStyling += " text-link"; } @@ -512,7 +512,7 @@ const renderSingleSecondaryCTAButton = ({ id: buttonId, className: buttonStyling, value: targetElement, - disabled: isDisabled(button?.disabled), + disabled: computeDisabled(button?.disabled), onClick: shimmedHandleAction })), isSplitButton ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_SubmenuButton__WEBPACK_IMPORTED_MODULE_5__.SubmenuButton, { content: content, @@ -529,13 +529,36 @@ const SecondaryCTA = props => { if (!buttonData) { return null; } + const buttons = react__WEBPACK_IMPORTED_MODULE_0___default().useMemo(() => Array.isArray(buttonData) ? buttonData : [buttonData], [buttonData]); + const [visibleButtons, setVisibleButtons] = react__WEBPACK_IMPORTED_MODULE_0___default().useState([]); + react__WEBPACK_IMPORTED_MODULE_0___default().useEffect(() => { + (async () => { + const filteredButtons = []; + for (const button of buttons) { + // No targeting, show by default for backwards compatibility + if (!button?.targeting) { + filteredButtons.push(button); + continue; + } + try { + const shouldShowButton = await window.AWEvaluateAttributeTargeting(button.targeting); + if (shouldShowButton) { + filteredButtons.push(button); + } + } catch (e) { + console.error("SecondaryCTA targeting failed:", button.targeting, e); + } + } + setVisibleButtons(filteredButtons); + })(); + }, [buttons]); + if (!visibleButtons.length) { + return null; + } if (Array.isArray(buttonData)) { - if (buttonData.length === 0) { - return null; - } return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", { className: "secondary-buttons-top-container" - }, buttonData.map((button, index) => renderSingleSecondaryCTAButton({ + }, visibleButtons.map((button, index) => renderSingleSecondaryCTAButton({ content, button, targetElement: `${targetElement}_${index}`, @@ -548,7 +571,7 @@ const SecondaryCTA = props => { } return renderSingleSecondaryCTAButton({ content, - button: buttonData, + button: visibleButtons[0], targetElement, position, handleAction: props.handleAction, diff --git a/browser/components/aboutwelcome/modules/AboutWelcomeDefaults.sys.mjs b/browser/components/aboutwelcome/modules/AboutWelcomeDefaults.sys.mjs @@ -42,7 +42,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { { id: "AW_BACKUP_RESTORE_EMBEDDED_BACKUP_FOUND", targeting: - "'messaging-system.backupRestoreEnabled'|preferenceValue == true && (backupsInfo.found || backupsInfo.multipleBackupsFound)", + "backupRestoreEnabled && (backupsInfo.found || backupsInfo.multipleBackupsFound)", content: { fullscreen: true, logo: {}, @@ -168,6 +168,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { type: "SHOW_FIREFOX_ACCOUNTS", addFlowParams: true, }, + targeting: "!isFxASignedIn", }, }, }, @@ -299,6 +300,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { type: "SHOW_FIREFOX_ACCOUNTS", addFlowParams: true, }, + targeting: "!isFxASignedIn", }, { label: { string_id: "restore-from-backup-secondary-top-button" }, @@ -309,6 +311,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { }, navigate: true, }, + targeting: "backupRestoreEnabled", }, ], }, @@ -418,6 +421,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { type: "SHOW_FIREFOX_ACCOUNTS", addFlowParams: true, }, + targeting: "!isFxASignedIn", }, { label: { string_id: "restore-from-backup-secondary-top-button" }, @@ -428,6 +432,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { }, navigate: true, }, + targeting: "backupRestoreEnabled", }, ], }, @@ -548,6 +553,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { type: "SHOW_FIREFOX_ACCOUNTS", addFlowParams: true, }, + targeting: "!isFxASignedIn", }, { label: { string_id: "restore-from-backup-secondary-top-button" }, @@ -558,6 +564,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { }, navigate: true, }, + targeting: "backupRestoreEnabled", }, ], }, @@ -656,6 +663,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { type: "SHOW_FIREFOX_ACCOUNTS", addFlowParams: true, }, + targeting: "!isFxASignedIn", }, { label: { string_id: "restore-from-backup-secondary-top-button" }, @@ -666,6 +674,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { }, navigate: true, }, + targeting: "backupRestoreEnabled", }, ], }, @@ -673,7 +682,7 @@ const MR_ABOUT_WELCOME_DEFAULT = { { id: "AW_BACKUP_RESTORE_EMBEDDED_NO_BACKUP_FOUND", targeting: - "'messaging-system.backupRestoreEnabled'|preferenceValue == true && 'messaging-system-action.showRestoreFromBackup' |preferenceValue == true", + "backupRestoreEnabled && 'messaging-system-action.showRestoreFromBackup' |preferenceValue == true", content: { fullscreen: true, logo: {}, @@ -1064,46 +1073,6 @@ async function prepareContentForReact(content) { content.skipFxA = true; } - // Temporary pref for QA testing until 1993272 lands - const showRestoreBackupTopBtn = Services.prefs.getBoolPref( - "messaging-system.backupRestoreEnabled", - false - ); - - const screensWithRestoreBtn = [ - "AW_EASY_SETUP_NEEDS_DEFAULT_AND_PIN", - "AW_EASY_SETUP_NEEDS_DEFAULT", - "AW_EASY_SETUP_NEEDS_PIN", - "AW_EASY_SETUP_ONLY_IMPORT", - ]; - - for (const id of screensWithRestoreBtn) { - const screen = content.screens.find(s => s.id === id); - if (!screen || !screen.content || !screen.content.secondary_button_top) { - continue; - } - - let secondaryBtnTop = screen.content.secondary_button_top; - if (!Array.isArray(secondaryBtnTop)) { - secondaryBtnTop = [secondaryBtnTop]; - } - - // Filter out the restore-from-backup button if the pref is false - const filteredBtn = secondaryBtnTop.filter( - btn => - btn?.label?.string_id !== "restore-from-backup-secondary-top-button" || - showRestoreBackupTopBtn - ); - - if (filteredBtn.length === 0) { - screen.content.secondary_button_top = undefined; - } else if (filteredBtn.length === 1) { - [screen.content.secondary_button_top] = filteredBtn; - } else { - screen.content.secondary_button_top = filteredBtn; - } - } - let shouldRemoveLanguageMismatchScreen = true; if (content.languageMismatchEnabled) { const screen = content?.screens?.find(s => s.id === "AW_LANGUAGE_MISMATCH"); diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_languageSwitcher.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_languageSwitcher.js @@ -15,9 +15,6 @@ add_task(function initSandbox() { Services.prefs.clearUserPref( "messaging-system-action.showRestoreFromBackup" ); - Services.prefs.clearUserPref( - "messaging-system-action.backupRestoreEnabled" - ); Services.prefs.clearUserPref("messaging-system-action.showEmbeddedImport"); sandbox.restore(); }); @@ -67,8 +64,8 @@ async function openAboutWelcome() { "!doesAppNeedPin && (unhandledCampaignAction != 'SET_DEFAULT_BROWSER') && (unhandledCampaignAction != 'PIN_AND_DEFAULT') && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser", "(unhandledCampaignAction != 'PIN_FIREFOX_TO_TASKBAR') && (unhandledCampaignAction != 'PIN_AND_DEFAULT') && doesAppNeedPin && (!'browser.shell.checkDefaultBrowser'|preferenceValue || isDefaultBrowser || (unhandledCampaignAction == 'SET_DEFAULT_BROWSER'))", "isDeviceMigration", - "'messaging-system.backupRestoreEnabled'|preferenceValue == true && 'messaging-system-action.showRestoreFromBackup' |preferenceValue == true", - "'messaging-system.backupRestoreEnabled'|preferenceValue == true && (backupsInfo.found || backupsInfo.multipleBackupsFound)", + "backupRestoreEnabled && 'messaging-system-action.showRestoreFromBackup' |preferenceValue == true", + "backupRestoreEnabled && (backupsInfo.found || backupsInfo.multipleBackupsFound)", ]; if (falseTargeting.includes(args)) { return Promise.resolve(false); diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_mr.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_multistage_mr.js @@ -46,6 +46,14 @@ add_setup(async function () { }); }); +add_task(function () { + registerCleanupFunction(() => { + Services.prefs.clearUserPref( + "messaging-system-action.showRestoreFromBackup" + ); + }); +}); + /** * Test MR message telemetry */ @@ -91,7 +99,11 @@ add_task(async function test_aboutwelcome_easy_setup_screen_impression() { ) .resolves(true) .withArgs("isDeviceMigration") - .resolves(false); + .resolves(false) + .withArgs("!isFxASignedIn") + .resolves(true) + .withArgs("backupRestoreEnabled") + .resolves(true); let impressionSpy = sandbox.spy( AboutWelcomeTelemetry.prototype, @@ -107,7 +119,8 @@ add_task(async function test_aboutwelcome_easy_setup_screen_impression() { [ `main.screen[pos="split"]`, "div.secondary-cta.top", - "button[value='secondary_button_top']", + "button[value='secondary_button_top_0']", //sign in button + "button[value='secondary_button_top_1']", //backup restore button ] ); @@ -773,7 +786,7 @@ add_task(async function test_aboutwelcome_multiselect() { /** * Test end of multistage url bar focus on new tab */ -add_task(async function test_AWMultistage_newtab_urlbar_focus() { +add_task(async function test_aboutwelcome_newtab_urlbar_focus() { const TEST_CONTENT = [ { id: "TEST_SCREEN", @@ -809,6 +822,7 @@ add_task(async function test_AWMultistage_newtab_urlbar_focus() { Assert.ok(gURLBar.focused, "focus should be on url bar"); BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); sandbox.restore(); }); @@ -881,3 +895,238 @@ add_task(async function test_aboutwelcome_gratitude() { await SpecialPowers.popPrefEnv(); await cleanup(); }); + +add_task(async function test_aboutwelcome_backup_found() { + const sandbox = sinon.createSandbox(); + sandbox + .stub(AWScreenUtils, "evaluateScreenTargeting") + .resolves(false) + .withArgs( + "backupRestoreEnabled && (backupsInfo.found || backupsInfo.multipleBackupsFound)" + ) + .resolves(true) + .withArgs("isDeviceMigration") + .resolves(false); + + let { browser, cleanup } = await openMRAboutWelcome(); + + await test_screen_content( + browser, + "Should render backups found screen as first screen when backupRestoreEnabled is true and backup is found", + [ + "main.AW_BACKUP_RESTORE_EMBEDDED_BACKUP_FOUND", + "[data-l10n-id='restore-from-backup-title']", + "[data-l10n-id='restore-from-backup-subtitle']", + ], + // Unexpected selectors + ["main.AW_BACKUP_RESTORE_EMBEDDED_NO_BACKUP_FOUND"] + ); + + await cleanup(); + sandbox.restore(); +}); + +add_task(async function test_aboutwelcome_no_backups() { + const sandbox = sinon.createSandbox(); + + sandbox + .stub(AWScreenUtils, "evaluateScreenTargeting") + .resolves(false) + .withArgs( + "backupRestoreEnabled && (backupsInfo.found || backupsInfo.multipleBackupsFound)" + ) + .resolves(false) + // Easy setup for secondary top button + .withArgs( + "doesAppNeedPin && (unhandledCampaignAction != 'SET_DEFAULT_BROWSER') && (unhandledCampaignAction != 'PIN_FIREFOX_TO_TASKBAR') && (unhandledCampaignAction != 'PIN_AND_DEFAULT') && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser" + ) + .resolves(true) + // Restore from backup pref gating + .withArgs("backupRestoreEnabled") + .resolves(true) + // No backups found + .withArgs( + "backupRestoreEnabled && 'messaging-system-action.showRestoreFromBackup' |preferenceValue == true" + ) + .resolves(true); + + let { browser, cleanup } = await openMRAboutWelcome(); + + await test_screen_content( + browser, + "Easy setup renders with restore secondary top button", + [ + "main.AW_EASY_SETUP_NEEDS_DEFAULT_AND_PIN, main.AW_EASY_SETUP_NEEDS_DEFAULT, main.AW_EASY_SETUP_NEEDS_PIN, main.AW_EASY_SETUP_ONLY_IMPORT", + "div.secondary-cta.top", + ], + //Unexpected selectors: + ["main.AW_BACKUP_RESTORE_EMBEDDED_BACKUP_FOUND"] + ); + + await clickVisibleButton( + browser, + "button[data-l10n-id='restore-from-backup-secondary-top-button']" + ); + + await test_screen_content( + browser, + "NO_BACKUP_FOUND screen renders afrer clicking restore from backup top cta", + [ + "main.AW_BACKUP_RESTORE_EMBEDDED_NO_BACKUP_FOUND", + "[data-l10n-id='restore-from-backup-title']", + "[data-l10n-id='restore-from-backup-subtitle']", + ] + ); + + await cleanup(); + sandbox.restore(); +}); + +add_task(async function test_aboutwelcome_secondary_top_signin_only() { + const sandbox = sinon.createSandbox(); + + sandbox + .stub(AWScreenUtils, "evaluateScreenTargeting") + .resolves(false) + // Mock Easy Setup for secondary button top testing + .withArgs( + "doesAppNeedPin && (unhandledCampaignAction != 'SET_DEFAULT_BROWSER') && (unhandledCampaignAction != 'PIN_FIREFOX_TO_TASKBAR') && (unhandledCampaignAction != 'PIN_AND_DEFAULT') && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser" + ) + .resolves(true) + // Sign in button targeting + .withArgs("!isFxASignedIn") + .resolves(true); + + let { browser, cleanup } = await openMRAboutWelcome(); + + await test_screen_content( + browser, + "Easy setup renders with secondary top button", + [ + "main.AW_EASY_SETUP_NEEDS_DEFAULT_AND_PIN", + ".secondary-buttons-top-container, div.secondary-cta.top", + ] + ); + + await SpecialPowers.spawn(browser, [], async () => { + const count = content.document.querySelectorAll( + "button[value^='secondary_button_top_']" + ).length; + Assert.equal(count, 1, "One top CTA is shown"); + Assert.ok( + content.document.querySelector( + "button[data-l10n-id='mr1-onboarding-sign-in-button-label']" + ), + "'Sign in' top button is present" + ); + Assert.ok( + !content.document.querySelector( + "button[data-l10n-id='restore-from-backup-secondary-top-button']" + ), + "'Restore Backup' top button not present" + ); + }); + + await cleanup(); + sandbox.restore(); +}); + +add_task(async function test_aboutwelcome_secondary_top_backup_restore_only() { + const sandbox = sinon.createSandbox(); + + sandbox + .stub(AWScreenUtils, "evaluateScreenTargeting") + .resolves(false) + // Mock Easy Setup for secondary button top testing + .withArgs( + "doesAppNeedPin && (unhandledCampaignAction != 'SET_DEFAULT_BROWSER') && (unhandledCampaignAction != 'PIN_FIREFOX_TO_TASKBAR') && (unhandledCampaignAction != 'PIN_AND_DEFAULT') && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser" + ) + .resolves(true) + // Show Restore Backup top button + .withArgs("backupRestoreEnabled") + .resolves(true); + + let { browser, cleanup } = await openMRAboutWelcome(); + + await test_screen_content( + browser, + "Easy setup renders with secondary top button", + [ + "main.AW_EASY_SETUP_NEEDS_DEFAULT_AND_PIN", + ".secondary-buttons-top-container, div.secondary-cta.top", + ] + ); + + await SpecialPowers.spawn(browser, [], async () => { + const count = content.document.querySelectorAll( + "button[value^='secondary_button_top_']" + ).length; + Assert.equal(count, 1, "One top CTA is shown"); + Assert.ok( + !content.document.querySelector( + "button[data-l10n-id='mr1-onboarding-sign-in-button-label']" + ), + "'Sign in' top button is present" + ); + Assert.ok( + content.document.querySelector( + "button[data-l10n-id='restore-from-backup-secondary-top-button']" + ), + "'Restore Backup' top button present" + ); + }); + + await cleanup(); + sandbox.restore(); +}); + +add_task(async function test_aboutwelcome_both_secondary_top_buttons() { + const sandbox = sinon.createSandbox(); + + sandbox + .stub(AWScreenUtils, "evaluateScreenTargeting") + // Mock Easy Setup for secondary button top testing + .withArgs( + "doesAppNeedPin && (unhandledCampaignAction != 'SET_DEFAULT_BROWSER') && (unhandledCampaignAction != 'PIN_FIREFOX_TO_TASKBAR') && (unhandledCampaignAction != 'PIN_AND_DEFAULT') && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser" + ) + .resolves(true) + // Show Sign-in top button + .withArgs("!isFxASignedIn") + .resolves(true) + // Show Restore top button + .withArgs("backupRestoreEnabled") + .resolves(true); + + let { browser, cleanup } = await openMRAboutWelcome(); + + await test_screen_content( + browser, + "Easy setup renders with secondary top button", + [ + "main.AW_EASY_SETUP_NEEDS_DEFAULT_AND_PIN", + ".secondary-buttons-top-container, div.secondary-cta.top", + ] + ); + + await SpecialPowers.spawn(browser, [], async () => { + const buttons = content.document.querySelectorAll( + "button[value^='secondary_button_top_']" + ); + Assert.equal(buttons.length, 2, "Two secondary top buttons are present"); + Assert.ok( + content.document.querySelector( + "button[data-l10n-id='mr1-onboarding-sign-in-button-label']" + ), + "'Sign in' top button present" + ); + Assert.ok( + content.document.querySelector( + "button[data-l10n-id='restore-from-backup-secondary-top-button']" + ), + "'Restore Backup' top button present" + ); + }); + + await cleanup(); + sandbox.restore(); +}); diff --git a/browser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx b/browser/components/aboutwelcome/tests/unit/MultiStageAWProton.test.jsx @@ -892,48 +892,6 @@ describe("MultiStageAboutWelcomeProton module", () => { assert.property(data, "skipFxA", true); assert.notProperty(data.screens[0].content, "secondary_button_top"); }); - - it("should enable restore from backup button top if pref enabled", async () => { - global.Services.prefs.getBoolPref.returns(true); - const data = await prepConfig({}, [ - "AW_WELCOME_BACK", - "RETURN_TO_AMO", - "AW_BACKUP_RESTORE_EMBEDDED_BACKUP_FOUND", - ]); - const secondaryBtnTop = data.screens[0].content.secondary_button_top; - - const restoreBtn = btn => - btn.label.string_id === "restore-from-backup-secondary-top-button"; - - assert.isTrue( - secondaryBtnTop.some(restoreBtn), - "Should include restore button top when pref is true" - ); - }); - - it("should remove the restore from backup button top if pref is disabled", async () => { - global.Services.prefs.getBoolPref.returns(false); - const data = await prepConfig({}, [ - "AW_WELCOME_BACK", - "RETURN_TO_AMO", - "AW_BACKUP_RESTORE_EMBEDDED_BACKUP_FOUND", - ]); - - const secondaryBtnTop = data.screens[0].content.secondary_button_top; - - // secondary_button_top should be an object instead of an array when restore from backup button is removed - assert.isObject( - secondaryBtnTop, - "secondary_button_top should be an object" - ); - - // Verify that object is not the restore from backup button - assert.notEqual( - secondaryBtnTop.label.string_id, - "restore-from-backup-secondary-top-button", - "restore button should not be present" - ); - }); }); describe("AboutWelcomeDefaults for MR split template proton", () => { diff --git a/browser/components/aboutwelcome/tests/unit/MultiStageAboutWelcome.test.jsx b/browser/components/aboutwelcome/tests/unit/MultiStageAboutWelcome.test.jsx @@ -43,6 +43,7 @@ describe("MultiStageAboutWelcome module", () => { AWWaitForMigrationClose: () => Promise.resolve(), AWSelectTheme: () => Promise.resolve(), AWFinish: () => Promise.resolve(), + AWEvaluateAttributeTargeting: () => Promise.resolve(false), }); sandbox = sinon.createSandbox(); }); @@ -245,6 +246,16 @@ describe("MultiStageAboutWelcome module", () => { assert.ok(wrapper.exists()); }); + it("should render a primary and secondary button", () => { + const wrapper = mount(<WelcomeScreen {...EASY_SETUP_SCREEN_PROPS} />); + assert.ok(wrapper.find(".primary").exists()); + assert.equal( + wrapper.find(".secondary-cta:not(.top) button.secondary").length, + 1, + "One secondary button" + ); + }); + it("should render secondary.top button", () => { let SCREEN_PROPS = { content: { @@ -260,6 +271,52 @@ describe("MultiStageAboutWelcome module", () => { assert.ok(wrapper.find("div.secondary-cta.top").exists()); }); + it("should render a secondary top 'Sign in' button if user isn't signed in", async () => { + globals.set("AWEvaluateAttributeTargeting", expression => { + return Promise.resolve(expression.includes("!isFxASignedIn")); + }); + + const wrapper = mount(<WelcomeScreen {...EASY_SETUP_SCREEN_PROPS} />); + await spinEventLoop(); + wrapper.update(); + + const secondaryTopButton = wrapper.find(".secondary-cta.top"); + assert.ok(secondaryTopButton.exists(), "secondary button top exists"); + + const signInButton = secondaryTopButton.find( + '[data-l10n-id="mr1-onboarding-sign-in-button-label"]' + ); + + assert.equal( + signInButton.length, + 1, + "One 'Sign in' secondary top button" + ); + }); + + it("should render a secondary top 'Restore Backup' button when backup restore is enabled", async () => { + globals.set("AWEvaluateAttributeTargeting", expression => { + return Promise.resolve(expression.includes("backupRestoreEnabled")); + }); + + const wrapper = mount(<WelcomeScreen {...EASY_SETUP_SCREEN_PROPS} />); + await spinEventLoop(); + wrapper.update(); + + const secondaryTopButton = wrapper.find(".secondary-cta.top"); + assert.ok(secondaryTopButton.exists(), "Secondary button top exists"); + + let backup = secondaryTopButton.find( + '[data-l10n-id="restore-from-backup-secondary-top-button"]' + ); + + assert.equal( + backup.length, + 1, + "One secondary 'Backup Restore' top button" + ); + }); + it("should render the arrow icon in the secondary button", () => { let SCREEN_PROPS = { content: { @@ -313,22 +370,6 @@ describe("MultiStageAboutWelcome module", () => { "50%" ); }); - - it("should have a primary, secondary and two secondary.top button", () => { - const wrapper = mount(<WelcomeScreen {...EASY_SETUP_SCREEN_PROPS} />); - assert.ok(wrapper.find(".primary").exists()); - assert.equal( - wrapper.find(".secondary-cta:not(.top) button.secondary").length, - 1, - "One secondary button" - ); - - assert.equal( - wrapper.find(".secondary-cta.top button.secondary").length, - 2, - "Two secondary button top (sign in and restore from backup)" - ); - }); }); describe("theme screen", () => { @@ -1077,7 +1118,7 @@ describe("MultiStageAboutWelcome module", () => { AboutWelcomeUtils.handleUserAction.resetHistory(); } }); - it("Should handle a campaign action when applicable", async () => { + it("should handle a campaign action when applicable", async () => { let actionSpy = sandbox.spy(AboutWelcomeUtils, "handleCampaignAction"); let telemetrySpy = sandbox.spy( AboutWelcomeUtils, @@ -1110,7 +1151,7 @@ describe("MultiStageAboutWelcome module", () => { assert.equal(telemetrySpy.firstCall.args[1], "CAMPAIGN_ACTION"); globals.restore(); }); - it("Should not handle a campaign action when the action has already been handled", async () => { + it("should not handle a campaign action when the action has already been handled", async () => { let actionSpy = sandbox.spy(AboutWelcomeUtils, "handleCampaignAction"); let telemetrySpy = sandbox.spy( AboutWelcomeUtils, @@ -1139,7 +1180,7 @@ describe("MultiStageAboutWelcome module", () => { assert.notCalled(telemetrySpy); globals.restore(); }); - it("Should not send telemetrty when campaign action handling fails", async () => { + it("should not send telemetrty when campaign action handling fails", async () => { let actionSpy = sandbox.spy(AboutWelcomeUtils, "handleCampaignAction"); let telemetrySpy = sandbox.spy( AboutWelcomeUtils, @@ -1237,7 +1278,7 @@ describe("MultiStageAboutWelcome module", () => { globals.restore(); }); - it("Should dismiss when resolve boolean is true and needAwait true", async () => { + it("should dismiss when resolve boolean is true and needAwait true", async () => { TEST_ACTION.dismiss = "actionResult"; TEST_ACTION.needsAwait = true; // `needsAwait` is true, so the handleUserAction function should return a `Promise<boolean>` @@ -1274,7 +1315,7 @@ describe("MultiStageAboutWelcome module", () => { assert.calledOnce(finishStub); }); - it("Should not dismiss when resolve boolean is false and needAwait true", async () => { + it("should not dismiss when resolve boolean is false and needAwait true", async () => { TEST_ACTION.dismiss = "actionResult"; TEST_ACTION.needsAwait = true; // `needsAwait` is true, so the handleUserAction function should return a `Promise<boolean>` @@ -1308,7 +1349,7 @@ describe("MultiStageAboutWelcome module", () => { assert.notCalled(finishStub); }); - it("Should dismiss when true and handleUserAction not awaited", async () => { + it("should dismiss when true and handleUserAction not awaited", async () => { TEST_ACTION.dismiss = true; // `needsAwait` is not set, so the handleUserAction function should return a `Promise<undefined>` awSendToParentStub.callsFake( diff --git a/browser/components/asrouter/modules/OnboardingMessageProvider.sys.mjs b/browser/components/asrouter/modules/OnboardingMessageProvider.sys.mjs @@ -2010,7 +2010,7 @@ const BASE_MESSAGES = () => [ ], }, targeting: - "source == 'startup' && !doesAppNeedPin && isDefaultBrowser && !'browser.shell.checkDefaultBrowser'|preferenceValue && !willShowDefaultPrompt && 'browser.backup.profile-restoration-date'|preferenceValue", + "backupRestoreEnabled && source == 'startup' && !doesAppNeedPin && isDefaultBrowser && !'browser.shell.checkDefaultBrowser'|preferenceValue && !willShowDefaultPrompt && 'browser.backup.profile-restoration-date'|preferenceValue", trigger: { id: "defaultBrowserCheck", }, @@ -2115,7 +2115,7 @@ const BASE_MESSAGES = () => [ ], }, targeting: - "source == 'startup' && doesAppNeedPin && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser && !willShowDefaultPrompt && 'browser.backup.profile-restoration-date'|preferenceValue", + "backupRestoreEnabled && source == 'startup' && doesAppNeedPin && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser && !willShowDefaultPrompt && 'browser.backup.profile-restoration-date'|preferenceValue", trigger: { id: "defaultBrowserCheck", }, @@ -2198,7 +2198,7 @@ const BASE_MESSAGES = () => [ ], }, targeting: - "source == 'startup' && !doesAppNeedPin && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser && !willShowDefaultPrompt && 'browser.backup.profile-restoration-date'|preferenceValue", + "backupRestoreEnabled && source == 'startup' && !doesAppNeedPin && 'browser.shell.checkDefaultBrowser'|preferenceValue && !isDefaultBrowser && !willShowDefaultPrompt && 'browser.backup.profile-restoration-date'|preferenceValue", trigger: { id: "defaultBrowserCheck", }, @@ -2292,7 +2292,7 @@ const BASE_MESSAGES = () => [ ], }, targeting: - "source == 'startup' && doesAppNeedPin && !willShowDefaultPrompt &&(!'browser.shell.checkDefaultBrowser'|preferenceValue || isDefaultBrowser) && 'browser.backup.profile-restoration-date'|preferenceValue", + "backupRestoreEnabled && source == 'startup' && doesAppNeedPin && !willShowDefaultPrompt &&(!'browser.shell.checkDefaultBrowser'|preferenceValue || isDefaultBrowser) && 'browser.backup.profile-restoration-date'|preferenceValue", trigger: { id: "defaultBrowserCheck", }, diff --git a/browser/components/asrouter/tests/browser/browser_feature_callout_panel.js b/browser/components/asrouter/tests/browser/browser_feature_callout_panel.js @@ -264,6 +264,14 @@ add_task(async function feature_callout_split_dismiss_button() { await testCalloutHiddenIf( async (win, calloutContainer) => { + // Wait until the submenu markup exists, since SecondaryCTA targeting renders async + await BrowserTestUtils.waitForCondition( + () => + calloutContainer.querySelector( + `#${calloutId} .fxms-multi-stage-submenu` + ), + "Wait for submenu to be attached" + ); let splitButtonContainer = calloutContainer.querySelector( `#${calloutId} .split-button-container` );