diff --git a/src/zen/tests/ub-actions/browser_ub_actions_search.js b/src/zen/tests/ub-actions/browser_ub_actions_search.js index 82e2f7b69..bdefbf4be 100644 --- a/src/zen/tests/ub-actions/browser_ub_actions_search.js +++ b/src/zen/tests/ub-actions/browser_ub_actions_search.js @@ -5,6 +5,8 @@ ChromeUtils.defineESModuleGetters(this, { globalActions: "resource:///modules/ZenUBGlobalActions.sys.mjs", + SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", + TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs", UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs", }); @@ -37,3 +39,29 @@ add_task(async function test_Ub_Actions_Search() { ); } }); + +add_task(async function test_Ub_Actions_Exact_Match_Ranks_First() { + // With a closed tab in history, "Reopen Closed Tab" is available too, and it + // contains "close tab". The exact label must still be the first action. + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "https://example.com" + ); + await TabStateFlusher.flush(tab.linkedBrowser); + BrowserTestUtils.removeTab(tab); + await TestUtils.waitForCondition( + () => SessionStore.getClosedTabCount(window) > 0, + "Waiting for the closed tab to be recorded" + ); + + const closeTab = globalActions.find(a => a.command === "cmd_close"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + waitForFocus, + value: closeTab.label, + }); + let { result } = await UrlbarTestUtils.getRowAt(window, 1); + Assert.equal(result.providerName, "ZenUrlbarProviderGlobalActions"); + Assert.equal(result.payload.title, closeTab.label); + SessionStore.forgetClosedTab(window, 0); +}); diff --git a/src/zen/urlbar/ZenUBActionsProvider.sys.mjs b/src/zen/urlbar/ZenUBActionsProvider.sys.mjs index cc98ee276..6c6273893 100644 --- a/src/zen/urlbar/ZenUBActionsProvider.sys.mjs +++ b/src/zen/urlbar/ZenUBActionsProvider.sys.mjs @@ -18,6 +18,13 @@ const MAX_RECENT_ACTIONS = 5; const MINIMUM_QUERY_SCORE = 92; const MINIMUM_PREFIXED_QUERY_SCORE = 30; +// Exact and prefix matches must outrank any fuzzy match. The fuzzy score grows +// quadratically with a run of consecutive matches, so a longer label that merely +// contains the query ("Reopen Closed Tab" for "close tab") could beat the label +// that is exactly the query. +const EXACT_MATCH_SCORE = 1_000_000; +const PREFIX_MATCH_SCORE = 100_000; + ChromeUtils.defineESModuleGetters(lazy, { UrlbarResult: "chrome://browser/content/urlbar/UrlbarResult.mjs", BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", @@ -298,11 +305,11 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { } // 1. Exact match gets the highest score. if (targetLower === queryLower) { - return 200; + return EXACT_MATCH_SCORE; } // 2. Exact prefix matches are heavily prioritized. if (targetLower.startsWith(queryLower)) { - return 100 + queryLen; + return PREFIX_MATCH_SCORE + queryLen; } let score = 0; let queryIndex = 0;