commit d4f70937db51f61e3524ce676174ed5921034086
parent acf02673980a1e28ea660340e393d366a814ec6f
Author: Moritz Beier <mbeier@mozilla.com>
Date: Fri, 12 Dec 2025 14:04:01 +0000
Bug 2002978 - Make sure the new search bar's search mode does not depend on the selected tab. r=adw,search-reviewers,urlbar-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D275807
Diffstat:
4 files changed, 152 insertions(+), 42 deletions(-)
diff --git a/browser/components/search/test/browser/browser.toml b/browser/components/search/test/browser/browser.toml
@@ -85,6 +85,8 @@ skip-if = [
["browser_new_searchbar_init.js"]
+["browser_new_searchbar_searchmode.js"]
+
["browser_oneOff.js"]
["browser_oneOffContextMenu.js"]
diff --git a/browser/components/search/test/browser/browser_new_searchbar_searchmode.js b/browser/components/search/test/browser/browser_new_searchbar_searchmode.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether search mode works as expected in the new search bar.
+ * E.g. there should be a global search mode and it
+ * should not be tied to the selected tab.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ SearchbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+add_setup(async function () {
+ SearchbarTestUtils.init(this);
+ SpecialPowers.pushPrefEnv({
+ set: [["browser.search.widget.new", true]],
+ });
+ await gCUITestUtils.addSearchBar();
+ registerCleanupFunction(() => gCUITestUtils.removeSearchBar());
+
+ await SearchTestUtils.updateRemoteSettingsConfig([
+ { identifier: "engine1" },
+ { identifier: "engine2" },
+ { identifier: "engine3" },
+ ]);
+});
+
+add_task(async function searchModeSurvivesTabSwitch() {
+ let tab1 = gBrowser.selectedTab;
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let popup = await SearchbarTestUtils.openSearchModeSwitcher(window);
+ info("Press on the engine1 menu button to enter search mode");
+ let popupHidden = SearchbarTestUtils.searchModeSwitcherPopupClosed(window);
+ popup.querySelector("menuitem[label=engine1]").click();
+ await popupHidden;
+
+ await SearchbarTestUtils.assertSearchMode(window, {
+ engineName: "engine1",
+ entry: "searchbutton",
+ source: 3,
+ });
+
+ info("Switching tab. Search mode should not be affected.");
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await SearchbarTestUtils.assertSearchMode(window, {
+ engineName: "engine1",
+ entry: "searchbutton",
+ source: 3,
+ });
+
+ SearchbarTestUtils.exitSearchMode(window);
+ gBrowser.removeTab(tab2);
+});
diff --git a/browser/components/urlbar/content/UrlbarInput.mjs b/browser/components/urlbar/content/UrlbarInput.mjs
@@ -2256,35 +2256,38 @@ export class UrlbarInput extends HTMLElement {
}
/**
- * Gets the search mode for a specific browser instance.
+ * Addressbar: Gets the search mode for a specific browser instance.
+ * Searchbar: Gets the window-global search mode.
*
* @param {MozBrowser} browser
* The search mode for this browser will be returned.
+ * Pass the selected browser for the searchbar.
* @param {boolean} [confirmedOnly]
* Normally, if the browser has both preview and confirmed modes, preview
* mode will be returned since it takes precedence. If this argument is
* true, then only confirmed search mode will be returned, or null if
* search mode hasn't been confirmed.
- * @returns {object}
- * A search mode object. See setSearchMode documentation. If the browser
- * is not in search mode, then null is returned.
+ * @returns {?object}
+ * A search mode object or null if the browser/window is not in search mode.
+ * See setSearchMode documentation.
*/
getSearchMode(browser, confirmedOnly = false) {
- let modes = this.getBrowserState(browser).searchModes;
+ let modes = this.#getSearchModesObject(browser);
// Return copies so that callers don't modify the stored values.
- if (!confirmedOnly && modes?.preview) {
+ if (!confirmedOnly && modes.preview) {
return { ...modes.preview };
}
- if (modes?.confirmed) {
+ if (modes.confirmed) {
return { ...modes.confirmed };
}
return null;
}
/**
- * Sets search mode for a specific browser instance. If the given browser is
- * selected, then this will also enter search mode.
+ * Addressbar: Sets the search mode for a specific browser instance.
+ * Searchbar: Sets the window-global search mode.
+ * If the given browser is selected, then this will also enter search mode.
*
* @param {object} searchMode
* A search mode object.
@@ -2303,6 +2306,7 @@ export class UrlbarInput extends HTMLElement {
* be interacted with right away. Defaults to true.
* @param {MozBrowser} browser
* The browser for which to set search mode.
+ * Pass the selected browser for the searchbar.
*/
async setSearchMode(searchMode, browser) {
let currentSearchMode = this.getSearchMode(browser);
@@ -2354,7 +2358,7 @@ export class UrlbarInput extends HTMLElement {
}
}
- let state = this.getBrowserState(browser);
+ let modes = this.#getSearchModesObject(browser);
if (searchMode) {
searchMode.isPreview = isPreview;
@@ -2366,16 +2370,15 @@ export class UrlbarInput extends HTMLElement {
searchMode.entry = "other";
}
- // Add the search mode to the map.
if (!searchMode.isPreview) {
- state.searchModes = { confirmed: searchMode };
+ modes.confirmed = searchMode;
+ delete modes.preview;
} else {
- let modes = state.searchModes || {};
modes.preview = searchMode;
- state.searchModes = modes;
}
} else {
- delete state.searchModes;
+ delete modes.preview;
+ delete modes.confirmed;
}
if (restrictType) {
@@ -2402,11 +2405,50 @@ export class UrlbarInput extends HTMLElement {
}
/**
+ * @typedef {object} SearchModesObject
+ *
+ * @property {object} [preview] preview search mode
+ * @property {object} [confirmed] confirmed search mode
+ */
+
+ /**
+ * @type {SearchModesObject|undefined}
+ *
+ * The (lazily initialized) search mode object for the searchbar.
+ * This is needed because the searchbar has one search mode per window that
+ * shouldn't change when switching tabs. For the address bar, the search mode
+ * is stored per browser in #browserStates and this is always undefined.
+ */
+ #searchbarSearchModes;
+
+ /**
+ * Addressbar: Gets the search modes object for a specific browser instance.
+ * Searchbar: Gets the window-global search modes object.
+ *
+ * @param {MozBrowser} browser
+ * The browser to get the search modes object for.
+ * Pass the selected browser for the searchbar.
+ * @returns {SearchModesObject}
+ */
+ #getSearchModesObject(browser) {
+ if (!this.#isAddressbar) {
+ // The passed browser doesn't matter here, but it does in setSearchMode.
+ this.#searchbarSearchModes ??= {};
+ return this.#searchbarSearchModes;
+ }
+
+ let state = this.getBrowserState(browser);
+ state.searchModes ??= {};
+ return state.searchModes;
+ }
+
+ /**
* Restores the current browser search mode from a previously stored state.
*/
restoreSearchModeState() {
- let state = this.getBrowserState(this.window.gBrowser.selectedBrowser);
- this.searchMode = state.searchModes?.confirmed;
+ this.searchMode = this.#getSearchModesObject(
+ this.window.gBrowser.selectedBrowser
+ ).confirmed;
}
/**
diff --git a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs
@@ -951,37 +951,46 @@ class UrlbarInputTestUtils {
"Expected searchMode"
);
- // Check the textContent and l10n attributes of the indicator and label.
- let expectedTextContent = "";
- let expectedL10n = { id: null, args: null };
- if (expectedSearchMode.engineName) {
- expectedTextContent = expectedSearchMode.engineName;
- } else if (expectedSearchMode.source) {
- let name = UrlbarUtils.getResultSourceName(expectedSearchMode.source);
- this.Assert.ok(name, "Expected result source should have a name");
- expectedL10n = { id: `urlbar-search-mode-${name}`, args: null };
- } else {
- this.Assert.ok(false, "Unexpected searchMode");
- }
+ // Only the addressbar still has the legacy search mode indicator.
+ if (this.#urlbar(window).sapName == "urlbar") {
+ // Check the textContent and l10n attributes of the indicator and label.
+ let expectedTextContent = "";
+ let expectedL10n = { id: null, args: null };
+ if (expectedSearchMode.engineName) {
+ expectedTextContent = expectedSearchMode.engineName;
+ } else if (expectedSearchMode.source) {
+ let name = UrlbarUtils.getResultSourceName(expectedSearchMode.source);
+ this.Assert.ok(name, "Expected result source should have a name");
+ expectedL10n = { id: `urlbar-search-mode-${name}`, args: null };
+ } else {
+ this.Assert.ok(false, "Unexpected searchMode");
+ }
- if (expectedTextContent) {
- this.Assert.equal(
- this.#urlbar(window)._searchModeIndicatorTitle.textContent,
- expectedTextContent,
- "Expected textContent"
+ if (expectedTextContent) {
+ this.Assert.equal(
+ this.#urlbar(window)._searchModeIndicatorTitle.textContent,
+ expectedTextContent,
+ "Expected textContent"
+ );
+ }
+ this.Assert.deepEqual(
+ window.document.l10n.getAttributes(
+ this.#urlbar(window)._searchModeIndicatorTitle
+ ),
+ expectedL10n,
+ "Expected l10n"
);
}
- this.Assert.deepEqual(
- window.document.l10n.getAttributes(
- this.#urlbar(window)._searchModeIndicatorTitle
- ),
- expectedL10n,
- "Expected l10n"
- );
// Check the input's placeholder.
let expectedPlaceholderL10n;
- if (expectedSearchMode.engineName) {
+ if (this.#urlbar(window).sapName == "searchbar") {
+ // Placeholder stays constant in searchbar.
+ expectedPlaceholderL10n = {
+ id: "searchbar-input",
+ args: null,
+ };
+ } else if (expectedSearchMode.engineName) {
expectedPlaceholderL10n = {
id: isGeneralPurposeEngine
? "urlbar-placeholder-search-mode-web-2"