tor-browser

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

commit 595a04dfb0a47baf5615974930143b020a347dda
parent a0dd1f06871848e2d73df3422ebdaed618c62f69
Author: Vasish Baungally <vbaungally@mozilla.com>
Date:   Tue,  2 Dec 2025 16:14:04 +0000

Bug 2002171 - Fix Smart Tab Grouping Checkbox Suggestions. r=tabbrowser-reviewers,sthompson

https://phabricator.services.mozilla.com/D268050 made a change where unchecking a checkbox defaults the "value"
to "on". This caused an issue with AI Tab Grouping where the tab wasn't being properly filtered when unchecked.
Added a test to capture this from now on.

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

Diffstat:
Mbrowser/components/tabbrowser/content/tabgroup-menu.js | 10+++-------
Mbrowser/components/tabbrowser/test/browser/smarttabgrouping/browser.toml | 2++
Abrowser/components/tabbrowser/test/browser/smarttabgrouping/browser_tab_grouping_suggestions_checkbox.js | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 203 insertions(+), 7 deletions(-)

diff --git a/browser/components/tabbrowser/content/tabgroup-menu.js b/browser/components/tabbrowser/content/tabgroup-menu.js @@ -1188,7 +1188,6 @@ #createRow(tab) { // Create Checkbox let checkbox = document.createElement("moz-checkbox"); - checkbox.value = tab; checkbox.label = tab.label; checkbox.iconSrc = tab.image; checkbox.checked = true; @@ -1197,14 +1196,11 @@ "text-truncated-ellipsis" ); checkbox.addEventListener("change", e => { - const isChecked = e.target.checked; - const currentTab = e.target.value; - - if (isChecked) { - this.#selectedSuggestedTabs.push(currentTab); + if (e.target.checked) { + this.#selectedSuggestedTabs.push(tab); } else { this.#selectedSuggestedTabs = this.#selectedSuggestedTabs.filter( - t => t != currentTab + t => t != tab ); } }); diff --git a/browser/components/tabbrowser/test/browser/smarttabgrouping/browser.toml b/browser/components/tabbrowser/test/browser/smarttabgrouping/browser.toml @@ -12,6 +12,8 @@ support-files = [ ["browser_tab_grouping_search.js"] +["browser_tab_grouping_suggestions_checkbox.js"] + ["browser_tab_grouping_telemetry.js"] ["browser_tab_grouping_utils.js"] diff --git a/browser/components/tabbrowser/test/browser/smarttabgrouping/browser_tab_grouping_suggestions_checkbox.js b/browser/components/tabbrowser/test/browser/smarttabgrouping/browser_tab_grouping_suggestions_checkbox.js @@ -0,0 +1,198 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* This test verifies that the Smart Tab Grouping suggestions checkbox + * correctly tracks the *tab* objects, not the moz-checkbox "value" + * string ("on"), and that unchecking a suggestion causes it to be + * excluded from the final addTabs() call. + */ + +"use strict"; +/** + * Helper that opens the tab group editor in "create" mode for the given tab + * and waits for the panel to be shown. + * + * @param {XULElement} tabgroupPanel + * @param {MozTabbrowserTab} tab + */ +async function openCreatePanel(tabgroupPanel, tab) { + let panelShown = BrowserTestUtils.waitForPopupEvent(tabgroupPanel, "shown"); + gBrowser.addTabGroup([tab], { + isUserTriggered: true, + }); + await panelShown; +} + +add_task(async function test_smart_tab_group_suggestions_checkbox_selection() { + // Make sure we start from a clean telemetry state so we don't interfere + // with the dedicated telemetry tests in this directory. + Services.fog.testResetFOG(); + + await SpecialPowers.pushPrefEnv({ + set: [ + // Enable tab groups + smart tab groups + ML. + ["browser.tabs.groups.enabled", true], + ["browser.tabs.groups.smart.enabled", true], + ["browser.tabs.groups.smart.userEnabled", true], + ["browser.tabs.groups.smart.optin", true], + ["browser.ml.enable", true], + ], + }); + + // ---- SmartTabGroupingManager stubs ------------------------------------ + // + // We want: + // - full control over which tabs are "suggested" + // - no Glean telemetry emitted from this test + + const origSmartTabGroupingForGroup = + SmartTabGroupingManager.prototype.smartTabGroupingForGroup; + const origHandleSuggestTelemetry = + SmartTabGroupingManager.prototype.handleSuggestTelemetry; + const origHandleLabelTelemetry = + SmartTabGroupingManager.prototype.handleLabelTelemetry; + + let suggestedTabs; + + SmartTabGroupingManager.prototype.smartTabGroupingForGroup = async function ( + _group, + _tabs + ) { + // Just return our test-controlled list. + return suggestedTabs; + }; + + SmartTabGroupingManager.prototype.handleSuggestTelemetry = function () {}; + SmartTabGroupingManager.prototype.handleLabelTelemetry = function () {}; + + registerCleanupFunction(() => { + SmartTabGroupingManager.prototype.smartTabGroupingForGroup = + origSmartTabGroupingForGroup; + SmartTabGroupingManager.prototype.handleSuggestTelemetry = + origHandleSuggestTelemetry; + SmartTabGroupingManager.prototype.handleLabelTelemetry = + origHandleLabelTelemetry; + + // Clean up any telemetry from this test so subsequent tests + // (especially the telemetry-focused ones) see a clean slate. + Services.fog.testResetFOG(); + }); + + // ---- Tab setup -------------------------------------------------------- + + // Base tab that will start in the group. + let baseTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + // Tabs that will be "suggested" by SmartTabGroupingManager. + let tab1 = BrowserTestUtils.addTab(gBrowser, "https://example.com/1"); + let tab2 = BrowserTestUtils.addTab(gBrowser, "https://example.com/2"); + let tab3 = BrowserTestUtils.addTab(gBrowser, "https://example.com/3"); + + suggestedTabs = [tab1, tab2, tab3]; + + registerCleanupFunction(() => { + for (let t of [baseTab, tab1, tab2, tab3]) { + if (t && !t.closing && gBrowser.tabs.includes(t)) { + BrowserTestUtils.removeTab(t); + } + } + }); + + // ---- Use the real tab group editor UI --------------------------------- + + let tabgroupEditor = document.getElementById("tab-group-editor"); + Assert.ok( + tabgroupEditor, + "We should have the built-in <tabgroup-menu id='tab-group-editor'> element" + ); + + let tabgroupPanel = tabgroupEditor.panel; + Assert.ok(tabgroupPanel, "tab-group-editor should expose a panel property"); + + // Open the editor panel in "create" mode for baseTab. + await openCreatePanel(tabgroupPanel, baseTab); + + // activeGroup is wired up by the tabgroup-menu implementation. + let group = tabgroupEditor.activeGroup; + Assert.ok(group, "tabgroup-editor should have an activeGroup after open"); + + // Kick off the AI suggestions flow via the button in the panel. + let suggestButton = tabgroupPanel.querySelector( + "#tab-group-suggestion-button" + ); + Assert.ok( + suggestButton, + "Suggestion button should exist in the tab group panel" + ); + Assert.ok( + !suggestButton.hidden, + "Suggestion button should be visible when smart tab groups is enabled" + ); + + suggestButton.click(); + + // Wait for moz-checkbox suggestion rows to be created. + await BrowserTestUtils.waitForCondition( + () => + tabgroupPanel.querySelectorAll(".tab-group-suggestion-checkbox") + .length === suggestedTabs.length, + "Waiting for suggestion checkboxes to be created" + ); + + let checkboxes = tabgroupPanel.querySelectorAll( + ".tab-group-suggestion-checkbox" + ); + Assert.equal( + checkboxes.length, + suggestedTabs.length, + "We should have one checkbox per suggested tab" + ); + + // Initially, all suggestions are checked. + for (let checkbox of checkboxes) { + Assert.ok(checkbox.checked, "Each suggestion checkbox starts checked"); + } + + // Uncheck the second suggested tab. + // + // The bug this test guards against: + // - moz-checkbox .value defaults to "on" + // - Using checkbox.value in the change handler stores "on" instead of + // the actual Tab object, so filtering by that later fails. + checkboxes[1].click(); + Assert.ok( + !checkboxes[1].checked, + "Second suggestion checkbox should now be unchecked" + ); + + // Confirm the suggestions. Internally this calls: + // this.activeGroup.addTabs(this.#selectedSuggestedTabs); + let doneButton = tabgroupPanel.querySelector( + "#tab-group-create-suggestions-button" + ); + Assert.ok(doneButton, "Should find the 'Done' suggestions button"); + + // Listen for TabGrouped events for the selected tabs before clicking Done. + let tab1Grouped = BrowserTestUtils.waitForEvent( + group, + "TabGrouped", + false, + e => e.detail == tab1 + ); + let tab3Grouped = BrowserTestUtils.waitForEvent( + group, + "TabGrouped", + false, + e => e.detail == tab3 + ); + + let panelHidden = BrowserTestUtils.waitForPopupEvent(tabgroupPanel, "hidden"); + doneButton.click(); + + await Promise.all([panelHidden, tab1Grouped, tab3Grouped]); + + Assert.ok( + !tab2.group, + "tab 2 should not have been grouped because it was deselected" + ); +});