tor-browser

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

commit 490ea2332c37dc922500a6c5749a449bd7bd058a
parent 9b1999e2513718c32e08a977e982f47892775e1b
Author: Tim Xia <txia@mozilla.com>
Date:   Fri, 24 Oct 2025 18:19:13 +0000

Bug 1984747: Enable Link Previews by default - r=Mardak,firefox-ai-ml-reviewers,fluent-reviewers,bolsson

- Add locale-based gating logic
- Link Previews with Key Points to all English‑speaking users (EN locale, all geos)
- Link Previews without Key Points to all other users (all other locale, all geos)
- use canShowKeyPoints() for locale and region condition check
- set canShowPreferences to true all the time
- update tests
- change test condition for canShowPreferences to always true
- set onboarding to false all the time.
- remove browser_link_preview_onboarding.js
- fix linter
- remove ref to browser_link_preview_onboarding.js and tests in telemetry test
- remove test_nimbus_link_preview test 'cause we are enabling Link Preview by default
- fix browser_contextmenu_sendpage.js test
- fix broewser_contextmenu test and access key conflict

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

Diffstat:
Mbrowser/app/profile/firefox.js | 5+++--
Mbrowser/base/content/test/contextMenu/browser_contextmenu.js | 24++++++++++++++++++++++++
Mbrowser/base/content/test/sync/browser_contextmenu_sendpage.js | 1+
Mbrowser/components/genai/LinkPreview.sys.mjs | 73+++++++++++++++++++++++++++++++------------------------------------------
Mbrowser/components/genai/content/link-preview-card.mjs | 13+++----------
Mbrowser/components/genai/tests/browser/browser.toml | 6------
Mbrowser/components/genai/tests/browser/browser_link_preview.js | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/genai/tests/browser/browser_link_preview_nimbus.js | 55-------------------------------------------------------
Dbrowser/components/genai/tests/browser/browser_link_preview_onboarding.js | 310-------------------------------------------------------------------------------
Mbrowser/components/genai/tests/browser/browser_link_preview_optin.js | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/genai/tests/browser/browser_link_preview_telemetry.js | 206+------------------------------------------------------------------------------
Mbrowser/locales/en-US/browser/browserContext.ftl | 2+-
12 files changed, 206 insertions(+), 630 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -2221,16 +2221,17 @@ pref("browser.ml.chat.sidebar", true); pref("browser.ml.linkPreview.allowedLanguages", "en"); pref("browser.ml.linkPreview.blockListEnabled", true); pref("browser.ml.linkPreview.collapsed", false); -pref("browser.ml.linkPreview.enabled", false); +pref("browser.ml.linkPreview.enabled", true); pref("browser.ml.linkPreview.ignoreMs", 2000); pref("browser.ml.linkPreview.longPress", true); pref("browser.ml.linkPreview.longPressMs", 1000); -pref("browser.ml.linkPreview.noKeyPointsRegions", "AD,AT,BE,BG,CH,CY,CZ,DE,DK,EE,ES,FI,FR,GR,HR,HU,IE,IS,IT,LI,LT,LU,LV,MT,NL,NO,PL,PT,RO,SE,SI,SK"); +pref("browser.ml.linkPreview.noKeyPointsRegions", ""); pref("browser.ml.linkPreview.optin", false); pref("browser.ml.linkPreview.outputSentences", 3); pref("browser.ml.linkPreview.recentTypingMs", 1000); pref("browser.ml.linkPreview.shift", false); pref("browser.ml.linkPreview.shiftAlt", false); +pref("browser.ml.linkPreview.supportedLocales", "en"); pref("browser.ml.pageAssist.enabled", false); pref("browser.ml.smartAssist.apiKey", ""); diff --git a/browser/base/content/test/contextMenu/browser_contextmenu.js b/browser/base/content/test/contextMenu/browser_contextmenu.js @@ -109,6 +109,8 @@ add_task(async function test_xul_text_link_label() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -209,6 +211,8 @@ const kLinkItems = [ true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -1836,6 +1840,8 @@ add_task(async function test_select_text_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -1907,6 +1913,8 @@ add_task(async function test_imagelink() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2152,6 +2160,8 @@ add_task(async function test_svg_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2189,6 +2199,8 @@ add_task(async function test_svg_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2226,6 +2238,8 @@ add_task(async function test_svg_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2265,6 +2279,8 @@ add_task(async function test_svg_relative_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2302,6 +2318,8 @@ add_task(async function test_svg_relative_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2339,6 +2357,8 @@ add_task(async function test_svg_relative_link() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2423,6 +2443,8 @@ add_task(async function test_background_image() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", @@ -2535,6 +2557,8 @@ add_task(async function test_strip_on_share_on_secure_about_page() { true, "context-openlinkprivate", true, + "context-previewlink", + true, "---", null, "context-bookmarklink", diff --git a/browser/base/content/test/sync/browser_contextmenu_sendpage.js b/browser/base/content/test/sync/browser_contextmenu_sendpage.js @@ -120,6 +120,7 @@ add_task(async function test_link_contextmenu() { : []), "context-openlink", "context-openlinkprivate", + "context-previewlink", "context-sep-open", "context-bookmarklink", "context-savelink", diff --git a/browser/components/genai/LinkPreview.sys.mjs b/browser/components/genai/LinkPreview.sys.mjs @@ -27,12 +27,6 @@ XPCOMUtils.defineLazyPreferenceGetter( ); XPCOMUtils.defineLazyPreferenceGetter( lazy, - "cfrFeatures", - "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", - true -); -XPCOMUtils.defineLazyPreferenceGetter( - lazy, "collapsed", "browser.ml.linkPreview.collapsed", null, @@ -80,12 +74,6 @@ XPCOMUtils.defineLazyPreferenceGetter( ); XPCOMUtils.defineLazyPreferenceGetter( lazy, - "onboardingCooldownPeriodMs", - "browser.ml.linkPreview.onboardingCooldownPeriodMs", - 7 * 24 * 60 * 60 * 1000 // Constant for onboarding reactivation cooldown period (7 days in milliseconds) -); -XPCOMUtils.defineLazyPreferenceGetter( - lazy, "onboardingHoverLinkMs", "browser.ml.linkPreview.onboardingHoverLinkMs", 1000 @@ -94,7 +82,7 @@ XPCOMUtils.defineLazyPreferenceGetter( lazy, "onboardingMaxShowFreq", "browser.ml.linkPreview.onboardingMaxShowFreq", - 2 + 0 ); XPCOMUtils.defineLazyPreferenceGetter( lazy, @@ -141,6 +129,11 @@ XPCOMUtils.defineLazyPreferenceGetter( null, (_pref, _old, val) => LinkPreview.onShiftAltPrefChange(val) ); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "supportedLocales", + "browser.ml.linkPreview.supportedLocales" +); export const LinkPreview = { // Shared downloading state to use across multiple previews @@ -172,7 +165,11 @@ export const LinkPreview = { }, get canShowKeyPoints() { - return this._isRegionSupported() && !this._isDisabledByPolicy(); + return ( + this._isRegionSupported() && + this._isLocaleSupported() && + !this._isDisabledByPolicy() + ); }, get canShowLegacy() { @@ -180,29 +177,12 @@ export const LinkPreview = { }, get canShowPreferences() { - // Show preferences if the user has ever enabled link previews. - // This is true if the feature is currently enabled, or if the onboarding UI - // was shown previously (which populates `onboardingTimes`). - // Note: showing onboarding requires link previews to be enabled at the time. - // This ensures users who later disable the feature can still access the settings. - return lazy.enabled || !!lazy.onboardingTimes.length; + // The setting is always shown. + return true; }, get showOnboarding() { - // Don't show onboarding if CFR features are disabled. This is true for - // automated tests. - if (!lazy.cfrFeatures) { - return false; - } - - const timesArray = lazy.onboardingTimes; - const lastValidTime = timesArray.at(-1) || 0; - const timeSinceLastOnboarding = Date.now() - lastValidTime; - - return ( - timesArray.length < lazy.onboardingMaxShowFreq && - timeSinceLastOnboarding >= lazy.onboardingCooldownPeriodMs - ); + return false; }, shouldShowContextMenu(nsContextMenu) { @@ -767,6 +747,20 @@ export const LinkPreview = { }, /** + * Checks if the user's locale is supported for key points generation. + * + * @returns {boolean} True if the locale is supported, false otherwise. + */ + _isLocaleSupported() { + const supportedLocales = lazy.supportedLocales + .split(",") + .map(locale => locale.trim().toLowerCase()); + + const userLocale = Services.locale.appLocaleAsBCP47.toLowerCase(); + return supportedLocales.some(locale => userLocale.startsWith(locale)); + }, + + /** * Checks if key points generation is disabled by policy. * * @returns {boolean} True if disabled by policy, false otherwise. @@ -795,7 +789,7 @@ export const LinkPreview = { ogCard.optin = lazy.optin; ogCard.collapsed = lazy.collapsed; - ogCard.regionSupported = this._isRegionSupported(); + ogCard.canShowKeyPoints = this.canShowKeyPoints; // Reflect the shared download progress to this preview. const updateProgress = () => { @@ -809,15 +803,10 @@ export const LinkPreview = { } }; updateProgress(); - - if (!this._isRegionSupported()) { - // Region not supported, just don't show key points section - return ogCard; - } - // Generate key points if we have content, language and configured for any - // language or restricted. + // language or restricted, and if key points can be shown. if ( + this.canShowKeyPoints && pageData.article.textContent && pageData.article.detectedLanguage && (!lazy.allowedLanguages || diff --git a/browser/components/genai/content/link-preview-card.mjs b/browser/components/genai/content/link-preview-card.mjs @@ -53,10 +53,10 @@ class LinkPreviewCard extends MozLitElement { isMissingDataErrorState: { type: Boolean }, generationError: { type: Object }, // null = no error, otherwise contains error info keyPoints: { type: Array }, + canShowKeyPoints: { type: Boolean }, optin: { type: Boolean }, pageData: { type: Object }, progress: { type: Number }, // -1 = off, 0-100 = download progress - regionSupported: { type: Boolean }, }; constructor() { @@ -65,10 +65,10 @@ class LinkPreviewCard extends MozLitElement { this.generationError = null; this.isMissingDataErrorState = false; this.keyPoints = []; + this.canShowKeyPoints = true; this.optin = false; this.optinRef = createRef(); this.progress = -1; - this.regionSupported = true; } /** @@ -420,14 +420,7 @@ class LinkPreviewCard extends MozLitElement { * @returns {import('lit').TemplateResult} The content card HTML */ renderKeyPointsSection(pageUrl) { - if (!this.regionSupported) { - return ""; - } - - if ( - !this.optin && - Services.prefs.prefIsLocked("browser.ml.linkPreview.optin") - ) { + if (!this.canShowKeyPoints) { return ""; } diff --git a/browser/components/genai/tests/browser/browser.toml b/browser/components/genai/tests/browser/browser.toml @@ -55,12 +55,6 @@ support-files = [ ["browser_link_preview_nimbus.js"] -["browser_link_preview_onboarding.js"] -support-files = [ - "data/readableFr.html", - "data/readableEn.html" -] - ["browser_link_preview_optin.js"] support-files = [ "data/readableFr.html", diff --git a/browser/components/genai/tests/browser/browser_link_preview.js b/browser/components/genai/tests/browser/browser_link_preview.js @@ -26,6 +26,7 @@ const TEST_LINK_URL = "https://example.com"; registerCleanupFunction(() => { Services.prefs.clearUserPref("browser.ml.linkPreview.onboardingTimes"); + Services.prefs.clearUserPref("browser.ml.linkPreview.supportedLocales"); }); /** @@ -940,3 +941,89 @@ add_task(async function test_toggle_expand_collapse() { generateStub.restore(); LinkPreview.keyboardComboActive = false; }); + +/** + * Test that the Link Preview feature does not generate key points in unsupported locales. + */ +add_task(async function test_no_key_points_in_unsupported_locale() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.ml.linkPreview.enabled", true], + ["browser.ml.linkPreview.optin", true], + ], + }); + + const localeStub = sinon + .stub(LinkPreview, "_isLocaleSupported") + .returns(false); + const generateStub = sinon.stub(LinkPreviewModel, "generateTextAI"); + + LinkPreview.keyboardComboActive = true; + XULBrowserWindow.setOverLink( + "https://example.com/browser/browser/components/genai/tests/browser/data/readableEn.html", + {} + ); + + let panel = await TestUtils.waitForCondition(() => + document.getElementById("link-preview-panel") + ); + await BrowserTestUtils.waitForEvent(panel, "popupshown"); + + const card = panel.querySelector("link-preview-card"); + ok(card, "Card created for link preview"); + + is( + generateStub.callCount, + 0, + "generateTextAI should not be called when locale is not supported" + ); + ok(!LinkPreview.canShowKeyPoints, "should not show key points"); + + panel.remove(); + LinkPreview.keyboardComboActive = false; + localeStub.restore(); + generateStub.restore(); +}); + +/** + * Test that the Link Preview feature does generate key points in supported locales. + */ +add_task(async function test_key_points_in_supported_locale() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.ml.linkPreview.enabled", true], + ["browser.ml.linkPreview.optin", true], + ], + }); + + const localeStub = sinon + .stub(LinkPreview, "_isLocaleSupported") + .returns(true); + const generateStub = sinon.stub(LinkPreviewModel, "generateTextAI"); + + LinkPreview.keyboardComboActive = true; + XULBrowserWindow.setOverLink( + "https://example.com/browser/browser/components/genai/tests/browser/data/readableEn.html", + {} + ); + + let panel = await TestUtils.waitForCondition(() => + document.getElementById("link-preview-panel") + ); + await BrowserTestUtils.waitForEvent(panel, "popupshown"); + + const card = panel.querySelector("link-preview-card"); + ok(card, "Card created for link preview"); + + is( + generateStub.callCount, + 1, + "generateTextAI should be called when locale is supported" + ); + ok(LinkPreview.canShowKeyPoints, "should show key points"); + + panel.remove(); + LinkPreview.keyboardComboActive = false; + localeStub.restore(); + generateStub.restore(); +}); diff --git a/browser/components/genai/tests/browser/browser_link_preview_nimbus.js b/browser/components/genai/tests/browser/browser_link_preview_nimbus.js @@ -9,61 +9,6 @@ const { NimbusTestUtils } = ChromeUtils.importESModule( ); /** - * Test nimbus experiment sets link preview enabled prefs. - */ -add_task(async function test_nimbus_link_preview() { - is( - Services.prefs.getBoolPref("browser.ml.linkPreview.enabled"), - false, - "default false" - ); - is(LinkPreview.canShowLegacy, false, "not legacy yet"); - is( - Services.prefs.prefHasUserValue("browser.ml.linkPreview.labs"), - false, - "not yet enrolled in labs" - ); - - let cleanup = await NimbusTestUtils.enrollWithFeatureConfig({ - featureId: "linkPreviews", - value: { enabled: true }, - }); - - ok( - Services.prefs.getBoolPref("browser.ml.linkPreview.enabled"), - "nimbus setPref enabled" - ); - ok(LinkPreview.canShowLegacy, "now legacy"); - is( - Services.prefs.getIntPref("browser.ml.linkPreview.labs"), - LABS_STATE.ENROLLED, - "indicates enrolled in labs" - ); - - await cleanup(); - - ok( - Services.prefs.getBoolPref("browser.ml.linkPreview.enabled"), - "still enabled" - ); - ok(LinkPreview.canShowLegacy, "still legacy"); - is( - Services.prefs.getIntPref("browser.ml.linkPreview.labs"), - LABS_STATE.ROLLOUT_ENDED, - "transitioned from labs" - ); - - ok( - Services.prefs.getStringPref("browser.ml.linkPreview.nimbus"), - "nimbus slug set" - ); - - Services.prefs.clearUserPref("browser.ml.linkPreview.enabled"); - Services.prefs.clearUserPref("browser.ml.linkPreview.labs"); - Services.prefs.clearUserPref("browser.ml.linkPreview.nimbus"); -}); - -/** * Check that enrolling into linkPreviews experiments sets user prefs. */ add_task(async function test_nimbus_user_prefs() { diff --git a/browser/components/genai/tests/browser/browser_link_preview_onboarding.js b/browser/components/genai/tests/browser/browser_link_preview_onboarding.js @@ -1,310 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -const { LinkPreview } = ChromeUtils.importESModule( - "moz-src:///browser/components/genai/LinkPreview.sys.mjs" -); - -const { sinon } = ChromeUtils.importESModule( - "resource://testing-common/Sinon.sys.mjs" -); - -const { LinkPreviewModel } = ChromeUtils.importESModule( - "moz-src:///browser/components/genai/LinkPreviewModel.sys.mjs" -); - -const TEST_LINK_URL_EN = - "https://example.com/browser/browser/components/genai/tests/browser/data/readableEn.html"; -const TEST_LINK_URL_FR = - "https://example.com/browser/browser/components/genai/tests/browser/data/readableFr.html"; - -function clearOverlink() { - // Clear the state by setting it to the FR URL - XULBrowserWindow.setOverLink(TEST_LINK_URL_FR); -} - -async function waitForPanelOpen( - message = "waiting for onboarding panel to open" -) { - return await TestUtils.waitForCondition(() => { - const panel = document.getElementById("link-preview-panel"); - return panel?.state == "open" ? panel : null; - }, message); -} - -add_setup(async function () { - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", - true, - ], - ], - }); -}); - -/** - * Tests that the onboarding panel is shown when preferences indicate it should be shown. - */ -add_task(async function test_show_onboarding_close_button() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ], - }); - - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - const panel = await waitForPanelOpen(); - ok(panel, "Panel created for onboarding"); - - const onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "onboarding element is present"); - const ogcard = onboarding_card.shadowRoot.querySelector( - ".og-card.onboarding" - ); - ok(ogcard, "ogcard element is present"); - - const closeButton = onboarding_card.shadowRoot.querySelector( - "#onboarding-close-button" - ); - ok(closeButton, "close button is present"); - closeButton.click(); - - ok( - Services.prefs.prefHasUserValue("browser.ml.linkPreview.onboardingTimes"), - "onboardingTimes was set" - ); - - // Cleanup - panel.remove(); -}); - -add_task(async function test_try_it_now_button() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ], - }); - - clearOverlink(); - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - const panel = await waitForPanelOpen(); - ok(panel, "Panel created for onboarding"); - - const onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "onboarding element is present"); - - const tryItNowButton = onboarding_card.shadowRoot.querySelector( - "moz-button[data-l10n-id='link-preview-onboarding-button']" - ); - ok(tryItNowButton, "Try it now button is present"); - tryItNowButton.click(); - - is( - Services.prefs.getBoolPref("browser.ml.linkPreview.collapsed"), - false, - "collapsed pref should be set to false after completing onboarding" - ); - - ok( - Services.prefs.prefHasUserValue("browser.ml.linkPreview.onboardingTimes"), - "onboardingTimes was set" - ); - - // Cleanup - panel.remove(); -}); - -/** - * Test that onboarding is shown when under max show frequency - */ -add_task(async function test_onboarding_max_show_frequency() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", "121"], - ["browser.ml.linkPreview.onboardingMaxShowFreq", 2], - ], - }); - - ok( - LinkPreview.showOnboarding, - "Should show onboarding when under max frequency" - ); - - clearOverlink(); - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - let panel = await TestUtils.waitForCondition(() => - document.getElementById("link-preview-panel") - ); - ok(panel, "Panel created for onboarding under max frequency"); - - let onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "Card created for onboarding under max frequency"); - - panel.remove(); - - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", "121,12212"], - ["browser.ml.linkPreview.onboardingMaxShowFreq", 2], - ], - }); - - ok( - !LinkPreview.showOnboarding, - "Should not show onboarding when max frequency is reached" - ); - - clearOverlink(); - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - panel = document.getElementById("link-preview-panel"); - is(panel, null, "Panel should not be created when max frequency is reached"); -}); - -/** - * Test that onboarding respects cooldown period - */ -add_task(async function test_onboarding_cooldown_period() { - // // Test initial setup - no timestamps, should show onboarding - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ["browser.ml.linkPreview.onboardingMaxShowFreq", 2], - [ - "browser.ml.linkPreview.onboardingCooldownPeriodMs", - 7 * 24 * 60 * 60 * 1000, - ], // 7 days cooldown - ], - }); - - // Initially should show onboarding since no previous timestamps - ok( - LinkPreview.showOnboarding, - "Should show onboarding initially with empty timestamps" - ); - - // Set the first timestamp (now) which means we just showed onboarding - const now = Date.now(); - await SpecialPowers.pushPrefEnv({ - set: [["browser.ml.linkPreview.onboardingTimes", `${now}`]], - }); - - // Should not show onboarding now since we just showed it (within cooldown period) - ok( - !LinkPreview.showOnboarding, - "Should not show onboarding immediately after showing it once" - ); - - // Change cooldown to 5 days and test with a 6-day-old timestamp - const eightDaysAgo = now - 8 * 24 * 60 * 60 * 1000; - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.onboardingTimes", `${eightDaysAgo}`], - ["browser.ml.linkPreview.onboardingMaxShowFreq", 2], - ], - }); - - // Should show onboarding since last time was 6 days ago, outside the 5-day cooldown - ok( - LinkPreview.showOnboarding, - "Should show onboarding after cooldown period has passed" - ); -}); - -/** - * Test that using link preview (not via onboarding) sets the onboardingTimes - * preference to effectively disable future onboarding prompts. - */ -add_task(async function test_onboarding_times_set_on_preview_usage() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingMaxShowFreq", 2], - ["browser.ml.linkPreview.onboardingTimes", ""], - ], - }); - - const generateStub = sinon.stub(LinkPreviewModel, "generateTextAI"); - - const READABLE_PAGE_URL = - "https://example.com/browser/browser/components/genai/tests/browser/data/readableEn.html"; - - LinkPreview.keyboardComboActive = "shift"; - XULBrowserWindow.setOverLink(READABLE_PAGE_URL, {}); - - const panel = await waitForPanelOpen(); - ok(panel, "Panel created for link preview"); - - // Check the preference value - // renderLinkPreviewPanel sets this to "0,0" when onboardingMaxShowFreq is 2 - // and source is not "onboarding". - is( - Services.prefs.getCharPref("browser.ml.linkPreview.onboardingTimes"), - "0,0", - "onboardingTimes should be set to '0,0' after link preview usage" - ); - - // Cleanup - panel.remove(); - generateStub.restore(); - LinkPreview.keyboardComboActive = false; - Services.prefs.clearUserPref("browser.ml.linkPreview.onboardingTimes"); -}); - -/** - * Tests that canShowPreferences is true after the feature has been used, - * even if it's subsequently disabled. - */ -add_task(async function test_can_show_preferences_after_usage() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", false], - ["browser.ml.linkPreview.onboardingTimes", ""], - ], - }); - - ok( - !LinkPreview.canShowPreferences, - "canShowPreferences should be false when disabled and never used" - ); - - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingMaxShowFreq", 2], - ], - }); - - const generateStub = sinon.stub(LinkPreviewModel, "generateTextAI"); - const READABLE_PAGE_URL = - "https://example.com/browser/browser/components/genai/tests/browser/data/readableEn.html"; - - LinkPreview.keyboardComboActive = "shift"; - XULBrowserWindow.setOverLink(READABLE_PAGE_URL, {}); - - const panel = await waitForPanelOpen(); - ok(panel, "Panel created for link preview, simulating usage"); - - await SpecialPowers.pushPrefEnv({ - set: [["browser.ml.linkPreview.enabled", false]], - }); - - ok( - LinkPreview.canShowPreferences, - "canShowPreferences should be true after usage, even if disabled" - ); - - panel.remove(); - generateStub.restore(); - LinkPreview.keyboardComboActive = false; - Services.prefs.clearUserPref("browser.ml.linkPreview.onboardingTimes"); -}); diff --git a/browser/components/genai/tests/browser/browser_link_preview_optin.js b/browser/components/genai/tests/browser/browser_link_preview_optin.js @@ -504,3 +504,57 @@ add_task(async function test_locked_preference() { Services.prefs.unlockPref("browser.ml.linkPreview.optin"); }); + +/** + * Test that for an unsupported locale, the opt-in card is not shown and + * key points are hidden. + * + * This test verifies that if the current locale is not supported, the opt-in + * prompt will not be shown and no attempt will be made to generate key points. + */ +add_task(async function test_no_optin_or_keypoints_in_unsupported_locale() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.ml.linkPreview.enabled", true], + ["browser.ml.linkPreview.optin", false], + ["browser.ml.linkPreview.collapsed", false], + ], + }); + + const localeStub = sinon + .stub(LinkPreview, "_isLocaleSupported") + .returns(false); + const generateStub = sinon.stub(LinkPreviewModel, "generateTextAI"); + + LinkPreview.keyboardComboActive = true; + XULBrowserWindow.setOverLink( + "https://example.com/browser/browser/components/genai/tests/browser/data/readableEn.html", + {} + ); + + let panel = await TestUtils.waitForCondition(() => + document.getElementById("link-preview-panel") + ); + await BrowserTestUtils.waitForEvent(panel, "popupshown"); + const card = panel.querySelector("link-preview-card"); + ok(card, "card created for link preview"); + + ok( + !LinkPreview.canShowKeyPoints, + "LinkPreview should indicate key points cannot be shown" + ); + + // Verify that the opt-in element is NOT present + const modelOptinElement = card.shadowRoot.querySelector("model-optin"); + ok(!modelOptinElement, "model-optin element should NOT be present"); + + is( + generateStub.callCount, + 0, + "generateTextAI should not be called in an unsupported locale" + ); + + panel.remove(); + localeStub.restore(); + generateStub.restore(); +}); diff --git a/browser/components/genai/tests/browser/browser_link_preview_telemetry.js b/browser/components/genai/tests/browser/browser_link_preview_telemetry.js @@ -49,8 +49,8 @@ add_task(async function test_default_telemetry() { ); Assert.equal( Glean.genaiLinkpreview.enabled.testGetValue(), - false, - "Got default enabled for testing" + true, + "Got default disabled for testing" ); Assert.equal( Glean.genaiLinkpreview.keyPoints.testGetValue(), @@ -347,208 +347,6 @@ add_task( ); /** - * Tests that telemetry is recorded when the onboarding close button is clicked. - */ -add_task(async function test_onboarding_close_button_telemetry() { - Services.fog.testResetFOG(); - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ], - }); - - Assert.equal( - Glean.genaiLinkpreview.onboardingCard.testGetValue(), - null, - "No onboardingCard events initially" - ); - - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - const panel = await waitForPanelOpen("wait for onboarding panel"); - ok(panel, "Panel created for onboarding"); - - const onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "onboarding element is present"); - - const closeButton = onboarding_card.shadowRoot.querySelector( - "#onboarding-close-button" - ); - closeButton.click(); - - const events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 2, "Two onboardingCard event recorded"); - Assert.equal(events[0].extra.action, "view", "View action recorded"); - Assert.equal(events[1].extra.action, "close", "Closed action recorded"); - - // Cleanup - panel.remove(); -}); - -/** - * Tests that telemetry is recorded when the onboarding try it now button is clicked. - */ -add_task(async function test_try_it_now_button_telemetry() { - Services.fog.testResetFOG(); - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ], - }); - - Assert.equal( - Glean.genaiLinkpreview.onboardingCard.testGetValue(), - null, - "No onboardingCard events initially" - ); - - clearOverlink(); - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - const panel = await waitForPanelOpen("wait for onboarding panel"); - ok(panel, "Panel created for onboarding"); - - const onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "onboarding element is present"); - - let events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 1, "One onboardingCard events recorded"); - Assert.equal(events[0].extra.action, "view", "view action recorded"); - const tryItNowButton = onboarding_card.shadowRoot.querySelector( - "moz-button[data-l10n-id='link-preview-onboarding-button']" - ); - tryItNowButton.click(); - - events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 2, "Two onboardingCard events recorded"); - Assert.equal( - events[1].extra.action, - "try_it_now", - "try_it_now action recorded" - ); - - events = Glean.genaiLinkpreview.start.testGetValue(); - Assert.equal(events.length, 1, "One genaiLinkpreview card events recorded"); - Assert.equal( - events[0].extra.source, - "onboarding", - "Source should be onboarding" - ); - // Cleanup - panel.remove(); -}); - -/** - * Tests that telemetry is recorded with the correct type when onboarding is triggered by long press. - */ -add_task(async function test_onboarding_long_press_type_telemetry() { - Services.fog.testResetFOG(); - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ["browser.ml.linkPreview.longPress", true], - ], - }); - - Assert.equal( - Glean.genaiLinkpreview.onboardingCard.testGetValue(), - null, - "No onboardingCard events initially" - ); - - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - const panel = await waitForPanelOpen("wait for onboarding panel"); - ok(panel, "Panel created for onboarding"); - - const onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "onboarding element is present"); - - let events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 1, "One onboardingCard event recorded"); - Assert.equal(events[0].extra.action, "view", "View action recorded"); - Assert.equal( - events[0].extra.type, - "longPress", - "longPress type recorded for view" - ); - - const closeButton = onboarding_card.shadowRoot.querySelector( - "#onboarding-close-button" - ); - ok(closeButton, "close button is present"); - closeButton.click(); - - events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 2, "Two onboardingCard events recorded"); - Assert.equal(events[1].extra.action, "close", "Close action recorded"); - Assert.equal( - events[1].extra.type, - "longPress", - "longPress type recorded for close" - ); - - panel.remove(); -}); - -/** - * Tests that telemetry is recorded with the correct type (shift) when longPress is disabled. - */ -add_task(async function test_onboarding_shift_type_telemetry() { - Services.fog.testResetFOG(); - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.ml.linkPreview.enabled", true], - ["browser.ml.linkPreview.onboardingTimes", ""], - ["browser.ml.linkPreview.longPress", false], - ], - }); - - Assert.equal( - Glean.genaiLinkpreview.onboardingCard.testGetValue(), - null, - "No onboardingCard events initially" - ); - - XULBrowserWindow.setOverLink(TEST_LINK_URL_EN); - - const panel = await waitForPanelOpen("wait for onboarding panel"); - ok(panel, "Panel created for onboarding"); - - const onboarding_card = panel.querySelector("link-preview-card-onboarding"); - ok(onboarding_card, "onboarding element is present"); - - let events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 1, "One onboardingCard event recorded"); - Assert.equal(events[0].extra.action, "view", "View action recorded"); - Assert.equal( - events[0].extra.type, - "longPress", - "longPress type recorded for view" - ); - - const closeButton = onboarding_card.shadowRoot.querySelector( - "#onboarding-close-button" - ); - ok(closeButton, "close button is present"); - closeButton.click(); - - events = Glean.genaiLinkpreview.onboardingCard.testGetValue(); - Assert.equal(events.length, 2, "Two onboardingCard events recorded"); - Assert.equal(events[1].extra.action, "close", "Close action recorded"); - Assert.equal( - events[1].extra.type, - "longPress", - "longPress type recorded for close" - ); - - panel.remove(); -}); - -/** * Tests that the pref_changed telemetry event is recorded correctly for various preferences. */ add_task(async function test_pref_changed_event_telemetry() { diff --git a/browser/locales/en-US/browser/browserContext.ftl b/browser/locales/en-US/browser/browserContext.ftl @@ -181,7 +181,7 @@ main-context-menu-copy-phone = main-context-menu-preview-link = .label = Preview Link - .accesskey = r + .accesskey = J # "Copy Clean Link" means that Firefox will remove things from the link you # copied, like items that identify you for advertising purposes, and other items