tor-browser

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

commit a4351b9ca598fe1ecaa5d542946c948a1fbccbaa
parent 2acbcaf1528001fbce3e98685f5f2f4f47b55f6a
Author: Narcis Beleuzu <nbeleuzu@mozilla.com>
Date:   Fri, 24 Oct 2025 03:06:27 +0300

Revert "Bug 1984747: Enable Link Previews by default - r=Mardak,firefox-ai-ml-reviewers" for causing bc failure on browser_contextmenu.js

This reverts commit de22534abec8ebcad2eb72dce464c4450d08f4bf.

Diffstat:
Mbrowser/app/profile/firefox.js | 5++---
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+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abrowser/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++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
10 files changed, 629 insertions(+), 181 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -2205,17 +2205,16 @@ 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", true); +pref("browser.ml.linkPreview.enabled", false); pref("browser.ml.linkPreview.ignoreMs", 2000); pref("browser.ml.linkPreview.longPress", true); pref("browser.ml.linkPreview.longPressMs", 1000); -pref("browser.ml.linkPreview.noKeyPointsRegions", ""); +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.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/sync/browser_contextmenu_sendpage.js b/browser/base/content/test/sync/browser_contextmenu_sendpage.js @@ -120,7 +120,6 @@ 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,6 +27,12 @@ XPCOMUtils.defineLazyPreferenceGetter( ); XPCOMUtils.defineLazyPreferenceGetter( lazy, + "cfrFeatures", + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", + true +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, "collapsed", "browser.ml.linkPreview.collapsed", null, @@ -74,6 +80,12 @@ 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 @@ -82,7 +94,7 @@ XPCOMUtils.defineLazyPreferenceGetter( lazy, "onboardingMaxShowFreq", "browser.ml.linkPreview.onboardingMaxShowFreq", - 0 + 2 ); XPCOMUtils.defineLazyPreferenceGetter( lazy, @@ -129,11 +141,6 @@ 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 @@ -165,11 +172,7 @@ export const LinkPreview = { }, get canShowKeyPoints() { - return ( - this._isRegionSupported() && - this._isLocaleSupported() && - !this._isDisabledByPolicy() - ); + return this._isRegionSupported() && !this._isDisabledByPolicy(); }, get canShowLegacy() { @@ -177,12 +180,29 @@ export const LinkPreview = { }, get canShowPreferences() { - // The setting is always shown. - return true; + // 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; }, get showOnboarding() { - return false; + // 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 + ); }, shouldShowContextMenu(nsContextMenu) { @@ -747,20 +767,6 @@ 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. @@ -789,7 +795,7 @@ export const LinkPreview = { ogCard.optin = lazy.optin; ogCard.collapsed = lazy.collapsed; - ogCard.canShowKeyPoints = this.canShowKeyPoints; + ogCard.regionSupported = this._isRegionSupported(); // Reflect the shared download progress to this preview. const updateProgress = () => { @@ -803,10 +809,15 @@ 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, and if key points can be shown. + // language or restricted. 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,7 +420,14 @@ class LinkPreviewCard extends MozLitElement { * @returns {import('lit').TemplateResult} The content card HTML */ renderKeyPointsSection(pageUrl) { - if (!this.canShowKeyPoints) { + if (!this.regionSupported) { + return ""; + } + + if ( + !this.optin && + Services.prefs.prefIsLocked("browser.ml.linkPreview.optin") + ) { return ""; } diff --git a/browser/components/genai/tests/browser/browser.toml b/browser/components/genai/tests/browser/browser.toml @@ -46,6 +46,12 @@ 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,7 +26,6 @@ const TEST_LINK_URL = "https://example.com"; registerCleanupFunction(() => { Services.prefs.clearUserPref("browser.ml.linkPreview.onboardingTimes"); - Services.prefs.clearUserPref("browser.ml.linkPreview.supportedLocales"); }); /** @@ -941,89 +940,3 @@ 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,6 +9,61 @@ 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 @@ -0,0 +1,310 @@ +/* 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,57 +504,3 @@ 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(), - true, - "Got default disabled for testing" + false, + "Got default enabled for testing" ); Assert.equal( Glean.genaiLinkpreview.keyPoints.testGetValue(), @@ -347,6 +347,208 @@ 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() {