diff --git a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs index 2c015df99754..98b5c34ecb3f 100644 --- a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs @@ -265,7 +265,7 @@ class ProviderQuickSuggest extends UrlbarProvider { let { result } = details; // Delegate to the result's feature if there is one. - let feature = lazy.QuickSuggest.getFeatureByResult(details.result); + let feature = lazy.QuickSuggest.getFeatureByResult(result); if (feature) { feature.onEngagement( queryContext, diff --git a/browser/components/urlbar/private/DynamicSuggestions.sys.mjs b/browser/components/urlbar/private/DynamicSuggestions.sys.mjs index 9e2ce4739c37..c470597897a7 100644 --- a/browser/components/urlbar/private/DynamicSuggestions.sys.mjs +++ b/browser/components/urlbar/private/DynamicSuggestions.sys.mjs @@ -117,6 +117,22 @@ export class DynamicSuggestions extends SuggestProvider { ); } + onEngagement(_queryContext, controller, details, _searchString) { + switch (details.selType) { + case "manage": + // "manage" is handled by UrlbarInput, no need to do anything here. + break; + case "dismiss": + let { result } = details; + lazy.QuickSuggest.dismissResult(result); + result.acknowledgeDismissalL10n = { + id: "firefox-suggest-dismissal-acknowledgment-one", + }; + controller.removeResult(result); + break; + } + } + #makeExposureResult(suggestion, payload) { // It doesn't really matter what kind of result we return since it won't be // shown. Use a dynamic result since that kind of makes sense and there are diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser.toml b/browser/components/urlbar/tests/quicksuggest/browser/browser.toml index 3782d2f03e06..cfbd5c81a03f 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser.toml +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser.toml @@ -21,6 +21,8 @@ prefs = ["browser.bookmarks.testing.skipDefaultBookmarksImport=true"] ["browser_quicksuggest_contextual_optin.js"] +["browser_quicksuggest_dynamicSuggestions.js"] + ["browser_quicksuggest_fakespot.js"] ["browser_quicksuggest_indexes.js"] diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_dynamicSuggestions.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_dynamicSuggestions.js new file mode 100644 index 000000000000..c77e70d34e58 --- /dev/null +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_dynamicSuggestions.js @@ -0,0 +1,252 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for dynamicSuggestions. + +const REMOTE_SETTINGS_DATA = [ + { + type: "dynamic-suggestions", + suggestion_type: "basic", + attachment: [ + { + keywords: ["basic"], + data: { + result: { + payload: { + title: "basic", + url: "https://example.com/basic", + icon: "https://example.com/basic.svg", + }, + }, + }, + }, + ], + }, + { + type: "dynamic-suggestions", + suggestion_type: "shouldShowUrl", + attachment: [ + { + keywords: ["shouldshowurl"], + data: { + result: { + payload: { + title: "shouldShowUrl", + url: "https://example.com/shouldShowUrl", + icon: "https://example.com/shouldShowUrl.svg", + shouldShowUrl: true, + }, + }, + }, + }, + ], + }, + { + type: "dynamic-suggestions", + suggestion_type: "isBlockable", + attachment: [ + { + keywords: ["isblockable"], + dismissal_key: "isblockable-dismissal-key", + data: { + result: { + payload: { + title: "isBlockable", + url: "https://example.com/isBlockable", + icon: "https://example.com/isBlockable.svg", + isBlockable: true, + }, + }, + }, + }, + ], + }, + { + type: "dynamic-suggestions", + suggestion_type: "rowLabel", + attachment: [ + { + keywords: ["rowlabel"], + data: { + result: { + rowLabel: { + id: "urlbar-group-search-suggestions", + args: { engine: "Test" }, + }, + payload: { + url: "https://example.com/rowLabel", + }, + }, + }, + }, + ], + }, +]; + +add_setup(async function () { + await QuickSuggestTestUtils.ensureQuickSuggestInit({ + remoteSettingsRecords: REMOTE_SETTINGS_DATA, + }); + await UrlbarTestUtils.initNimbusFeature({ + quickSuggestDynamicSuggestionTypes: + "basic,shouldShowUrl,isBlockable,rowLabel", + }); + + // Wait until dynamic suggestion is available. + await BrowserTestUtils.waitForCondition(async () => { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "basic", + }); + const result = UrlbarTestUtils.getResultCount(window) == 2; + await UrlbarTestUtils.promisePopupClose(window); + return result; + }); +}); + +add_task(async function basic() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + info("Open urlbar with keyword"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "basic", + }); + Assert.equal(UrlbarTestUtils.getResultCount(window), 2); + + info("Check the result"); + const { element, result } = await UrlbarTestUtils.getDetailsOfResultAt( + window, + 1 + ); + Assert.equal(result.providerName, UrlbarProviderQuickSuggest.name); + Assert.equal(result.payload.provider, "Dynamic"); + assertUI( + element.row, + REMOTE_SETTINGS_DATA[0].attachment[0].data.result.payload + ); + + info("Activate this item"); + const onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + result.payload.url + ); + EventUtils.synthesizeMouseAtCenter(element.row, {}); + await onLoad; + Assert.ok(true, "Expected page is loaded"); + }); + + await PlacesUtils.history.clear(); +}); + +add_task(async function basic_learn_more() { + info("Open urlbar with keyword"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "basic", + }); + + info("Selecting Learn more item from the result menu"); + let tabOpenPromise = BrowserTestUtils.waitForNewTab( + gBrowser, + QuickSuggest.HELP_URL + ); + await UrlbarTestUtils.openResultMenuAndClickItem(window, "help", { + resultIndex: 1, + }); + await tabOpenPromise; + gBrowser.removeCurrentTab(); +}); + +add_task(async function basic_manage() { + await doManageTest({ index: 1, input: "basic" }); +}); + +add_task(async function shouldShowUrl() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + info("Open urlbar with keyword"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "shouldshowurl", + }); + Assert.equal(UrlbarTestUtils.getResultCount(window), 2); + + info("Check the result"); + const { element, result } = await UrlbarTestUtils.getDetailsOfResultAt( + window, + 1 + ); + Assert.equal(result.providerName, UrlbarProviderQuickSuggest.name); + Assert.equal(result.payload.provider, "Dynamic"); + assertUI( + element.row, + REMOTE_SETTINGS_DATA[1].attachment[0].data.result.payload + ); + }); +}); + +add_task(async function isBlockable() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + info("Open urlbar with keyword"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "isblockable", + }); + Assert.equal(UrlbarTestUtils.getResultCount(window), 2); + + info("Check the result"); + const { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); + Assert.equal(result.providerName, UrlbarProviderQuickSuggest.name); + Assert.equal(result.payload.provider, "Dynamic"); + + info("Dismiss this item"); + let dismissalPromise = TestUtils.topicObserved( + "quicksuggest-dismissals-changed" + ); + await UrlbarTestUtils.openResultMenuAndClickItem(window, "dismiss", { + resultIndex: 1, + }); + await dismissalPromise; + + Assert.ok( + UrlbarTestUtils.isPopupOpen(window), + "View remains open after blocking result" + ); + Assert.ok( + await QuickSuggest.isResultDismissed(result), + "Result should be dismissed" + ); + + await UrlbarTestUtils.promisePopupClose(window); + await QuickSuggest.clearDismissedSuggestions(); + }); +}); + +add_task(async function rowLabel() { + info("Open urlbar with keyword"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "rowlabel", + }); + Assert.equal(UrlbarTestUtils.getResultCount(window), 2); + + info("Check the row label"); + const { element } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); + Assert.equal(element.row.getAttribute("label"), "Test suggestions"); +}); + +function assertUI(row, payload) { + const titleElement = row.querySelector(".urlbarView-title"); + Assert.equal(titleElement.textContent, payload.title); + + const faviconElement = row.querySelector(".urlbarView-favicon"); + Assert.equal(faviconElement.src, payload.icon); + + const urlElement = row.querySelector(".urlbarView-url"); + const displayUrl = payload.shouldShowUrl + ? payload.url.replace(/^https:\/\//, "") + : ""; + Assert.equal(urlElement.textContent, displayUrl); +} diff --git a/browser/themes/shared/urlbarView.css b/browser/themes/shared/urlbarView.css index 501ef29704da..8d212e463fb1 100644 --- a/browser/themes/shared/urlbarView.css +++ b/browser/themes/shared/urlbarView.css @@ -40,6 +40,7 @@ --urlbarView-favicon-margin-end: var(--urlbarView-icon-margin-end); --urlbarView-rich-suggestion-default-icon-size: 28px; + --urlbarView-top-pick-large-icon-box-size: 52px; --urlbarView-result-button-size: 24px; --urlbarView-result-button-background-opacity: 60%; @@ -858,6 +859,12 @@ flex-basis: 24px; } + &[icon-size="32"] { + width: 32px; + height: 32px; + flex-basis: 32px; + } + &[icon-size="38"] { width: 38px; height: 38px; @@ -938,7 +945,7 @@ } .urlbarView-row:is([type$=_amo], [type$=_pocket])[icon-size="24"] > .urlbarView-row-inner > .urlbarView-favicon { - padding: calc((52px - 24px) / 2); + padding: calc((var(--urlbarView-top-pick-large-icon-box-size) - 24px) / 2); background-color: var(--urlbar-box-focus-bgcolor); border-radius: 2px; } @@ -947,6 +954,10 @@ background-color: var(--urlbarView-result-button-selected-background-color); } +.urlbarView-row[type$=_vpn][icon-size="32"] > .urlbarView-row-inner > .urlbarView-favicon { + padding: calc((var(--urlbarView-top-pick-large-icon-box-size) - 32px) / 2); +} + .urlbarView-row[dynamicType=weather], .urlbarView-row[type=weather] { /* Use the colors in the icon SVG files except in HCM and when the row is