From 40a355a42d4a9444dc753c04c6608dade2f06a23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:27 +0200 Subject: Adding upstream version 125.0.1. Signed-off-by: Daniel Baumann --- .../urlbar/QuickActionsLoaderDefault.sys.mjs | 9 + browser/components/urlbar/UrlbarController.sys.mjs | 132 ++--- .../components/urlbar/UrlbarEventBufferer.sys.mjs | 4 +- browser/components/urlbar/UrlbarInput.sys.mjs | 153 +++-- browser/components/urlbar/UrlbarPrefs.sys.mjs | 8 +- .../urlbar/UrlbarProviderAboutPages.sys.mjs | 2 +- .../urlbar/UrlbarProviderAliasEngines.sys.mjs | 3 +- .../urlbar/UrlbarProviderAutofill.sys.mjs | 44 +- .../urlbar/UrlbarProviderCalculator.sys.mjs | 4 +- .../urlbar/UrlbarProviderClipboard.sys.mjs | 5 +- .../urlbar/UrlbarProviderContextualSearch.sys.mjs | 14 +- .../urlbar/UrlbarProviderHeuristicFallback.sys.mjs | 23 +- .../urlbar/UrlbarProviderInputHistory.sys.mjs | 55 +- .../urlbar/UrlbarProviderInterventions.sys.mjs | 7 +- .../urlbar/UrlbarProviderOmnibox.sys.mjs | 4 +- .../urlbar/UrlbarProviderOpenTabs.sys.mjs | 94 +++- .../components/urlbar/UrlbarProviderPlaces.sys.mjs | 4 +- .../urlbar/UrlbarProviderPrivateSearch.sys.mjs | 3 +- .../urlbar/UrlbarProviderQuickActions.sys.mjs | 4 +- .../urlbar/UrlbarProviderQuickSuggest.sys.mjs | 17 +- ...lbarProviderQuickSuggestContextualOptIn.sys.mjs | 8 +- .../urlbar/UrlbarProviderRemoteTabs.sys.mjs | 2 +- .../urlbar/UrlbarProviderSearchSuggestions.sys.mjs | 6 +- .../urlbar/UrlbarProviderSearchTips.sys.mjs | 9 +- .../urlbar/UrlbarProviderTabToSearch.sys.mjs | 15 +- .../urlbar/UrlbarProviderTokenAliasEngines.sys.mjs | 21 +- .../urlbar/UrlbarProviderTopSites.sys.mjs | 100 ++-- .../urlbar/UrlbarProviderUnitConversion.sys.mjs | 2 +- .../urlbar/UrlbarProviderWeather.sys.mjs | 6 +- .../components/urlbar/UrlbarSearchOneOffs.sys.mjs | 6 +- .../components/urlbar/UrlbarSearchUtils.sys.mjs | 8 +- browser/components/urlbar/UrlbarUtils.sys.mjs | 116 ++-- browser/components/urlbar/UrlbarView.sys.mjs | 115 ++-- .../urlbar/content/enUS-searchFeatures.ftl | 5 + .../urlbar/content/quicksuggestOnboarding.css | 2 +- browser/components/urlbar/docs/overview.rst | 18 +- browser/components/urlbar/docs/ranking.rst | 2 +- browser/components/urlbar/docs/telemetry.rst | 13 +- browser/components/urlbar/docs/testing.rst | 16 +- browser/components/urlbar/docs/utilities.rst | 8 +- browser/components/urlbar/metrics.yaml | 152 +---- .../urlbar/private/AddonSuggestions.sys.mjs | 2 +- .../components/urlbar/private/AdmWikipedia.sys.mjs | 2 +- .../components/urlbar/private/BaseFeature.sys.mjs | 28 +- .../urlbar/private/MDNSuggestions.sys.mjs | 4 +- .../urlbar/private/SuggestBackendJs.sys.mjs | 1 - .../urlbar/private/SuggestBackendRust.sys.mjs | 43 +- browser/components/urlbar/private/Weather.sys.mjs | 151 +++-- .../urlbar/private/YelpSuggestions.sys.mjs | 4 +- .../urlbar/tests/UrlbarTestUtils.sys.mjs | 7 +- .../browser-tips/browser_searchTips_interaction.js | 2 +- .../browser_appendSpanCount.js | 2 +- .../urlbar/tests/browser-updateResults/head.js | 2 +- .../components/urlbar/tests/browser/browser.toml | 3 +- .../browser_acknowledgeFeedbackAndDismissal.js | 162 ++++-- .../tests/browser/browser_add_search_engine.js | 5 +- .../urlbar/tests/browser/browser_autoOpen.js | 2 +- .../urlbar/tests/browser/browser_bestMatch.js | 6 +- .../urlbar/tests/browser/browser_blanking.js | 2 +- .../urlbar/tests/browser/browser_clipboard.js | 43 +- .../tests/browser/browser_copy_during_load.js | 2 +- .../urlbar/tests/browser/browser_copying.js | 2 +- .../urlbar/tests/browser/browser_dragdropURL.js | 4 +- .../urlbar/tests/browser/browser_dynamicResults.js | 6 +- .../urlbar/tests/browser/browser_groupLabels.js | 4 +- .../tests/browser/browser_locationBarCommand.js | 2 +- .../browser/browser_locationBarExternalLoad.js | 4 +- .../browser_locationchange_urlbar_edit_dos.js | 2 +- .../browser/browser_oneOffs_heuristicRestyle.js | 2 +- .../tests/browser/browser_oneOffs_settings.js | 2 +- .../urlbar/tests/browser/browser_recentsearches.js | 94 +++- .../urlbar/tests/browser/browser_redirect_error.js | 2 +- .../urlbar/tests/browser/browser_remove_match.js | 2 +- .../tests/browser/browser_result_onSelection.js | 2 +- .../browser/browser_results_format_displayValue.js | 2 +- .../browser/browser_retainedResultsOnFocus.js | 2 +- .../urlbar/tests/browser/browser_revert.js | 2 +- .../tests/browser/browser_searchMode_indicator.js | 4 +- .../browser_searchMode_indicator_clickthrough.js | 4 +- .../browser_searchMode_localOneOffs_actionText.js | 2 +- .../browser/browser_searchMode_suggestions.js | 20 +- .../browser_searchSingleWordNotification.js | 10 +- .../tests/browser/browser_search_continuation.js | 64 ++- .../tests/browser/browser_selectStaleResults.js | 2 +- .../browser/browser_shortcuts_add_search_engine.js | 2 +- ...ser_speculative_connect_not_with_client_cert.js | 2 +- .../urlbar/tests/browser/browser_stop.js | 2 +- .../urlbar/tests/browser/browser_strip_on_share.js | 4 +- .../browser/browser_strip_on_share_telemetry.js | 2 +- .../browser/browser_switchTab_inputHistory.js | 69 ++- .../tests/browser/browser_switchTab_override.js | 2 +- .../browser/browser_tabMatchesInAwesomebar.js | 2 +- .../urlbar/tests/browser/browser_tabToSearch.js | 6 +- .../tests/browser/browser_top_sites_switchtab.js | 209 +++++++ .../browser/browser_urlbar_telemetry_dynamic.js | 2 +- .../tests/browser/browser_urlbar_telemetry_tip.js | 4 +- .../tests/browser/browser_valueOnTabSwitch.js | 2 +- .../browser/browser_view_removedSelectedElement.js | 2 +- .../tests/browser/browser_view_selectionByMouse.js | 7 +- browser/components/urlbar/tests/browser/head.js | 2 +- .../tests/engagementTelemetry/browser/browser.toml | 22 +- .../browser_glean_telemetry_abandonment_tips.js | 2 +- .../browser_glean_telemetry_abandonment_type.js | 109 ++++ ...rowser_glean_telemetry_engagement_edge_cases.js | 8 +- .../browser_glean_telemetry_engagement_groups.js | 8 +- ...owser_glean_telemetry_engagement_interaction.js | 8 +- ...r_glean_telemetry_engagement_selected_result.js | 269 ++++++--- .../browser_glean_telemetry_engagement_tips.js | 4 +- .../browser_glean_telemetry_engagement_type.js | 16 +- .../browser_glean_telemetry_exposure_edge_cases.js | 626 +++++++++++++++++++-- .../browser_glean_telemetry_impression_groups.js | 258 --------- ...owser_glean_telemetry_impression_interaction.js | 68 --- ..._interaction_persisted_search_terms_disabled.js | 57 -- ...n_interaction_persisted_search_terms_enabled.js | 61 -- ...r_glean_telemetry_impression_n_chars_n_words.js | 40 -- ...owser_glean_telemetry_impression_preferences.js | 41 -- .../browser_glean_telemetry_impression_sap.js | 38 -- ...elemetry_impression_search_engine_default_id.js | 28 - ...owser_glean_telemetry_impression_search_mode.js | 72 --- .../browser_glean_telemetry_impression_timing.js | 91 --- .../engagementTelemetry/browser/head-groups.js | 32 +- .../browser/head-interaction.js | 23 +- .../browser/head-n_chars_n_words.js | 8 +- .../tests/engagementTelemetry/browser/head-sap.js | 5 +- .../browser/head-search_engine_default_id.js | 4 +- .../browser/head-search_mode.js | 12 +- .../tests/engagementTelemetry/browser/head.js | 28 +- .../tests/quicksuggest/MerinoTestUtils.sys.mjs | 18 +- .../quicksuggest/RemoteSettingsServer.sys.mjs | 30 +- .../browser_quicksuggest_onboardingDialog.js | 3 - .../browser_telemetry_impressionEdgeCases.js | 2 +- .../tests/quicksuggest/browser/browser_weather.js | 107 +++- .../urlbar/tests/quicksuggest/browser/head.js | 82 ++- .../urlbar/tests/quicksuggest/unit/head.js | 113 +++- .../tests/quicksuggest/unit/test_quicksuggest.js | 58 +- .../quicksuggest/unit/test_quicksuggest_addons.js | 12 +- .../quicksuggest/unit/test_quicksuggest_pocket.js | 4 +- .../tests/quicksuggest/unit/test_rust_ingest.js | 33 +- .../urlbar/tests/quicksuggest/unit/test_weather.js | 200 +++---- .../quicksuggest/unit/test_weather_keywords.js | 139 +---- .../urlbar/tests/quicksuggest/unit/xpcshell.toml | 1 + browser/components/urlbar/tests/unit/head.js | 33 +- .../urlbar/tests/unit/test_UrlbarPrefs.js | 8 +- .../urlbar/tests/unit/test_UrlbarSearchUtils.js | 24 +- .../urlbar/tests/unit/test_about_urls.js | 6 + .../tests/unit/test_autofill_originsAndQueries.js | 12 +- .../urlbar/tests/unit/test_heuristic_cancel.js | 2 +- .../urlbar/tests/unit/test_hideSponsoredHistory.js | 2 +- .../urlbar/tests/unit/test_match_javascript.js | 5 +- .../tests/unit/test_providerHistoryUrlHeuristic.js | 2 + .../urlbar/tests/unit/test_providerOpenTabs.js | 99 +++- .../urlbar/tests/unit/test_providerTabToSearch.js | 4 +- .../tests/unit/test_providersManager_filtering.js | 6 +- .../urlbar/tests/unit/test_remote_tabs.js | 4 +- .../urlbar/tests/unit/test_search_suggestions.js | 4 +- browser/components/urlbar/tests/unit/xpcshell.toml | 4 - 156 files changed, 2946 insertions(+), 2260 deletions(-) create mode 100644 browser/components/urlbar/tests/browser/browser_top_sites_switchtab.js create mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_type.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js delete mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js (limited to 'browser/components/urlbar') diff --git a/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs b/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs index 12a4a43a1b..0ab9c4c83e 100644 --- a/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs +++ b/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs @@ -303,6 +303,9 @@ function shuffle(array, seed) { * Loads the default QuickActions. */ export class QuickActionsLoaderDefault { + // Track the loading of the QuickActions to ensure they aren't loaded multiple times. + static #loadedPromise = null; + static async load() { let keys = Object.keys(DEFAULT_ACTIONS); if (lazy.UrlbarPrefs.get("quickactions.randomOrderActions")) { @@ -328,4 +331,10 @@ export class QuickActionsLoaderDefault { lazy.UrlbarProviderQuickActions.addAction(key, actionData); } } + static async ensureLoaded() { + if (!this.#loadedPromise) { + this.#loadedPromise = this.load(); + } + await this.#loadedPromise; + } } diff --git a/browser/components/urlbar/UrlbarController.sys.mjs b/browser/components/urlbar/UrlbarController.sys.mjs index 8a17bc16ae..9bfc3a645d 100644 --- a/browser/components/urlbar/UrlbarController.sys.mjs +++ b/browser/components/urlbar/UrlbarController.sys.mjs @@ -12,8 +12,6 @@ ChromeUtils.defineESModuleGetters(lazy, { UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs", UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs", UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs", - clearTimeout: "resource://gre/modules/Timer.sys.mjs", - setTimeout: "resource://gre/modules/Timer.sys.mjs", }); const TELEMETRY_1ST_RESULT = "PLACES_AUTOCOMPLETE_1ST_RESULT_TIME_MS"; @@ -154,10 +152,6 @@ export class UrlbarController { * can't be cancelled. */ cancelQuery() { - // We must clear the pause impression timer in any case, even if the query - // already finished. - this.engagementEvent.clearPauseImpressionTimer(); - // If the query finished already, don't handle cancel. if (!this._lastQueryContextWrapper || this._lastQueryContextWrapper.done) { return; @@ -186,11 +180,6 @@ export class UrlbarController { TelemetryStopwatch.finish(TELEMETRY_6_FIRST_RESULTS, queryContext); } - this.engagementEvent.startPauseImpressionTimer( - queryContext, - this.input.getSearchSource() - ); - if (queryContext.firstResultChanged) { // Notify the input so it can make adjustments based on the first result. if (this.input.onFirstResult(queryContext.results[0])) { @@ -719,7 +708,6 @@ class TelemetryEvent { constructor(controller, category) { this._controller = controller; this._category = category; - this.#exposureResultTypes = new Set(); this.#beginObservingPingPrefs(); } @@ -833,8 +821,6 @@ class TelemetryEvent { * @param {DOMElement} [details.element] The picked view element. */ record(event, details) { - this.clearPauseImpressionTimer(); - // This should never throw, or it may break the urlbar. try { this._internalRecord(event, details); @@ -853,52 +839,6 @@ class TelemetryEvent { } } - /** - * Clear the pause impression timer started by startPauseImpressionTimer(). - */ - clearPauseImpressionTimer() { - lazy.clearTimeout(this._pauseImpressionTimer); - } - - /** - * Start a timer that records the pause impression telemetry for given context. - * The telemetry will be recorded after - * "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs" ms. - * If want to clear this timer, please use clearPauseImpressionTimer(). - * - * @param {UrlbarQueryContext} queryContext - * The query details that will be recorded as pause impression telemetry. - * @param {string} searchSource - * The seach source that will be recorded as pause impression telemetry. - */ - startPauseImpressionTimer(queryContext, searchSource) { - if (this._impressionStartEventInfo === this._startEventInfo) { - // Already took an impression telemetry for this session. - return; - } - - this.clearPauseImpressionTimer(); - this._pauseImpressionTimer = lazy.setTimeout(() => { - let { numChars, numWords, searchWords } = this._parseSearchString( - queryContext.searchString - ); - this._recordSearchEngagementTelemetry( - queryContext, - "impression", - this._startEventInfo, - { - reason: "pause", - numChars, - numWords, - searchWords, - searchSource, - } - ); - - this._impressionStartEventInfo = this._startEventInfo; - }, lazy.UrlbarPrefs.get("searchEngagementTelemetry.pauseImpressionIntervalMs")); - } - _internalRecord(event, details) { const startEventInfo = this._startEventInfo; @@ -933,6 +873,8 @@ class TelemetryEvent { startEventInfo.interactionType == "dropped" ? "drop_go" : "paste_go"; } else if (event.type == "blur") { action = "blur"; + } else if (event.type == "tabswitch") { + action = "tab_switch"; } else if ( details.element?.dataset.command && // The "help" selType is recognized by legacy telemetry, and `action` @@ -951,7 +893,8 @@ class TelemetryEvent { action = "enter"; } - let method = action == "blur" ? "abandonment" : "engagement"; + let method = + action == "blur" || action == "tab_switch" ? "abandonment" : "engagement"; if (method == "engagement") { // Not all engagements end the search session. The session remains ongoing @@ -1119,6 +1062,7 @@ class TelemetryEvent { }; } else if (method === "abandonment") { eventInfo = { + abandonment_type: action, sap, interaction, search_mode, @@ -1148,18 +1092,20 @@ class TelemetryEvent { } // First check to see if we can record an exposure event - if ( - (method === "abandonment" || method === "engagement") && - this.#exposureResultTypes.size - ) { - const exposureResults = Array.from(this.#exposureResultTypes).join(","); - this._controller.logger.debug( - `exposure event: ${JSON.stringify({ results: exposureResults })}` - ); - Glean.urlbar.exposure.record({ results: exposureResults }); + if (method === "abandonment" || method === "engagement") { + if (this.#exposureResultTypes.size) { + let exposure = { + results: [...this.#exposureResultTypes].sort().join(","), + }; + this._controller.logger.debug( + `exposure event: ${JSON.stringify(exposure)}` + ); + Glean.urlbar.exposure.record(exposure); + } // reset the provider list on the controller this.#exposureResultTypes.clear(); + this.#tentativeExposureResultTypes.clear(); } this._controller.logger.info( @@ -1170,9 +1116,13 @@ class TelemetryEvent { } /** - * Add result type to engagementEvent instance exposureResultTypes Set. + * Registers an exposure for a result in the current urlbar session. All + * exposures that are added during a session are recorded in an exposure event + * at the end of the session. Exposures are cleared at the end of each session + * and do not carry over to the next session. * - * @param {UrlbarResult} result UrlbarResult to have exposure recorded. + * @param {UrlbarResult} result An exposure will be added for this result if + * exposures are enabled for its result type. */ addExposure(result) { if (result.exposureResultType) { @@ -1180,6 +1130,40 @@ class TelemetryEvent { } } + /** + * Registers a tentative exposure for a result in the current urlbar session. + * Exposures that remain tentative at the end of the session are discarded and + * are not recorded in the exposure event. + * + * @param {UrlbarResult} result A tentative exposure will be added for this + * result if exposures are enabled for its result type. + */ + addTentativeExposure(result) { + if (result.exposureResultType) { + this.#tentativeExposureResultTypes.add(result.exposureResultType); + } + } + + /** + * Converts all tentative exposures that were added and not yet discarded + * during the current urlbar session into actual exposures that will be + * recorded at the end of the session. + */ + acceptTentativeExposures() { + for (let type of this.#tentativeExposureResultTypes) { + this.#exposureResultTypes.add(type); + } + this.#tentativeExposureResultTypes.clear(); + } + + /** + * Discards all tentative exposures that were added and not yet accepted + * during the current urlbar session. + */ + discardTentativeExposures() { + this.#tentativeExposureResultTypes.clear(); + } + #getInteractionType( method, startEventInfo, @@ -1306,7 +1290,6 @@ class TelemetryEvent { * no-op. */ discard() { - this.clearPauseImpressionTimer(); if (this._startEventInfo) { this._startEventInfo = null; this._discarded = true; @@ -1369,5 +1352,6 @@ class TelemetryEvent { #previousSearchWordsSet = null; - #exposureResultTypes; + #exposureResultTypes = new Set(); + #tentativeExposureResultTypes = new Set(); } diff --git a/browser/components/urlbar/UrlbarEventBufferer.sys.mjs b/browser/components/urlbar/UrlbarEventBufferer.sys.mjs index 81092151d0..4b654386dc 100644 --- a/browser/components/urlbar/UrlbarEventBufferer.sys.mjs +++ b/browser/components/urlbar/UrlbarEventBufferer.sys.mjs @@ -102,11 +102,11 @@ export class UrlbarEventBufferer { } } - onQueryCancelled(queryContext) { + onQueryCancelled() { this._lastQuery.status = QUERY_STATUS.COMPLETE; } - onQueryFinished(queryContext) { + onQueryFinished() { this._lastQuery.status = QUERY_STATUS.COMPLETE; } diff --git a/browser/components/urlbar/UrlbarInput.sys.mjs b/browser/components/urlbar/UrlbarInput.sys.mjs index 3053e32ca2..6aefd18f9c 100644 --- a/browser/components/urlbar/UrlbarInput.sys.mjs +++ b/browser/components/urlbar/UrlbarInput.sys.mjs @@ -627,7 +627,7 @@ export class UrlbarInput { handleNavigation({ event, oneOffParams, triggeringPrincipal }) { let element = this.view.selectedElement; let result = this.view.getResultFromElement(element); - let openParams = oneOffParams?.openParams || {}; + let openParams = oneOffParams?.openParams || { triggeringPrincipal }; // If the value was submitted during composition, the result may not have // been updated yet, because the input event happens after composition end. @@ -794,7 +794,7 @@ export class UrlbarInput { this.pickResult(newResult, event, null, browser); } }) - .catch(ex => { + .catch(() => { if (url) { // Something went wrong, we should always have a heuristic result, // otherwise it means we're not able to search at all, maybe because @@ -923,25 +923,9 @@ export class UrlbarInput { return; } - let urlOverride; if (element?.dataset.command) { - this.controller.engagementEvent.record(event, { - result, - element, - searchString: this._lastSearchString, - selType: - element.dataset.command == "help" && - result.type == lazy.UrlbarUtils.RESULT_TYPE.TIP - ? "tiphelp" - : element.dataset.command, - }); - if (element.dataset.command == "help") { - urlOverride = result.payload.helpUrl; - } - urlOverride ||= element.dataset.url; - if (!urlOverride) { - return; - } + this.#pickMenuResult(result, event, element, browser); + return; } // When a one-off is selected, we restyle heuristic results to look like @@ -976,9 +960,13 @@ export class UrlbarInput { return; } - urlOverride ||= element?.dataset.url; + let resultUrl = element?.dataset.url; let originalUntrimmedValue = this.untrimmedValue; - let isCanonized = this.setValueFromResult({ result, event, urlOverride }); + let isCanonized = this.setValueFromResult({ + result, + event, + urlOverride: resultUrl, + }); let where = this._whereToOpen(event); let openParams = { allowInheritPrincipal: false, @@ -992,7 +980,7 @@ export class UrlbarInput { }; if ( - urlOverride && + resultUrl && result.type != lazy.UrlbarUtils.RESULT_TYPE.TIP && where == "current" ) { @@ -1018,8 +1006,8 @@ export class UrlbarInput { return; } - let { url, postData } = urlOverride - ? { url: urlOverride, postData: null } + let { url, postData } = resultUrl + ? { url: resultUrl, postData: null } : lazy.UrlbarUtils.getUrlFromResult(result); openParams.postData = postData; @@ -1162,7 +1150,7 @@ export class UrlbarInput { // to the list that we use to make decisions. // Because we are directly asking for a search here, bypassing the // docShell, we need to do the same ourselves. - // See also URIFixupChild.jsm and keyword-uri-fixup. + // See also URIFixupChild.sys.mjs and keyword-uri-fixup. let fixupInfo = this._getURIFixupInfo(originalUntrimmedValue.trim()); if (fixupInfo) { this.window.gKeywordURIFixup.check( @@ -1196,10 +1184,7 @@ export class UrlbarInput { break; } case lazy.UrlbarUtils.RESULT_TYPE.TIP: { - let scalarName = - element.dataset.command == "help" - ? `${result.payload.type}-help` - : `${result.payload.type}-picked`; + let scalarName = `${result.payload.type}-picked`; Services.telemetry.keyedScalarAdd("urlbar.tips", scalarName, 1); if (url) { break; @@ -2219,11 +2204,24 @@ export class UrlbarInput { this.formatValue(); this._resetSearchState(); - // Switching tabs doesn't always change urlbar focus, so we must try to - // reopen here too, not just on focus. // We don't use the original TabSelect event because caching it causes // leaks on MacOS. - if (this.view.autoOpen({ event: new CustomEvent("tabswitch") })) { + const event = new CustomEvent("tabswitch"); + // If the urlbar is focused after a tab switch, record a potential + // engagement event. When switching from a focused to a non-focused urlbar, + // the blur event would record the abandonment. When switching from an + // unfocused to a focused urlbar, there should be no search session ongoing, + // so this will be a no-op. + if (this.focused) { + this.controller.engagementEvent.record(event, { + searchString: this._lastSearchString, + searchSource: this.getSearchSource(event), + }); + } + + // Switching tabs doesn't always change urlbar focus, so we must try to + // reopen here too, not just on focus. + if (this.view.autoOpen({ event })) { return; } // The input may retain focus when switching tabs in which case we @@ -2743,6 +2741,73 @@ export class UrlbarInput { }; } + /** + * Called when a menu item from results menu is picked. + * + * @param {UrlbarResult} result The result that was picked. + * @param {Event} event The event that picked the result. + * @param {DOMElement} element the picked view element, if available. + * @param {object} browser The browser to use for the load. + */ + #pickMenuResult(result, event, element, browser) { + this.controller.engagementEvent.record(event, { + result, + element, + searchString: this._lastSearchString, + selType: + element.dataset.command == "help" && + result.type == lazy.UrlbarUtils.RESULT_TYPE.TIP + ? "tiphelp" + : element.dataset.command, + }); + + if (element.dataset.command == "manage") { + this.window.openPreferences("search-locationBar"); + return; + } + + let url = + element.dataset.command == "help" + ? result.payload.helpUrl + : element.dataset.url; + if (!url) { + return; + } + + let where = this._whereToOpen(event); + if ( + url && + result.type != lazy.UrlbarUtils.RESULT_TYPE.TIP && + where == "current" + ) { + // Open non-tip help links in a new tab unless the user held a modifier. + // TODO (bug 1696232): Do this for tip help links, too. + where = "tab"; + } + + this.view.close({ elementPicked: true }); + + if (result.type == lazy.UrlbarUtils.RESULT_TYPE.TIP) { + let scalarName = `${result.payload.type}-help`; + Services.telemetry.keyedScalarAdd("urlbar.tips", scalarName, 1); + } + + this._loadURL( + url, + event, + where, + { + allowInheritPrincipal: false, + private: this.isPrivate, + }, + { + source: result.source, + type: result.type, + }, + browser + ); + } + /** * Loads the url in the appropriate place. * @@ -3465,11 +3530,11 @@ export class UrlbarInput { Services.obs.notifyObservers(null, "urlbar-focus"); } - _on_mouseover(event) { + _on_mouseover() { this._updateUrlTooltip(); } - _on_draggableregionleftmousedown(event) { + _on_draggableregionleftmousedown() { if (!lazy.UrlbarPrefs.get("ui.popup.disable_autohide")) { this.view.close(); } @@ -3477,7 +3542,7 @@ export class UrlbarInput { _on_mousedown(event) { switch (event.currentTarget) { - case this.textbox: + case this.textbox: { this._mousedownOnUrlbarDescendant = true; if ( @@ -3522,6 +3587,7 @@ export class UrlbarInput { }); } break; + } case this.window: if (this._mousedownOnUrlbarDescendant) { this._mousedownOnUrlbarDescendant = false; @@ -3643,7 +3709,7 @@ export class UrlbarInput { }); } - _on_selectionchange(event) { + _on_selectionchange() { // Confirm placeholder as user text if it gets explicitly deselected. This // happens when the user wants to modify the autofilled text by either // clicking on it, or pressing HOME, END, RIGHT, … @@ -3658,7 +3724,7 @@ export class UrlbarInput { } } - _on_select(event) { + _on_select() { // On certain user input, AutoCopyListener::OnSelectionChange() updates // the primary selection with user-selected text (when supported). // Selection::NotifySelectionListeners() then dispatches a "select" event @@ -3827,8 +3893,9 @@ export class UrlbarInput { isPrivate: this.isPrivate, maxResults: lazy.UrlbarPrefs.get("maxRichResults"), searchString, - userContextId: - this.window.gBrowser.selectedBrowser.getAttribute("usercontextid"), + userContextId: parseInt( + this.window.gBrowser.selectedBrowser.getAttribute("usercontextid") || 0 + ), currentPage: this.window.gBrowser.currentURI.spec, formHistoryName: this.formHistoryName, prohibitRemoteResults: @@ -3848,11 +3915,11 @@ export class UrlbarInput { return new lazy.UrlbarQueryContext(options); } - _on_scrollend(event) { + _on_scrollend() { this.updateTextOverflow(); } - _on_TabSelect(event) { + _on_TabSelect() { this._gotTabSelect = true; this._afterTabSelectAndFocusChange(); } @@ -3927,7 +3994,7 @@ export class UrlbarInput { } } - _on_compositionstart(event) { + _on_compositionstart() { if (this._compositionState == lazy.UrlbarUtils.COMPOSITION.COMPOSING) { throw new Error("Trying to start a nested composition?"); } diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs index 6736ddfcd3..c521264fa5 100644 --- a/browser/components/urlbar/UrlbarPrefs.sys.mjs +++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs @@ -317,9 +317,6 @@ const PREF_URLBAR_DEFAULTS = new Map([ // If true, we show tail suggestions when available. ["richSuggestions.tail", true], - // Interval time until taking pause impression telemetry. - ["searchEngagementTelemetry.pauseImpressionIntervalMs", 1000], - // Hidden pref. Disables checks that prevent search tips being shown, thus // showing them every time the newtab page or the default search engine // homepage is opened. @@ -426,7 +423,7 @@ const PREF_URLBAR_DEFAULTS = new Map([ // Controls whether searching for open tabs returns tabs from any container // or only from the current container. - ["switchTabs.searchAllContainers", false], + ["switchTabs.searchAllContainers", true], // The number of remaining times the user can interact with tab-to-search // onboarding results before we stop showing them. @@ -528,6 +525,7 @@ const NIMBUS_DEFAULTS = { weatherKeywords: null, weatherKeywordsMinimumLength: 0, weatherKeywordsMinimumLengthCap: 0, + weatherSimpleUI: false, }; // Maps preferences under browser.urlbar.suggest to behavior names, as defined @@ -1271,7 +1269,7 @@ class Preferences { } } - _migrateFirefoxSuggestPrefsTo_2(scenario) { + _migrateFirefoxSuggestPrefsTo_2() { // In previous versions of the prefs for online, suggestions were disabled // by default; in version 2, they're enabled by default. For users who were // already in online and did not enable suggestions (because they did not diff --git a/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs b/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs index 9b471d26c4..62f85b2348 100644 --- a/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs @@ -66,7 +66,7 @@ class ProviderAboutPages extends UrlbarProvider { if (aboutUrl.startsWith(searchString)) { let result = new lazy.UrlbarResult( UrlbarUtils.RESULT_TYPE.URL, - UrlbarUtils.RESULT_SOURCE.HISTORY, + UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, { title: [aboutUrl, UrlbarUtils.HIGHLIGHT.TYPED], url: [aboutUrl, UrlbarUtils.HIGHLIGHT.TYPED], diff --git a/browser/components/urlbar/UrlbarProviderAliasEngines.sys.mjs b/browser/components/urlbar/UrlbarProviderAliasEngines.sys.mjs index 77ed07f13c..d80f3e055d 100644 --- a/browser/components/urlbar/UrlbarProviderAliasEngines.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderAliasEngines.sys.mjs @@ -72,6 +72,7 @@ class ProviderAliasEngines extends UrlbarProvider { alias, queryContext.searchString ); + let icon = await engine?.getIconURL(); if (!engine || instance != this.queryInstance) { return; } @@ -83,7 +84,7 @@ class ProviderAliasEngines extends UrlbarProvider { engine: engine.name, keyword: alias, query: query.trimStart(), - icon: engine.getIconURL(), + icon, }) ); result.heuristic = true; diff --git a/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs b/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs index b61376220c..32e605206e 100644 --- a/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs @@ -38,15 +38,11 @@ const ORIGIN_FRECENCY_FIELD = ORIGIN_USE_ALT_FRECENCY ? "alt_frecency" : "frecency"; -// `WITH` clause for the autofill queries. autofill_frecency_threshold.value is -// the mean of all moz_origins.frecency values + stddevMultiplier * one standard -// deviation. This is inlined directly in the SQL (as opposed to being a custom -// Sqlite function for example) in order to be as efficient as possible. -// For alternative frecency, a NULL frecency will be normalized to 0.0, and when -// it will graduate, it will likely become 1 (official frecency is NOT NULL). -// Thus we set a minimum threshold of 2.0, otherwise if all the visits are older -// than the cutoff, we end up checking 0.0 (frecency) >= 0.0 (threshold) and -// autofill everything instead of nothing. +// `WITH` clause for the autofill queries. +// A NULL frecency is normalized to 1.0, because the table doesn't support NULL. +// Because of that, here we must set a minimum threshold of 2.0, otherwise when +// all the visits are older than the cutoff, we'd check +// 0.0 (frecency) >= 0.0 (threshold) and autofill everything instead of nothing. const SQL_AUTOFILL_WITH = ORIGIN_USE_ALT_FRECENCY ? ` WITH @@ -59,20 +55,11 @@ const SQL_AUTOFILL_WITH = ORIGIN_USE_ALT_FRECENCY ` : ` WITH - frecency_stats(count, sum, squares) AS ( - SELECT - CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_count') AS REAL), - CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_sum') AS REAL), - CAST((SELECT IFNULL(value, 0.0) FROM moz_meta WHERE key = 'origin_frecency_sum_of_squares') AS REAL) - ), autofill_frecency_threshold(value) AS ( - SELECT - CASE count - WHEN 0 THEN 0.0 - WHEN 1 THEN sum - ELSE (sum / count) + (:stddevMultiplier * sqrt((squares - ((sum * sum) / count)) / count)) - END - FROM frecency_stats + SELECT IFNULL( + (SELECT value FROM moz_meta WHERE key = 'origin_frecency_threshold'), + 2.0 + ) ) `; @@ -405,10 +392,9 @@ class ProviderAutofill extends UrlbarProvider { /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {number} The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return 0; } @@ -439,10 +425,8 @@ class ProviderAutofill extends UrlbarProvider { /** * Cancels a running query. - * - * @param {object} queryContext The query context object */ - cancelQuery(queryContext) { + cancelQuery() { if (this._autofillData?.instance == this.queryInstance) { this._autofillData = null; } @@ -463,9 +447,6 @@ class ProviderAutofill extends UrlbarProvider { let conditions = []; // Pay attention to the order of params, since they are not named. let params = [...hosts]; - if (!ORIGIN_USE_ALT_FRECENCY) { - params.unshift(lazy.UrlbarPrefs.get("autoFill.stddevMultiplier")); - } let sources = queryContext.sources; if ( sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY) && @@ -536,9 +517,6 @@ class ProviderAutofill extends UrlbarProvider { query_type: QUERYTYPE.AUTOFILL_ORIGIN, searchString: searchStr.toLowerCase(), }; - if (!ORIGIN_USE_ALT_FRECENCY) { - opts.stddevMultiplier = lazy.UrlbarPrefs.get("autoFill.stddevMultiplier"); - } if (this._strippedPrefix) { opts.prefix = this._strippedPrefix; } diff --git a/browser/components/urlbar/UrlbarProviderCalculator.sys.mjs b/browser/components/urlbar/UrlbarProviderCalculator.sys.mjs index 72c64f9646..a55531167c 100644 --- a/browser/components/urlbar/UrlbarProviderCalculator.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderCalculator.sys.mjs @@ -157,7 +157,7 @@ class ProviderCalculator extends UrlbarProvider { return viewUpdate; } - onEngagement(state, queryContext, details, controller) { + onEngagement(state, queryContext, details) { let { result } = details; if (result?.providerName == this.name) { lazy.ClipboardHelper.copyString(result.payload.value); @@ -306,7 +306,7 @@ Parser.prototype = { // This method returns an array of objects with these properties: // - number: true/false // - value: the token value - parse(input) { + parse() { // The input must be a "block" without any digit left. if (!this._tokenizeBlock() || this._chars.length) { throw new Error("Wrong input"); diff --git a/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs b/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs index f1d0a0beb2..5337e610cc 100644 --- a/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs @@ -51,7 +51,8 @@ class ProviderClipboard extends UrlbarProvider { if ( !lazy.UrlbarPrefs.get("clipboard.featureGate") || !lazy.UrlbarPrefs.get("suggest.clipboard") || - queryContext.searchString + queryContext.searchString || + queryContext.searchMode ) { return false; } @@ -113,7 +114,7 @@ class ProviderClipboard extends UrlbarProvider { return null; } - getPriority(queryContext) { + getPriority() { // Zero-prefix suggestions have the same priority as top sites. return 1; } diff --git a/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs b/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs index f54afb8e70..63c94ee8f3 100644 --- a/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs @@ -135,12 +135,17 @@ class ProviderContextualSearch extends UrlbarProvider { engine = ( await lazy.UrlbarSearchUtils.enginesForDomainPrefix(host, { matchAllDomainLevels: true, - onlyEnabled: false, }) )[0]; } if (engine) { + let instance = this.queryInstance; + let icon = await engine.getIconURL(); + if (instance != this.queryInstance) { + return; + } + this.engines.set(hostname, engine); // Check to see if the engine that was found is the default engine. // The default engine will often be used to populate the heuristic result, @@ -156,7 +161,7 @@ class ProviderContextualSearch extends UrlbarProvider { let result = this.makeResult({ url, engine: engine.name, - icon: engine.getIconURL(), + icon, input: queryContext.searchString, shouldNavigate: true, }); @@ -215,12 +220,9 @@ class ProviderContextualSearch extends UrlbarProvider { * See the base UrlbarProvider class for more. * * @param {UrlbarResult} result The result whose view will be updated. - * @param {Map} idsByName - * A Map from an element's name, as defined by the provider; to its ID in - * the DOM, as defined by the browser. * @returns {object} An object describing the view update. */ - getViewUpdate(result, idsByName) { + getViewUpdate(result) { return { icon: { attributes: { diff --git a/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs b/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs index ee85fbffa8..052b121ab3 100644 --- a/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderHeuristicFallback.sys.mjs @@ -53,20 +53,18 @@ class ProviderHeuristicFallback extends UrlbarProvider { * If this method returns false, the providers manager won't start a query * with this provider, to save on resources. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {boolean} Whether this provider should be invoked for the search. */ - isActive(queryContext) { + isActive() { return true; } /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {number} The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return 0; } @@ -99,7 +97,7 @@ class ProviderHeuristicFallback extends UrlbarProvider { }) || lazy.UrlbarTokenizer.REGEXP_COMMON_EMAIL.test(str)) ) { - let searchResult = this._engineSearchResult(queryContext); + let searchResult = await this._engineSearchResult(queryContext); if (instance != this.queryInstance) { return; } @@ -109,13 +107,16 @@ class ProviderHeuristicFallback extends UrlbarProvider { return; } - result = this._searchModeKeywordResult(queryContext); + result = await this._searchModeKeywordResult(queryContext); + if (instance != this.queryInstance) { + return; + } if (result) { addCallback(this, result); return; } - result = this._engineSearchResult(queryContext); + result = await this._engineSearchResult(queryContext); if (instance != this.queryInstance) { return; } @@ -230,7 +231,7 @@ class ProviderHeuristicFallback extends UrlbarProvider { return result; } - _searchModeKeywordResult(queryContext) { + async _searchModeKeywordResult(queryContext) { if (!queryContext.tokens.length) { return null; } @@ -268,7 +269,7 @@ class ProviderHeuristicFallback extends UrlbarProvider { let result; if (queryContext.restrictSource == UrlbarUtils.RESULT_SOURCE.SEARCH) { - result = this._engineSearchResult(queryContext, firstToken); + result = await this._engineSearchResult(queryContext, firstToken); } else { result = new lazy.UrlbarResult( UrlbarUtils.RESULT_TYPE.SEARCH, @@ -283,7 +284,7 @@ class ProviderHeuristicFallback extends UrlbarProvider { return result; } - _engineSearchResult(queryContext, keyword = null) { + async _engineSearchResult(queryContext, keyword = null) { let engine; if (queryContext.searchMode?.engineName) { engine = lazy.UrlbarSearchUtils.getEngineByName( @@ -317,7 +318,7 @@ class ProviderHeuristicFallback extends UrlbarProvider { UrlbarUtils.RESULT_SOURCE.SEARCH, ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, { engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED], - icon: engine.getIconURL(), + icon: await engine.getIconURL(), query: [query, UrlbarUtils.HIGHLIGHT.NONE], keyword: keyword ? [keyword, UrlbarUtils.HIGHLIGHT.NONE] : undefined, }) diff --git a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs index c1f0cfb289..f929a1c003 100644 --- a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs @@ -23,16 +23,6 @@ ChromeUtils.defineESModuleGetters(lazy, { UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs", }); -// Sqlite result row index constants. -const QUERYINDEX = { - URL: 0, - TITLE: 1, - BOOKMARKED: 2, - BOOKMARKTITLE: 3, - TAGS: 4, - SWITCHTAB: 8, -}; - // Constants to support an alternative frecency algorithm. const PAGES_USE_ALT_FRECENCY = Services.prefs.getBoolPref( "places.frecency.pages.alternative.featureGate", @@ -42,23 +32,20 @@ const PAGES_FRECENCY_FIELD = PAGES_USE_ALT_FRECENCY ? "alt_frecency" : "frecency"; -// This SQL query fragment provides the following: -// - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED) -// - the bookmark title, if it is a bookmark (QUERYINDEX_BOOKMARKTITLE) -// - the tags associated with a bookmarked entry (QUERYINDEX_TAGS) -const SQL_BOOKMARK_TAGS_FRAGMENT = `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked, - ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL - ORDER BY lastModified DESC LIMIT 1 - ) AS btitle, - ( SELECT GROUP_CONCAT(t.title ORDER BY t.title) - FROM moz_bookmarks b - JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent - WHERE b.fk = h.id - ) AS tags`; - const SQL_ADAPTIVE_QUERY = `/* do not warn (bug 487789) */ - SELECT h.url, h.title, ${SQL_BOOKMARK_TAGS_FRAGMENT}, h.visit_count, - h.typed, h.id, t.open_count, ${PAGES_FRECENCY_FIELD} + SELECT h.url, + h.title, + EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked, + ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL + ORDER BY lastModified DESC LIMIT 1 + ) AS bookmark_title, + ( SELECT GROUP_CONCAT(t.title ORDER BY t.title) + FROM moz_bookmarks b + JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent + WHERE b.fk = h.id + ) AS tags, + t.open_count, + t.userContextId FROM ( SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank, place_id @@ -71,7 +58,7 @@ const SQL_ADAPTIVE_QUERY = `/* do not warn (bug 487789) */ ON t.url = h.url AND (t.userContextId = :userContextId OR (t.userContextId <> -1 AND :userContextId IS NULL)) WHERE AUTOCOMPLETE_MATCH(NULL, h.url, - IFNULL(btitle, h.title), tags, + IFNULL(bookmark_title, h.title), tags, h.visit_count, h.typed, bookmarked, t.open_count, :matchBehavior, :searchBehavior, @@ -142,14 +129,14 @@ class ProviderInputHistory extends UrlbarProvider { } for (let row of rows) { - const url = row.getResultByIndex(QUERYINDEX.URL); - const openPageCount = row.getResultByIndex(QUERYINDEX.SWITCHTAB) || 0; - const historyTitle = row.getResultByIndex(QUERYINDEX.TITLE) || ""; - const bookmarked = row.getResultByIndex(QUERYINDEX.BOOKMARKED); + const url = row.getResultByName("url"); + const openPageCount = row.getResultByName("open_count") || 0; + const historyTitle = row.getResultByName("title") || ""; + const bookmarked = row.getResultByName("bookmarked"); const bookmarkTitle = bookmarked - ? row.getResultByIndex(QUERYINDEX.BOOKMARKTITLE) + ? row.getResultByName("bookmark_title") : null; - const tags = row.getResultByIndex(QUERYINDEX.TAGS) || ""; + const tags = row.getResultByName("tags") || ""; let resultTitle = historyTitle; if (openPageCount > 0 && lazy.UrlbarPrefs.get("suggest.openpage")) { @@ -164,7 +151,7 @@ class ProviderInputHistory extends UrlbarProvider { url: [url, UrlbarUtils.HIGHLIGHT.TYPED], title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED], icon: UrlbarUtils.getIconForUrl(url), - userContextId: queryContext.userContextId || 0, + userContextId: row.getResultByName("userContextId") || 0, }) ); addCallback(this, result); diff --git a/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs b/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs index a8185b8bea..08b4ea36b7 100644 --- a/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs @@ -544,7 +544,7 @@ class ProviderInterventions extends UrlbarProvider { ); } - async _setCurrentTipFromAppUpdaterStatus(waitForCheck) { + async _setCurrentTipFromAppUpdaterStatus() { // The update tips depend on the app's update status, so check for updates // now (if we haven't already checked within the update-check period). If // we're running in an xpcshell test, then checkForBrowserUpdate's attempt @@ -667,11 +667,8 @@ class ProviderInterventions extends UrlbarProvider { /** * Cancels a running query, - * - * @param {UrlbarQueryContext} queryContext the query context object to cancel - * query for. */ - cancelQuery(queryContext) { + cancelQuery() { // If we're waiting for appUpdater to finish its update check, // this._appUpdaterListener will be defined. We can stop listening now. if (this._appUpdaterListener) { diff --git a/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs b/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs index 1658101236..351e8ff60b 100644 --- a/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs @@ -90,12 +90,10 @@ class ProviderOmnibox extends UrlbarProvider { /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext - * The query context object. * @returns {number} * The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return 0; } diff --git a/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs b/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs index a6941cbd0a..8e76ad53db 100644 --- a/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderOpenTabs.sys.mjs @@ -26,6 +26,12 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () => const PRIVATE_USER_CONTEXT_ID = -1; +/** + * Maps the open tabs by userContextId. + * Each entry is a Map of url => count. + */ +var gOpenTabUrls = new Map(); + /** * Class used to create the provider. */ @@ -57,10 +63,9 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { * If this method returns false, the providers manager won't start a query * with this provider, to save on resources. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {boolean} Whether this provider should be invoked for the search. */ - isActive(queryContext) { + isActive() { // For now we don't actually use this provider to query open tabs, instead // we join the temp table in UrlbarProviderPlaces. return false; @@ -73,27 +78,60 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { */ static memoryTableInitialized = false; - /** - * Maps the open tabs by userContextId. - * Each entry is a Map of url => count. - */ - static _openTabs = new Map(); - /** * Return unique urls that are open for given user context id. * - * @param {integer} userContextId Containers user context id + * @param {integer|string} userContextId Containers user context id * @param {boolean} [isInPrivateWindow] In private browsing window or not * @returns {Array} urls */ - static getOpenTabs(userContextId, isInPrivateWindow = false) { + static getOpenTabUrlsForUserContextId( + userContextId, + isInPrivateWindow = false + ) { + // It's fairly common to retrieve the value from an HTML attribute, that + // means we're getting sometimes a string, sometimes an integer. As we're + // using this as key of a Map, we must treat it consistently. + userContextId = parseInt(userContextId); userContextId = UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable( userContextId, isInPrivateWindow ); - return Array.from( - UrlbarProviderOpenTabs._openTabs.get(userContextId)?.keys() ?? [] - ); + return Array.from(gOpenTabUrls.get(userContextId)?.keys() ?? []); + } + + /** + * Return unique urls that are open, along with their user context id. + * + * @param {boolean} [isInPrivateWindow] Whether it's for a private browsing window + * @returns {Map} { url => Set({userContextIds}) } + */ + static getOpenTabUrls(isInPrivateWindow = false) { + let uniqueUrls = new Map(); + if (isInPrivateWindow) { + let urls = UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId( + PRIVATE_USER_CONTEXT_ID, + true + ); + for (let url of urls) { + uniqueUrls.set(url, new Set([PRIVATE_USER_CONTEXT_ID])); + } + } else { + for (let [userContextId, urls] of gOpenTabUrls) { + if (userContextId == PRIVATE_USER_CONTEXT_ID) { + continue; + } + for (let url of urls.keys()) { + let userContextIds = uniqueUrls.get(url); + if (!userContextIds) { + userContextIds = new Set(); + uniqueUrls.set(url, userContextIds); + } + userContextIds.add(userContextId); + } + } + } + return uniqueUrls; } /** @@ -155,7 +193,7 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { // Must be set before populating. UrlbarProviderOpenTabs.memoryTableInitialized = true; // Populate the table with the current cached tabs. - for (let [userContextId, entries] of UrlbarProviderOpenTabs._openTabs) { + for (let [userContextId, entries] of gOpenTabUrls) { for (let [url, count] of entries) { await addToMemoryTable(url, userContextId, count).catch( console.error @@ -168,10 +206,22 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { * Registers a tab as open. * * @param {string} url Address of the tab - * @param {integer} userContextId Containers user context id + * @param {integer|string} userContextId Containers user context id * @param {boolean} isInPrivateWindow In private browsing window or not */ static async registerOpenTab(url, userContextId, isInPrivateWindow) { + // It's fairly common to retrieve the value from an HTML attribute, that + // means we're getting sometimes a string, sometimes an integer. As we're + // using this as key of a Map, we must treat it consistently. + userContextId = parseInt(userContextId); + if (!Number.isInteger(userContextId)) { + lazy.logger.error("Invalid userContextId while registering openTab: ", { + url, + userContextId, + isInPrivateWindow, + }); + return; + } lazy.logger.info("Registering openTab: ", { url, userContextId, @@ -182,10 +232,10 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { isInPrivateWindow ); - let entries = UrlbarProviderOpenTabs._openTabs.get(userContextId); + let entries = gOpenTabUrls.get(userContextId); if (!entries) { entries = new Map(); - UrlbarProviderOpenTabs._openTabs.set(userContextId, entries); + gOpenTabUrls.set(userContextId, entries); } entries.set(url, (entries.get(url) ?? 0) + 1); await addToMemoryTable(url, userContextId).catch(console.error); @@ -195,10 +245,14 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { * Unregisters a previously registered open tab. * * @param {string} url Address of the tab - * @param {integer} userContextId Containers user context id + * @param {integer|string} userContextId Containers user context id * @param {boolean} isInPrivateWindow In private browsing window or not */ static async unregisterOpenTab(url, userContextId, isInPrivateWindow) { + // It's fairly common to retrieve the value from an HTML attribute, that + // means we're getting sometimes a string, sometimes an integer. As we're + // using this as key of a Map, we must treat it consistently. + userContextId = parseInt(userContextId); lazy.logger.info("Unregistering openTab: ", { url, userContextId, @@ -209,7 +263,7 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { isInPrivateWindow ); - let entries = UrlbarProviderOpenTabs._openTabs.get(userContextId); + let entries = gOpenTabUrls.get(userContextId); if (entries) { let oldCount = entries.get(url); if (oldCount == 0) { @@ -218,6 +272,8 @@ export class UrlbarProviderOpenTabs extends UrlbarProvider { } if (oldCount == 1) { entries.delete(url); + // Note: `entries` might be an empty Map now, though we don't remove it + // from `gOpenTabUrls` as it's likely to be reused later. } else { entries.set(url, oldCount - 1); } diff --git a/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs b/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs index 3c54102abb..650acd1730 100644 --- a/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs @@ -1472,10 +1472,8 @@ class ProviderPlaces extends UrlbarProvider { /** * Cancels a running query. - * - * @param {object} queryContext The query context object */ - cancelQuery(queryContext) { + cancelQuery() { if (this._currentSearch) { this._currentSearch.stop(); } diff --git a/browser/components/urlbar/UrlbarProviderPrivateSearch.sys.mjs b/browser/components/urlbar/UrlbarProviderPrivateSearch.sys.mjs index 9761053477..d672953248 100644 --- a/browser/components/urlbar/UrlbarProviderPrivateSearch.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderPrivateSearch.sys.mjs @@ -110,6 +110,7 @@ class ProviderPrivateSearch extends UrlbarProvider { logger: this.logger, }).promise; + let icon = await engine.getIconURL(); if (instance != this.queryInstance) { return; } @@ -120,7 +121,7 @@ class ProviderPrivateSearch extends UrlbarProvider { ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, { engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED], query: [searchString, UrlbarUtils.HIGHLIGHT.NONE], - icon: engine.getIconURL(), + icon, inPrivateWindow: true, isPrivateEngine, }) diff --git a/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs index 83eea9fcf6..f199b6b892 100644 --- a/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs @@ -40,9 +40,6 @@ class ProviderQuickActions extends UrlbarProvider { constructor() { super(); lazy.UrlbarResult.addDynamicResultType(DYNAMIC_TYPE_NAME); - Services.tm.idleDispatchToMainThread(() => - lazy.QuickActionsLoaderDefault.load() - ); } /** @@ -97,6 +94,7 @@ class ProviderQuickActions extends UrlbarProvider { * @returns {Promise} */ async startQuery(queryContext, addCallback) { + await lazy.QuickActionsLoaderDefault.ensureLoaded(); let input = queryContext.trimmedSearchString.toLowerCase(); if ( diff --git a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs index 202e51c9e5..78e254616e 100644 --- a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs @@ -128,11 +128,15 @@ class ProviderQuickSuggest extends UrlbarProvider { // Trim only the start of the search string because a trailing space can // affect the suggestions. let trimmedSearchString = queryContext.searchString.trimStart(); - if (!trimmedSearchString) { + + // Per product requirements, at least two characters must be typed to + // trigger a Suggest suggestion. Suggestion keywords should always be at + // least two characters long, but we check here anyway to be safe. Note we + // called `trimStart()` above, so we only call `trimEnd()` here. + if (trimmedSearchString.trimEnd().length < 2) { return false; } this._trimmedSearchString = trimmedSearchString; - return true; } @@ -773,9 +777,6 @@ class ProviderQuickSuggest extends UrlbarProvider { * @param {UrlbarResult} options.result * The quick suggest result related to the engagement, or null if no result * was present. - * @param {string} options.resultSelType - * If an element in the result's row was clicked, this should be its - * `selType`. Otherwise it should be an empty string. * @param {boolean} options.resultClicked * True if the main part of the result's row was clicked; false if a button * like help or dismiss was clicked or if no part of the row was clicked. @@ -786,7 +787,6 @@ class ProviderQuickSuggest extends UrlbarProvider { #recordNavSuggestionTelemetry({ queryContext, result, - resultSelType, resultClicked, details, }) { @@ -829,11 +829,8 @@ class ProviderQuickSuggest extends UrlbarProvider { /** * Cancels the current query. - * - * @param {UrlbarQueryContext} queryContext - * The query context. */ - cancelQuery(queryContext) { + cancelQuery() { // Cancel the Rust query. let backend = lazy.QuickSuggest.getFeature("SuggestBackendRust"); if (backend?.isEnabled) { diff --git a/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs index 67a4e39a86..48006d09c0 100644 --- a/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs @@ -129,7 +129,7 @@ class ProviderQuickSuggestContextualOptIn extends UrlbarProvider { ); } - getPriority(queryContext) { + getPriority() { return lazy.UrlbarProviderTopSites.PRIORITY; } @@ -138,13 +138,9 @@ class ProviderQuickSuggestContextualOptIn extends UrlbarProvider { * the view of one of the results of the provider. It should return an object * describing the view update. * - * @param {UrlbarResult} result The result whose view will be updated. - * @param {Map} idsByName - * A Map from an element's name, as defined by the provider; to its ID in - * the DOM, as defined by the browser. * @returns {object} An object describing the view update. */ - getViewUpdate(result, idsByName) { + getViewUpdate() { let alternativeCopy = lazy.UrlbarPrefs.get( "quicksuggest.contextualOptIn.sayHello" ); diff --git a/browser/components/urlbar/UrlbarProviderRemoteTabs.sys.mjs b/browser/components/urlbar/UrlbarProviderRemoteTabs.sys.mjs index 1b84750b25..80614dcbe3 100644 --- a/browser/components/urlbar/UrlbarProviderRemoteTabs.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderRemoteTabs.sys.mjs @@ -167,7 +167,7 @@ class ProviderRemoteTabs extends UrlbarProvider { ); // We want to return the most relevant remote tabs and thus the most - // recent ones. While SyncedTabs.jsm returns tabs that are sorted by + // recent ones. While SyncedTabs.sys.mjs returns tabs that are sorted by // most recent client, then most recent tab, we can do better. For // example, the most recent client might have one recent tab and then // many very stale tabs. Those very stale tabs will push out more recent diff --git a/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs b/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs index 636524ea51..8cb3532d94 100644 --- a/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs @@ -317,10 +317,8 @@ class ProviderSearchSuggestions extends UrlbarProvider { /** * Cancels a running query. - * - * @param {object} queryContext The query context object */ - cancelQuery(queryContext) { + cancelQuery() { if (this._suggestionsController) { this._suggestionsController.stop(); this._suggestionsController = null; @@ -508,7 +506,7 @@ class ProviderSearchSuggestions extends UrlbarProvider { trending: entry.trending, description: entry.description || undefined, query: [searchString.trim(), UrlbarUtils.HIGHLIGHT.NONE], - icon: !entry.value ? engine.getIconURL() : entry.icon, + icon: !entry.value ? await engine.getIconURL() : entry.icon, }; if (entry.trending) { diff --git a/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs b/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs index 209453263a..b19528619c 100644 --- a/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs @@ -160,20 +160,18 @@ class ProviderSearchTips extends UrlbarProvider { * If this method returns false, the providers manager won't start a query * with this provider, to save on resources. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {boolean} Whether this provider should be invoked for the search. */ - isActive(queryContext) { + isActive() { return this.currentTip && lazy.cfrFeaturesUserPref; } /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {number} The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return this.PRIORITY; } @@ -194,6 +192,7 @@ class ProviderSearchTips extends UrlbarProvider { this.currentTip = TIPS.NONE; let defaultEngine = await Services.search.getDefault(); + let icon = await defaultEngine.getIconURL(); if (instance != this.queryInstance) { return; } @@ -204,7 +203,7 @@ class ProviderSearchTips extends UrlbarProvider { { type: tip, buttons: [{ l10n: { id: "urlbar-search-tips-confirm" } }], - icon: defaultEngine.getIconURL(), + icon, } ); diff --git a/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs b/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs index a328ea0922..9aabef3d19 100644 --- a/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs @@ -143,10 +143,9 @@ class ProviderTabToSearch extends UrlbarProvider { /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {number} The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return 0; } @@ -156,12 +155,9 @@ class ProviderTabToSearch extends UrlbarProvider { * describing the view update. * * @param {UrlbarResult} result The result whose view will be updated. - * @param {Map} idsByName - * A Map from an element's name, as defined by the provider; to its ID in - * the DOM, as defined by the browser. * @returns {object} An object describing the view update. */ - getViewUpdate(result, idsByName) { + getViewUpdate(result) { return { icon: { attributes: { @@ -202,10 +198,8 @@ class ProviderTabToSearch extends UrlbarProvider { * * @param {UrlbarResult} result * The result that was selected. - * @param {Element} element - * The element in the result's view that was selected. */ - onSelection(result, element) { + onSelection(result) { // We keep track of the number of times the user interacts with // tab-to-search onboarding results so we stop showing them after // `tabToSearch.onboard.interactionsLeft` interactions. @@ -232,7 +226,7 @@ class ProviderTabToSearch extends UrlbarProvider { } } - onEngagement(state, queryContext, details, controller) { + onEngagement(state, queryContext, details) { let { result, element } = details; if ( result?.providerName == this.name && @@ -354,7 +348,6 @@ class ProviderTabToSearch extends UrlbarProvider { searchStr, { matchAllDomainLevels: true, - onlyEnabled: true, } ); if (!engines.length) { diff --git a/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs b/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs index 8b71c2d8e5..b3a91bcbe4 100644 --- a/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs @@ -98,8 +98,8 @@ class ProviderTokenAliasEngines extends UrlbarProvider { // If the user is typing a potential engine name, autofill it. if (lazy.UrlbarPrefs.get("autoFill") && queryContext.allowAutofill) { - let result = this._getAutofillResult(queryContext); - if (result) { + let result = await this._getAutofillResult(queryContext); + if (result && instance == this.queryInstance) { this._autofillData = { result, instance }; return true; } @@ -127,6 +127,7 @@ class ProviderTokenAliasEngines extends UrlbarProvider { addCallback(this, this._autofillData.result); } + let instance = this.queryInstance; for (let { engine, tokenAliases } of this._engines) { if ( tokenAliases[0].startsWith(queryContext.trimmedSearchString) && @@ -139,10 +140,13 @@ class ProviderTokenAliasEngines extends UrlbarProvider { engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED], keyword: [tokenAliases[0], UrlbarUtils.HIGHLIGHT.TYPED], query: ["", UrlbarUtils.HIGHLIGHT.TYPED], - icon: engine.getIconURL(), + icon: await engine.getIconURL(), providesSearchMode: true, }) ); + if (instance != this.queryInstance) { + break; + } addCallback(this, result); } } @@ -153,25 +157,22 @@ class ProviderTokenAliasEngines extends UrlbarProvider { /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {number} The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return this.PRIORITY; } /** * Cancels a running query. - * - * @param {object} queryContext The query context object */ - cancelQuery(queryContext) { + cancelQuery() { if (this._autofillData?.instance == this.queryInstance) { this._autofillData = null; } } - _getAutofillResult(queryContext) { + async _getAutofillResult(queryContext) { let lowerCaseSearchString = queryContext.searchString.toLowerCase(); // The user is typing a specific engine. We should show a heuristic result. @@ -206,7 +207,7 @@ class ProviderTokenAliasEngines extends UrlbarProvider { engine: [engine.name, UrlbarUtils.HIGHLIGHT.TYPED], keyword: [aliasPreservingUserCase, UrlbarUtils.HIGHLIGHT.TYPED], query: ["", UrlbarUtils.HIGHLIGHT.TYPED], - icon: engine.getIconURL(), + icon: await engine.getIconURL(), providesSearchMode: true, } ) diff --git a/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs b/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs index fd178769c8..e9d968f20f 100644 --- a/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs @@ -96,10 +96,9 @@ class ProviderTopSites extends UrlbarProvider { /** * Gets the provider's priority. * - * @param {UrlbarQueryContext} queryContext The query context object * @returns {number} The provider's priority for the given query. */ - getPriority(queryContext) { + getPriority() { return this.PRIORITY; } @@ -140,7 +139,7 @@ class ProviderTopSites extends UrlbarProvider { } // This is done here, rather than in the global scope, because - // TOP_SITES_DEFAULT_ROWS causes the import of Reducers.jsm, and we want to + // TOP_SITES_DEFAULT_ROWS causes the import of Reducers.sys.mjs, and we want to // do that only when actually querying for Top Sites. if (this.topSitesRows === undefined) { XPCOMUtils.defineLazyPreferenceGetter( @@ -199,6 +198,24 @@ class ProviderTopSites extends UrlbarProvider { this.sponsoredSites = sponsoredSites; } + let tabUrlsToContextIds; + if (lazy.UrlbarPrefs.get("suggest.openpage")) { + if (lazy.UrlbarPrefs.get("switchTabs.searchAllContainers")) { + tabUrlsToContextIds = lazy.UrlbarProviderOpenTabs.getOpenTabUrls( + queryContext.isPrivate + ); + } else { + // Build an object compatible with the output of getOpenTabs. + tabUrlsToContextIds = new Map(); + for (let url of lazy.UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId( + queryContext.userContextId, + queryContext.isPrivate + )) { + tabUrlsToContextIds.set(url, new Set([queryContext.userContextId])); + } + } + } + for (let site of sites) { switch (site.type) { case "url": { @@ -208,49 +225,60 @@ class ProviderTopSites extends UrlbarProvider { icon: site.favicon, isPinned: site.isPinned, isSponsored: site.isSponsored, - sendAttributionRequest: site.sendAttributionRequest, }; - if (site.isSponsored) { - payload = { - ...payload, - sponsoredTileId: site.sponsoredTileId, - sponsoredClickUrl: site.sponsoredClickUrl, - }; + + // Fuzzy match both the URL as-is, and the URL without ref, then + // generate a merged Set. + if (tabUrlsToContextIds) { + let tabUserContextIds = new Set([ + ...(tabUrlsToContextIds.get(site.url) ?? []), + ...(tabUrlsToContextIds.get(site.url.replace(/#.*$/, "")) ?? []), + ]); + if (tabUserContextIds.size) { + for (let userContextId of tabUserContextIds) { + payload.userContextId = userContextId; + let result = new lazy.UrlbarResult( + UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + UrlbarUtils.RESULT_SOURCE.TABS, + ...lazy.UrlbarResult.payloadAndSimpleHighlights( + queryContext.tokens, + payload + ) + ); + addCallback(this, result); + } + break; + } } - let result = new lazy.UrlbarResult( - UrlbarUtils.RESULT_TYPE.URL, - UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, - ...lazy.UrlbarResult.payloadAndSimpleHighlights( - queryContext.tokens, - payload - ) - ); - let tabs; - if (lazy.UrlbarPrefs.get("suggest.openpage")) { - tabs = lazy.UrlbarProviderOpenTabs.getOpenTabs( - queryContext.userContextId || 0, - queryContext.isPrivate - ); + if (site.isSponsored) { + payload.sponsoredTileId = site.sponsoredTileId; + payload.sponsoredClickUrl = site.sponsoredClickUrl; } + payload.sendAttributionRequest = site.sendAttributionRequest; - if (tabs && tabs.includes(site.url.replace(/#.*$/, ""))) { - result.type = UrlbarUtils.RESULT_TYPE.TAB_SWITCH; - result.source = UrlbarUtils.RESULT_SOURCE.TABS; - } else if (lazy.UrlbarPrefs.get("suggest.bookmark")) { + let resultSource = UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL; + if (lazy.UrlbarPrefs.get("suggest.bookmark")) { let bookmark = await lazy.PlacesUtils.bookmarks.fetch({ - url: new URL(result.payload.url), + url: new URL(payload.url), }); + // Check if query has been cancelled. + if (instance != this.queryInstance) { + break; + } if (bookmark) { - result.source = UrlbarUtils.RESULT_SOURCE.BOOKMARKS; + resultSource = UrlbarUtils.RESULT_SOURCE.BOOKMARKS; } } - // Our query has been cancelled. - if (instance != this.queryInstance) { - break; - } - + let result = new lazy.UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + resultSource, + ...lazy.UrlbarResult.payloadAndSimpleHighlights( + queryContext.tokens, + payload + ) + ); addCallback(this, result); break; } @@ -305,7 +333,7 @@ class ProviderTopSites extends UrlbarProvider { } } - onEngagement(state, queryContext, details, controller) { + onEngagement(state, queryContext) { if ( !queryContext.isPrivate && this.sponsoredSites && diff --git a/browser/components/urlbar/UrlbarProviderUnitConversion.sys.mjs b/browser/components/urlbar/UrlbarProviderUnitConversion.sys.mjs index 02408a4c6b..98c4d025e4 100644 --- a/browser/components/urlbar/UrlbarProviderUnitConversion.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderUnitConversion.sys.mjs @@ -169,7 +169,7 @@ class ProviderUnitConversion extends UrlbarProvider { addCallback(this, result); } - onEngagement(state, queryContext, details, controller) { + onEngagement(state, queryContext, details) { let { result, element } = details; if (result?.providerName == this.name) { const { textContent } = element.querySelector( diff --git a/browser/components/urlbar/UrlbarProviderWeather.sys.mjs b/browser/components/urlbar/UrlbarProviderWeather.sys.mjs index fc1b1ca86d..24342fecab 100644 --- a/browser/components/urlbar/UrlbarProviderWeather.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderWeather.sys.mjs @@ -157,13 +157,9 @@ class ProviderWeather extends UrlbarProvider { * * @param {UrlbarResult} result * The result whose view will be updated. - * @param {Map} idsByName - * A Map from an element's name, as defined by the provider; to its ID in - * the DOM, as defined by the browser.This is useful if parts of the view - * update depend on element IDs, as some ARIA attributes do. * @returns {object} An object describing the view update. */ - getViewUpdate(result, idsByName) { + getViewUpdate(result) { return lazy.QuickSuggest.weather.getViewUpdate(result); } diff --git a/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs b/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs index cdbb3aea53..48ae3b14b9 100644 --- a/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs +++ b/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs @@ -27,7 +27,7 @@ export class UrlbarSearchOneOffs extends SearchOneOffs { this.view = view; this.input = view.input; lazy.UrlbarPrefs.addObserver(this); - // Override the SearchOneOffs.jsm value for the Address Bar. + // Override the SearchOneOffs.sys.mjs value for the Address Bar. this.disableOneOffsHorizontalKeyNavigation = true; this._webEngines = []; this.addEventListener("rebuild", this); @@ -495,8 +495,8 @@ export class UrlbarSearchOneOffs extends SearchOneOffs { * @param {Array} addEngines * The engines that can be added. */ - _rebuildEngineList(engines, addEngines) { - super._rebuildEngineList(engines, addEngines); + async _rebuildEngineList(engines, addEngines) { + await super._rebuildEngineList(engines, addEngines); for (let { source, pref, restrict } of lazy.UrlbarUtils .LOCAL_SEARCH_MODES) { diff --git a/browser/components/urlbar/UrlbarSearchUtils.sys.mjs b/browser/components/urlbar/UrlbarSearchUtils.sys.mjs index c574e8f139..30cbdb73c4 100644 --- a/browser/components/urlbar/UrlbarSearchUtils.sys.mjs +++ b/browser/components/urlbar/UrlbarSearchUtils.sys.mjs @@ -65,16 +65,10 @@ class SearchUtils { * Match at each sub domain, for example "a.b.c.com" will be matched at * "a.b.c.com", "b.c.com", and "c.com". Partial matches are always returned * after perfect matches. - * @param {boolean} [options.onlyEnabled] - * Match only engines that have not been disabled on the Search Preferences - * list. * @returns {Array} * An array of all matching engines. An empty array if there are none. */ - async enginesForDomainPrefix( - prefix, - { matchAllDomainLevels = false, onlyEnabled = false } = {} - ) { + async enginesForDomainPrefix(prefix, { matchAllDomainLevels = false } = {}) { try { await this.init(); } catch { diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs index c7d595635b..2bbb5d1ab0 100644 --- a/browser/components/urlbar/UrlbarUtils.sys.mjs +++ b/browser/components/urlbar/UrlbarUtils.sys.mjs @@ -12,7 +12,6 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { FormHistory: "resource://gre/modules/FormHistory.sys.mjs", KeywordUtils: "resource://gre/modules/KeywordUtils.sys.mjs", - Log: "resource://gre/modules/Log.sys.mjs", PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs", PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", @@ -1030,8 +1029,9 @@ export var UrlbarUtils = { isPrivate: lazy.PrivateBrowsingUtils.isWindowPrivate(window), maxResults: 1, searchString, - userContextId: - window.gBrowser.selectedBrowser.getAttribute("usercontextid"), + userContextId: parseInt( + window.gBrowser.selectedBrowser.getAttribute("usercontextid") || 0 + ), prohibitRemoteResults: true, providers: ["AliasEngines", "BookmarkKeywords", "HeuristicFallback"], }; @@ -1051,31 +1051,27 @@ export var UrlbarUtils = { }, /** - * Creates a logger. - * Logging level can be controlled through browser.urlbar.loglevel. + * Creates a console logger. + * Logging level can be controlled through the `browser.urlbar.loglevel` + * preference. * - * @param {string} [prefix] Prefix to use for the logged messages, "::" will - * be appended automatically to the prefix. - * @returns {object} The logger. + * @param {object} [options] Options for the logger. + * @param {string} [options.prefix] Prefix to use for the logged messages. + * @returns {ConsoleInstance} The console logger. */ getLogger({ prefix = "" } = {}) { - if (!this._logger) { - this._logger = lazy.Log.repository.getLogger("urlbar"); - this._logger.manageLevelFromPref("browser.urlbar.loglevel"); - this._logger.addAppender( - new lazy.Log.ConsoleAppender(new lazy.Log.BasicFormatter()) - ); - } - if (prefix) { - // This is not an early return because it is necessary to invoke getLogger - // at least once before getLoggerWithMessagePrefix; it replaces a - // method of the original logger, rather than using an actual Proxy. - return lazy.Log.repository.getLoggerWithMessagePrefix( - "urlbar", - prefix + " :: " - ); - } - return this._logger; + if (!this._loggers) { + this._loggers = new Map(); + } + let logger = this._loggers.get(prefix); + if (!logger) { + logger = console.createInstance({ + prefix: `URLBar${prefix ? " - " + prefix : ""}`, + maxLogLevelPref: "browser.urlbar.loglevel", + }); + this._loggers.set(prefix, logger); + } + return logger; }, /** @@ -1651,6 +1647,12 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = { icon: { type: "string", }, + isPinned: { + type: "boolean", + }, + isSponsored: { + type: "boolean", + }, title: { type: "string", }, @@ -2151,11 +2153,11 @@ export class UrlbarQueryContext { for (let [prop, checkFn, defaultValue] of [ ["currentPage", v => typeof v == "string" && !!v.length], ["formHistoryName", v => typeof v == "string" && !!v.length], - ["prohibitRemoteResults", v => true, false], + ["prohibitRemoteResults", () => true, false], ["providers", v => Array.isArray(v) && v.length], ["searchMode", v => v && typeof v == "object"], ["sources", v => Array.isArray(v) && v.length], - ["view", v => true], + ["view", () => true], ]) { if (prop in options) { if (!checkFn(options[prop])) { @@ -2311,10 +2313,10 @@ export class UrlbarMuxer { /** * Sorts queryContext results in-place. * - * @param {UrlbarQueryContext} queryContext the context to sort results for. + * @param {UrlbarQueryContext} _queryContext the context to sort results for. * @abstract */ - sort(queryContext) { + sort(_queryContext) { throw new Error("Trying to access the base class, must be overridden"); } } @@ -2374,11 +2376,11 @@ export class UrlbarProvider { * If this method returns false, the providers manager won't start a query * with this provider, to save on resources. * - * @param {UrlbarQueryContext} queryContext The query context object + * @param {UrlbarQueryContext} _queryContext The query context object * @returns {boolean} Whether this provider should be invoked for the search. * @abstract */ - isActive(queryContext) { + isActive(_queryContext) { throw new Error("Trying to access the base class, must be overridden"); } @@ -2388,11 +2390,11 @@ export class UrlbarProvider { * larger values are higher priorities. For a given query, `startQuery` is * called on only the active and highest-priority providers. * - * @param {UrlbarQueryContext} queryContext The query context object + * @param {UrlbarQueryContext} _queryContext The query context object * @returns {number} The provider's priority for the given query. * @abstract */ - getPriority(queryContext) { + getPriority(_queryContext) { // By default, all providers share the lowest priority. return 0; } @@ -2403,30 +2405,30 @@ export class UrlbarProvider { * Note: Extended classes should return a Promise resolved when the provider * is done searching AND returning results. * - * @param {UrlbarQueryContext} queryContext The query context object - * @param {Function} addCallback Callback invoked by the provider to add a new + * @param {UrlbarQueryContext} _queryContext The query context object + * @param {Function} _addCallback Callback invoked by the provider to add a new * result. A UrlbarResult should be passed to it. * @abstract */ - startQuery(queryContext, addCallback) { + startQuery(_queryContext, _addCallback) { throw new Error("Trying to access the base class, must be overridden"); } /** * Cancels a running query, * - * @param {UrlbarQueryContext} queryContext the query context object to cancel + * @param {UrlbarQueryContext} _queryContext the query context object to cancel * query for. * @abstract */ - cancelQuery(queryContext) { + cancelQuery(_queryContext) { // Override this with your clean-up on cancel code. } /** * Called when the user starts and ends an engagement with the urlbar. * - * @param {string} state + * @param {string} _state * The state of the engagement, one of the following strings: * * start @@ -2440,11 +2442,11 @@ export class UrlbarProvider { * urlbar has discarded the engagement for some reason, and the * `onEngagement` implementation should ignore it. * - * @param {UrlbarQueryContext} queryContext + * @param {UrlbarQueryContext} _queryContext * The engagement's query context. This is *not* guaranteed to be defined * when `state` is "start". It will always be defined for "engagement" and * "abandonment". - * @param {object} details + * @param {object} _details * This object is non-empty only when `state` is "engagement" or * "abandonment", and it describes the search string and engaged result. * @@ -2469,27 +2471,27 @@ export class UrlbarProvider { * The index of the picked result. * {string} selType * The type of the selected result. See TelemetryEvent.record() in - * UrlbarController.jsm. + * UrlbarController.sys.mjs. * {string} provider * The name of the provider that produced the picked result. * * For "abandonment", only `searchString` is defined. - * @param {UrlbarController} controller + * @param {UrlbarController} _controller * The associated controller. */ - onEngagement(state, queryContext, details, controller) {} + onEngagement(_state, _queryContext, _details, _controller) {} /** * Called before a result from the provider is selected. See `onSelection` * for details on what that means. * - * @param {UrlbarResult} result + * @param {UrlbarResult} _result * The result that was selected. - * @param {Element} element + * @param {Element} _element * The element in the result's view that was selected. * @abstract */ - onBeforeSelection(result, element) {} + onBeforeSelection(_result, _element) {} /** * Called when a result from the provider is selected. "Selected" refers to @@ -2498,13 +2500,13 @@ export class UrlbarProvider { * event of a click, onSelection is called just before onEngagement. Note that * this is called when heuristic results are pre-selected. * - * @param {UrlbarResult} result + * @param {UrlbarResult} _result * The result that was selected. - * @param {Element} element + * @param {Element} _element * The element in the result's view that was selected. * @abstract */ - onSelection(result, element) {} + onSelection(_result, _element) {} /** * This is called only for dynamic result types, when the urlbar view updates @@ -2543,9 +2545,9 @@ export class UrlbarProvider { * element's name is not specified, then it will not be updated and will * retain its current state. * - * @param {UrlbarResult} result + * @param {UrlbarResult} _result * The result whose view will be updated. - * @param {Map} idsByName + * @param {Map} _idsByName * A Map from an element's name, as defined by the provider; to its ID in * the DOM, as defined by the browser. The browser manages element IDs for * dynamic results to prevent collisions. However, a provider may need to @@ -2572,7 +2574,7 @@ export class UrlbarProvider { * {string} [textContent] * A string that will be set as `element.textContent`. */ - getViewUpdate(result, idsByName) { + getViewUpdate(_result, _idsByName) { return null; } @@ -2582,7 +2584,7 @@ export class UrlbarProvider { * be handled by implementing `onEngagement()` with the possible exception of * commands automatically handled by the urlbar, like "help". * - * @param {UrlbarResult} result + * @param {UrlbarResult} _result * The menu will be shown for this result. * @returns {Array} * If the result doesn't have any commands, this should return null. @@ -2601,7 +2603,7 @@ export class UrlbarProvider { * If specified, a submenu will be created with the given child commands. * Each object in the array must be a command object. */ - getResultCommands(result) { + getResultCommands(_result) { return null; } @@ -2922,10 +2924,8 @@ export class L10nCache { * The subject of the notification. * @param {string} topic * The topic of the notification. - * @param {string} data - * The data attached to the notification. */ - async observe(subject, topic, data) { + async observe(subject, topic) { switch (topic) { case "intl:app-locales-changed": { await this.l10n.ready; diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs index 440c06d4a1..b5fe1e1955 100644 --- a/browser/components/urlbar/UrlbarView.sys.mjs +++ b/browser/components/urlbar/UrlbarView.sys.mjs @@ -463,15 +463,18 @@ export class UrlbarView { this.#setRowSelectable(row, false); // Replace the row with a dismissal acknowledgment tip. - let tip = new lazy.UrlbarResult( - lazy.UrlbarUtils.RESULT_TYPE.TIP, - lazy.UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, - { - type: "dismissalAcknowledgment", - titleL10n, - buttons: [{ l10n: { id: "urlbar-search-tips-confirm-short" } }], - icon: "chrome://branding/content/icon32.png", - } + let tip = Object.assign( + new lazy.UrlbarResult( + lazy.UrlbarUtils.RESULT_TYPE.TIP, + lazy.UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + { + type: "dismissalAcknowledgment", + titleL10n, + buttons: [{ l10n: { id: "urlbar-search-tips-confirm-short" } }], + icon: "chrome://branding/content/icon32.png", + } + ), + { rowLabel: this.#rowLabel(row) } ); this.#updateRow(row, tip); this.#updateIndices(); @@ -693,7 +696,7 @@ export class UrlbarView { this.#cacheL10nStrings(); } - onQueryCancelled(queryContext) { + onQueryCancelled() { this.#queryWasCancelled = true; this.#cancelRemoveStaleRowsTimer(); } @@ -1130,6 +1133,8 @@ export class UrlbarView { */ #rowCanUpdateToResult(rowIndex, result, seenSearchSuggestion) { // The heuristic result must always be current, thus it's always compatible. + // Note that the `updateResults` code, when updating the selection, relies + // on the fact the heuristic is the first selectable row. if (result.heuristic) { return true; } @@ -1176,6 +1181,10 @@ export class UrlbarView { // future we should make it support any type of result. Or, even better, // results should be grouped, thus we can directly update groups. + // Discard tentative exposures. This is analogous to marking the + // hypothetical hidden rows of hidden-exposure results as stale. + this.controller.engagementEvent.discardTentativeExposures(); + // Walk rows and find an insertion index for results. To avoid flicker, we // skip rows until we find one compatible with the result we want to apply. // If we couldn't find a compatible range, we'll just update. @@ -1212,7 +1221,7 @@ export class UrlbarView { ) { // We can replace the row's current result with the new one. if (result.exposureResultHidden) { - this.#addExposure(result); + this.controller.engagementEvent.addExposure(result); } else { this.#updateRow(row, result); } @@ -1282,7 +1291,13 @@ export class UrlbarView { newSpanCount <= this.#queryContext.maxResults && !seenMisplacedResult; if (result.exposureResultHidden) { if (canBeVisible) { - this.#addExposure(result); + this.controller.engagementEvent.addExposure(result); + } else { + // Add a tentative exposure: The hypothetical row for this + // hidden-exposure result can't be visible now, but as long as it were + // not marked stale in a later update, it would be shown when stale + // rows are removed. + this.controller.engagementEvent.addTentativeExposure(result); } continue; } @@ -1323,10 +1338,17 @@ export class UrlbarView { // on the row itself so that screen readers ignore it. item.setAttribute("role", "presentation"); + // These are used to cleanup result specific entities when row contents are + // cleared to reuse the row for a different result. + item._sharedAttributes = new Set( + [...item.attributes].map(v => v.name).concat(["stale", "id"]) + ); + item._sharedClassList = new Set(item.classList); + return item; } - #createRowContent(item, result) { + #createRowContent(item) { // The url is the only element that can wrap, thus all the other elements // are child of noWrap. let noWrap = this.#createElement("span"); @@ -1505,7 +1527,7 @@ export class UrlbarView { return classes; } - #createRowContentForRichSuggestion(item, result) { + #createRowContentForRichSuggestion(item) { item._content.toggleAttribute("selectable", true); let favicon = this.#createElement("img"); @@ -1615,7 +1637,7 @@ export class UrlbarView { // eslint-disable-next-line complexity #updateRow(item, result) { let oldResult = item.result; - let oldResultType = item.result && item.result.type; + let oldResultType = item.result?.type; let provider = lazy.UrlbarProvidersManager.getProvider(result.providerName); item.result = result; item.removeAttribute("stale"); @@ -1638,6 +1660,18 @@ export class UrlbarView { oldResult.payload.buttons, result.payload.buttons ) || + // Reusing a non-heuristic as a heuristic is risky as it may have DOM + // nodes/attributes/classes that are normally not present in a heuristic + // result. This may happen for example when switching from a zero-prefix + // search not having a heuristic to a search string one. + result.heuristic != oldResult.heuristic || + // Container switch-tab results have a more complex DOM content that is + // only updated correctly by another switch-tab result. + (oldResultType == lazy.UrlbarUtils.RESULT_TYPE.TAB_SWITCH && + lazy.UrlbarProviderOpenTabs.isContainerUserContextId( + oldResult.payload.userContextId + ) && + result.type != oldResultType) || result.testForceNewContent; if (needsNewContent) { @@ -1649,8 +1683,18 @@ export class UrlbarView { item._content = this.#createElement("span"); item._content.className = "urlbarView-row-inner"; item.appendChild(item._content); - item.removeAttribute("tip-type"); - item.removeAttribute("dynamicType"); + // Clear previously set attributes and classes that may refer to a + // different result type. + for (const attribute of item.attributes) { + if (!item._sharedAttributes.has(attribute.name)) { + item.removeAttribute(attribute.name); + } + } + for (const className of item.classList) { + if (!item._sharedClassList.has(className)) { + item.classList.remove(className); + } + } if (item.result.type == lazy.UrlbarUtils.RESULT_TYPE.DYNAMIC) { this.#createRowContentForDynamicType(item, result); } else if (result.isRichSuggestion) { @@ -1875,7 +1919,6 @@ export class UrlbarView { item.toggleAttribute("has-url", setURL); let url = item._elements.get("url"); if (setURL) { - item.setAttribute("has-url", "true"); let displayedUrl = result.payload.displayUrl; let urlHighlights = result.payloadHighlights.displayUrl || []; if (lazy.UrlbarUtils.isTextDirectionRTL(displayedUrl, this.window)) { @@ -2093,7 +2136,7 @@ export class UrlbarView { let visible = this.#isElementVisible(item); if (visible) { if (item.result.exposureResultType) { - this.#addExposure(item.result); + this.controller.engagementEvent.addExposure(item.result); } this.visibleResults.push(item.result); } @@ -2200,6 +2243,10 @@ export class UrlbarView { return null; } + if (row.result.rowLabel) { + return row.result.rowLabel; + } + let engineName = row.result.payload.engine || Services.search.defaultEngine.name; @@ -2326,6 +2373,10 @@ export class UrlbarView { row = next; } this.#updateIndices(); + + // Accept tentative exposures. This is analogous to unhiding the + // hypothetical non-stale hidden rows of hidden-exposure results. + this.controller.engagementEvent.acceptTentativeExposures(); } #startRemoveStaleRowsTimer() { @@ -2645,7 +2696,7 @@ export class UrlbarView { if ( actionNode.classList.contains("urlbarView-userContext") && label && - actionNode.querySelector("span").innerText == label + actionNode.querySelector("span")?.innerText == label ) { return; } @@ -2681,8 +2732,6 @@ export class UrlbarView { if (identity.icon) { let userContextIcon = this.#createElement("img"); userContextIcon.classList.add("urlbarView-userContext-icon"); - - userContextIcon.classList.add("identity-icon-" + identity.icon); userContextIcon.setAttribute("alt", label); userContextIcon.src = "resource://usercontext-content/" + identity.icon + ".svg"; @@ -3131,6 +3180,7 @@ export class UrlbarView { let engine = this.oneOffSearchButtons.selectedButton?.engine; let source = this.oneOffSearchButtons.selectedButton?.source; + let icon = this.oneOffSearchButtons.selectedButton?.image; let localSearchMode; if (source) { @@ -3254,7 +3304,15 @@ export class UrlbarView { } // Update result favicons. - let iconOverride = localSearchMode?.icon || engine?.getIconURL(); + let iconOverride = localSearchMode?.icon; + // If the icon is the default one-off search placeholder, assume we + // don't have an icon for the engine. + if ( + !iconOverride && + icon != "chrome://browser/skin/search-engine-placeholder.png" + ) { + iconOverride = icon; + } if (!iconOverride && (localSearchMode || engine)) { // For one-offs without an icon, do not allow restyled URL results to // use their own icons. @@ -3273,7 +3331,7 @@ export class UrlbarView { } } - on_blur(event) { + on_blur() { // If the view is open without the input being focused, it will not close // automatically when the window loses focus. We might be in this state // after a Search Tip is shown on an engine homepage. @@ -3416,15 +3474,6 @@ export class UrlbarView { this.#populateResultMenu(); } } - - /** - * Add result to exposure set on the controller. - * - * @param {UrlbarResult} result UrlbarResult for which to record an exposure. - */ - #addExposure(result) { - this.controller.engagementEvent.addExposure(result); - } } /** diff --git a/browser/components/urlbar/content/enUS-searchFeatures.ftl b/browser/components/urlbar/content/enUS-searchFeatures.ftl index daddc22378..52ae0648e0 100644 --- a/browser/components/urlbar/content/enUS-searchFeatures.ftl +++ b/browser/components/urlbar/content/enUS-searchFeatures.ftl @@ -60,6 +60,11 @@ urlbar-result-menu-learn-more-about-firefox-suggest = .label = Learn more about { -firefox-suggest-brand-name } .accesskey = L +# Manage menu item shown in the result menu of Firefox Suggest results. +urlbar-result-menu-manage-firefox-suggest = + .label = Manage { -firefox-suggest-brand-name } + .accesskey = M + # A message shown in a result when the user gives feedback on it. firefox-suggest-feedback-acknowledgment = Thanks for your feedback diff --git a/browser/components/urlbar/content/quicksuggestOnboarding.css b/browser/components/urlbar/content/quicksuggestOnboarding.css index 6ed8454398..97a1fa0390 100644 --- a/browser/components/urlbar/content/quicksuggestOnboarding.css +++ b/browser/components/urlbar/content/quicksuggestOnboarding.css @@ -4,7 +4,7 @@ /** * When making changes, follow the example of the AboutWelcome messaging surface for font sizes, line heights, - * etc. See: https://searchfox.org/mozilla-central/source/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss + * etc. See: https://searchfox.org/mozilla-central/source/browser/components/aboutwelcome/content-src/aboutwelcome.scss */ :root { diff --git a/browser/components/urlbar/docs/overview.rst b/browser/components/urlbar/docs/overview.rst index acc0db5874..992d847a21 100644 --- a/browser/components/urlbar/docs/overview.rst +++ b/browser/components/urlbar/docs/overview.rst @@ -70,7 +70,7 @@ The Model The *Model* is the component responsible for retrieving search results based on the user's input, and sorting them accordingly to their importance. -At the core is the `UrlbarProvidersManager `_, +At the core is the `UrlbarProvidersManager `_, a component tracking all the available search providers, and managing searches across them. @@ -90,7 +90,7 @@ Queries can be canceled. terminating any running and future SQL query, unless a query is running inside a *runInCriticalSection* task. -The *searchString* gets tokenized by the `UrlbarTokenizer `_ +The *searchString* gets tokenized by the `UrlbarTokenizer `_ component into tokens, some of these tokens have a special meaning and can be used by the user to restrict the search to specific result type (See the *UrlbarTokenizer::TYPE* enum). @@ -118,7 +118,7 @@ UrlbarProvider A provider is specialized into searching and returning results from different information sources. Internal providers are usually implemented in separate -*jsm* modules with a *UrlbarProvider* name prefix. External providers can be +*sys.mjs* modules with a *UrlbarProvider* name prefix. External providers can be registered as *Objects* through the *UrlbarProvidersManager*. Each provider is independent and must satisfy a base API, while internal implementation details may vary deeply among different providers. @@ -235,7 +235,7 @@ indicated by the UrlbarQueryContext.muxer property. The Controller -------------- -`UrlbarController `_ +`UrlbarController `_ is the component responsible for reacting to user's input, by communicating proper course of action to the Model (e.g. starting/stopping a query) and the View (e.g. showing/hiding a panel). It is also responsible for reporting Telemetry. @@ -268,8 +268,8 @@ user and handling their input. The View is a replaceable component, as such what is described here is a reference for the default View, but may not be valid for other implementations. -`UrlbarInput.jsm `_ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`UrlbarInput.sys.mjs `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implements an input box *View*, owns an *UrlbarView*. @@ -312,8 +312,8 @@ Implements an input box *View*, owns an *UrlbarView*. value; } -`UrlbarView.jsm `_ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`UrlbarView.sys.mjs `_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Represents the base *View* implementation, communicates with the *Controller*. @@ -342,7 +342,7 @@ Represents the base *View* implementation, communicates with the *Controller*. UrlbarResult ------------ -An `UrlbarResult `_ +An `UrlbarResult `_ instance represents a single search result with a result type, that identifies specific kind of results. Each kind has its own properties, that the *View* may support, and a few common diff --git a/browser/components/urlbar/docs/ranking.rst b/browser/components/urlbar/docs/ranking.rst index a1c9d03c3c..917fd4a38d 100644 --- a/browser/components/urlbar/docs/ranking.rst +++ b/browser/components/urlbar/docs/ranking.rst @@ -7,7 +7,7 @@ Before results appear in the UrlbarView, they are fetched from providers. Each `UrlbarProvider `_ implements its own internal ranking and returns sorted results. -Externally all the results are ranked by the `UrlbarMuxer `_ +Externally all the results are ranked by the `UrlbarMuxer `_ according to an hardcoded list of groups and sub-grups. .. NOTE:: Preferences can influence the groups order, for example by putting diff --git a/browser/components/urlbar/docs/telemetry.rst b/browser/components/urlbar/docs/telemetry.rst index 46e56c8093..502cdc3a11 100644 --- a/browser/components/urlbar/docs/telemetry.rst +++ b/browser/components/urlbar/docs/telemetry.rst @@ -515,16 +515,15 @@ following documents for the details. complete an engagement action, usually unfocusing the urlbar. This also happens when the user switches to another window, if the results popup was opening. - - `Impression`_ : - It is defined as an action where the results had been shown to the user for - a while. In default, it will be recorded when the same results have been - shown and 1 sec has elapsed. The interval value can be modified through the - `browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs` - preference. .. _Engagement: https://dictionary.telemetry.mozilla.org/apps/firefox_desktop/metrics/urlbar_engagement .. _Abandonment: https://dictionary.telemetry.mozilla.org/apps/firefox_desktop/metrics/urlbar_abandonment -.. _Impression: https://dictionary.telemetry.mozilla.org/apps/firefox_desktop/metrics/urlbar_impression + +Changelog + Firefox 125 + The "impression" engagement event has been removed. [Bug `1878983`_] + +.. _1878983: https://bugzilla.mozilla.org/show_bug.cgi?id=1878983 Custom pings for Contextual Services diff --git a/browser/components/urlbar/docs/testing.rst b/browser/components/urlbar/docs/testing.rst index a56bd297a7..14569918dc 100644 --- a/browser/components/urlbar/docs/testing.rst +++ b/browser/components/urlbar/docs/testing.rst @@ -53,10 +53,10 @@ If you are writing a test for a urlbarProvider, you can test the Provider through a XPCShell test. Providers do not modify the UI, instead what they do is receive a url string query, search for the string and bring back the result. An example is the `ProviderPlaces `_, which fetches +rce/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs>`_, which fetches results from the Places database. Another component that’s good for writing XPCShell test is the `urlbarMuxer `_. +source/browser/components/urlbar/UrlbarMuxerUnifiedComplete.sys.mjs>`_. There may be times where writing both an XPCShell test and browser test is necessary. In these situations, you could be testing the result from a Provider @@ -131,10 +131,10 @@ This section describes common test utilities which may be useful when writing a test for the address bar. Below are a description of common utils where you can find helpful testing methods. -Many test utils modules end with ``TestUtils.jsm``. However not every testing -function will end with ``TestUtils.jsm``. For example, `PlacesUtils `_ does not have “Test” within its name. +sys.mjs>`_ does not have “Test” within its name. A critical function to remember is the ``registerCleanupFunction`` within the ``head.js`` file mentioned below. This function's purpose may be to clean @@ -150,7 +150,7 @@ test and contains imports to modules which are useful for each test. Any tasks ``head.js`` adds (via add_task) will run first for each test, and any variables and functions it defines will be available in the scope of each test. This file is small because most of our Utils are actually in other -`.jsm` files. +`.sys.mjs` files. The ``XPCOMUtils.defineLazyModuleGetters`` method within ``head.js`` sets up modules names to where they can be found, their paths. ``Lazy`` means the files @@ -189,7 +189,7 @@ new or private windows, and etc. TestUtils ~~~~~~~~~ -`TestUtils.jsm <../../testing/testutils.html>`_ is useful for general +`TestUtils.sys.mjs <../../testing/testutils.html>`_ is useful for general purpose testing and does not depend on the browser window. This file contains methods that are useful when waiting for a condition to return true, waiting for a specific preference to change, and etc. @@ -204,7 +204,7 @@ EventUtils ~~~~~~~~~~ `EventUtils.js `_ is an older test file and does not -need to be imported because it is not a ``.jsm`` file. ``EventUtils`` is only +need to be imported because it is not a ``.sys.mjs`` file. ``EventUtils`` is only used for browser tests, unlike the other TestUtils listed above which are used for browser tests, XPCShell tests and other tests. diff --git a/browser/components/urlbar/docs/utilities.rst b/browser/components/urlbar/docs/utilities.rst index 9e30087872..27ee15df6f 100644 --- a/browser/components/urlbar/docs/utilities.rst +++ b/browser/components/urlbar/docs/utilities.rst @@ -3,8 +3,8 @@ Utilities Various modules provide shared utilities to the other components: -`UrlbarPrefs.jsm `_ -------------------------------------------------------------------------------------------------------------- +`UrlbarPrefs.sys.mjs `_ +------------------------------------------------------------------------------------------------------------------- Implements a Map-like storage or urlbar related preferences. The values are kept up-to-date. @@ -19,7 +19,7 @@ up-to-date. Newly added preferences should always be properly documented in UrlbarPrefs. -`UrlbarUtils.jsm `_ -------------------------------------------------------------------------------------------------------------- +`UrlbarUtils.sys.mjs `_ +------------------------------------------------------------------------------------------------------------------- Includes shared utils and constants shared across all the components. diff --git a/browser/components/urlbar/metrics.yaml b/browser/components/urlbar/metrics.yaml index 867cf0d2e6..95337d84eb 100644 --- a/browser/components/urlbar/metrics.yaml +++ b/browser/components/urlbar/metrics.yaml @@ -15,6 +15,11 @@ urlbar: type: event description: Recorded when the user abandons a search (blurring the urlbar). extra_keys: + abandonment_type: + description: > + Records the reason that resulted in an abandonment. The possible + values are: `blur` and `tab_switch`. + type: string sap: description: > `sap` is the meaning of `search access point`. It records where the @@ -316,6 +321,7 @@ urlbar: `go_button`, `help`, `inaccurate_location`, + `manage`, `not_interested`, `not_relevant`, `paste_go`, @@ -406,152 +412,6 @@ urlbar: notification_emails: - fx-search-telemetry@mozilla.com expires: never - impression: - disabled: true - type: event - description: Recorded when urlbar results are shown to the user. - extra_keys: - reason: - description: Reason for the impression. - type: string - sap: - description: > - `sap` is the meaning of `search access point`. It records where the - user started the search action from. The possible values are: `urlbar` - , `handoff`, `urlbar_newtab` and `urlbar_addonpage`. - type: string - interaction: - description: > - How the user started the search action. The possible values are: - `typed`, `pasted`, `topsite_search` (clicked on a topsite search - shortcut), `topsites` (selected a topsite result with empty search - string), `returned` (The user abandoned a search, then returned to it) - , `restarted` (The user abandoned a search, then returned to it, - cleared it and typed a completely different string), `refined` (The - user abandoned a search, then returned to it, and partially modified - the string), `persisted_search_terms` (The user returned to a previous - successful search that persisted terms in the urlbar), - `persisted_search_terms_restarted` (The user returned to a previous - successful search that persisted terms in the urlbar, then cleared it - and typed a completely different string) and - `persisted_search_terms_refined` (The user returned to a previous - successful search that persisted terms in the urlbar, and partially - modified the string). - type: string - search_mode: - description: > - If the urlbar is in search mode, thus restricting results to a - specific search engine or local source, this is set to the search mode - source. The possible sources are: `actions`, `bookmarks`, `history`, - `search_engine`, and `tabs`. If search mode is active but the source - did not fall into any of these categories, this will be `unknown`. If - search mode is not active, this will be an empty string. - type: string - search_engine_default_id: - description: > - The telemetry id of the search engine. - Reflects `search.engine.default.engine_id`. - type: string - n_chars: - description: > - The length of string used for the search. It includes whitespaces. - type: quantity - n_words: - description: > - The length of words used for the search. The words are made by - splitting the search string by whitespaces, thus this doesn’t support - CJK languages. For performance reasons a maximum of 255 characters are - considered when splitting. - type: quantity - n_results: - description: > - The number of results shown to the user. If this is high the results - list below may be truncated due to technical limitations. Also note in - that case not all the results may be physically visible due to the - screen size limitation. - type: quantity - groups: - description: > - Comma separated list of result groups in the order they were shown to - the user. The groups may be repeated, since the list will match 1:1 - the results list, so we can link each result to a group. The possible - group names are: `heuristic`, `adaptive_history`, `search_history`, - `search_suggest`, `search_suggest_rich`, `trending_search`, - `trending_search_rich`, `top_pick`, `top_site`, `remote_tab`, - `addon`, `general`, `suggest`, `about_page` and `suggested_index`. If - the group did not fall into any of these, this will be `unknown` and - a bug should be filed to investigate it. - type: string - results: - description: > - Comma separated list of result types in the order they were shown to - the user. The `unknown` type should not occur and indicates a bug. The - possible types are: - `action`, - `addon`, - `autofill_about`, - `autofill_adaptive`, - `autofill_origin`, - `autofill_unknown`, - `autofill_url`, - `bookmark`, - `calc`, - `clipboard`, - `fxsuggest_data_sharing_opt_in`, - `history`, - `intervention_clear`, - `intervention_refresh`, - `intervention_unknown`, - `intervention_update`, - `keyword`, - `merino_adm_nonsponsored`, - `merino_adm_sponsored`, - `merino_amo`, - `merino_top_picks`, - `merino_wikipedia`, - `remote_tab`, - `rs_adm_nonsponsored`, - `rs_adm_sponsored`, - `rs_amo`, - `rs_mdn`, - `rs_pocket`, - `rust_adm_nonsponsored`, - `rust_adm_sponsored`, - `rust_amo`, - `rust_mdn`, - `rust_pocket`, - `rust_yelp`, - `search_engine`, - `search_history`, - `search_suggest`, - `search_suggest_rich`, - `tab`, - `tab_to_search`, - `tip_dismissal_acknowledgment`, - `tip_onboard`, - `tip_persist`, - `tip_redirect`, - `tip_unknown`, - `top_site`, - `trending_search`, - `trending_search_rich`, - `unit`, - `url`, - `weather` - type: string - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1800579 - - https://bugzilla.mozilla.org/show_bug.cgi?id=1805717 - - https://bugzilla.mozilla.org/show_bug.cgi?id=1842247 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1800579#c4 - - https://bugzilla.mozilla.org/show_bug.cgi?id=1805717#c4 - - https://bugzilla.mozilla.org/show_bug.cgi?id=1842247#c3 - data_sensitivity: - - interaction - notification_emails: - - fx-search-telemetry@mozilla.com - expires: never exposure: type: event diff --git a/browser/components/urlbar/private/AddonSuggestions.sys.mjs b/browser/components/urlbar/private/AddonSuggestions.sys.mjs index 35849e5cd1..23311cec1c 100644 --- a/browser/components/urlbar/private/AddonSuggestions.sys.mjs +++ b/browser/components/urlbar/private/AddonSuggestions.sys.mjs @@ -178,7 +178,7 @@ export class AddonSuggestions extends BaseFeature { ); } - getResultCommands(result) { + getResultCommands() { const commands = []; if (this.canShowLessFrequently) { diff --git a/browser/components/urlbar/private/AdmWikipedia.sys.mjs b/browser/components/urlbar/private/AdmWikipedia.sys.mjs index 0e266ced40..3ab5bad09f 100644 --- a/browser/components/urlbar/private/AdmWikipedia.sys.mjs +++ b/browser/components/urlbar/private/AdmWikipedia.sys.mjs @@ -135,7 +135,7 @@ export class AdmWikipedia extends BaseFeature { this.#suggestionsMap = suggestionsMap; } - makeResult(queryContext, suggestion, searchString) { + makeResult(queryContext, suggestion) { let originalUrl; if (suggestion.source == "rust") { // The Rust backend defines `rawUrl` on AMP suggestions, and its value is diff --git a/browser/components/urlbar/private/BaseFeature.sys.mjs b/browser/components/urlbar/private/BaseFeature.sys.mjs index d95ace6940..7155d9b7e6 100644 --- a/browser/components/urlbar/private/BaseFeature.sys.mjs +++ b/browser/components/urlbar/private/BaseFeature.sys.mjs @@ -89,10 +89,10 @@ export class BaseFeature { * This method should initialize or uninitialize any state related to the * feature. * - * @param {boolean} enabled + * @param {boolean} _enabled * Whether the feature should be enabled or not. */ - enable(enabled) {} + enable(_enabled) {} /** * If the feature manages suggestions from remote settings that should be @@ -100,12 +100,12 @@ export class BaseFeature { * method. It should return remote settings suggestions matching the given * search string. * - * @param {string} searchString + * @param {string} _searchString * The search string. * @returns {Array} * An array of matching suggestions, or null if not implemented. */ - async queryRemoteSettings(searchString) { + async queryRemoteSettings(_searchString) { return null; } @@ -114,10 +114,10 @@ export class BaseFeature { * override this method. It should fetch the data and build whatever data * structures are necessary to support the feature. * - * @param {RemoteSettings} rs + * @param {RemoteSettings} _rs * The `RemoteSettings` client object. */ - async onRemoteSettingsSync(rs) {} + async onRemoteSettingsSync(_rs) {} /** * If the feature manages suggestions that either aren't served by Merino or @@ -126,12 +126,12 @@ export class BaseFeature { * given suggestion. A telemetry type uniquely identifies a type of suggestion * as well as the kind of `UrlbarResult` instances created from it. * - * @param {object} suggestion + * @param {object} _suggestion * A suggestion from either remote settings or Merino. * @returns {string} * The suggestion's telemetry type. */ - getSuggestionTelemetryType(suggestion) { + getSuggestionTelemetryType(_suggestion) { return this.merinoProvider; } @@ -143,13 +143,13 @@ export class BaseFeature { * fine to rely on the default implementation here because the suggestion type * will be enabled iff the feature itself is enabled. * - * @param {string} type + * @param {string} _type * A Rust suggestion type name as defined in `suggest.udl`. See also * `rustSuggestionTypes`. * @returns {boolean} * Whether the suggestion type is enabled. */ - isRustSuggestionTypeEnabled(type) { + isRustSuggestionTypeEnabled(_type) { return true; } @@ -158,18 +158,18 @@ export class BaseFeature { * override this method. It should return a new `UrlbarResult` for a given * suggestion, which can come from either remote settings or Merino. * - * @param {UrlbarQueryContext} queryContext + * @param {UrlbarQueryContext} _queryContext * The query context. - * @param {object} suggestion + * @param {object} _suggestion * The suggestion from either remote settings or Merino. - * @param {string} searchString + * @param {string} _searchString * The search string that was used to fetch the suggestion. It may be * different from `queryContext.searchString` due to trimming, lower-casing, * etc. This is included as a param in case it's useful. * @returns {UrlbarResult} * A new result for the suggestion. */ - async makeResult(queryContext, suggestion, searchString) { + async makeResult(_queryContext, _suggestion, _searchString) { return null; } diff --git a/browser/components/urlbar/private/MDNSuggestions.sys.mjs b/browser/components/urlbar/private/MDNSuggestions.sys.mjs index 7547b0adff..c9e7da18af 100644 --- a/browser/components/urlbar/private/MDNSuggestions.sys.mjs +++ b/browser/components/urlbar/private/MDNSuggestions.sys.mjs @@ -91,7 +91,7 @@ export class MDNSuggestions extends BaseFeature { this.#suggestionsMap = suggestionsMap; } - async makeResult(queryContext, suggestion, searchString) { + async makeResult(queryContext, suggestion) { if (!this.isEnabled) { // The feature is disabled on the client, but Merino may still return // mdn suggestions anyway, and we filter them out here. @@ -134,7 +134,7 @@ export class MDNSuggestions extends BaseFeature { ); } - getResultCommands(result) { + getResultCommands() { return [ { l10n: { diff --git a/browser/components/urlbar/private/SuggestBackendJs.sys.mjs b/browser/components/urlbar/private/SuggestBackendJs.sys.mjs index 4a91e41b59..58d0ca5edf 100644 --- a/browser/components/urlbar/private/SuggestBackendJs.sys.mjs +++ b/browser/components/urlbar/private/SuggestBackendJs.sys.mjs @@ -278,7 +278,6 @@ export class SuggestBackendJs extends BaseFeature { #config = {}; #emitter = null; - #logger = null; #onSettingsSync = null; } diff --git a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs index fe54feaee8..2d96e7540f 100644 --- a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs +++ b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs @@ -216,7 +216,21 @@ export class SuggestBackendRust extends BaseFeature { } async #init() { - // Create the store. + // Important note on schema updates: + // + // The first time the Suggest store is accessed after a schema version + // update, its backing database will be deleted and a new empty database + // will be created. The database will remain empty until we tell the store + // to ingest. If we wait to ingest as usual until our ingest timer fires, + // the store will remain empty for up to 24 hours, which means we won't + // serve any suggestions at all during that time. + // + // Therefore we simply always ingest here in `#init()`. We'll sometimes + // ingest unnecessarily but that's better than the alternative. (As a + // reminder, for users who have Suggest enabled `#init()` is called whenever + // the Rust backend is enabled, including on startup.) + + // Initialize the store. let path = this.#storePath; this.logger.info("Initializing SuggestStore: " + path); try { @@ -235,17 +249,20 @@ export class SuggestBackendRust extends BaseFeature { return; } - // Before registering the ingest timer, check the last-update pref, which is - // created by the timer manager the first time we register it. If the pref - // doesn't exist, this is the first time the Rust backend has been enabled - // in this profile. In that case, perform ingestion immediately to make - // automated and manual testing easier. Otherwise we'd need to wait at least - // 30s (`app.update.timerFirstInterval`) for the timer manager to call us - // back (and we'd also need to pass false for `skipFirst` below). + // Log the last ingest time for debugging. let lastIngestSecs = Services.prefs.getIntPref( INGEST_TIMER_LAST_UPDATE_PREF, 0 ); + if (lastIngestSecs) { + this.logger.debug( + `Last ingest time: ${lastIngestSecs}s (${ + Math.round(Date.now() / 1000) - lastIngestSecs + }s ago)` + ); + } else { + this.logger.debug("Last ingest time: none"); + } // Register the ingest timer. lazy.timerManager.registerTimer( @@ -255,14 +272,8 @@ export class SuggestBackendRust extends BaseFeature { true // skipFirst ); - if (lastIngestSecs) { - this.logger.info( - `Last ingest: ${lastIngestSecs}s since epoch. Not ingesting now` - ); - } else { - this.logger.info("Last ingest time not found. Ingesting now"); - await this.#ingest(); - } + // Ingest. + await this.#ingest(); } #uninit() { diff --git a/browser/components/urlbar/private/Weather.sys.mjs b/browser/components/urlbar/private/Weather.sys.mjs index c4dfa8c618..51ff5b2f3f 100644 --- a/browser/components/urlbar/private/Weather.sys.mjs +++ b/browser/components/urlbar/private/Weather.sys.mjs @@ -33,8 +33,8 @@ const NOTIFICATIONS = { }; const RESULT_MENU_COMMAND = { - HELP: "help", INACCURATE_LOCATION: "inaccurate_location", + MANAGE: "manage", NOT_INTERESTED: "not_interested", NOT_RELEVANT: "not_relevant", SHOW_LESS_FREQUENTLY: "show_less_frequently", @@ -171,14 +171,14 @@ export class Weather extends BaseFeature { return ["Weather"]; } - isRustSuggestionTypeEnabled(type) { + isRustSuggestionTypeEnabled() { // When weather keywords are defined in Nimbus, weather suggestions are // served by UrlbarProviderWeather. Return false here so the quick suggest // provider doesn't try to serve them too. return !lazy.UrlbarPrefs.get("weatherKeywords"); } - getSuggestionTelemetryType(suggestion) { + getSuggestionTelemetryType() { return "weather"; } @@ -190,6 +190,16 @@ export class Weather extends BaseFeature { return this.#suggestion; } + /** + * @returns {Promise} + * If suggestion fetching is disabled, this will be null. Otherwise, if a + * fetch is pending this will be resolved when it's done; if a fetch is not + * pending then it was resolved when the previous fetch finished. + */ + get fetchPromise() { + return this.#fetchPromise; + } + /** * @returns {Set} * The set of keywords that should trigger the weather suggestion. This will @@ -291,20 +301,6 @@ export class Weather extends BaseFeature { } } - /** - * Returns a promise that resolves when all pending fetches finish, if there - * are pending fetches. If there aren't, the promise resolves when all pending - * fetches starting with the next fetch finish. - * - * @returns {Promise} - */ - waitForFetches() { - if (!this.#waitForFetchesDeferred) { - this.#waitForFetchesDeferred = Promise.withResolvers(); - } - return this.#waitForFetchesDeferred.promise; - } - async onRemoteSettingsSync(rs) { this.logger.debug("Loading weather config from remote settings"); let records = await rs.get({ filters: { type: "weather" } }); @@ -356,7 +352,6 @@ export class Weather extends BaseFeature { { url: suggestion.url, iconId: suggestion.current_conditions.icon_id, - helpUrl: lazy.QuickSuggest.HELP_URL, requestId: suggestion.request_id, dynamicType: WEATHER_DYNAMIC_TYPE, city: suggestion.city_name, @@ -410,17 +405,19 @@ export class Weather extends BaseFeature { url: { textContent: result.payload.url, }, - summaryText: { - l10n: { - id: "firefox-suggest-weather-summary-text", - args: { - currentConditions: result.payload.currentConditions, - forecast: result.payload.forecast, + summaryText: lazy.UrlbarPrefs.get("weatherSimpleUI") + ? { textContent: result.payload.currentConditions } + : { + l10n: { + id: "firefox-suggest-weather-summary-text", + args: { + currentConditions: result.payload.currentConditions, + forecast: result.payload.forecast, + }, + cacheable: true, + excludeArgsFromCacheKey: true, + }, }, - cacheable: true, - excludeArgsFromCacheKey: true, - }, - }, highLow: { l10n: { id: "firefox-suggest-weather-high-low", @@ -453,7 +450,7 @@ export class Weather extends BaseFeature { }; } - getResultCommands(result) { + getResultCommands() { let commands = [ { name: RESULT_MENU_COMMAND.INACCURATE_LOCATION, @@ -494,9 +491,9 @@ export class Weather extends BaseFeature { }, { name: "separator" }, { - name: RESULT_MENU_COMMAND.HELP, + name: RESULT_MENU_COMMAND.MANAGE, l10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", + id: "urlbar-result-menu-manage-firefox-suggest", }, } ); @@ -506,8 +503,8 @@ export class Weather extends BaseFeature { handleCommand(view, result, selType) { switch (selType) { - case RESULT_MENU_COMMAND.HELP: - // "help" is handled by UrlbarInput, no need to do anything here. + case RESULT_MENU_COMMAND.MANAGE: + // "manage" is handled by UrlbarInput, no need to do anything here. break; // selType == "dismiss" when the user presses the dismiss key shortcut. case "dismiss": @@ -616,6 +613,21 @@ export class Weather extends BaseFeature { } async #fetch() { + // Keep a handle on the `MerinoClient` instance that exists at the start of + // this fetch. If fetching stops or this `Weather` instance is uninitialized + // during the fetch, `#merino` will be nulled, and the fetch should stop. We + // can compare `merino` to `#merino` to tell when this occurs. + let merino = this.#merino; + let fetchInstance = (this.#fetchInstance = {}); + + await this.#fetchPromise; + if (fetchInstance != this.#fetchInstance || merino != this.#merino) { + return; + } + await (this.#fetchPromise = this.#fetchHelper({ fetchInstance, merino })); + } + + async #fetchHelper({ fetchInstance, merino }) { this.logger.info("Fetching suggestion"); if (this.#vpnDetected) { @@ -626,36 +638,19 @@ export class Weather extends BaseFeature { // new fetch. this.logger.info("VPN detected, not fetching"); this.#suggestion = null; - if (!this.#pendingFetchCount) { - this.#waitForFetchesDeferred?.resolve(); - this.#waitForFetchesDeferred = null; - } return; } - // This `Weather` instance may be uninitialized while awaiting the fetch or - // even uninitialized and re-initialized a number of times. Multiple fetches - // may also happen at once. Ignore the fetch below if `#merino` changes or - // another fetch happens in the meantime. - let merino = this.#merino; - let instance = (this.#fetchInstance = {}); - this.#restartFetchTimer(); this.#lastFetchTimeMs = Date.now(); - this.#pendingFetchCount++; - let suggestions; - try { - suggestions = await merino.fetch({ - query: "", - providers: [MERINO_PROVIDER], - timeoutMs: this.#timeoutMs, - extraLatencyHistogram: HISTOGRAM_LATENCY, - extraResponseHistogram: HISTOGRAM_RESPONSE, - }); - } finally { - this.#pendingFetchCount--; - } + let suggestions = await merino.fetch({ + query: "", + providers: [MERINO_PROVIDER], + timeoutMs: this.#timeoutMs, + extraLatencyHistogram: HISTOGRAM_LATENCY, + extraResponseHistogram: HISTOGRAM_RESPONSE, + }); // Reset the Merino client's session so different fetches use different // sessions. A single session is intended to represent a single user @@ -665,28 +660,23 @@ export class Weather extends BaseFeature { // to keep it ticking in the meantime. merino.resetSession(); - if (merino != this.#merino || instance != this.#fetchInstance) { - this.logger.info("Fetch finished but is out of date, ignoring"); - } else { - let suggestion = suggestions?.[0]; - if (!suggestion) { - // No suggestion was received. The network may be offline or there may - // be some other problem. Set the suggestion to null: Better to show - // nothing than outdated weather information. When the network comes - // back online, one or more network notifications will be sent, - // triggering a new fetch. - this.logger.info("No suggestion received"); - this.#suggestion = null; - } else { - this.logger.info("Got suggestion"); - this.logger.debug(JSON.stringify({ suggestion })); - this.#suggestion = { ...suggestion, source: "merino" }; - } + if (fetchInstance != this.#fetchInstance || merino != this.#merino) { + this.logger.info("Fetch is out of date, discarding suggestion"); + return; } - if (!this.#pendingFetchCount) { - this.#waitForFetchesDeferred?.resolve(); - this.#waitForFetchesDeferred = null; + let suggestion = suggestions?.[0]; + if (!suggestion) { + // No suggestion was received. The network may be offline or there may be + // some other problem. Set the suggestion to null: Better to show nothing + // than outdated weather information. When the network comes back online, + // one or more network notifications will be sent, triggering a new fetch. + this.logger.info("No suggestion received"); + this.#suggestion = null; + } else { + this.logger.info("Got suggestion"); + this.logger.debug(JSON.stringify({ suggestion })); + this.#suggestion = { ...suggestion, source: "merino" }; } } @@ -865,10 +855,6 @@ export class Weather extends BaseFeature { return this.#merino; } - get _test_pendingFetchCount() { - return this.#pendingFetchCount; - } - async _test_fetch() { await this.#fetch(); } @@ -884,13 +870,12 @@ export class Weather extends BaseFeature { #fetchDelayAfterComingOnlineMs = FETCH_DELAY_AFTER_COMING_ONLINE_MS; #fetchInstance = null; #fetchIntervalMs = FETCH_INTERVAL_MS; + #fetchPromise = null; #fetchTimer = 0; #keywords = null; #lastFetchTimeMs = 0; #merino = null; - #pendingFetchCount = 0; #rsConfig = null; #suggestion = null; #timeoutMs = MERINO_TIMEOUT_MS; - #waitForFetchesDeferred = null; } diff --git a/browser/components/urlbar/private/YelpSuggestions.sys.mjs b/browser/components/urlbar/private/YelpSuggestions.sys.mjs index 546c7ce216..a1ac13177b 100644 --- a/browser/components/urlbar/private/YelpSuggestions.sys.mjs +++ b/browser/components/urlbar/private/YelpSuggestions.sys.mjs @@ -55,7 +55,7 @@ export class YelpSuggestions extends BaseFeature { return !cap || this.showLessFrequentlyCount < cap; } - getSuggestionTelemetryType(suggestion) { + getSuggestionTelemetryType() { return "yelp"; } @@ -127,7 +127,7 @@ export class YelpSuggestions extends BaseFeature { ); } - getResultCommands(result) { + getResultCommands() { let commands = [ { name: RESULT_MENU_COMMAND.INACCURATE_LOCATION, diff --git a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs index a8e422526c..cfc9ecb3d8 100644 --- a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs +++ b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs @@ -545,6 +545,7 @@ export var UrlbarTestUtils = { details.title = result.title; details.tags = "tags" in result.payload ? result.payload.tags : []; details.isSponsored = result.payload.isSponsored; + details.userContextId = result.payload.userContextId; let actions = element.getElementsByClassName("urlbarView-action"); let urls = element.getElementsByClassName("urlbarView-url"); let typeIcon = element.querySelector(".urlbarView-type-icon"); @@ -1536,11 +1537,11 @@ class TestProvider extends UrlbarProvider { return this._type; } - getPriority(context) { + getPriority(_context) { return this.priority; } - isActive(context) { + isActive(_context) { return true; } @@ -1565,7 +1566,7 @@ class TestProvider extends UrlbarProvider { } } - cancelQuery(context) { + cancelQuery(_context) { this._onCancel?.(); } diff --git a/browser/components/urlbar/tests/browser-tips/browser_searchTips_interaction.js b/browser/components/urlbar/tests/browser-tips/browser_searchTips_interaction.js index a5cee02dae..72d05cf632 100644 --- a/browser/components/urlbar/tests/browser-tips/browser_searchTips_interaction.js +++ b/browser/components/urlbar/tests/browser-tips/browser_searchTips_interaction.js @@ -680,7 +680,7 @@ add_task(async function noActionWhenDisabled() { ], }); - await withDNSRedirect("www.bing.com", "/", async url => { + await withDNSRedirect("www.bing.com", "/", async () => { Assert.ok( !UrlbarTestUtils.isPopupOpen(window), "The UrlbarView should not be open." diff --git a/browser/components/urlbar/tests/browser-updateResults/browser_appendSpanCount.js b/browser/components/urlbar/tests/browser-updateResults/browser_appendSpanCount.js index 60838d0c8b..ccea889a9c 100644 --- a/browser/components/urlbar/tests/browser-updateResults/browser_appendSpanCount.js +++ b/browser/components/urlbar/tests/browser-updateResults/browser_appendSpanCount.js @@ -99,7 +99,7 @@ add_task(async function viewUpdateAppendHidden() { // The `- 2` subtracts the heuristic and tip result. let newExpectedRowCount = 2 * expectedRowCount - 2; let mutationPromise = new Promise(resolve => { - let observer = new MutationObserver(mutations => { + let observer = new MutationObserver(() => { let childCount = UrlbarTestUtils.getResultCount(window); info(`Rows mutation observer called, childCount now ${childCount}`); if (newExpectedRowCount <= childCount) { diff --git a/browser/components/urlbar/tests/browser-updateResults/head.js b/browser/components/urlbar/tests/browser-updateResults/head.js index 3d46d83018..77b110990e 100644 --- a/browser/components/urlbar/tests/browser-updateResults/head.js +++ b/browser/components/urlbar/tests/browser-updateResults/head.js @@ -386,7 +386,7 @@ async function doSuggestedIndexTest({ search1, search2, duringUpdate }) { // update and delaying resolving the provider's finishQueryPromise. let mutationPromise = new Promise(resolve => { let lastRowState = duringUpdate[duringUpdate.length - 1]; - let observer = new MutationObserver(mutations => { + let observer = new MutationObserver(() => { observer.disconnect(); resolve(); }); diff --git a/browser/components/urlbar/tests/browser/browser.toml b/browser/components/urlbar/tests/browser/browser.toml index a77a831fab..b9934aa838 100644 --- a/browser/components/urlbar/tests/browser/browser.toml +++ b/browser/components/urlbar/tests/browser/browser.toml @@ -314,7 +314,6 @@ support-files = [ ["browser_oneOffs_heuristicRestyle.js"] skip-if = [ "os == 'linux' && bits == 64 && !debug", # Bug 1775811 - "a11y_checks", # Bugs 1858041 and 1854661 to investigate intermittent a11y_checks results ] ["browser_oneOffs_keyModifiers.js"] @@ -596,6 +595,8 @@ https_first_disabled = true ["browser_top_sites_private.js"] https_first_disabled = true +["browser_top_sites_switchtab.js"] + ["browser_typed_value.js"] ["browser_unitConversion.js"] diff --git a/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js b/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js index 5ad8dfc75d..8c4b05501e 100644 --- a/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js +++ b/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js @@ -7,6 +7,10 @@ "use strict"; +// See the comment in the setup task for the expected index of the main test +// result. +const RESULT_INDEX = 2; + // The command that dismisses a single result. const DISMISS_ONE_COMMAND = "dismiss-one"; @@ -20,18 +24,40 @@ const FEEDBACK_COMMAND = "show_less_frequently"; let gTestProvider; add_setup(async function () { + // This test expects the following results in the following order: + // + // 1. The heuristic + // 2. A history result + // 3. A result from our test provider. This will be the main result we'll use + // during this test. + // 4. Another history result + // + // This ensures a couple things: + // + // * The main test result has rows above and below it. Feedback and dismissal + // acknowledgments in the main result row should not affect adjacent rows, + // except that when the dismissal acknowledgment itself is dismissed, it + // should be replaced by the row below it. + // * The main result does not have a row label (a.k.a. group label). There's a + // separate task that specifically checks the row label, and that way this + // test covers both cases, where the row does and does not have a row label. gTestProvider = new TestProvider({ results: [ - new UrlbarResult( - UrlbarUtils.RESULT_TYPE.URL, - UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, - { - url: "https://example.com/", - isBlockable: true, - blockL10n: { - id: "urlbar-result-menu-dismiss-firefox-suggest", - }, - } + Object.assign( + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + { + url: "https://example.com/", + isBlockable: true, + blockL10n: { + id: "urlbar-result-menu-dismiss-firefox-suggest", + }, + } + ), + // This ensures the result is sandwiched between the two history results + // in the Firefox Suggest group. + { suggestedIndex: 1, isSuggestedIndexRelativeToGroup: true } ), ], }); @@ -39,13 +65,15 @@ add_setup(async function () { gTestProvider.commandCount = {}; UrlbarProvidersManager.registerProvider(gTestProvider); - // Add a visit so that there's one result above the test result (the - // heuristic) and one below (the visit) just to make sure removing the test - // result doesn't mess up adjacent results. await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); await UrlbarTestUtils.formHistory.clear(); - await PlacesTestUtils.addVisits("https://example.com/aaa"); + + // Add visits for the two history results. + await PlacesTestUtils.addVisits([ + "https://example.com/aaa", + "https://example.com/bbb", + ]); registerCleanupFunction(() => { UrlbarProvidersManager.unregisterProvider(gTestProvider); @@ -72,13 +100,9 @@ add_task(async function acknowledgeDismissal_rowSelected() { }); // Select the row. - let resultIndex = await getTestResultIndex(); - while (gURLBar.view.selectedRowIndex != resultIndex) { - this.EventUtils.synthesizeKey("KEY_ArrowDown"); - } + gURLBar.view.selectedRowIndex = RESULT_INDEX; await doDismissTest({ - resultIndex, command: DISMISS_ONE_COMMAND, shouldBeSelected: true, }); @@ -95,12 +119,14 @@ add_task(async function acknowledgeFeedbackAndDismissal() { value: "test", }); - let resultIndex = await getTestResultIndex(); - let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex); + let details = await UrlbarTestUtils.getDetailsOfResultAt( + window, + RESULT_INDEX + ); // Click the feedback command. await UrlbarTestUtils.openResultMenuAndClickItem(window, FEEDBACK_COMMAND, { - resultIndex, + resultIndex: RESULT_INDEX, }); Assert.equal( @@ -121,7 +147,6 @@ add_task(async function acknowledgeFeedbackAndDismissal() { info("Doing dismissal"); await doDismissTest({ - resultIndex, command: DISMISS_ONE_COMMAND, shouldBeSelected: true, }); @@ -139,6 +164,29 @@ add_task(async function acknowledgeDismissal_all() { }); }); +// When a row with a row label (a.k.a. group label) is dismissed, the dismissal +// acknowledgment tip should retain the label. When the tip is then dismissed, +// the row that replaces it should also retain the label. +add_task(async function acknowledgeDismissal_rowLabel() { + // Show the result as the first row in the Firefox Suggest section so that it + // has the "Firefox Suggest" group label. + let { suggestedIndex } = gTestProvider.results[0]; + gTestProvider.results[0].suggestedIndex = 0; + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test", + }); + await doDismissTest({ + resultIndex: 1, + command: DISMISS_ALL_COMMAND, + shouldBeSelected: false, + expectedLabel: "Firefox Suggest", + }); + + gTestProvider.results[0].suggestedIndex = suggestedIndex; +}); + /** * Does a dismissal test: * @@ -158,10 +206,25 @@ add_task(async function acknowledgeDismissal_all() { * @param {number} options.resultIndex * The index of the test result, if known beforehand. Leave -1 to find it * automatically. + * @param {string} options.expectedLabel + * The row label (a.k.a. group label) the row is expected to have. This should + * be the expected translated en-US string, not an l10n object. If null, the + * row is expected not to have a row label at all. */ -async function doDismissTest({ command, shouldBeSelected, resultIndex = -1 }) { - if (resultIndex < 0) { - resultIndex = await getTestResultIndex(); +async function doDismissTest({ + command, + shouldBeSelected, + resultIndex = 2, + expectedLabel = null, +}) { + let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex); + Assert.equal( + details.result.providerName, + gTestProvider.name, + "The test result should be at the expected index" + ); + if (details.result.providerName != gTestProvider.name) { + return; } let selectedElement = gURLBar.view.selectedElement; @@ -181,6 +244,9 @@ async function doDismissTest({ command, shouldBeSelected, resultIndex = -1 }) { ); } + info("Checking the row label on the original row"); + await checkRowLabel(resultIndex, expectedLabel); + let resultCount = UrlbarTestUtils.getResultCount(window); // Click the command. @@ -204,7 +270,7 @@ async function doDismissTest({ command, shouldBeSelected, resultIndex = -1 }) { "The result count should not haved changed after dismissal" ); - let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex); + details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex); Assert.equal( details.type, UrlbarUtils.RESULT_TYPE.TIP, @@ -235,6 +301,9 @@ async function doDismissTest({ command, shouldBeSelected, resultIndex = -1 }) { "Row should not have feedback acknowledgment after dismissal" ); + info("Checking the row label on the dismissal acknowledgment tip"); + await checkRowLabel(resultIndex, expectedLabel); + // Get the dismissal acknowledgment's "Got it" button. let gotItButton = UrlbarTestUtils.getButtonForResultIndex( window, @@ -284,6 +353,11 @@ async function doDismissTest({ command, shouldBeSelected, resultIndex = -1 }) { ); } + info( + "Checking the row label on the row that replaced the dismissal acknowledgment tip" + ); + await checkRowLabel(resultIndex, expectedLabel); + await UrlbarTestUtils.promisePopupClose(window); } @@ -291,7 +365,7 @@ async function doDismissTest({ command, shouldBeSelected, resultIndex = -1 }) { * A provider that acknowledges feedback and dismissals. */ class TestProvider extends UrlbarTestUtils.TestProvider { - getResultCommands(result) { + getResultCommands(_result) { // The l10n values aren't important. return [ { @@ -347,15 +421,27 @@ class TestProvider extends UrlbarTestUtils.TestProvider { } } -async function getTestResultIndex() { - let index = 0; - let resultCount = UrlbarTestUtils.getResultCount(window); - for (; index < resultCount; index++) { - let details = await UrlbarTestUtils.getDetailsOfResultAt(window, index); - if (details.result.providerName == gTestProvider.name) { - break; - } +async function checkRowLabel(resultIndex, expectedLabel) { + let details = await UrlbarTestUtils.getDetailsOfResultAt(window, resultIndex); + let { row } = details.element; + let before = getComputedStyle(row, "::before"); + + if (expectedLabel) { + Assert.equal( + before.content, + "attr(label)", + "::before content should use the row label" + ); + Assert.equal( + row.getAttribute("label"), + expectedLabel, + "Row should have the expected label attribute" + ); + } else { + Assert.equal(before.content, "none", "::before content should be 'none'"); + Assert.ok( + !row.hasAttribute("label"), + "Row should not have a label attribute" + ); } - Assert.less(index, resultCount, "The test result should be present"); - return index; } diff --git a/browser/components/urlbar/tests/browser/browser_add_search_engine.js b/browser/components/urlbar/tests/browser/browser_add_search_engine.js index cfcaccfdd5..5ee41649ba 100644 --- a/browser/components/urlbar/tests/browser/browser_add_search_engine.js +++ b/browser/components/urlbar/tests/browser/browser_add_search_engine.js @@ -85,9 +85,6 @@ add_task(async function context_one() { add_task(async function context_invalid() { info("Checks the context menu with a page that offers an invalid engine."); - await SpecialPowers.pushPrefEnv({ - set: [["prompts.contentPromptSubDialog", false]], - }); let url = getRootDirectory(gTestPath) + "add_search_engine_invalid.html"; await BrowserTestUtils.withNewTab(url, async tab => { @@ -321,5 +318,5 @@ function promiseEngine(expectedData, expectedEngineName) { expectedEngineName == engine.wrappedJSObject.name ); } - ).then(([engine, data]) => engine); + ).then(([engine]) => engine); } diff --git a/browser/components/urlbar/tests/browser/browser_autoOpen.js b/browser/components/urlbar/tests/browser/browser_autoOpen.js index bfe491fc61..19035e4149 100644 --- a/browser/components/urlbar/tests/browser/browser_autoOpen.js +++ b/browser/components/urlbar/tests/browser/browser_autoOpen.js @@ -41,7 +41,7 @@ add_setup(async function () { add_task(async function test() { await BrowserTestUtils.withNewTab( { gBrowser, url: "about:blank" }, - async browser => { + async () => { await checkOpensOnFocus(); } ); diff --git a/browser/components/urlbar/tests/browser/browser_bestMatch.js b/browser/components/urlbar/tests/browser/browser_bestMatch.js index 21c97405a6..a21f81dfee 100644 --- a/browser/components/urlbar/tests/browser/browser_bestMatch.js +++ b/browser/components/urlbar/tests/browser/browser_bestMatch.js @@ -117,11 +117,7 @@ add_task(async function keySelection() { }); }); -async function checkBestMatchRow({ - result, - isSponsored = false, - hasHelpUrl = false, -}) { +async function checkBestMatchRow({ result, hasHelpUrl = false }) { Assert.equal( UrlbarTestUtils.getResultCount(window), 1, diff --git a/browser/components/urlbar/tests/browser/browser_blanking.js b/browser/components/urlbar/tests/browser/browser_blanking.js index f68c4d894a..b5ec976eac 100644 --- a/browser/components/urlbar/tests/browser/browser_blanking.js +++ b/browser/components/urlbar/tests/browser/browser_blanking.js @@ -46,7 +46,7 @@ add_task(async function () { await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { // This is sync, so by the time we return we should have changed the URL bar. content.location.reload(); - }).catch(e => { + }).catch(() => { // Ignore expected exception. }); is( diff --git a/browser/components/urlbar/tests/browser/browser_clipboard.js b/browser/components/urlbar/tests/browser/browser_clipboard.js index f6127ef8d9..2332f595cc 100644 --- a/browser/components/urlbar/tests/browser/browser_clipboard.js +++ b/browser/components/urlbar/tests/browser/browser_clipboard.js @@ -19,8 +19,10 @@ add_setup(async function () { ["browser.urlbar.suggest.clipboard", true], ], }); - registerCleanupFunction(() => { + + registerCleanupFunction(async () => { SpecialPowers.clipboardCopyString(""); + await PlacesUtils.history.clear(); }); }); @@ -52,7 +54,7 @@ add_task(async function testFormattingOfClipboardSuggestion() { await BrowserTestUtils.withNewTab( { gBrowser, url: "about:home" }, - async browser => { + async () => { let { result } = await searchEmptyStringAndGetFirstRow(); Assert.equal( @@ -73,6 +75,7 @@ add_task(async function testFormattingOfClipboardSuggestion() { } ); }); + // Verifies that a valid URL copied to the clipboard results in the // display of a corresponding suggestion in the URL bar as the first // suggestion with accurate URL and icon. Also ensures that engaging @@ -121,6 +124,7 @@ add_task(async function testUserEngagementWithClipboardSuggestion() { await checkClipboardSuggestionAbsent(0); } ); + await PlacesUtils.history.clear(); }); // This test confirms that dismissing the result from the result menu @@ -347,3 +351,38 @@ add_task(async function testScalarAndStopWatchTelemetry() { } ); }); + +add_task(async function emptySearch_withClipboardEntry() { + SpecialPowers.clipboardCopyString("https://example.com/1"); + const MAX_RESULTS = 3; + let expectedHistoryResults = []; + + for (let i = 0; i < MAX_RESULTS; i++) { + await PlacesTestUtils.addVisits([`http://mochi.test/${i}`]); + expectedHistoryResults.push(`http://mochi.test/${i}`); + } + + await BrowserTestUtils.withNewTab("about:robots", async function () { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "", + }); + await UrlbarTestUtils.enterSearchMode(window, { + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + }); + + let urls = []; + + for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) { + let url = (await UrlbarTestUtils.getDetailsOfResultAt(window, i)).url; + urls.push(url); + } + + urls.reverse(); + Assert.deepEqual(expectedHistoryResults, urls); + + await UrlbarTestUtils.exitSearchMode(window, { clickClose: true }); + await UrlbarTestUtils.promisePopupClose(window); + }); + await PlacesUtils.history.clear(); +}); diff --git a/browser/components/urlbar/tests/browser/browser_copy_during_load.js b/browser/components/urlbar/tests/browser/browser_copy_during_load.js index 4a81ff08be..3eaa53bcda 100644 --- a/browser/components/urlbar/tests/browser/browser_copy_during_load.js +++ b/browser/components/urlbar/tests/browser/browser_copy_during_load.js @@ -13,7 +13,7 @@ add_task(async function () { "http://www.example.com" ) + "slow-page.sjs"; - await BrowserTestUtils.withNewTab(gBrowser, async tab => { + await BrowserTestUtils.withNewTab(gBrowser, async () => { gURLBar.focus(); gURLBar.value = SLOW_PAGE; let promise = TestUtils.waitForCondition( diff --git a/browser/components/urlbar/tests/browser/browser_copying.js b/browser/components/urlbar/tests/browser/browser_copying.js index 111df58fd1..d9ad6ebaaf 100644 --- a/browser/components/urlbar/tests/browser/browser_copying.js +++ b/browser/components/urlbar/tests/browser/browser_copying.js @@ -727,7 +727,7 @@ add_task(async function loadingPageInBlank() { async function waitForNewTabWithLoadRequest() { return new Promise(resolve => gBrowser.addTabsProgressListener({ - onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags) { if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) { gBrowser.removeTabsProgressListener(this); resolve(gBrowser.getTabForBrowser(aBrowser)); diff --git a/browser/components/urlbar/tests/browser/browser_dragdropURL.js b/browser/components/urlbar/tests/browser/browser_dragdropURL.js index 52c19e8965..1e194ee6af 100644 --- a/browser/components/urlbar/tests/browser/browser_dragdropURL.js +++ b/browser/components/urlbar/tests/browser/browser_dragdropURL.js @@ -43,7 +43,7 @@ function simulateURLBarDrop(content) { } add_task(async function checkDragURL() { - await BrowserTestUtils.withNewTab(TEST_URL, function (browser) { + await BrowserTestUtils.withNewTab(TEST_URL, function () { info("Check dragging a normal url to the urlbar"); const DRAG_URL = "http://www.example.com/"; simulateURLBarDrop({ type: "text/plain", data: DRAG_URL }); @@ -61,7 +61,7 @@ add_task(async function checkDragURL() { }); add_task(async function checkDragForbiddenURL() { - await BrowserTestUtils.withNewTab(TEST_URL, function (browser) { + await BrowserTestUtils.withNewTab(TEST_URL, function () { // See also browser_removeUnsafeProtocolsFromURLBarPaste.js for other // examples. In general we trust that function, we pick some testcases to // ensure we disallow dropping trimmed text. diff --git a/browser/components/urlbar/tests/browser/browser_dynamicResults.js b/browser/components/urlbar/tests/browser/browser_dynamicResults.js index 976ae3b9cb..aad15e0145 100644 --- a/browser/components/urlbar/tests/browser/browser_dynamicResults.js +++ b/browser/components/urlbar/tests/browser/browser_dynamicResults.js @@ -580,7 +580,7 @@ add_task(async function highlighting() { addCallback(this, result); } - getViewUpdate(result, idsByName) { + getViewUpdate(_result, _idsByName) { return {}; } } @@ -617,7 +617,7 @@ add_task(async function highlighting() { * Provides a dynamic result with highlighted text that is then overridden. */ class TestHighlightProviderOveridden extends TestHighlightProvider { - getViewUpdate(result, idsByName) { + getViewUpdate(_result, _idsByName) { return { text: { textContent: "Test title", @@ -904,7 +904,7 @@ class TestProvider extends UrlbarTestUtils.TestProvider { }; } - onEngagement(state, queryContext, details, controller) { + onEngagement(state, queryContext, details, _controller) { if (this._pickPromiseResolve) { let { result, element } = details; this._pickPromiseResolve([result, element]); diff --git a/browser/components/urlbar/tests/browser/browser_groupLabels.js b/browser/components/urlbar/tests/browser/browser_groupLabels.js index 2b43990b77..d04b6bc194 100644 --- a/browser/components/urlbar/tests/browser/browser_groupLabels.js +++ b/browser/components/urlbar/tests/browser/browser_groupLabels.js @@ -139,7 +139,7 @@ add_task(async function generalBeforeSuggestions() { add_task(async function generalBeforeSuggestions_suggestionsOnly() { await PlacesUtils.history.clear(); - await withSuggestions(async engine => { + await withSuggestions(async () => { await SpecialPowers.pushPrefEnv({ set: [[SUGGESTIONS_FIRST_PREF, false]], }); @@ -196,7 +196,7 @@ add_task(async function suggestedIndex_only() { let provider = new SuggestedIndexProvider(index); UrlbarProvidersManager.registerProvider(provider); - await withSuggestions(async engine => { + await withSuggestions(async () => { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "test", diff --git a/browser/components/urlbar/tests/browser/browser_locationBarCommand.js b/browser/components/urlbar/tests/browser/browser_locationBarCommand.js index 670b9741f4..84c45e586a 100644 --- a/browser/components/urlbar/tests/browser/browser_locationBarCommand.js +++ b/browser/components/urlbar/tests/browser/browser_locationBarCommand.js @@ -299,7 +299,7 @@ async function triggerCommand(eventType, details = {}) { function promiseLoadStarted() { return new Promise(resolve => { gBrowser.addTabsProgressListener({ - onStateChange(browser, webProgress, req, flags, status) { + onStateChange(browser, webProgress, req, flags) { if (flags & Ci.nsIWebProgressListener.STATE_START) { gBrowser.removeTabsProgressListener(this); resolve(); diff --git a/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js b/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js index 5a44db54ce..13457db52e 100644 --- a/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js +++ b/browser/components/urlbar/tests/browser/browser_locationBarExternalLoad.js @@ -52,7 +52,7 @@ function promiseLoaded(browser) { }); } -async function testURL(url, loadFunc, endFunc) { +async function testURL(url, loadFunc) { let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); let browser = tab.linkedBrowser; @@ -70,7 +70,7 @@ async function testURL(url, loadFunc, endFunc) { await SpecialPowers.spawn( browser, [{ isRemote: gMultiProcessBrowser }], - async function (arg) { + async function () { Assert.equal( Services.focus.focusedElement, null, diff --git a/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js b/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js index b50446a4c9..4e91069b4a 100644 --- a/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js +++ b/browser/components/urlbar/tests/browser/browser_locationchange_urlbar_edit_dos.js @@ -5,7 +5,7 @@ const TEST_URL = `${TEST_BASE_URL}file_urlbar_edit_dos.html`; -async function checkURLBarValueStays(browser) { +async function checkURLBarValueStays() { gURLBar.select(); EventUtils.sendString("a"); is(gURLBar.value, "a", "URL bar value should match after sending a key"); diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js b/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js index 8f7f058dd8..6badfca72e 100644 --- a/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js +++ b/browser/components/urlbar/tests/browser/browser_oneOffs_heuristicRestyle.js @@ -188,7 +188,7 @@ async function heuristicIsRestyled( if (engine) { Assert.equal( resultDetails.image, - engine.getIconURL() || UrlbarUtils.ICON.SEARCH_GLASS, + (await engine.getIconURL()) || UrlbarUtils.ICON.SEARCH_GLASS, "The restyled result's icon should be the engine's icon." ); } else if (source) { diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js b/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js index b4b1e7006e..2def88b5f9 100644 --- a/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js +++ b/browser/components/urlbar/tests/browser/browser_oneOffs_settings.js @@ -36,7 +36,7 @@ add_setup(async function () { async function selectSettings(win, activateFn) { await BrowserTestUtils.withNewTab( { gBrowser: win.gBrowser, url: "about:blank" }, - async browser => { + async () => { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window: win, value: "example.com", diff --git a/browser/components/urlbar/tests/browser/browser_recentsearches.js b/browser/components/urlbar/tests/browser/browser_recentsearches.js index e0ba5f684f..d2b91a3a53 100644 --- a/browser/components/urlbar/tests/browser/browser_recentsearches.js +++ b/browser/components/urlbar/tests/browser/browser_recentsearches.js @@ -6,10 +6,51 @@ const CONFIG_DEFAULT = [ { webExtension: { id: "basic@search.mozilla.org" }, appliesTo: [{ included: { everywhere: true } }], + urls: { + trending: { + fullPath: + "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + query: "", + }, + }, default: "yes", }, ]; +const CONFIG_DEFAULT_V2 = [ + { + recordType: "engine", + identifier: "basic", + base: { + name: "basic", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + trending: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + }, + }, + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "defaultEngines", + globalDefault: "basic", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + const TOP_SITES = [ "https://example-1.com/", "https://example-2.com/", @@ -38,7 +79,9 @@ add_setup(async () => { }); SearchTestUtils.useMockIdleService(); - await SearchTestUtils.updateRemoteSettingsConfig(CONFIG_DEFAULT); + await SearchTestUtils.updateRemoteSettingsConfig( + SearchUtils.newSearchConfigEnabled ? CONFIG_DEFAULT_V2 : CONFIG_DEFAULT + ); Services.telemetry.clearScalars(); registerCleanupFunction(async () => { @@ -135,4 +178,53 @@ add_task(async () => { Assert.equal(result.providerName, "RecentSearches"); await BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); + +// Test that triggering the help menu of trending suggestions does not +// record that selection as a search. +add_task(async () => { + await UrlbarTestUtils.formHistory.clear(); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.topsites", false], + ["browser.urlbar.suggest.trending", true], + ["browser.urlbar.trending.featureGate", true], + ["browser.urlbar.trending.requireSearchMode", false], + ["app.support.baseURL", "https://example.com"], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "data:text/html," + ); + + info("Open the urlbar and pick the help menu of a trending result."); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "", + }); + + await UrlbarTestUtils.openResultMenuAndClickItem(window, "help", { + resultIndex: 1, + openByMouse: true, + }); + + info("Open the urlbar and check that a recent search has not been added."); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "", + }); + + let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); + Assert.notEqual( + result.providerName, + "RecentSearches", + "Click on help URL did not record a search" + ); + + await BrowserTestUtils.removeTab(gBrowser.selectedTab); + await BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); }); diff --git a/browser/components/urlbar/tests/browser/browser_redirect_error.js b/browser/components/urlbar/tests/browser/browser_redirect_error.js index ae8dec3da6..5f5194e5fc 100644 --- a/browser/components/urlbar/tests/browser/browser_redirect_error.js +++ b/browser/components/urlbar/tests/browser/browser_redirect_error.js @@ -67,7 +67,7 @@ var gWebProgressListener = { // onSecurityChange: function() {}, // ---------------------------------------------------------------------------- - onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { + onLocationChange(aWebProgress, aRequest, aLocation) { if (!aRequest) { // This is bug 673752, or maybe initial "about:blank". return; diff --git a/browser/components/urlbar/tests/browser/browser_remove_match.js b/browser/components/urlbar/tests/browser/browser_remove_match.js index b9e97044e4..503f01875c 100644 --- a/browser/components/urlbar/tests/browser/browser_remove_match.js +++ b/browser/components/urlbar/tests/browser/browser_remove_match.js @@ -186,7 +186,7 @@ add_task(async function test_searchMode_removeRestyledHistory() { let url = `https://example.com/?q=${query}bar`; await PlacesTestUtils.addVisits(url); - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: query, diff --git a/browser/components/urlbar/tests/browser/browser_result_onSelection.js b/browser/components/urlbar/tests/browser/browser_result_onSelection.js index 2a5f8c3760..04f2847591 100644 --- a/browser/components/urlbar/tests/browser/browser_result_onSelection.js +++ b/browser/components/urlbar/tests/browser/browser_result_onSelection.js @@ -48,7 +48,7 @@ add_task(async function test() { let provider = new UrlbarTestUtils.TestProvider({ results, priority: 1, - onSelection: (result, element) => { + onSelection: () => { selectionCount++; }, }); diff --git a/browser/components/urlbar/tests/browser/browser_results_format_displayValue.js b/browser/components/urlbar/tests/browser/browser_results_format_displayValue.js index d0ec3d3818..5575293d06 100644 --- a/browser/components/urlbar/tests/browser/browser_results_format_displayValue.js +++ b/browser/components/urlbar/tests/browser/browser_results_format_displayValue.js @@ -22,7 +22,7 @@ add_task(async function test_receive_punycode_result() { addCallback(this, result); } - getViewUpdate(result, idsByName) { + getViewUpdate(_result, _idsByName) { return {}; } } diff --git a/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js index 3cc26a5757..fe3b24a675 100644 --- a/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js +++ b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js @@ -95,7 +95,7 @@ async function test_window(win) { // we just wait for the expected currentURI value. await BrowserTestUtils.withNewTab( { gBrowser: win.gBrowser, url, waitForLoad: false }, - async browser => { + async () => { await TestUtils.waitForCondition( () => win.gBrowser.currentURI.spec == url, "Ensure we're on the expected page" diff --git a/browser/components/urlbar/tests/browser/browser_revert.js b/browser/components/urlbar/tests/browser/browser_revert.js index b68ad0ff91..faa2d92366 100644 --- a/browser/components/urlbar/tests/browser/browser_revert.js +++ b/browser/components/urlbar/tests/browser/browser_revert.js @@ -7,7 +7,7 @@ add_task(async function () { gBrowser, url: "http://example.com", }, - async function (browser) { + async function () { let originalValue = gURLBar.value; let tab = gBrowser.selectedTab; info("Put a typed value."); diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js b/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js index 707a4ea38e..f86b1527bf 100644 --- a/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js +++ b/browser/components/urlbar/tests/browser/browser_searchMode_indicator.js @@ -196,7 +196,7 @@ add_task(async function escapeOnInitialPage() { add_task(async function escapeOnBrowsingPage() { info("Tests the indicator's interaction with the ESC key on browsing page"); - await BrowserTestUtils.withNewTab("http://example.com", async browser => { + await BrowserTestUtils.withNewTab("http://example.com", async () => { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: TEST_QUERY, @@ -355,7 +355,7 @@ add_task(async function menubar_item() { // Tests that entering search mode invalidates pageproxystate and that // pageproxystate remains invalid after exiting search mode. add_task(async function invalidate_pageproxystate() { - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await UrlbarTestUtils.promisePopupOpen(window, () => { EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); }); diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js b/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js index 214448ee61..f53417834e 100644 --- a/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js +++ b/browser/components/urlbar/tests/browser/browser_searchMode_indicator_clickthrough.js @@ -14,7 +14,7 @@ add_task(async function test() { set: [["browser.search.suggest.enabled", false]], }); - await BrowserTestUtils.withNewTab("about:robots", async browser => { + await BrowserTestUtils.withNewTab("about:robots", async () => { // View open, with string. await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, @@ -69,7 +69,7 @@ add_task(async function test() { Assert.ok(!BrowserTestUtils.isVisible(labelBox)); }); - await BrowserTestUtils.withNewTab("about:robots", async browser => { + await BrowserTestUtils.withNewTab("about:robots", async () => { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "test", diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js b/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js index 2068d4c1d5..7b045e517c 100644 --- a/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js +++ b/browser/components/urlbar/tests/browser/browser_searchMode_localOneOffs_actionText.js @@ -94,7 +94,7 @@ add_task(async function localOneOff() { ); Assert.equal( result.image, - oneOffButtons.selectedButton.engine.getIconURL(), + await oneOffButtons.selectedButton.engine.getIconURL(), "Check the heuristic icon" ); diff --git a/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js b/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js index 6e9b3c1031..607cd220a1 100644 --- a/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js +++ b/browser/components/urlbar/tests/browser/browser_searchMode_suggestions.js @@ -74,7 +74,7 @@ add_setup(async function () { }); add_task(async function emptySearch() { - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.update2.emptySearchBehavior", 2]], }); @@ -109,7 +109,7 @@ add_task(async function emptySearch_withRestyledHistory() { // Can be restyled but does not dupe form history. "http://mochi.test:8888/?terms=ciao", ]); - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.update2.emptySearchBehavior", 2]], }); @@ -162,7 +162,7 @@ add_task(async function emptySearch_withRestyledHistory_noSearchHistory() { // Can be restyled but does not dupe form history. "http://mochi.test:8888/?terms=ciao", ]); - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.update2.emptySearchBehavior", 2], @@ -204,7 +204,7 @@ add_task(async function emptySearch_behavior() { // URLs with the same host as the search engine. await PlacesTestUtils.addVisits([`http://mochi.test/`]); - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.update2.emptySearchBehavior", 0]], }); @@ -245,7 +245,7 @@ add_task(async function emptySearch_behavior() { await SpecialPowers.popPrefEnv(); }); - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.update2.emptySearchBehavior", 1]], }); @@ -269,7 +269,7 @@ add_task(async function emptySearch_behavior() { add_task(async function emptySearch_local() { await PlacesTestUtils.addVisits([`http://mochi.test/`]); - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await SpecialPowers.pushPrefEnv({ set: [["browser.urlbar.update2.emptySearchBehavior", 0]], }); @@ -300,7 +300,7 @@ add_task(async function emptySearch_local() { }); add_task(async function nonEmptySearch() { - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { let query = "hello"; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, @@ -348,7 +348,7 @@ add_task(async function nonEmptySearch() { }); add_task(async function nonEmptySearch_nonMatching() { - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { let query = "ciao"; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, @@ -422,7 +422,7 @@ add_task(async function nonEmptySearch_withHistory() { }; } - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: query, @@ -520,7 +520,7 @@ add_task(async function nonEmptySearch_withHistory() { }); add_task(async function nonEmptySearch_url() { - await BrowserTestUtils.withNewTab("about:robots", async function (browser) { + await BrowserTestUtils.withNewTab("about:robots", async function () { let query = "http://www.example.com/"; await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, diff --git a/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js b/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js index 36a065d58e..f217915939 100644 --- a/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js +++ b/browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js @@ -62,7 +62,7 @@ async function runURLBarSearchTest({ for (let i = 0; i < setValueFns.length; ++i) { await setValueFns[i](valueToOpen); let topic = "uri-fixup-check-dns"; - let observer = (aSubject, aTopicInner, aData) => { + let observer = (aSubject, aTopicInner) => { if (aTopicInner == topic) { gDNSResolved = true; } @@ -248,7 +248,7 @@ function get_test_function_for_localhost_with_hostname( gBrowser: win.gBrowser, url: "about:blank", }, - browser => + () => runURLBarSearchTest({ valueToOpen: hostName, expectSearch: true, @@ -268,7 +268,7 @@ function get_test_function_for_localhost_with_hostname( gBrowser: win.gBrowser, url: "about:blank", }, - browser => + () => runURLBarSearchTest({ valueToOpen: hostName, expectSearch: true, @@ -289,7 +289,7 @@ function get_test_function_for_localhost_with_hostname( gBrowser: win.gBrowser, url: "about:blank", }, - browser => + () => runURLBarSearchTest({ valueToOpen: hostName, expectSearch: isPrivate, @@ -325,7 +325,7 @@ add_task(async function test_dnsResolveSingleWordsAfterSearch() { gBrowser, url: "about:blank", }, - browser => + () => runURLBarSearchTest({ valueToOpen: "localhost", expectSearch: true, diff --git a/browser/components/urlbar/tests/browser/browser_search_continuation.js b/browser/components/urlbar/tests/browser/browser_search_continuation.js index 8a24d57856..19c853c2e5 100644 --- a/browser/components/urlbar/tests/browser/browser_search_continuation.js +++ b/browser/components/urlbar/tests/browser/browser_search_continuation.js @@ -21,6 +21,65 @@ const CONFIG_DEFAULT = [ }, ]; +const CONFIG_V2 = [ + { + recordType: "engine", + identifier: "basic", + base: { + name: "basic", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + trending: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + }, + }, + aliases: ["basic"], + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "engine", + identifier: "private", + base: { + name: "private", + urls: { + search: { + base: "https://example.com", + searchTermParamName: "q", + }, + suggestions: { + base: "https://example.com", + method: "GET", + searchTermParamName: "search", + }, + }, + aliases: ["private"], + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "defaultEngines", + globalDefault: "basic", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, +]; + add_setup(async () => { await SpecialPowers.pushPrefEnv({ set: [ @@ -39,7 +98,10 @@ add_setup(async () => { }); await UrlbarTestUtils.formHistory.clear(); - await SearchTestUtils.setupTestEngines("search-engines", CONFIG_DEFAULT); + await SearchTestUtils.setupTestEngines( + "search-engines", + SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG_DEFAULT + ); registerCleanupFunction(async () => { await UrlbarTestUtils.formHistory.clear(); diff --git a/browser/components/urlbar/tests/browser/browser_selectStaleResults.js b/browser/components/urlbar/tests/browser/browser_selectStaleResults.js index c381478712..97f3768881 100644 --- a/browser/components/urlbar/tests/browser/browser_selectStaleResults.js +++ b/browser/components/urlbar/tests/browser/browser_selectStaleResults.js @@ -258,7 +258,7 @@ add_task(async function staleReplacedWithFresh() { // test2 // test1 let mutationPromise = new Promise(resolve => { - let observer = new MutationObserver(mutations => { + let observer = new MutationObserver(() => { let row = UrlbarTestUtils.getRowAt(window, maxResults - 2); if (row && row._elements.get("title").textContent == "test2") { observer.disconnect(); diff --git a/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js b/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js index 92eebf1997..43a3f7de9f 100644 --- a/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js +++ b/browser/components/urlbar/tests/browser/browser_shortcuts_add_search_engine.js @@ -206,7 +206,7 @@ function promiseEngine(expectedData, expectedEngineName) { expectedEngineName == engine.wrappedJSObject.name ); } - ).then(([engine, data]) => engine); + ).then(([engine]) => engine); } add_task(async function shortcuts_without_other_engines() { diff --git a/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js b/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js index 62aec6f67a..36c8c23330 100644 --- a/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js +++ b/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js @@ -57,7 +57,7 @@ class SecurityObserver { this.output = output; } - onHandshakeDone(socket, status) { + onHandshakeDone() { info("TLS handshake done"); handshakeDone = true; diff --git a/browser/components/urlbar/tests/browser/browser_stop.js b/browser/components/urlbar/tests/browser/browser_stop.js index 285071a3ff..84667eaa53 100644 --- a/browser/components/urlbar/tests/browser/browser_stop.js +++ b/browser/components/urlbar/tests/browser/browser_stop.js @@ -57,7 +57,7 @@ async function typeAndSubmitAndStop(url) { // urlbar value has been updated, add our own progress listener here. let progressPromise = new Promise(resolve => { let listener = { - onStateChange(browser, webProgress, request, stateFlags, status) { + onStateChange(browser, webProgress, request, stateFlags) { if ( webProgress.isTopLevel && stateFlags & Ci.nsIWebProgressListener.STATE_STOP diff --git a/browser/components/urlbar/tests/browser/browser_strip_on_share.js b/browser/components/urlbar/tests/browser/browser_strip_on_share.js index 508106ccdc..9e045cee9c 100644 --- a/browser/components/urlbar/tests/browser/browser_strip_on_share.js +++ b/browser/components/urlbar/tests/browser/browser_strip_on_share.js @@ -112,7 +112,7 @@ async function testMenuItemDisabled(url, prefEnabled, selection) { await SpecialPowers.pushPrefEnv({ set: [["privacy.query_stripping.strip_on_share.enabled", prefEnabled]], }); - await BrowserTestUtils.withNewTab(url, async function (browser) { + await BrowserTestUtils.withNewTab(url, async function () { gURLBar.focus(); if (selection) { //select only part of the url @@ -175,7 +175,7 @@ async function testMenuItemEnabled({ await listService.testSetList(testJson); } - await BrowserTestUtils.withNewTab(validUrl, async function (browser) { + await BrowserTestUtils.withNewTab(validUrl, async function () { gURLBar.focus(); if (selectWholeUrl) { gURLBar.select(); diff --git a/browser/components/urlbar/tests/browser/browser_strip_on_share_telemetry.js b/browser/components/urlbar/tests/browser/browser_strip_on_share_telemetry.js index 48a8b6c729..7b6fb53e3b 100644 --- a/browser/components/urlbar/tests/browser/browser_strip_on_share_telemetry.js +++ b/browser/components/urlbar/tests/browser/browser_strip_on_share_telemetry.js @@ -80,7 +80,7 @@ add_task(async function testMultiQueryParams() { }); async function testStripOnShare(validUrl, strippedUrl) { - await BrowserTestUtils.withNewTab(validUrl, async function (browser) { + await BrowserTestUtils.withNewTab(validUrl, async function () { gURLBar.focus(); gURLBar.select(); let menuitem = await promiseContextualMenuitem("strip-on-share"); diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js b/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js index 0da3161d0e..3dab8e7e64 100644 --- a/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js +++ b/browser/components/urlbar/tests/browser/browser_switchTab_inputHistory.js @@ -25,16 +25,15 @@ add_task(async function test_adaptive_with_search_term_and_switch_tab() { info(`Load tabs in same order as urls`); let tabs = []; + let waitForVisits = PlacesTestUtils.waitForNotification( + "page-visited", + events => events.some(e => e.url === urls[3]) + ); for (let url of urls) { - let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url, false, true); - gBrowser.loadTabs([url], { - inBackground: true, - triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), - }); - - let tab = await tabPromise; - tabs.push(tab); + tabs.push(await BrowserTestUtils.openNewForegroundTab({ gBrowser, url })); } + // Ensure visits have been added. + await waitForVisits; info(`Switch to tab 0`); await BrowserTestUtils.switchTab(gBrowser, tabs[0]); @@ -89,3 +88,57 @@ add_task(async function test_adaptive_with_search_term_and_switch_tab() { BrowserTestUtils.removeTab(tab); } }); + +add_task( + async function test_adaptive_nonadaptive_container_dedupe_switch_tab() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.userContext.enabled", true], + ["browser.urlbar.switchTabs.searchAllContainers", true], + ], + }); + // Add a url both to history and input history, ensure that the Muxer will + // properly dedupe the 2 entries, also with containers involved. + await PlacesUtils.history.clear(); + const url = "https://example.com/"; + + let promiseVisited = PlacesTestUtils.waitForNotification( + "page-visited", + events => events.some(e => e.url === url) + ); + let tab = BrowserTestUtils.addTab(gBrowser, url, { userContextId: 1 }); + await promiseVisited; + + async function queryAndCheckOneSwitchTabResult() { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "xampl", + }); + Assert.equal( + 2, + UrlbarTestUtils.getResultCount(window), + "Check number of results" + ); + let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); + Assert.equal(url, result.url, `Url is the first non-heuristic result`); + Assert.equal( + UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + result.type, + "Should be a switch tab result" + ); + Assert.equal( + 1, + result.result.payload.userContextId, + "Should use the expected container" + ); + } + info("Check the tab is returned as history by a search."); + await queryAndCheckOneSwitchTabResult(); + info("Add the same url to input history."); + await UrlbarUtils.addToInputHistory(url, "xampl"); + info("Repeat the query."); + await queryAndCheckOneSwitchTabResult(); + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); + } +); diff --git a/browser/components/urlbar/tests/browser/browser_switchTab_override.js b/browser/components/urlbar/tests/browser/browser_switchTab_override.js index 66426a154b..507c975cba 100644 --- a/browser/components/urlbar/tests/browser/browser_switchTab_override.js +++ b/browser/components/urlbar/tests/browser/browser_switchTab_override.js @@ -54,7 +54,7 @@ add_task(async function test_switchtab_override() { info("Override switch-to-tab"); let deferred = Promise.withResolvers(); // In case of failure this would switch tab. - let onTabSelect = event => { + let onTabSelect = () => { deferred.reject(new Error("Should have overridden switch to tab")); }; gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect); diff --git a/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js b/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js index 354cd3a802..94d46b3b81 100644 --- a/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js +++ b/browser/components/urlbar/tests/browser/browser_tabMatchesInAwesomebar.js @@ -138,7 +138,7 @@ function loadTab(tab, url) { // Because adding visits is async, we will not be notified immediately. let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser); let visited = new Promise(resolve => { - Services.obs.addObserver(function observer(aSubject, aTopic, aData) { + Services.obs.addObserver(function observer(aSubject, aTopic) { if (url != aSubject.QueryInterface(Ci.nsIURI).spec) { return; } diff --git a/browser/components/urlbar/tests/browser/browser_tabToSearch.js b/browser/components/urlbar/tests/browser/browser_tabToSearch.js index a336980583..deba60ea1e 100644 --- a/browser/components/urlbar/tests/browser/browser_tabToSearch.js +++ b/browser/components/urlbar/tests/browser/browser_tabToSearch.js @@ -284,14 +284,14 @@ add_task(async function tab_key_race() { get type() { return UrlbarUtils.PROVIDER_TYPE.PROFILE; } - isActive(context) { + isActive(_context) { executeSoon(resolve); return false; } - isRestricting(context) { + isRestricting(_context) { return false; } - async startQuery(context, addCallback) { + async startQuery(_context, _addCallback) { // Nothing to do. } } diff --git a/browser/components/urlbar/tests/browser/browser_top_sites_switchtab.js b/browser/components/urlbar/tests/browser/browser_top_sites_switchtab.js new file mode 100644 index 0000000000..d4edce0f11 --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_top_sites_switchtab.js @@ -0,0 +1,209 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test that TopSites are showing an appropriate Switch-tab status, depending + * on the state of the `browser.urlbar.switchTabs.searchAllContainers` pref. + * When the feature is enabled, in a normal window they should show the + * tab container, otherwise it's only possible to switch to a tab in the same + * container. + * In private windows it's only possible to switch to private tabs in the + * private container. Similarly non-private windows don't see private tabs. + * This test is not checking that switching to the appropriate tab works as that + * is already covered by other tests. + */ + +ChromeUtils.defineESModuleGetters(this, { + AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs", + NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", +}); + +const EN_US_TOPSITES = + "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/"; +const OUR_TOPSITE_URL = "https://example.com/"; +const REF_TOPSITE_URL = OUR_TOPSITE_URL + "#someref"; +const TOPSITES_COUNT = EN_US_TOPSITES.split(",").length + 1; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.suggest.topsites", true], + ["browser.urlbar.suggest.quickactions", false], + ["browser.newtabpage.activity-stream.default.sites", EN_US_TOPSITES], + ], + }); + registerCleanupFunction(PlacesUtils.history.clear); +}); + +add_task(async function test_ignoreRef() { + info("Add some visits to a URL."); + await addAsFirstTopSite(REF_TOPSITE_URL); + + for (let val of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.switchTabs.searchAllContainers", val]], + }); + info("Test with searchAllContainer set to " + val.toString()); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + REF_TOPSITE_URL + ); + // Switch back to the originating tab, to check for switch to the current tab. + await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]); + await openAddressBarAndCheckResults(window, TOPSITES_COUNT, new Set([0])); + await BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); + } + await PlacesUtils.history.remove(REF_TOPSITE_URL); +}); + +add_task(async function test_topSitesTabSwitch() { + await addAsFirstTopSite(OUR_TOPSITE_URL); + + for (let val of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.switchTabs.searchAllContainers", val]], + }); + info("Test with searchAllContainer set to " + val.toString()); + await doTest(); + await SpecialPowers.popPrefEnv(); + } + await PlacesUtils.history.remove(OUR_TOPSITE_URL); +}); + +async function doTest() { + info("Non-private window"); + // Add a normal tab and a container tab. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + OUR_TOPSITE_URL + ); + let containerTab = await loadNewForegroundContainerTab(OUR_TOPSITE_URL, 1); + // Switch back to the originating tab, to check for switch to the current tab. + await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]); + let expectedUserContextIds = UrlbarPrefs.get("switchTabs.searchAllContainers") + ? new Set([0, 1]) + : new Set([0]); + await openAddressBarAndCheckResults( + window, + TOPSITES_COUNT + expectedUserContextIds.size - 1, + expectedUserContextIds + ); + + info("Private window"); + let pbWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + await openAddressBarAndCheckResults(pbWin, TOPSITES_COUNT, new Set()); + + info("Close the original tab and open the url in the private window instead"); + await BrowserTestUtils.removeTab(tab); + await BrowserTestUtils.openNewForegroundTab(pbWin.gBrowser, OUR_TOPSITE_URL); + // Switch back to the originating tab, to check for switch to the current tab. + await BrowserTestUtils.switchTab(pbWin.gBrowser, pbWin.gBrowser.tabs[0]); + await openAddressBarAndCheckResults( + window, + TOPSITES_COUNT, + UrlbarPrefs.get("switchTabs.searchAllContainers") ? new Set([1]) : new Set() + ); + await openAddressBarAndCheckResults(pbWin, TOPSITES_COUNT, new Set([-1])); + + // We're done with the private window. + await BrowserTestUtils.closeWindow(pbWin); + + info("Check Top sites in the same container tab"); + let blankSameContainerTab = await loadNewForegroundContainerTab( + "about:blank", + 1 + ); + await openAddressBarAndCheckResults(window, TOPSITES_COUNT, new Set([1])); + await BrowserTestUtils.removeTab(blankSameContainerTab); + + info("Check Top sites in a different container tab"); + let blankDiffContainerTab = await loadNewForegroundContainerTab( + "about:blank", + 2 + ); + await openAddressBarAndCheckResults( + window, + TOPSITES_COUNT, + UrlbarPrefs.get("switchTabs.searchAllContainers") ? new Set([1]) : new Set() + ); + await BrowserTestUtils.removeTab(blankDiffContainerTab); + + await BrowserTestUtils.removeTab(containerTab); +} + +async function openAddressBarAndCheckResults( + win, + expectedResultCount, + expectedTabSwitchUserContextIds +) { + info("Open zero-prefix results."); + await UrlbarTestUtils.promisePopupOpen(win, () => { + win.gURLBar.blur(); + if (win.gURLBar.getAttribute("pageproxystate") == "invalid") { + win.gURLBar.handleRevert(); + } + EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win); + }); + await UrlbarTestUtils.promiseSearchComplete(win); + let resultCount = UrlbarTestUtils.getResultCount(win); + Assert.equal(expectedResultCount, resultCount, "Check number of results."); + + for (let i = 0; i < resultCount; ++i) { + let result = await UrlbarTestUtils.getDetailsOfResultAt(win, i); + + if (result.url != "https://example.com/") { + // We don't care about other top sites. + continue; + } + + if (!expectedTabSwitchUserContextIds.size) { + // No more tab switch results expected. + Assert.notEqual( + UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + result.type, + "Should not be a tab switch result." + ); + continue; + } + + // Must be a tab switch result with an expected userContextId. + Assert.equal( + UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + result.type, + "Should be a tab switch result." + ); + let hasUserContextId = expectedTabSwitchUserContextIds.delete( + result.userContextId + ); + Assert.ok( + hasUserContextId, + `UserContextId ${result.userContextId} tab switch was expected in + ${expectedTabSwitchUserContextIds}` + ); + } +} + +async function addAsFirstTopSite(url) { + info("Add some visits to a URL."); + await PlacesTestUtils.addVisits(Array(10).fill(url)); + info("Add top sites and await for our page to be the first"); + await updateTopSites(sites => { + return sites && sites.length == TOPSITES_COUNT && sites[0].url == url; + }); +} + +async function loadNewForegroundContainerTab(url, userContextId, win = window) { + let tab = BrowserTestUtils.addTab(win.gBrowser, url, { + userContextId, + }); + await Promise.all([ + BrowserTestUtils.browserLoaded(tab.linkedBrowser), + BrowserTestUtils.switchTab(win.gBrowser, tab), + ]); + return tab; +} diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js index d4f4e77d57..1eef6fc0e0 100644 --- a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js +++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_dynamic.js @@ -39,7 +39,7 @@ class TestProvider extends UrlbarTestUtils.TestProvider { }); } - getViewUpdate(result, idsByName) { + getViewUpdate(_result, _idsByName) { return { title: { textContent: "This is a dynamic result.", diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js index 345b063441..66ddb225fb 100644 --- a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js +++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tip.js @@ -115,10 +115,10 @@ class TipProvider extends UrlbarProvider { get type() { return UrlbarUtils.PROVIDER_TYPE.PROFILE; } - isActive(context) { + isActive(_context) { return true; } - getPriority(context) { + getPriority(_context) { return 1; } async startQuery(context, addCallback) { diff --git a/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js b/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js index ba249adb3b..03afeb3410 100644 --- a/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js +++ b/browser/components/urlbar/tests/browser/browser_valueOnTabSwitch.js @@ -90,7 +90,7 @@ add_task(async function () { } function urlbarBackspace(removeAll) { - return new Promise((resolve, reject) => { + return new Promise(resolve => { gBrowser.selectedBrowser.focus(); gURLBar.addEventListener( "input", diff --git a/browser/components/urlbar/tests/browser/browser_view_removedSelectedElement.js b/browser/components/urlbar/tests/browser/browser_view_removedSelectedElement.js index 532f9e10a2..8ccb49d4ae 100644 --- a/browser/components/urlbar/tests/browser/browser_view_removedSelectedElement.js +++ b/browser/components/urlbar/tests/browser/browser_view_removedSelectedElement.js @@ -25,7 +25,7 @@ add_task(async function () { let firstSelectedElement; let delayResultsPromise = new Promise(resolve => { gURLBar.controller.addQueryListener({ - async onQueryResults(queryContext) { + async onQueryResults() { Assert.ok(!receivedResults, "Should execute only once"); gURLBar.controller.removeQueryListener(this); receivedResults = true; diff --git a/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js b/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js index fc617220b6..10110a8928 100644 --- a/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js +++ b/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js @@ -324,6 +324,9 @@ add_task(async function withDnsFirstForSingleWordsPref() { url: "https://example.org/", title: "example", }); + // An unvisited bookmark may have a lower ranking than a page visited many + // times, so let's clear history to ensure our bookmark is autofilled. + await PlacesUtils.history.clear(); await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); await UrlbarTestUtils.promiseAutocompleteResultPopup({ @@ -333,13 +336,13 @@ add_task(async function withDnsFirstForSingleWordsPref() { const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); const target = details.element.action; - EventUtils.synthesizeMouseAtCenter(target, { type: "mousedown" }); + const onLoaded = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, "https://example.org/" ); - EventUtils.synthesizeMouseAtCenter(target, { type: "mouseup" }); + EventUtils.synthesizeMouseAtCenter(target, {}); await onLoaded; Assert.ok(true, "Expected page is opened"); diff --git a/browser/components/urlbar/tests/browser/head.js b/browser/components/urlbar/tests/browser/head.js index a81e8e4811..f78624e68e 100644 --- a/browser/components/urlbar/tests/browser/head.js +++ b/browser/components/urlbar/tests/browser/head.js @@ -67,7 +67,7 @@ function waitForLoadStartOrTimeout(win = window, timeoutMs = 1000) { return Promise.race([ new Promise(resolve => { listener = { - onStateChange(browser, webprogress, request, flags, status) { + onStateChange(browser, webprogress, request, flags) { if (flags & Ci.nsIWebProgressListener.STATE_START) { resolve(request.QueryInterface(Ci.nsIChannel).URI); } diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml b/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml index 68a7881399..cf6bc80318 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml @@ -26,6 +26,8 @@ prefs = ["browser.bookmarks.testing.skipDefaultBookmarksImport=true"] ["browser_glean_telemetry_abandonment_n_chars_n_words.js"] +["browser_glean_telemetry_abandonment_type.js"] + ["browser_glean_telemetry_abandonment_sap.js"] ["browser_glean_telemetry_abandonment_search_engine_default_id.js"] @@ -64,24 +66,4 @@ skip-if = ["verify"] # Bug 1852375 - MerinoTestUtils.initWeather() doesn't play ["browser_glean_telemetry_exposure_edge_cases.js"] -["browser_glean_telemetry_impression_groups.js"] - -["browser_glean_telemetry_impression_interaction.js"] - -["browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js"] - -["browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js"] - -["browser_glean_telemetry_impression_n_chars_n_words.js"] - -["browser_glean_telemetry_impression_preferences.js"] - -["browser_glean_telemetry_impression_sap.js"] - -["browser_glean_telemetry_impression_search_engine_default_id.js"] - -["browser_glean_telemetry_impression_search_mode.js"] - -["browser_glean_telemetry_impression_timing.js"] - ["browser_glean_telemetry_record_preferences.js"] diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js index 71087d03d0..ca21ca476c 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_tips.js @@ -38,7 +38,7 @@ add_setup(async function () { }); add_task(async function tip_persist() { - await doTest(async browser => { + await doTest(async () => { await showPersistSearchTip("test"); gURLBar.focus(); await UrlbarTestUtils.promisePopupClose(window, () => { diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_type.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_type.js new file mode 100644 index 0000000000..99145d7cc3 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_type.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test for the following data of abandonment telemetry. +// - reason + +function checkUrlbarFocus(win, focusState) { + let urlbar = win.gURLBar; + is( + focusState ? win.document.activeElement : !win.document.activeElement, + focusState ? urlbar.inputField : !urlbar.inputField, + `URL Bar should ${focusState ? "" : "not "}be focused` + ); +} + +// Tests that a tab switch from a focused URL bar to another tab with a focused +// URL bar records the correct abandonment telemetry with abandonment type +// "tab_swtich". +add_task(async function tabSwitchFocusedToFocused() { + await doTest(async browser => { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test search", + }); + checkUrlbarFocus(window, true); + + let promiseTabOpened = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabOpen" + ); + EventUtils.synthesizeMouseAtCenter(gBrowser.tabContainer.newTabButton, {}); + let openEvent = await promiseTabOpened; + let tab2 = openEvent.target; + checkUrlbarFocus(window, true); + + await assertAbandonmentTelemetry([{ abandonment_type: "tab_switch" }]); + + await BrowserTestUtils.removeTab(tab2); + }); +}); + +// Verifies that switching from a tab with a focused URL bar to a tab where the +// URL bar loses focus logs abandonment telemetry with abandonment type +// "blur". +add_task(async function tabSwitchFocusedToUnfocused() { + await doTest(async browser => { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test search", + }); + checkUrlbarFocus(window, true); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(window.gBrowser); + checkUrlbarFocus(window, false); + + await assertAbandonmentTelemetry([{ abandonment_type: "blur" }]); + + await BrowserTestUtils.removeTab(tab2); + }); +}); + +// Ensures that switching from a tab with an unfocused URL bar to a tab where +// the URL bar gains focus does not record any abandonment telemetry, reflecting +// no change in focus state relevant to abandonment. +add_task(async function tabSwitchUnFocusedToFocused() { + await doTest(async browser => { + checkUrlbarFocus(window, false); + + let promiseTabOpened = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabOpen" + ); + EventUtils.synthesizeMouseAtCenter(gBrowser.tabContainer.newTabButton, {}); + let openEvent = await promiseTabOpened; + let tab2 = openEvent.target; + checkUrlbarFocus(window, true); + + const telemetries = Glean.urlbar.abandonment.testGetValue() ?? []; + Assert.equal( + telemetries.length, + 0, + "Telemetry event length matches expected event length." + ); + + await BrowserTestUtils.removeTab(tab2); + }); +}); + +// Checks that switching between two tabs, both with unfocused URL bars, does +// not trigger any abandonment telmetry. +add_task(async function tabSwitchUnFocusedToUnFocused() { + await doTest(async browser => { + checkUrlbarFocus(window, false); + + let tab2 = await BrowserTestUtils.openNewForegroundTab(window.gBrowser); + checkUrlbarFocus(window, false); + + const telemetries = Glean.urlbar.abandonment.testGetValue() ?? []; + Assert.equal( + telemetries.length, + 0, + "Telemetry event length matches expected event length." + ); + + await BrowserTestUtils.removeTab(tab2); + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js index fcac924879..04ef7e9757 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js @@ -22,7 +22,7 @@ class NoResponseTestProvider extends UrlbarTestUtils.TestProvider { return UrlbarUtils.PROVIDER_TYPE.HEURISTIC; } - async startQuery(context, addCallback) { + async startQuery(_context, _addCallback) { await this.#deferred.promise; } @@ -98,7 +98,7 @@ add_task(async function engagement_before_showing_results() { }; registerCleanupFunction(cleanup); - await doTest(async browser => { + await doTest(async () => { // Try to show the results. await UrlbarTestUtils.inputIntoURLBar(window, "exam"); @@ -156,7 +156,7 @@ add_task(async function engagement_after_closing_results() { ]; for (const trigger of TRIGGERS) { - await doTest(async browser => { + await doTest(async () => { await openPopup("test"); await UrlbarTestUtils.promisePopupClose(window, () => { trigger(); @@ -186,7 +186,7 @@ add_task(async function engagement_after_closing_results() { }); add_task(async function enter_to_reload_current_url() { - await doTest(async browser => { + await doTest(async () => { // Open a URL once. await openPopup("https://example.com"); await doEnter(); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js index 8779487960..d46c874403 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js @@ -247,13 +247,13 @@ add_task(async function always_empty_if_drop_go() { }, ]; - await doTest(async browser => { + await doTest(async () => { await doDropAndGo("example.com"); assertEngagementTelemetry(expected); }); - await doTest(async browser => { + await doTest(async () => { // Open the results view once. await showResultByArrowDown(); await UrlbarTestUtils.promisePopupClose(window); @@ -274,13 +274,13 @@ add_task(async function always_empty_if_paste_go() { }, ]; - await doTest(async browser => { + await doTest(async () => { await doPasteAndGo("example.com"); assertEngagementTelemetry(expected); }); - await doTest(async browser => { + await doTest(async () => { // Open the results view once. await showResultByArrowDown(); await UrlbarTestUtils.promisePopupClose(window); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js index f4880d2205..2866186c30 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js @@ -35,13 +35,13 @@ add_task(async function typed() { }); add_task(async function dropped() { - await doTest(async browser => { + await doTest(async () => { await doDropAndGo("example.com"); assertEngagementTelemetry([{ interaction: "dropped" }]); }); - await doTest(async browser => { + await doTest(async () => { await showResultByArrowDown(); await doDropAndGo("example.com"); @@ -60,13 +60,13 @@ add_task(async function pasted() { assert: () => assertEngagementTelemetry([{ interaction: "pasted" }]), }); - await doTest(async browser => { + await doTest(async () => { await doPasteAndGo("www.example.com"); assertEngagementTelemetry([{ interaction: "pasted" }]); }); - await doTest(async browser => { + await doTest(async () => { await showResultByArrowDown(); await doPasteAndGo("www.example.com"); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js index 6a3422d939..bea266dbf4 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js @@ -13,6 +13,7 @@ ChromeUtils.defineESModuleGetters(this, { UrlbarProviderClipboard: "resource:///modules/UrlbarProviderClipboard.sys.mjs", + SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs", }); // This test has many subtests and can time out in verify mode. @@ -23,7 +24,7 @@ add_setup(async function () { }); add_task(async function selected_result_autofill_about() { - await doTest(async browser => { + await doTest(async () => { await openPopup("about:about"); await doEnter(); @@ -44,7 +45,7 @@ add_task(async function selected_result_autofill_adaptive() { set: [["browser.urlbar.autoFill.adaptiveHistory.enabled", true]], }); - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits("https://example.com/test"); await UrlbarUtils.addToInputHistory("https://example.com/test", "exa"); await openPopup("exa"); @@ -65,7 +66,7 @@ add_task(async function selected_result_autofill_adaptive() { }); add_task(async function selected_result_autofill_origin() { - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits("https://example.com/test"); await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); await openPopup("exa"); @@ -84,7 +85,7 @@ add_task(async function selected_result_autofill_origin() { }); add_task(async function selected_result_autofill_url() { - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits("https://example.com/test"); await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); await openPopup("https://example.com/test"); @@ -103,7 +104,7 @@ add_task(async function selected_result_autofill_url() { }); add_task(async function selected_result_bookmark() { - await doTest(async browser => { + await doTest(async () => { await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, url: "https://example.com/bookmark", @@ -131,7 +132,7 @@ add_task(async function selected_result_history() { set: [["browser.urlbar.autoFill", false]], }); - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits("https://example.com/test"); await openPopup("example"); @@ -153,7 +154,7 @@ add_task(async function selected_result_history() { }); add_task(async function selected_result_keyword() { - await doTest(async browser => { + await doTest(async () => { await PlacesUtils.keywords.insert({ keyword: "keyword", url: "https://example.com/?q=%s", @@ -177,7 +178,7 @@ add_task(async function selected_result_keyword() { }); add_task(async function selected_result_search_engine() { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await doEnter(); @@ -201,7 +202,7 @@ add_task(async function selected_result_search_suggest() { ], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("foo"); await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); await doEnter(); @@ -228,7 +229,7 @@ add_task(async function selected_result_search_history() { ], }); - await doTest(async browser => { + await doTest(async () => { await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]); await openPopup("foo"); @@ -250,7 +251,7 @@ add_task(async function selected_result_search_history() { }); add_task(async function selected_result_url() { - await doTest(async browser => { + await doTest(async () => { await openPopup("https://example.com/"); await doEnter(); @@ -267,7 +268,7 @@ add_task(async function selected_result_url() { }); add_task(async function selected_result_action() { - await doTest(async browser => { + await doTest(async () => { await showResultByArrowDown(); await selectRowByProvider("quickactions"); const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); @@ -289,7 +290,7 @@ add_task(async function selected_result_action() { add_task(async function selected_result_tab() { const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/"); - await doTest(async browser => { + await doTest(async () => { await openPopup("example"); await selectRowByProvider("Places"); EventUtils.synthesizeKey("KEY_Enter"); @@ -312,7 +313,7 @@ add_task(async function selected_result_tab() { add_task(async function selected_result_remote_tab() { const remoteTab = await loadRemoteTab("https://example.com"); - await doTest(async browser => { + await doTest(async () => { await openPopup("example"); await selectRowByProvider("RemoteTabs"); await doEnter(); @@ -335,7 +336,7 @@ add_task(async function selected_result_addon() { const addon = loadOmniboxAddon({ keyword: "omni" }); await addon.startup(); - await doTest(async browser => { + await doTest(async () => { await openPopup("omni test"); await doEnter(); @@ -363,7 +364,7 @@ add_task(async function selected_result_tab_to_search() { search_url: "https://mozengine/", }); - await doTest(async browser => { + await doTest(async () => { for (let i = 0; i < 3; i++) { await PlacesTestUtils.addVisits(["https://mozengine/"]); } @@ -389,7 +390,7 @@ add_task(async function selected_result_tab_to_search() { }); add_task(async function selected_result_top_site() { - await doTest(async browser => { + await doTest(async () => { await addTopSites("https://example.com/"); await showResultByArrowDown(); await selectRowByURL("https://example.com/"); @@ -412,7 +413,7 @@ add_task(async function selected_result_calc() { set: [["browser.urlbar.suggest.calculator", true]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("8*8"); await selectRowByProvider("calculator"); await SimpleTest.promiseClipboardChange("64", () => { @@ -444,7 +445,7 @@ add_task(async function selected_result_clipboard() { "https://example.com/selected_result_clipboard" ); - await doTest(async browser => { + await doTest(async () => { await openPopup(""); await selectRowByProvider("UrlbarProviderClipboard"); await doEnter(); @@ -470,7 +471,7 @@ add_task(async function selected_result_unit() { set: [["browser.urlbar.unitConversion.enabled", true]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("1m to cm"); await selectRowByProvider("UnitConversion"); await SimpleTest.promiseClipboardChange("100 cm", () => { @@ -496,7 +497,7 @@ add_task(async function selected_result_site_specific_contextual_search() { set: [["browser.urlbar.contextualSearch.enabled", true]], }); - await doTest(async browser => { + await doTest(async () => { const extension = await SearchTestUtils.installSearchExtension( { name: "Contextual", @@ -540,7 +541,7 @@ add_task(async function selected_result_rs_adm_sponsored() { prefs: [["quicksuggest.rustEnabled", false]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("sponsored"); await selectRowByURL("https://example.com/sponsored"); await doEnter(); @@ -564,7 +565,7 @@ add_task(async function selected_result_rs_adm_nonsponsored() { prefs: [["quicksuggest.rustEnabled", false]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("nonsponsored"); await selectRowByURL("https://example.com/nonsponsored"); await doEnter(); @@ -594,13 +595,13 @@ add_task(async function selected_result_input_field() { }, ]; - await doTest(async browser => { + await doTest(async () => { await doDropAndGo("example.com"); assertEngagementTelemetry(expected); }); - await doTest(async browser => { + await doTest(async () => { await doPasteAndGo("example.com"); assertEngagementTelemetry(expected); @@ -618,7 +619,7 @@ add_task(async function selected_result_weather() { let provider = UrlbarPrefs.get("quickSuggestRustEnabled") ? "UrlbarProviderQuickSuggest" : "Weather"; - await doTest(async browser => { + await doTest(async () => { await openPopup(MerinoTestUtils.WEATHER_KEYWORD); await selectRowByProvider(provider); await doEnter(); @@ -653,7 +654,7 @@ add_task(async function selected_result_navigational() { ], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("only match the Merino suggestion"); await selectRowByProvider("UrlbarProviderQuickSuggest"); await doEnter(); @@ -688,7 +689,7 @@ add_task(async function selected_result_dynamic_wikipedia() { ], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("only match the Merino suggestion"); await selectRowByProvider("UrlbarProviderQuickSuggest"); await doEnter(); @@ -708,7 +709,7 @@ add_task(async function selected_result_dynamic_wikipedia() { }); add_task(async function selected_result_search_shortcut_button() { - await doTest(async browser => { + await doTest(async () => { const oneOffSearchButtons = UrlbarTestUtils.getOneOffSearchButtons(window); await openPopup("x"); Assert.ok(!oneOffSearchButtons.selectedButton); @@ -756,31 +757,78 @@ add_task(async function selected_result_trending() { }); let defaultEngine = await Services.search.getDefault(); - let extension = await SearchTestUtils.installSearchExtension( - { - name: "mozengine", - search_url: "https://example.org/", - }, - { setAsDefault: true, skipUnload: true } - ); + let extension; + if (!SearchUtils.newSearchConfigEnabled) { + extension = await SearchTestUtils.installSearchExtension( + { + name: "mozengine", + search_url: "https://example.org/", + }, + { setAsDefault: true, skipUnload: true } + ); + } SearchTestUtils.useMockIdleService(); - await SearchTestUtils.updateRemoteSettingsConfig([ - { - webExtension: { id: "mozengine@tests.mozilla.org" }, - urls: { - trending: { - fullPath: - "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", - query: "", - }, - }, - appliesTo: [{ included: { everywhere: true } }], - default: "yes", - }, - ]); + await SearchTestUtils.updateRemoteSettingsConfig( + SearchUtils.newSearchConfigEnabled + ? [ + { + recordType: "engine", + identifier: "mozengine", + base: { + name: "mozengine", + urls: { + search: { + base: "https://example.org/", + searchTermParamName: "q", + }, + trending: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + }, + }, + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "defaultEngines", + globalDefault: "mozengine", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, + ] + : [ + { + webExtension: { id: "mozengine@tests.mozilla.org" }, + urls: { + trending: { + fullPath: + "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + query: "", + }, + }, + appliesTo: [{ included: { everywhere: true } }], + default: "yes", + }, + ] + ); - await doTest(async browser => { + if (SearchUtils.newSearchConfigEnabled) { + let engine = Services.search.getEngineByName("mozengine"); + await Services.search.setDefault( + engine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + } + + await doTest(async () => { await openPopup(""); await selectRowByProvider("SearchSuggestions"); await doEnter(); @@ -796,7 +844,13 @@ add_task(async function selected_result_trending() { ]); }); - await extension.unload(); + if (SearchUtils.newSearchConfigEnabled) { + let engine = Services.search.getEngineByName("mozengine"); + await Services.search.removeEngine(engine); + } else { + await extension.unload(); + } + await Services.search.setDefault( defaultEngine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN @@ -823,31 +877,84 @@ add_task(async function selected_result_trending_rich() { }); let defaultEngine = await Services.search.getDefault(); - let extension = await SearchTestUtils.installSearchExtension( - { - name: "mozengine", - search_url: "https://example.org/", - }, - { setAsDefault: true, skipUnload: true } - ); + let extension; + if (!SearchUtils.newSearchConfigEnabled) { + extension = await SearchTestUtils.installSearchExtension( + { + name: "mozengine", + search_url: "https://example.org/", + }, + { setAsDefault: true, skipUnload: true } + ); + } SearchTestUtils.useMockIdleService(); - await SearchTestUtils.updateRemoteSettingsConfig([ - { - webExtension: { id: "mozengine@tests.mozilla.org" }, - urls: { - trending: { - fullPath: - "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs?richsuggestions=true", - query: "", - }, - }, - appliesTo: [{ included: { everywhere: true } }], - default: "yes", - }, - ]); + await SearchTestUtils.updateRemoteSettingsConfig( + SearchUtils.newSearchConfigEnabled + ? [ + { + recordType: "engine", + identifier: "mozengine", + base: { + name: "mozengine", + urls: { + search: { + base: "https://example.org/", + searchTermParamName: "q", + }, + trending: { + base: "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs", + method: "GET", + params: [ + { + name: "richsuggestions", + value: "true", + }, + ], + }, + }, + }, + variants: [ + { + environment: { allRegionsAndLocales: true }, + }, + ], + }, + { + recordType: "defaultEngines", + globalDefault: "mozengine", + specificDefaults: [], + }, + { + recordType: "engineOrders", + orders: [], + }, + ] + : [ + { + webExtension: { id: "mozengine@tests.mozilla.org" }, + urls: { + trending: { + fullPath: + "https://example.com/browser/browser/components/search/test/browser/trendingSuggestionEngine.sjs?richsuggestions=true", + query: "", + }, + }, + appliesTo: [{ included: { everywhere: true } }], + default: "yes", + }, + ] + ); - await doTest(async browser => { + if (SearchUtils.newSearchConfigEnabled) { + let engine = Services.search.getEngineByName("mozengine"); + await Services.search.setDefault( + engine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + } + + await doTest(async () => { await openPopup(""); await selectRowByProvider("SearchSuggestions"); await doEnter(); @@ -863,7 +970,13 @@ add_task(async function selected_result_trending_rich() { ]); }); - await extension.unload(); + if (SearchUtils.newSearchConfigEnabled) { + let engine = Services.search.getEngineByName("mozengine"); + await Services.search.removeEngine(engine); + } else { + await extension.unload(); + } + await Services.search.setDefault( defaultEngine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN @@ -905,7 +1018,7 @@ add_task(async function selected_result_addons() { ], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("only match the Merino suggestion"); await selectRowByProvider("UrlbarProviderQuickSuggest"); await doEnter(); @@ -930,7 +1043,7 @@ add_task(async function selected_result_rust_adm_sponsored() { prefs: [["quicksuggest.rustEnabled", true]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("sponsored"); await selectRowByURL("https://example.com/sponsored"); await doEnter(); @@ -954,7 +1067,7 @@ add_task(async function selected_result_rust_adm_nonsponsored() { prefs: [["quicksuggest.rustEnabled", true]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("nonsponsored"); await selectRowByURL("https://example.com/nonsponsored"); await doEnter(); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js index 2b38631747..ff31bdc52a 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_tips.js @@ -69,7 +69,7 @@ add_task(async function selected_result_tip() { }); UrlbarProvidersManager.registerProvider(provider); - await doTest(async browser => { + await doTest(async () => { await openPopup("example"); await selectRowByType(type); EventUtils.synthesizeKey("VK_RETURN"); @@ -159,7 +159,7 @@ add_task(async function selected_result_intervention_update() { }); async function doInterventionTest(keyword, type, dialog, expectedTelemetry) { - await doTest(async browser => { + await doTest(async () => { await openPopup(keyword); await selectRowByType(type); const onDialog = BrowserTestUtils.promiseAlertDialog("cancel", dialog, { diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js index 5972dd331d..6b1dedbce2 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_type.js @@ -14,7 +14,7 @@ add_setup(async function () { }); add_task(async function engagement_type_click() { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await doClick(); @@ -23,7 +23,7 @@ add_task(async function engagement_type_click() { }); add_task(async function engagement_type_enter() { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await doEnter(); @@ -32,7 +32,7 @@ add_task(async function engagement_type_enter() { }); add_task(async function engagement_type_go_button() { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); EventUtils.synthesizeMouseAtCenter(gURLBar.goButton, {}); @@ -41,7 +41,7 @@ add_task(async function engagement_type_go_button() { }); add_task(async function engagement_type_drop_go() { - await doTest(async browser => { + await doTest(async () => { await doDropAndGo("example.com"); assertEngagementTelemetry([{ engagement_type: "drop_go" }]); @@ -49,7 +49,7 @@ add_task(async function engagement_type_drop_go() { }); add_task(async function engagement_type_paste_go() { - await doTest(async browser => { + await doTest(async () => { await doPasteAndGo("www.example.com"); assertEngagementTelemetry([{ engagement_type: "paste_go" }]); @@ -59,7 +59,7 @@ add_task(async function engagement_type_paste_go() { add_task(async function engagement_type_dismiss() { const cleanupQuickSuggest = await ensureQuickSuggestInit(); - await doTest(async browser => { + await doTest(async () => { await openPopup("sponsored"); const originalResultCount = UrlbarTestUtils.getResultCount(window); @@ -84,7 +84,7 @@ add_task(async function engagement_type_dismiss() { ]); }); - await doTest(async browser => { + await doTest(async () => { await openPopup("sponsored"); const originalResultCount = UrlbarTestUtils.getResultCount(window); @@ -103,7 +103,7 @@ add_task(async function engagement_type_dismiss() { add_task(async function engagement_type_help() { const cleanupQuickSuggest = await ensureQuickSuggestInit(); - await doTest(async browser => { + await doTest(async () => { await openPopup("sponsored"); await selectRowByURL("https://example.com/sponsored"); const onTabOpened = BrowserTestUtils.waitForNewTab(gBrowser); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js index d28352b417..725240c9b1 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure_edge_cases.js @@ -38,8 +38,12 @@ add_setup(async function () { gProvider = new TestProvider(); UrlbarProvidersManager.registerProvider(gProvider); - // Increase the timeout of the stale-rows timer so it doesn't interfere with - // this test, which specifically tests what happens before the timer fires. + // This test specifically checks the view's behavior before and after it + // removes stale rows, so it needs to control when that occurs. There are two + // times the view removes stale rows: (1) when the stale-rows timer fires, (2) + // when a query finishes. We prevent (1) from occuring by increasing the + // timer's timeout so it never fires during the test. We'll rely on (2) to + // trigger stale rows removal. let originalRemoveStaleRowsTimeout = UrlbarView.removeStaleRowsTimeout; UrlbarView.removeStaleRowsTimeout = 30000; @@ -49,11 +53,17 @@ add_setup(async function () { }); }); -// Does one query that fills up the view with search suggestions, starts a -// second query that returns a history result, and cancels it before it can -// finish but after the view is updated. Regardless of `showExposureResults`, -// the history result should not trigger an exposure since it never had a chance -// to be visible in the view. +// Does the following: +// +// 1. Starts and finishes a query that fills up the view +// 2. Starts a second query with results that cannot replace rows from the first +// query and that therefore must be appended and hidden +// 3. Cancels the second query before it finishes (so that stale rows are not +// removed) +// +// Results in the second query should not trigger an exposure. They can never be +// visible in the view since the second query is canceled before stale rows are +// removed. add_task(async function noExposure() { for (let showExposureResults of [true, false]) { await do_noExposure(showExposureResults); @@ -116,7 +126,7 @@ async function do_noExposure(showExposureResults) { // When the provider's `startQuery()` is called, let it add its results but // don't let it return. That will cause the view to be updated with the new // results but prevent it from showing hidden rows since the query won't - // finish. + // finish (so stale rows won't be removed). let queryResolver = Promise.withResolvers(); gProvider.finishQueryPromise = queryResolver.promise; @@ -147,14 +157,24 @@ async function do_noExposure(showExposureResults) { // Make sure the view is full of visible rows as expected, plus the one or two // hidden rows for the history and/or bookmark results. - let rows = UrlbarTestUtils.getResultsContainer(window); - let expectedCount = MAX_RESULT_COUNT + 1; + let expected = [ + { + source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + type: UrlbarUtils.RESULT_TYPE.URL, + url: bookmarkUrl, + }, + ]; if (showExposureResults) { - expectedCount++; + expected.unshift({ + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + type: UrlbarUtils.RESULT_TYPE.URL, + url: historyUrl, + }); } + let rows = UrlbarTestUtils.getResultsContainer(window); Assert.equal( rows.children.length, - expectedCount, + MAX_RESULT_COUNT + expected.length, "The view has the expected number of rows" ); @@ -176,24 +196,15 @@ async function do_noExposure(showExposureResults) { } // Check the hidden history and/or bookmark rows. - let expected = [ - { source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS, url: bookmarkUrl }, - ]; - if (showExposureResults) { - expected.unshift({ - source: UrlbarUtils.RESULT_SOURCE.HISTORY, - url: historyUrl, - }); - } for (let i = 0; i < expected.length; i++) { - let { source, url } = expected[i]; + let { source, type, url } = expected[i]; let row = rows.children[MAX_RESULT_COUNT + i]; Assert.ok(row, `rows[${i}] should exist`); Assert.ok(BrowserTestUtils.isHidden(row), `rows[${i}] should be hidden`); Assert.equal( row.result.type, - UrlbarUtils.RESULT_TYPE.URL, - `rows[${i}].result.type should be URL` + type, + `rows[${i}].result.type should be as expected` ); Assert.equal( row.result.source, @@ -212,8 +223,8 @@ async function do_noExposure(showExposureResults) { await UrlbarTestUtils.promisePopupClose(window); gURLBar.blur(); - // No exposure should have been recorded since the history result was never - // visible. + // No exposure should have been recorded since the history result could never + // be visible. assertExposureTelemetry([]); // Clean up. @@ -221,17 +232,24 @@ async function do_noExposure(showExposureResults) { await queryPromise; await SpecialPowers.popPrefEnv(); Services.fog.testResetFOG(); + gProvider.finishQueryPromise = null; } -// Does one query that underfills the view and then a second query that returns -// a search suggestion. The search suggestion should be appended and trigger an -// exposure. When `showExposureResults` is true, it should also be shown. After -// the view is updated, it shouldn't matter whether or not the second query is -// canceled. -add_task(async function exposure_append() { +// Does the following: +// +// 1. Starts and finishes a query that underfills the view +// 2. Starts a second query +// 3. Waits for rows from the second query to be appended. They will be +// immediately visible since the first query underfilled the view. +// 4. Either cancels the second query (so stale rows are not removed) or waits +// for it to finish (so stale rows are removed) +// +// Results in the second query should trigger an exposure since they are made +// visible in step 3. Step 4 should not actually matter. +add_task(async function exposure_append_underfilled() { for (let showExposureResults of [true, false]) { for (let cancelSecondQuery of [true, false]) { - await do_exposure_append({ + await do_exposure_append_underfilled({ showExposureResults, cancelSecondQuery, }); @@ -239,7 +257,10 @@ add_task(async function exposure_append() { } }); -async function do_exposure_append({ showExposureResults, cancelSecondQuery }) { +async function do_exposure_append_underfilled({ + showExposureResults, + cancelSecondQuery, +}) { info( "Starting do_exposure_append: " + JSON.stringify({ showExposureResults, cancelSecondQuery }) @@ -287,7 +308,8 @@ async function do_exposure_append({ showExposureResults, cancelSecondQuery }) { // When the provider's `startQuery()` is called, let it add its results but // don't let it return. That will cause the view to be updated with the new - // results but let us test the specific case where the query doesn't finish. + // results but let us test the specific case where the query doesn't finish + // (so stale rows are not removed). let queryResolver = Promise.withResolvers(); gProvider.finishQueryPromise = queryResolver.promise; @@ -357,13 +379,19 @@ async function do_exposure_append({ showExposureResults, cancelSecondQuery }) { await queryPromise; await SpecialPowers.popPrefEnv(); Services.fog.testResetFOG(); + gProvider.finishQueryPromise = null; } -// Does one query that returns a search suggestion and then a second query that -// returns a new search suggestion. The new search suggestion can replace the -// old one, so it should trigger an exposure. When `showExposureResults` is -// true, it should actually replace it. After the view is updated, it shouldn't -// matter whether or not the second query is canceled. +// Does the following: +// +// 1. Starts and finishes a query +// 2. Starts a second query with a result that can replace an existing row from +// the previous query +// 3. Either cancels the second query (so stale rows are not removed) or waits +// for it to finish (so stale rows are removed) +// +// The result in the second query should trigger an exposure since it's made +// visible in step 2. Step 3 should not actually matter. add_task(async function exposure_replace() { for (let showExposureResults of [true, false]) { for (let cancelSecondQuery of [true, false]) { @@ -433,7 +461,8 @@ async function do_exposure_replace({ showExposureResults, cancelSecondQuery }) { // When the provider's `startQuery()` is called, let it add its results but // don't let it return. That will cause the view to be updated with the new - // results but let us test the specific case where the query doesn't finish. + // results but let us test the specific case where the query doesn't finish + // (so stale rows are not removed). let queryResolver = Promise.withResolvers(); gProvider.finishQueryPromise = queryResolver.promise; @@ -503,6 +532,519 @@ async function do_exposure_replace({ showExposureResults, cancelSecondQuery }) { await queryPromise; await SpecialPowers.popPrefEnv(); Services.fog.testResetFOG(); + gProvider.finishQueryPromise = null; +} + +// Does the following: +// +// 1. Starts and finishes a query that fills up the view +// 2. Starts a second query with a result that cannot replace any rows from the +// first query and that therefore must be appended and hidden +// 3. Finishes the second query +// +// The result in the second query should trigger an exposure since it's made +// visible in step 3. +add_task(async function exposure_append_full() { + for (let showExposureResults of [true, false]) { + await do_exposure_append_full(showExposureResults); + } +}); + +async function do_exposure_append_full(showExposureResults) { + info( + "Starting do_exposure_append_full: " + + JSON.stringify({ showExposureResults }) + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.exposureResults", "history"], + ["browser.urlbar.showExposureResults", showExposureResults], + ], + }); + + // Make the provider return enough search suggestions to fill the view. + gProvider.results = []; + for (let i = 0; i < MAX_RESULT_COUNT; i++) { + gProvider.results.push( + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + suggestion: "suggestion " + i, + engine: Services.search.defaultEngine.name, + } + ) + ); + } + + // Do the first query to fill the view with search suggestions. + info("Doing first query"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 1", + }); + + // Now make the provider return a history result and bookmark. If + // `showExposureResults` is true, the history result will be added to the view + // but it should be hidden since the view is already full. If it's false, it + // shouldn't be added at all. The bookmark will always be added, which will + // tell us when the view has been updated either way. (It also will be hidden + // since the view is already full.) + let historyUrl = "https://example.com/history"; + let bookmarkUrl = "https://example.com/bookmark"; + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.HISTORY, + { url: historyUrl } + ), + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + { url: bookmarkUrl } + ), + ]; + + // When the provider's `startQuery()` is called, let it add its results but + // don't let it return. That will cause the view to be updated with the new + // results but prevent it from showing hidden rows since the query won't + // finish (so stale rows won't be removed). + let queryResolver = Promise.withResolvers(); + gProvider.finishQueryPromise = queryResolver.promise; + + // Observe when the view appends the bookmark row. This will tell us when the + // view has been updated with the provider's new results. The bookmark row + // will be hidden since the view is already full with search suggestions. + let lastRowPromise = promiseLastRowAppended( + row => row.result.payload.url == bookmarkUrl + ); + + // Now start the second query but don't await it. + info("Starting second query"); + let queryPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 2", + reopenOnBlur: false, + }); + + // Wait for the view to be updated. + info("Waiting for last row"); + let lastRow = await lastRowPromise; + info("Done waiting for last row"); + + Assert.ok( + BrowserTestUtils.isHidden(lastRow), + "The new bookmark row should be hidden since the view is full" + ); + + // Make sure the view is full of visible rows as expected, plus the one or two + // hidden rows for the history and bookmark results. + let expected = [ + { + source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + type: UrlbarUtils.RESULT_TYPE.URL, + url: bookmarkUrl, + }, + ]; + if (showExposureResults) { + expected.unshift({ + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + type: UrlbarUtils.RESULT_TYPE.URL, + url: historyUrl, + }); + } + let rows = UrlbarTestUtils.getResultsContainer(window); + Assert.equal( + rows.children.length, + MAX_RESULT_COUNT + expected.length, + "The view has the expected number of rows" + ); + + // Check the visible rows. + for (let i = 0; i < MAX_RESULT_COUNT; i++) { + let row = rows.children[i]; + Assert.ok(BrowserTestUtils.isVisible(row), `rows[${i}] should be visible`); + Assert.ok( + row.result.type == UrlbarUtils.RESULT_TYPE.SEARCH, + `rows[${i}].result.type should be SEARCH` + ); + // The heuristic won't have a suggestion so skip it. + if (i > 0) { + Assert.ok( + row.result.payload.suggestion, + `rows[${i}] should have a suggestion` + ); + } + } + + // Check the hidden history and bookmark rows. + for (let i = 0; i < expected.length; i++) { + let { source, type, url } = expected[i]; + let row = rows.children[MAX_RESULT_COUNT + i]; + Assert.ok(row, `rows[${i}] should exist`); + Assert.ok(BrowserTestUtils.isHidden(row), `rows[${i}] should be hidden`); + Assert.equal( + row.result.type, + type, + `rows[${i}].result.type should be as expected` + ); + Assert.equal( + row.result.source, + source, + `rows[${i}].result.source should be as expected` + ); + Assert.equal( + row.result.payload.url, + url, + `rows[${i}] URL should be as expected` + ); + } + + // Now let the query finish (so stale rows are removed). + queryResolver.resolve(); + info("Waiting for second query to finish"); + await queryPromise; + info("Second query finished"); + + rows = UrlbarTestUtils.getResultsContainer(window); + Assert.equal( + rows.children.length, + // + 1 for the heurustic. + 1 + expected.length, + "The view has the expected number of rows" + ); + + // Check the visible rows (except the heuristic). + for (let i = 0; i < expected.length; i++) { + let { source, type, url } = expected[i]; + let index = i + 1; + let row = rows.children[index]; + Assert.ok(row, `rows[${index}] should exist`); + Assert.ok( + BrowserTestUtils.isVisible(row), + `rows[${index}] should be visible` + ); + Assert.equal( + row.result.type, + type, + `rows[${index}].result.type should be as expected` + ); + Assert.equal( + row.result.source, + source, + `rows[${index}].result.source should be as expected` + ); + Assert.equal( + row.result.payload.url, + url, + `rows[${index}] URL should be as expected` + ); + } + + // Close the view. Blur the urlbar to end the session. + info("Closing view and blurring"); + await UrlbarTestUtils.promisePopupClose(window); + gURLBar.blur(); + + // An exposure for the history result should have been recorded. + assertExposureTelemetry([{ results: "history" }]); + + // Clean up. + await SpecialPowers.popPrefEnv(); + Services.fog.testResetFOG(); + gProvider.finishQueryPromise = null; +} + +// Does the following: +// +// 1. Starts and finishes a query that fills up the view +// 2. Starts a second query with results that cannot replace rows from the first +// query and that therefore must be appended and hidden +// 3. Before the second query finishes (i.e., before stale rows are removed), +// starts and finishes a third query (after which stale rows are removed) +// +// Results in the third query should trigger an exposure since they become +// visible when the query finishes (and stale rows are removed) in step 3. +// Results in the second query should not trigger an exposure since they could +// never be visible since the query is canceled before stale rows are removed. +add_task(async function exposure_append_full_twice() { + for (let showExposureResults of [true, false]) { + await do_exposure_append_full_twice(showExposureResults); + } +}); + +async function do_exposure_append_full_twice(showExposureResults) { + info( + "Starting do_exposure_append_full_twice: " + + JSON.stringify({ showExposureResults }) + ); + + // Exposure results are history and tab. + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.exposureResults", "history,tab"], + ["browser.urlbar.showExposureResults", showExposureResults], + ], + }); + + // Make the provider return enough search suggestions to fill the view. + gProvider.results = []; + for (let i = 0; i < MAX_RESULT_COUNT; i++) { + gProvider.results.push( + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.SEARCH, + UrlbarUtils.RESULT_SOURCE.SEARCH, + { + suggestion: "suggestion " + i, + engine: Services.search.defaultEngine.name, + } + ) + ); + } + + // Do the first query to fill the view with search suggestions. + info("Doing first query"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 1", + }); + + // Now make the provider return a history result, tab, and bookmark. If + // `showExposureResults` is true, the history and tab results will be added to + // the view but they should be hidden since the view is already full. If it's + // false, they shouldn't be added at all. The bookmark will always be added, + // which will tell us when the view has been updated either way. (It also will + // be hidden since the view is already full.) + let historyUrl = "https://example.com/history"; + let tabUrl = "https://example.com/tab"; + let bookmarkUrl = "https://example.com/bookmark"; + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.HISTORY, + { url: historyUrl } + ), + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + UrlbarUtils.RESULT_SOURCE.TABS, + { url: tabUrl } + ), + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + { url: bookmarkUrl } + ), + ]; + + // When the provider's `startQuery()` is called, let it add its results but + // don't let it return. That will cause the view to be updated with the new + // results but prevent it from showing hidden rows since the query won't + // finish (so stale rows won't be removed). + let secondQueryResolver = Promise.withResolvers(); + gProvider.finishQueryPromise = secondQueryResolver.promise; + + // Observe when the view appends the bookmark row. This will tell us when the + // view has been updated with the provider's new results. The bookmark row + // will be hidden since the view is already full with search suggestions. + let lastRowPromise = promiseLastRowAppended( + row => row.result.payload.url == bookmarkUrl + ); + + // Now start the second query but don't await it. + info("Starting second query"); + let secondQueryPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 2", + reopenOnBlur: false, + }); + + // Wait for the view to be updated. + info("Waiting for last row"); + let lastRow = await lastRowPromise; + info("Done waiting for last row"); + + Assert.ok( + BrowserTestUtils.isHidden(lastRow), + "The new bookmark row should be hidden since the view is full" + ); + + // Make sure the view is full of visible rows as expected, plus the one or + // three hidden rows for the history, tab, and bookmark results. + let expected = [ + { + source: UrlbarUtils.RESULT_SOURCE.BOOKMARKS, + type: UrlbarUtils.RESULT_TYPE.URL, + url: bookmarkUrl, + }, + ]; + if (showExposureResults) { + expected.unshift( + { + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + type: UrlbarUtils.RESULT_TYPE.URL, + url: historyUrl, + }, + { + source: UrlbarUtils.RESULT_SOURCE.TABS, + type: UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + url: tabUrl, + } + ); + } + let rows = UrlbarTestUtils.getResultsContainer(window); + Assert.equal( + rows.children.length, + MAX_RESULT_COUNT + expected.length, + "The view has the expected number of rows" + ); + + // Check the visible rows. + for (let i = 0; i < MAX_RESULT_COUNT; i++) { + let row = rows.children[i]; + Assert.ok(BrowserTestUtils.isVisible(row), `rows[${i}] should be visible`); + Assert.ok( + row.result.type == UrlbarUtils.RESULT_TYPE.SEARCH, + `rows[${i}].result.type should be SEARCH` + ); + // The heuristic won't have a suggestion so skip it. + if (i > 0) { + Assert.ok( + row.result.payload.suggestion, + `rows[${i}] should have a suggestion` + ); + } + } + + // Check the hidden history, tab, and bookmark rows. + for (let i = 0; i < expected.length; i++) { + let { source, type, url } = expected[i]; + let row = rows.children[MAX_RESULT_COUNT + i]; + Assert.ok(row, `rows[${i}] should exist`); + Assert.ok(BrowserTestUtils.isHidden(row), `rows[${i}] should be hidden`); + Assert.equal( + row.result.type, + type, + `rows[${i}].result.type should be as expected` + ); + Assert.equal( + row.result.source, + source, + `rows[${i}].result.source should be as expected` + ); + Assert.equal( + row.result.payload.url, + url, + `rows[${i}] URL should be as expected` + ); + } + + // Now make the provider return only a history result. + gProvider.results = [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.HISTORY, + { url: historyUrl } + ), + ]; + + // Without waiting for the second query to finish (i.e., before stale rows are + // removed), do a third query and allow it to finish (so stale rows are + // removed). An exposure should be recorded for the history result since it's + // present in the third query. An exposure should not be recorded for the tab + // result because it could not have been visible since the second query did + // not finish. + + let thirdQueryStartedPromise = new Promise(resolve => { + let queryListener = { + onQueryStarted: () => { + gURLBar.controller.removeQueryListener(queryListener); + resolve(); + }, + }; + gURLBar.controller.addQueryListener(queryListener); + }); + + info("Starting third query"); + let thirdQueryPromise = UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test 3", + reopenOnBlur: false, + }); + + // The test provider's `startQuery()` is still awaiting its + // `finishQueryPromise`, so we need to resolve it so the provider can respond + // to the third query. But before we do that, we need to make sure the third + // query has started and canceled the second query because otherwise the + // second query could finish and cause stale rows to be removed. + info("Waiting for third query to start"); + await thirdQueryStartedPromise; + info("Resolving provider's finishQueryPromise"); + secondQueryResolver.resolve(); + + // Now wait for the third query to finish. + info("Waiting for third query to finish"); + await thirdQueryPromise; + + expected = []; + if (showExposureResults) { + expected.unshift({ + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + type: UrlbarUtils.RESULT_TYPE.URL, + url: historyUrl, + }); + } + + rows = UrlbarTestUtils.getResultsContainer(window); + Assert.equal( + rows.children.length, + // + 1 for the heurustic. + 1 + expected.length, + "The view has the expected number of rows" + ); + + // Check the history row. + for (let i = 0; i < expected.length; i++) { + let { source, type, url } = expected[i]; + let index = i + 1; + let row = rows.children[index]; + Assert.ok(row, `rows[${index}] should exist`); + Assert.ok( + BrowserTestUtils.isVisible(row), + `rows[${index}] should be visible` + ); + Assert.equal( + row.result.type, + type, + `rows[${index}].result.type should be as expected` + ); + Assert.equal( + row.result.source, + source, + `rows[${index}].result.source should be as expected` + ); + Assert.equal( + row.result.payload.url, + url, + `rows[${index}] URL should be as expected` + ); + } + + // Close the view. Blur the urlbar to end the session. + info("Closing view and blurring"); + await UrlbarTestUtils.promisePopupClose(window); + gURLBar.blur(); + + // An exposure only for the history result should have been recorded. If an + // exposure was also incorrectly recorded for the tab result, this will fail + // with "history,tab" instead of only "history". + assertExposureTelemetry([{ results: "history" }]); + + // Clean up. + await secondQueryPromise; + await SpecialPowers.popPrefEnv(); + Services.fog.testResetFOG(); + gProvider.finishQueryPromise = null; } /** @@ -523,7 +1065,7 @@ class TestProvider extends UrlbarTestUtils.TestProvider { function promiseLastRowAppended(predicate) { return new Promise(resolve => { let rows = UrlbarTestUtils.getResultsContainer(window); - let observer = new MutationObserver(mutations => { + let observer = new MutationObserver(() => { let lastRow = rows.children[rows.children.length - 1]; info( "Observed mutation, lastRow.result is: " + diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js deleted file mode 100644 index 354876e512..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_groups.js +++ /dev/null @@ -1,258 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the following data of impression telemetry. -// - groups -// - results -// - n_results - -// This test has many subtests and can time out in verify mode. -requestLongerTimeout(5); - -add_setup(async function () { - await initGroupTest(); - // Increase the pausing time to ensure to ready for all suggestions. - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", - 500, - ], - ], - }); -}); - -add_task(async function heuristics() { - await doHeuristicsTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause", groups: "heuristic", results: "search_engine" }, - ]), - }); -}); - -add_task(async function adaptive_history() { - await doAdaptiveHistoryTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,adaptive_history", - results: "search_engine,history", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function search_history() { - await doSearchHistoryTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,search_history,search_history", - results: "search_engine,search_history,search_history", - n_results: 3, - }, - ]), - }); -}); - -add_task(async function recent_search() { - await doRecentSearchTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "recent_search,suggested_index", - results: "recent_search,action", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function search_suggest() { - await doSearchSuggestTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,search_suggest,search_suggest", - results: "search_engine,search_suggest,search_suggest", - n_results: 3, - }, - ]), - }); - - await doTailSearchSuggestTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,search_suggest", - results: "search_engine,search_suggest", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function top_pick() { - await doTopPickTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,top_pick,search_suggest,search_suggest", - results: - "search_engine,merino_top_picks,search_suggest,search_suggest", - n_results: 4, - }, - ]), - }); -}); - -add_task(async function top_site() { - await doTopSiteTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "top_site,suggested_index", - results: "top_site,action", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function clipboard() { - await doClipboardTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "general,suggested_index", - results: "clipboard,action", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function remote_tab() { - await doRemoteTabTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,remote_tab", - results: "search_engine,remote_tab", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function addon() { - await doAddonTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "addon", - results: "addon", - n_results: 1, - }, - ]), - }); -}); - -add_task(async function general() { - await doGeneralBookmarkTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,suggested_index,general", - results: "search_engine,action,bookmark", - n_results: 3, - }, - ]), - }); - - await doGeneralHistoryTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,general", - results: "search_engine,history", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function suggest() { - await doSuggestTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - groups: "heuristic,suggest", - results: UrlbarPrefs.get("quickSuggestRustEnabled") - ? "search_engine,rust_adm_nonsponsored" - : "search_engine,rs_adm_nonsponsored", - n_results: 2, - }, - ]), - }); -}); - -add_task(async function about_page() { - await doAboutPageTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,about_page,about_page", - results: "search_engine,history,history", - n_results: 3, - }, - ]), - }); -}); - -add_task(async function suggested_index() { - await doSuggestedIndexTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { - reason: "pause", - groups: "heuristic,suggested_index", - results: "search_engine,unit", - n_results: 2, - }, - ]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js deleted file mode 100644 index a16b55cac6..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction.js +++ /dev/null @@ -1,68 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the following data of impression telemetry. -// - interaction - -add_setup(async function () { - await initInteractionTest(); -}); - -add_task(async function topsites() { - await doTopsitesTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", interaction: "topsites" }]), - }); -}); - -add_task(async function typed() { - await doTypedTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", interaction: "typed" }]), - }); - - await doTypedWithResultsPopupTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", interaction: "typed" }]), - }); -}); - -add_task(async function pasted() { - await doPastedTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", interaction: "pasted" }]), - }); - - await doPastedWithResultsPopupTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", interaction: "pasted" }]), - }); -}); - -add_task(async function topsite_search() { - await doTopsitesSearchTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause", interaction: "topsite_search" }, - ]), - }); -}); - -add_task(async function returned_restarted_refined() { - await doReturnedRestartedRefinedTest({ - trigger: () => waitForPauseImpression(), - assert: expected => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause", interaction: expected }, - ]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js deleted file mode 100644 index af7134b3a0..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_disabled.js +++ /dev/null @@ -1,57 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test impression telemetry with persisted search terms disabled. - -// Allow more time for Mac machines so they don't time out in verify mode. -if (AppConstants.platform == "macosx") { - requestLongerTimeout(3); -} - -add_setup(async function () { - await initInteractionTest(); - - await SpecialPowers.pushPrefEnv({ - set: [["browser.urlbar.showSearchTerms.featureGate", false]], - }); -}); - -add_task(async function persisted_search_terms() { - await doPersistedSearchTermsTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause", interaction: "typed" }, - ]), - }); -}); - -add_task(async function persisted_search_terms_restarted_refined() { - await doPersistedSearchTermsRestartedRefinedTest({ - enabled: false, - trigger: () => waitForPauseImpression(), - assert: expected => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause", interaction: expected }, - ]), - }); -}); - -add_task( - async function persisted_search_terms_restarted_refined_via_abandonment() { - await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ - enabled: false, - trigger: () => waitForPauseImpression(), - assert: expected => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause" }, - { reason: "pause", interaction: expected }, - ]), - }); - } -); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js deleted file mode 100644 index a29ff98b78..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_interaction_persisted_search_terms_enabled.js +++ /dev/null @@ -1,61 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test impression telemetry with persisted search terms enabled. - -// Allow more time for Mac machines so they don't time out in verify mode. -if (AppConstants.platform == "macosx") { - requestLongerTimeout(3); -} - -add_setup(async function () { - await initInteractionTest(); - - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.urlbar.showSearchTerms.featureGate", true], - ["browser.urlbar.showSearchTerms.enabled", true], - ["browser.search.widget.inNavBar", false], - ], - }); -}); - -add_task(async function interaction_persisted_search_terms() { - await doPersistedSearchTermsTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause", interaction: "persisted_search_terms" }, - ]), - }); -}); - -add_task(async function interaction_persisted_search_terms_restarted_refined() { - await doPersistedSearchTermsRestartedRefinedTest({ - enabled: true, - trigger: () => waitForPauseImpression(), - assert: expected => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause", interaction: expected }, - ]), - }); -}); - -add_task( - async function interaction_persisted_search_terms_restarted_refined_via_abandonment() { - await doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ - enabled: true, - trigger: () => waitForPauseImpression(), - assert: expected => - assertImpressionTelemetry([ - { reason: "pause" }, - { reason: "pause" }, - { reason: "pause", interaction: expected }, - ]), - }); - } -); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js deleted file mode 100644 index 528cc318e0..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_n_chars_n_words.js +++ /dev/null @@ -1,40 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the following data of impression telemetry. -// - n_chars -// - n_words - -add_setup(async function () { - await initNCharsAndNWordsTest(); -}); - -add_task(async function n_chars() { - await doNCharsTest({ - trigger: () => waitForPauseImpression(), - assert: nChars => - assertImpressionTelemetry([{ reason: "pause", n_chars: nChars }]), - }); - - await doNCharsWithOverMaxTextLengthCharsTest({ - trigger: () => waitForPauseImpression(), - assert: nChars => - assertImpressionTelemetry([{ reason: "pause", n_chars: nChars }]), - }); -}); - -add_task(async function n_words() { - await doNWordsTest({ - trigger: () => waitForPauseImpression(), - assert: nWords => - assertImpressionTelemetry([{ reason: "pause", n_words: nWords }]), - }); - - await doNWordsWithOverMaxTextLengthCharsTest({ - trigger: () => waitForPauseImpression(), - assert: nWords => - assertImpressionTelemetry([{ reason: "pause", n_words: nWords }]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js deleted file mode 100644 index 344e238e24..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_preferences.js +++ /dev/null @@ -1,41 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test the impression telemetry behavior with its preferences. - -add_setup(async function () { - await setup(); -}); - -add_task(async function pauseImpressionIntervalMs() { - const additionalInterval = 1000; - const originalInterval = UrlbarPrefs.get( - "searchEngagementTelemetry.pauseImpressionIntervalMs" - ); - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", - originalInterval + additionalInterval, - ], - ], - }); - - await doTest(async browser => { - await openPopup("https://example.com"); - - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - await new Promise(r => setTimeout(r, originalInterval)); - await Services.fog.testFlushAllChildren(); - assertImpressionTelemetry([]); - - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - await new Promise(r => setTimeout(r, additionalInterval)); - await Services.fog.testFlushAllChildren(); - assertImpressionTelemetry([{ sap: "urlbar_newtab" }]); - }); - - await SpecialPowers.popPrefEnv(); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js deleted file mode 100644 index 482b906024..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_sap.js +++ /dev/null @@ -1,38 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the following data of impression telemetry. -// - sap - -add_setup(async function () { - await initSapTest(); -}); - -add_task(async function urlbar() { - await doUrlbarTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause", sap: "urlbar_newtab" }, - { reason: "pause", sap: "urlbar" }, - ]), - }); -}); - -add_task(async function handoff() { - await doHandoffTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", sap: "handoff" }]), - }); -}); - -add_task(async function urlbar_addonpage() { - await doUrlbarAddonpageTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", sap: "urlbar_addonpage" }]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js deleted file mode 100644 index c5bd983d7f..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_engine_default_id.js +++ /dev/null @@ -1,28 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the following data of impression telemetry. -// - search_engine_default_id - -add_setup(async function () { - await initSearchEngineDefaultIdTest(); - // Increase the pausing time to ensure to ready for all suggestions. - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", - 500, - ], - ], - }); -}); - -add_task(async function basic() { - await doSearchEngineDefaultIdTest({ - trigger: () => waitForPauseImpression(), - assert: engineId => - assertImpressionTelemetry([{ search_engine_default_id: engineId }]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js deleted file mode 100644 index 727afa3cef..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_search_mode.js +++ /dev/null @@ -1,72 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the following data of impression telemetry. -// - search_mode - -add_setup(async function () { - await initSearchModeTest(); - // Increase the pausing time to ensure entering search mode. - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", - 1000, - ], - ], - }); -}); - -add_task(async function not_search_mode() { - await doNotSearchModeTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", search_mode: "" }]), - }); -}); - -add_task(async function search_engine() { - await doSearchEngineTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause", search_mode: "search_engine" }, - ]), - }); -}); - -add_task(async function bookmarks() { - await doBookmarksTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([ - { reason: "pause", search_mode: "bookmarks" }, - ]), - }); -}); - -add_task(async function history() { - await doHistoryTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", search_mode: "history" }]), - }); -}); - -add_task(async function tabs() { - await doTabTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", search_mode: "tabs" }]), - }); -}); - -add_task(async function actions() { - await doActionsTest({ - trigger: () => waitForPauseImpression(), - assert: () => - assertImpressionTelemetry([{ reason: "pause", search_mode: "actions" }]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js deleted file mode 100644 index 31f64996f3..0000000000 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_impression_timing.js +++ /dev/null @@ -1,91 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// Test for the taking timing for the impression telemetry. - -add_setup(async function () { - await setup(); -}); - -add_task(async function cancelImpressionTimerByEngagementEvent() { - const additionalInterval = 1000; - const originalInterval = UrlbarPrefs.get( - "searchEngagementTelemetry.pauseImpressionIntervalMs" - ); - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", - originalInterval + additionalInterval, - ], - ], - }); - - for (const trigger of [doEnter, doBlur]) { - await doTest(async browser => { - await openPopup("https://example.com"); - await trigger(); - - // Check whether the impression timer was canceled. - await new Promise(r => - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - setTimeout(r, originalInterval + additionalInterval) - ); - assertImpressionTelemetry([]); - }); - } - - await SpecialPowers.popPrefEnv(); -}); - -add_task(async function cancelInpressionTimerByType() { - const originalInterval = UrlbarPrefs.get( - "searchEngagementTelemetry.pauseImpressionIntervalMs" - ); - - await doTest(async browser => { - await openPopup("x"); - await new Promise(r => - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - setTimeout(r, originalInterval / 10) - ); - assertImpressionTelemetry([]); - - EventUtils.synthesizeKey(" "); - EventUtils.synthesizeKey("z"); - await UrlbarTestUtils.promiseSearchComplete(window); - assertImpressionTelemetry([]); - await waitForPauseImpression(); - - assertImpressionTelemetry([{ n_chars: 3 }]); - }); -}); - -add_task(async function oneImpressionInOneSession() { - await doTest(async browser => { - await openPopup("x"); - await waitForPauseImpression(); - - // Sanity check. - assertImpressionTelemetry([{ n_chars: 1 }]); - - // Add a keyword to start new query. - EventUtils.synthesizeKey(" "); - EventUtils.synthesizeKey("z"); - await UrlbarTestUtils.promiseSearchComplete(window); - await waitForPauseImpression(); - - // No more taking impression telemetry. - assertImpressionTelemetry([{ n_chars: 1 }]); - - // Finish the current session. - await doEnter(); - - // Should take pause impression since new session started. - await openPopup("x z y"); - await waitForPauseImpression(); - assertImpressionTelemetry([{ n_chars: 1 }, { n_chars: 5 }]); - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js index e86c664b46..cc73c7509f 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-groups.js @@ -11,7 +11,7 @@ ChromeUtils.defineESModuleGetters(this, { }); async function doHeuristicsTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await trigger(); @@ -24,7 +24,7 @@ async function doAdaptiveHistoryTest({ trigger, assert }) { set: [["browser.urlbar.autoFill", false]], }); - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits(["https://example.com/test"]); await UrlbarUtils.addToInputHistory("https://example.com/test", "examp"); @@ -46,7 +46,7 @@ async function doSearchHistoryTest({ trigger, assert }) { ], }); - await doTest(async browser => { + await doTest(async () => { await UrlbarTestUtils.formHistory.add(["foofoo", "foobar"]); await openPopup("foo"); @@ -64,7 +64,7 @@ async function doRecentSearchTest({ trigger, assert }) { set: [["browser.urlbar.recentsearches.featureGate", true]], }); - await doTest(async browser => { + await doTest(async () => { await UrlbarTestUtils.formHistory.add([ { value: "foofoo", source: Services.search.defaultEngine.name }, ]); @@ -87,7 +87,7 @@ async function doSearchSuggestTest({ trigger, assert }) { ], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("foo"); await selectRowByURL("http://mochi.test:8888/?terms=foofoo"); @@ -101,7 +101,7 @@ async function doSearchSuggestTest({ trigger, assert }) { async function doTailSearchSuggestTest({ trigger, assert }) { const cleanup = await _useTailSuggestionsEngine(); - await doTest(async browser => { + await doTest(async () => { await openPopup("hello"); await selectRowByProvider("SearchSuggestions"); @@ -127,7 +127,7 @@ async function doTopPickTest({ trigger, assert }) { ], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("navigational"); await selectRowByURL("https://example.com/navigational-suggestion"); @@ -139,7 +139,7 @@ async function doTopPickTest({ trigger, assert }) { } async function doTopSiteTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await addTopSites("https://example.com/"); await showResultByArrowDown(); @@ -158,7 +158,7 @@ async function doClipboardTest({ trigger, assert }) { ], }); SpecialPowers.clipboardCopyString("https://example.com/clipboard"); - await doTest(async browser => { + await doTest(async () => { await showResultByArrowDown(); await selectRowByURL("https://example.com/clipboard"); @@ -173,7 +173,7 @@ async function doClipboardTest({ trigger, assert }) { async function doRemoteTabTest({ trigger, assert }) { const remoteTab = await loadRemoteTab("https://example.com"); - await doTest(async browser => { + await doTest(async () => { await openPopup("example"); await selectRowByProvider("RemoteTabs"); @@ -188,7 +188,7 @@ async function doAddonTest({ trigger, assert }) { const addon = loadOmniboxAddon({ keyword: "omni" }); await addon.startup(); - await doTest(async browser => { + await doTest(async () => { await openPopup("omni test"); await trigger(); @@ -199,7 +199,7 @@ async function doAddonTest({ trigger, assert }) { } async function doGeneralBookmarkTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, url: "https://example.com/bookmark", @@ -219,7 +219,7 @@ async function doGeneralHistoryTest({ trigger, assert }) { set: [["browser.urlbar.autoFill", false]], }); - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits("https://example.com/test"); await openPopup("example"); @@ -235,7 +235,7 @@ async function doGeneralHistoryTest({ trigger, assert }) { async function doSuggestTest({ trigger, assert }) { const cleanupQuickSuggest = await ensureQuickSuggestInit(); - await doTest(async browser => { + await doTest(async () => { await openPopup("nonsponsored"); await selectRowByURL("https://example.com/nonsponsored"); @@ -251,7 +251,7 @@ async function doAboutPageTest({ trigger, assert }) { set: [["browser.urlbar.maxRichResults", 3]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("about:"); await selectRowByURL("about:robots"); @@ -267,7 +267,7 @@ async function doSuggestedIndexTest({ trigger, assert }) { set: [["browser.urlbar.unitConversion.enabled", true]], }); - await doTest(async browser => { + await doTest(async () => { await openPopup("1m to cm"); await selectRowByProvider("UnitConversion"); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js index 244e27d272..58c55b416f 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js @@ -14,7 +14,7 @@ ChromeUtils.defineESModuleGetters(this, { }); async function doTopsitesTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await addTopSites("https://example.com/"); await showResultByArrowDown(); @@ -120,7 +120,7 @@ async function doTopsitesSearchTest({ trigger, assert }) { } async function doTypedTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await trigger(); @@ -129,7 +129,7 @@ async function doTypedTest({ trigger, assert }) { } async function doTypedWithResultsPopupTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await showResultByArrowDown(); EventUtils.synthesizeKey("x"); await UrlbarTestUtils.promiseSearchComplete(window); @@ -140,7 +140,7 @@ async function doTypedWithResultsPopupTest({ trigger, assert }) { } async function doPastedTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await doPaste("www.example.com"); await trigger(); @@ -149,7 +149,7 @@ async function doPastedTest({ trigger, assert }) { } async function doPastedWithResultsPopupTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await showResultByArrowDown(); await doPaste("x"); @@ -189,9 +189,8 @@ async function doReturnedRestartedRefinedTest({ trigger, assert }) { ]; for (const { firstInput, secondInput, expected } of testData) { - await doTest(async browser => { + await doTest(async () => { await openPopup(firstInput); - await waitForPauseImpression(); await doBlur(); await UrlbarTestUtils.promisePopupOpen(window, () => { @@ -211,9 +210,8 @@ async function doReturnedRestartedRefinedTest({ trigger, assert }) { } async function doPersistedSearchTermsTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); - await waitForPauseImpression(); await doEnter(); await openPopup("x"); @@ -258,9 +256,8 @@ async function doPersistedSearchTermsRestartedRefinedTest({ ]; for (const { firstInput, secondInput, expected } of testData) { - await doTest(async browser => { + await doTest(async () => { await openPopup(firstInput); - await waitForPauseImpression(); await doEnter(); await UrlbarTestUtils.promisePopupOpen(window, () => { @@ -314,13 +311,11 @@ async function doPersistedSearchTermsRestartedRefinedViaAbandonmentTest({ ]; for (const { firstInput, secondInput, expected } of testData) { - await doTest(async browser => { + await doTest(async () => { await openPopup("any search"); - await waitForPauseImpression(); await doEnter(); await openPopup(firstInput); - await waitForPauseImpression(); await doBlur(); await UrlbarTestUtils.promisePopupOpen(window, () => { diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js index 6d4c61c7f0..154f3ec11c 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-n_chars_n_words.js @@ -7,7 +7,7 @@ async function doNCharsTest({ trigger, assert }) { for (const input of ["x", "xx", "xx x", "xx x "]) { - await doTest(async browser => { + await doTest(async () => { await openPopup(input); await trigger(); @@ -17,7 +17,7 @@ async function doNCharsTest({ trigger, assert }) { } async function doNCharsWithOverMaxTextLengthCharsTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { let input = ""; for (let i = 0; i < UrlbarUtils.MAX_TEXT_LENGTH * 2; i++) { input += "x"; @@ -31,7 +31,7 @@ async function doNCharsWithOverMaxTextLengthCharsTest({ trigger, assert }) { async function doNWordsTest({ trigger, assert }) { for (const input of ["x", "xx", "xx x", "xx x "]) { - await doTest(async browser => { + await doTest(async () => { await openPopup(input); await trigger(); @@ -42,7 +42,7 @@ async function doNWordsTest({ trigger, assert }) { } async function doNWordsWithOverMaxTextLengthCharsTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { const word = "1234 "; let input = ""; while (input.length < UrlbarUtils.MAX_TEXT_LENGTH * 2) { diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js index ef95873813..ee74136b22 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-sap.js @@ -6,7 +6,7 @@ /* import-globals-from head.js */ async function doUrlbarNewTabTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await trigger(); @@ -15,9 +15,8 @@ async function doUrlbarNewTabTest({ trigger, assert }) { } async function doUrlbarTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); - await waitForPauseImpression(); await doEnter(); await openPopup("y"); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js index c0af764e7f..5bf8cc3735 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_engine_default_id.js @@ -6,7 +6,7 @@ /* import-globals-from head.js */ async function doSearchEngineDefaultIdTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { info("Test with current engine"); const defaultEngine = await Services.search.getDefault(); @@ -15,7 +15,7 @@ async function doSearchEngineDefaultIdTest({ trigger, assert }) { await assert(defaultEngine.telemetryId); }); - await doTest(async browser => { + await doTest(async () => { info("Test with new engine"); const defaultEngine = await Services.search.getDefault(); const newEngineName = "NewDummyEngine"; diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js index 5c877da05f..86151e1ba3 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js @@ -6,7 +6,7 @@ /* import-globals-from head.js */ async function doNotSearchModeTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await trigger(); @@ -15,7 +15,7 @@ async function doNotSearchModeTest({ trigger, assert }) { } async function doSearchEngineTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("x"); await UrlbarTestUtils.enterSearchMode(window); @@ -25,7 +25,7 @@ async function doSearchEngineTest({ trigger, assert }) { } async function doBookmarksTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid, url: "https://example.com/bookmark", @@ -47,7 +47,7 @@ async function doHistoryTest({ trigger, assert }) { set: [["browser.urlbar.autoFill", false]], }); - await doTest(async browser => { + await doTest(async () => { await PlacesTestUtils.addVisits("https://example.com/test"); await openPopup("example"); await UrlbarTestUtils.enterSearchMode(window, { @@ -65,7 +65,7 @@ async function doHistoryTest({ trigger, assert }) { async function doTabTest({ trigger, assert }) { const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/"); - await doTest(async browser => { + await doTest(async () => { await openPopup("example"); await UrlbarTestUtils.enterSearchMode(window, { source: UrlbarUtils.RESULT_SOURCE.TABS, @@ -80,7 +80,7 @@ async function doTabTest({ trigger, assert }) { } async function doActionsTest({ trigger, assert }) { - await doTest(async browser => { + await doTest(async () => { await openPopup("add"); await UrlbarTestUtils.enterSearchMode(window, { source: UrlbarUtils.RESULT_SOURCE.ACTIONS, diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js index 367387b0e8..4317a50930 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js @@ -58,10 +58,6 @@ function assertEngagementTelemetry(expectedExtraList) { _assertGleanTelemetry("engagement", expectedExtraList); } -function assertImpressionTelemetry(expectedExtraList) { - _assertGleanTelemetry("impression", expectedExtraList); -} - function assertExposureTelemetry(expectedExtraList) { _assertGleanTelemetry("exposure", expectedExtraList); } @@ -204,12 +200,6 @@ async function doPasteAndGo(data) { async function doTest(testFn) { await Services.fog.testFlushAllChildren(); Services.fog.testResetFOG(); - // Enable recording telemetry for impression, as it is disabled by default. - Services.fog.setMetricsFeatureConfig( - JSON.stringify({ - "urlbar.impression": true, - }) - ); gURLBar.controller.engagementEvent.reset(); await PlacesUtils.history.clear(); @@ -223,8 +213,8 @@ async function doTest(testFn) { try { await BrowserTestUtils.withNewTab(gBrowser, testFn); - } finally { - Services.fog.setMetricsFeatureConfig("{}"); + } catch (e) { + console.error(e); } } @@ -423,10 +413,6 @@ async function setup() { ["browser.urlbar.quickactions.minimumSearchString", 0], ["browser.urlbar.suggest.quickactions", true], ["browser.urlbar.shortcuts.quickactions", true], - [ - "browser.urlbar.searchEngagementTelemetry.pauseImpressionIntervalMs", - 100, - ], ], }); @@ -461,13 +447,3 @@ async function showResultByArrowDown() { }); await UrlbarTestUtils.promiseSearchComplete(window); } - -async function waitForPauseImpression() { - await new Promise(r => - setTimeout( - r, - UrlbarPrefs.get("searchEngagementTelemetry.pauseImpressionIntervalMs") - ) - ); - await Services.fog.testFlushAllChildren(); -} diff --git a/browser/components/urlbar/tests/quicksuggest/MerinoTestUtils.sys.mjs b/browser/components/urlbar/tests/quicksuggest/MerinoTestUtils.sys.mjs index 6cda9bb9a7..1828ddbceb 100644 --- a/browser/components/urlbar/tests/quicksuggest/MerinoTestUtils.sys.mjs +++ b/browser/components/urlbar/tests/quicksuggest/MerinoTestUtils.sys.mjs @@ -355,20 +355,16 @@ class _MerinoTestUtils { lazy.QuickSuggest.weather._test_fetchIntervalMs = WEATHER_FETCH_INTERVAL_MS; - // Enabling weather will trigger a fetch. Wait for it to finish so the - // suggestion is ready when this function returns. - this.info("MockMerinoServer initializing weather, waiting for fetch"); - let fetchPromise = lazy.QuickSuggest.weather.waitForFetches(); + // Enabling weather will trigger a fetch. Queue another fetch and await it + // so no fetches are ongoing when this function returns. + this.info("MockMerinoServer initializing weather, setting prefs"); lazy.UrlbarPrefs.set("weather.featureGate", true); lazy.UrlbarPrefs.set("suggest.weather", true); - await fetchPromise; - this.info("MockMerinoServer initializing weather, got fetch"); - - this.Assert.equal( - lazy.QuickSuggest.weather._test_pendingFetchCount, - 0, - "No pending fetches after awaiting initial fetch" + this.info( + "MockMerinoServer initializing weather, done setting prefs, starting fetch" ); + await lazy.QuickSuggest.weather._test_fetch(); + this.info("MockMerinoServer initializing weather, done awaiting fetch"); this.registerCleanupFunction?.(async () => { lazy.UrlbarPrefs.clear("weather.featureGate"); diff --git a/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs b/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs index 32b42198c3..c2aeca1991 100644 --- a/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs +++ b/browser/components/urlbar/tests/quicksuggest/RemoteSettingsServer.sys.mjs @@ -9,7 +9,6 @@ ChromeUtils.defineESModuleGetters(lazy, { HttpError: "resource://testing-common/httpd.sys.mjs", HttpServer: "resource://testing-common/httpd.sys.mjs", HTTP_404: "resource://testing-common/httpd.sys.mjs", - Log: "resource://gre/modules/Log.sys.mjs", }); const SERVER_PREF = "services.settings.server"; @@ -23,23 +22,16 @@ export class RemoteSettingsServer { * The server must be started by calling `start()`. * * @param {object} options - * @param {number} options.logLevel - * A `Log.Level` value from `Log.sys.mjs`. `Log.Level.Info` logs basic info - * on requests and responses like paths and status codes. Pass - * `Log.Level.Debug` to log more info like headers, response bodies, and - * added and removed records. + * @param {number} options.maxLogLevel + * A log level value as defined by ConsoleInstance. `Info` logs basic info + * on requests and responses like paths and status codes. Pass `Debug` to + * log more info like headers, response bodies, added and removed records. */ - constructor({ logLevel = lazy.Log.Level.Info } = {}) { - this.#log = lazy.Log.repository.getLogger("RemoteSettingsServer"); - this.#log.level = logLevel; - - // Use `DumpAppender` instead of `ConsoleAppender`. The xpcshell and browser - // test harnesses buffer console messages and log them later, which makes it - // really hard to debug problems. `DumpAppender` logs to stdout, which the - // harnesses log immediately. - this.#log.addAppender( - new lazy.Log.DumpAppender(new lazy.Log.BasicFormatter()) - ); + constructor({ maxLogLevel = "Info" } = {}) { + this.#log = console.createInstance({ + prefix: "RemoteSettingsServer", + maxLogLevel, + }); } /** @@ -587,14 +579,12 @@ export class RemoteSettingsServer { * The associated request. * @param {integer} options.status * The HTTP status code of the response. - * @param {string} options.statusText - * The description of the status code. * @param {object} options.headers * An object mapping from response header names to values. * @param {object} options.body * The response body, if any. */ - #logResponse({ request, status, statusText, headers, body }) { + #logResponse({ request, status, headers, body }) { this.#log.info(`> ${status} ${request.path}`); for (let [name, value] of Object.entries(headers)) { this.#log.debug(`${name}: ${value}`); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js index 6256a5aec2..dbd8f59ade 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_onboardingDialog.js @@ -373,7 +373,6 @@ const VARIATION_TEST_DATA = [ "onboardingNext", "onboardingAccept", "onboardingLearnMore", - "onboardingReject", "onboardingSkipLink", "onboardingDialog", "onboardingAccept", @@ -706,7 +705,6 @@ const VARIATION_TEST_DATA = [ "onboardingNext", "onboardingLearnMore", "onboardingAccept", - "onboardingReject", "onboardingSkipLink", "onboardingDialog", "onboardingLearnMore", @@ -762,7 +760,6 @@ const VARIATION_TEST_DATA = [ defaultFocusOrder: [ "onboardingLearnMore", "onboardingAccept", - "onboardingReject", "onboardingSkipLink", "onboardingDialog", "onboardingLearnMore", diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js index 8682f1f53a..821c5cf470 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js @@ -172,7 +172,7 @@ add_task(async function hiddenRow() { // mutation listener to the view so we can tell when the quick suggest row is // added. let mutationPromise = new Promise(resolve => { - let observer = new MutationObserver(mutations => { + let observer = new MutationObserver(() => { let rows = UrlbarTestUtils.getResultsContainer(window).children; for (let row of rows) { if (row.result.providerName == "UrlbarProviderQuickSuggest") { diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js index 1c3f0e62e7..e10e87b516 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_weather.js @@ -11,6 +11,11 @@ ChromeUtils.defineESModuleGetters(this, { UrlbarProviderWeather: "resource:///modules/UrlbarProviderWeather.sys.mjs", }); +// This test takes a while and can time out in verify mode. Each task is run +// twice, once with Rust enabled and once with it disabled. Once we remove the +// JS backend this should improve a lot, but for now request a longer timeout. +requestLongerTimeout(5); + add_setup(async function () { await QuickSuggestTestUtils.ensureQuickSuggestInit({ remoteSettingsRecords: [ @@ -21,6 +26,15 @@ add_setup(async function () { ], }); await MerinoTestUtils.initWeather(); + + // When `add_tasks_with_rust()` disables the Rust backend and forces sync, the + // JS backend will sync `Weather` with remote settings. Since keywords are + // present in remote settings at that point (we added them above), `Weather` + // will then start fetching. The fetch may or may not be done before our test + // task starts. To make sure it's done, queue another fetch and await it. + registerAddTasksWithRustSetup(async () => { + await QuickSuggest.weather._test_fetch(); + }); }); // Basic checks of the row DOM. @@ -341,11 +355,8 @@ async function doDismissTest(command) { await UrlbarTestUtils.promisePopupClose(window); // Enable the weather suggestion again and wait for it to be fetched. - let fetchPromise = QuickSuggest.weather.waitForFetches(); UrlbarPrefs.clear("suggest.weather"); - info("Waiting for weather fetch after re-enabling the suggestion"); - await fetchPromise; - info("Got weather fetch"); + await QuickSuggest.weather._test_fetch(); // Wait for keywords to be re-synced from remote settings. await QuickSuggestTestUtils.forceSync(); @@ -396,6 +407,94 @@ async function doSessionOngoingCommandTest(command) { await doDismissTest("not_interested"); } +// Test for menu item to mange the suggest. +add_tasks_with_rust(async function manage() { + await BrowserTestUtils.withNewTab({ gBrowser }, async browser => { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: MerinoTestUtils.WEATHER_KEYWORD, + }); + + let resultIndex = 1; + let details = await UrlbarTestUtils.getDetailsOfResultAt( + window, + resultIndex + ); + assertIsWeatherResult(details.result, true); + + const managePage = "about:preferences#search"; + let onManagePageLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + managePage + ); + // Click the command. + await UrlbarTestUtils.openResultMenuAndClickItem(window, "manage", { + resultIndex, + }); + await onManagePageLoaded; + Assert.equal( + browser.currentURI.spec, + managePage, + "The manage page is loaded" + ); + + await UrlbarTestUtils.promisePopupClose(window); + }); +}); + +// Test for simple UI. +add_tasks_with_rust(async function simpleUI() { + const testData = [ + { + weatherSimpleUI: true, + expectedSummary: + MerinoTestUtils.WEATHER_SUGGESTION.current_conditions.summary, + }, + { + weatherSimpleUI: false, + expectedSummary: `${MerinoTestUtils.WEATHER_SUGGESTION.current_conditions.summary}; ${MerinoTestUtils.WEATHER_SUGGESTION.forecast.summary}`, + }, + ]; + + for (let { weatherSimpleUI, expectedSummary } of testData) { + let nimbusCleanup = await UrlbarTestUtils.initNimbusFeature({ + weatherSimpleUI, + }); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: MerinoTestUtils.WEATHER_KEYWORD, + }); + + let resultIndex = 1; + let details = await UrlbarTestUtils.getDetailsOfResultAt( + window, + resultIndex + ); + assertIsWeatherResult(details.result, true); + + let { row } = details.element; + let summary = row.querySelector(".urlbarView-dynamic-weather-summaryText"); + + // `getViewUpdate()` is allowed to be async and `UrlbarView` awaits it even + // though the `Weather` implementation is not async. That means the summary + // text content will be updated asyncly, so we need to wait for it. + await TestUtils.waitForCondition( + () => summary.textContent == expectedSummary, + "Waiting for the row's summary text to be updated" + ); + Assert.equal( + summary.textContent, + expectedSummary, + "The summary text should be correct" + ); + + await UrlbarTestUtils.promisePopupClose(window); + await nimbusCleanup(); + } +}); + function assertIsWeatherResult(result, isWeatherResult) { let provider = UrlbarPrefs.get("quickSuggestRustEnabled") ? UrlbarProviderQuickSuggest diff --git a/browser/components/urlbar/tests/quicksuggest/browser/head.js b/browser/components/urlbar/tests/quicksuggest/browser/head.js index 7d62a44d45..cc5f449e94 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/head.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/head.js @@ -636,20 +636,49 @@ function _assertGleanPing(ping) { } } +let gAddTasksWithRustSetup; + /** * Adds two tasks: One with the Rust backend disabled and one with it enabled. * The names of the task functions will be the name of the passed-in task - * function appended with "_rustDisabled" and "_rustEnabled" respectively. Call - * with the usual `add_task()` arguments. + * function appended with "_rustDisabled" and "_rustEnabled". If the passed-in + * task doesn't have a name, "anonymousTask" will be used. + * + * Call this with the usual `add_task()` arguments. Additionally, an object with + * the following properties can be specified as any argument: + * + * {boolean} skip_if_rust_enabled + * If true, a "_rustEnabled" task won't be added. Useful when Rust is enabled + * by default but the task doesn't make sense with Rust and you still want to + * test some behavior when Rust is disabled. * * @param {...any} args * The usual `add_task()` arguments. */ function add_tasks_with_rust(...args) { + let skipIfRustEnabled = false; + let i = args.findIndex(a => a.skip_if_rust_enabled); + if (i >= 0) { + skipIfRustEnabled = true; + args.splice(i, 1); + } + let taskFnIndex = args.findIndex(a => typeof a == "function"); let taskFn = args[taskFnIndex]; for (let rustEnabled of [false, true]) { + let newTaskName = + (taskFn.name || "anonymousTask") + + (rustEnabled ? "_rustEnabled" : "_rustDisabled"); + + if (rustEnabled && skipIfRustEnabled) { + info( + "add_tasks_with_rust: Skipping due to skip_if_rust_enabled: " + + newTaskName + ); + continue; + } + let newTaskFn = async (...taskFnArgs) => { info("add_tasks_with_rust: Setting rustEnabled: " + rustEnabled); UrlbarPrefs.set("quicksuggest.rustEnabled", rustEnabled); @@ -660,12 +689,30 @@ function add_tasks_with_rust(...args) { await QuickSuggestTestUtils.forceSync(); info("add_tasks_with_rust: Done forcing sync"); + if (gAddTasksWithRustSetup) { + info("add_tasks_with_rust: Calling setup function"); + await gAddTasksWithRustSetup(); + info("add_tasks_with_rust: Done calling setup function"); + } + let rv; try { info( "add_tasks_with_rust: Calling original task function: " + taskFn.name ); rv = await taskFn(...taskFnArgs); + } catch (e) { + // Clearly report any unusual errors to make them easier to spot and to + // make the flow of the test clearer. The harness throws NS_ERROR_ABORT + // when a normal assertion fails, so don't report that. + if (e.result != Cr.NS_ERROR_ABORT) { + Assert.ok( + false, + "add_tasks_with_rust: The original task function threw an error: " + + e + ); + } + throw e; } finally { info( "add_tasks_with_rust: Done calling original task function: " + @@ -683,11 +730,32 @@ function add_tasks_with_rust(...args) { return rv; }; - Object.defineProperty(newTaskFn, "name", { - value: taskFn.name + (rustEnabled ? "_rustEnabled" : "_rustDisabled"), - }); - let addTaskArgs = [...args]; - addTaskArgs[taskFnIndex] = newTaskFn; + Object.defineProperty(newTaskFn, "name", { value: newTaskName }); + + let addTaskArgs = []; + for (let j = 0; j < args.length; j++) { + addTaskArgs[j] = + j == taskFnIndex + ? newTaskFn + : Cu.cloneInto(args[j], this, { cloneFunctions: true }); + } add_task(...addTaskArgs); } } + +/** + * Registers a setup function that `add_tasks_with_rust()` will await before + * calling each of your original tasks. Call this at most once in your test file + * (i.e., in `add_setup()`). This is useful when enabling/disabling Rust has + * side effects related to your particular test that need to be handled or + * awaited for each of your tasks. On the other hand, if only one or two of your + * tasks need special setup, do it directly in those tasks instead of using + * this. The passed-in `setupFn` is automatically unregistered on cleanup. + * + * @param {Function} setupFn + * A function that will be awaited before your original tasks are called. + */ +function registerAddTasksWithRustSetup(setupFn) { + gAddTasksWithRustSetup = setupFn; + registerCleanupFunction(() => (gAddTasksWithRustSetup = null)); +} diff --git a/browser/components/urlbar/tests/quicksuggest/unit/head.js b/browser/components/urlbar/tests/quicksuggest/unit/head.js index c468e4526f..73bedf468e 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/head.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/head.js @@ -20,18 +20,49 @@ add_setup(async function setUpQuickSuggestXpcshellTest() { UrlbarPrefs._testSkipTelemetryEnvironmentInit = true; }); +let gAddTasksWithRustSetup; + /** * Adds two tasks: One with the Rust backend disabled and one with it enabled. * The names of the task functions will be the name of the passed-in task * function appended with "_rustDisabled" and "_rustEnabled". If the passed-in - * task doesn't have a name, "anonymousTask" will be used. Call this with the - * usual `add_task()` arguments. + * task doesn't have a name, "anonymousTask" will be used. + * + * Call this with the usual `add_task()` arguments. Additionally, an object with + * the following properties can be specified as any argument: + * + * {boolean} skip_if_rust_enabled + * If true, a "_rustEnabled" task won't be added. Useful when Rust is enabled + * by default but the task doesn't make sense with Rust and you still want to + * test some behavior when Rust is disabled. + * + * @param {...any} args + * The usual `add_task()` arguments. */ function add_tasks_with_rust(...args) { + let skipIfRustEnabled = false; + let i = args.findIndex(a => a.skip_if_rust_enabled); + if (i >= 0) { + skipIfRustEnabled = true; + args.splice(i, 1); + } + let taskFnIndex = args.findIndex(a => typeof a == "function"); let taskFn = args[taskFnIndex]; for (let rustEnabled of [false, true]) { + let newTaskName = + (taskFn.name || "anonymousTask") + + (rustEnabled ? "_rustEnabled" : "_rustDisabled"); + + if (rustEnabled && skipIfRustEnabled) { + info( + "add_tasks_with_rust: Skipping due to skip_if_rust_enabled: " + + newTaskName + ); + continue; + } + let newTaskFn = async (...taskFnArgs) => { info("add_tasks_with_rust: Setting rustEnabled: " + rustEnabled); UrlbarPrefs.set("quicksuggest.rustEnabled", rustEnabled); @@ -42,6 +73,12 @@ function add_tasks_with_rust(...args) { await QuickSuggestTestUtils.forceSync(); info("add_tasks_with_rust: Done forcing sync"); + if (gAddTasksWithRustSetup) { + info("add_tasks_with_rust: Calling setup function"); + await gAddTasksWithRustSetup(); + info("add_tasks_with_rust: Done calling setup function"); + } + let rv; try { info( @@ -77,17 +114,35 @@ function add_tasks_with_rust(...args) { return rv; }; - Object.defineProperty(newTaskFn, "name", { - value: - (taskFn.name || "anonymousTask") + - (rustEnabled ? "_rustEnabled" : "_rustDisabled"), - }); - let addTaskArgs = [...args]; - addTaskArgs[taskFnIndex] = newTaskFn; + Object.defineProperty(newTaskFn, "name", { value: newTaskName }); + + let addTaskArgs = []; + for (let j = 0; j < args.length; j++) { + addTaskArgs[j] = + j == taskFnIndex + ? newTaskFn + : Cu.cloneInto(args[j], this, { cloneFunctions: true }); + } add_task(...addTaskArgs); } } +/** + * Registers a setup function that `add_tasks_with_rust()` will await before + * calling each of your original tasks. Call this at most once in your test file + * (i.e., in `add_setup()`). This is useful when enabling/disabling Rust has + * side effects related to your particular test that need to be handled or + * awaited for each of your tasks. On the other hand, if only one or two of your + * tasks need special setup, do it directly in those tasks instead of using + * this. + * + * @param {Function} setupFn + * A function that will be awaited before your original tasks are called. + */ +function registerAddTasksWithRustSetup(setupFn) { + gAddTasksWithRustSetup = setupFn; +} + /** * Returns an expected Wikipedia (non-sponsored) result that can be passed to * `check_results()` regardless of whether the Rust backend is enabled. @@ -106,7 +161,7 @@ function makeWikipediaResult({ iconBlob = new Blob([new Uint8Array([])]), impressionUrl = "http://example.com/wikipedia-impression", clickUrl = "http://example.com/wikipedia-click", - blockId = 1, + blockId = 2, advertiser = "Wikipedia", iabCategory = "5 - Education", suggestedIndex = -1, @@ -362,7 +417,6 @@ function makeWeatherResult({ temperatureUnit, url: MerinoTestUtils.WEATHER_SUGGESTION.url, iconId: "6", - helpUrl: QuickSuggest.HELP_URL, requestId: MerinoTestUtils.server.response.body.request_id, source: "merino", provider: "accuweather", @@ -909,3 +963,40 @@ async function doRustProvidersTests({ searchString, tests }) { UrlbarPrefs.clear("quicksuggest.rustEnabled"); await QuickSuggestTestUtils.forceSync(); } + +/** + * Waits for `Weather` to start and finish a new fetch. Typically you call this + * before you know a new fetch will start, save but don't await the promise, do + * the thing that triggers the new fetch, and then await the promise to wait for + * the fetch to finish. + * + * If a fetch is currently ongoing, this will first wait for a new fetch to + * start, which might not be what you want. If you only want to wait for any + * ongoing fetch to finish, await `QuickSuggest.weather.fetchPromise` instead. + */ +async function waitForNewWeatherFetch() { + let { fetchPromise: oldFetchPromise } = QuickSuggest.weather; + + // Wait for a new fetch to start. + let newFetchPromise; + await TestUtils.waitForCondition(() => { + let { fetchPromise } = QuickSuggest.weather; + if ( + (oldFetchPromise && fetchPromise != oldFetchPromise) || + (!oldFetchPromise && fetchPromise) + ) { + newFetchPromise = fetchPromise; + return true; + } + return false; + }, "Waiting for a new weather fetch to start"); + + Assert.equal( + QuickSuggest.weather.fetchPromise, + newFetchPromise, + "Sanity check: fetchPromise hasn't changed since waitForCondition returned" + ); + + // Wait for the new fetch to finish. + await newFetchPromise; +} diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js index e4c145aabb..59c0b26241 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest.js @@ -18,6 +18,8 @@ const HTTP_SEARCH_STRING = "http prefix"; const HTTPS_SEARCH_STRING = "https prefix"; const PREFIX_SUGGESTIONS_STRIPPED_URL = "example.com/prefix-test"; +const ONE_CHAR_SEARCH_STRINGS = ["x", "x ", " x", " x "]; + const { TIMESTAMP_TEMPLATE, TIMESTAMP_LENGTH } = QuickSuggest; const TIMESTAMP_SEARCH_STRING = "timestamp"; const TIMESTAMP_SUGGESTION_URL = `http://example.com/timestamp-${TIMESTAMP_TEMPLATE}`; @@ -69,6 +71,11 @@ const REMOTE_SETTINGS_RESULTS = [ iab_category: "22 - Shopping", icon: "1234", }, + QuickSuggestTestUtils.ampRemoteSettings({ + keywords: [...ONE_CHAR_SEARCH_STRINGS, "12", "a longer keyword"], + title: "Suggestion with 1-char keyword", + url: "http://example.com/1-char-keyword", + }), ]; function expectedNonSponsoredResult() { @@ -751,10 +758,10 @@ async function doDedupeAgainstURLTest({ } // Tests the remote settings latency histogram. -add_task( +add_tasks_with_rust( { // Not supported by the Rust backend. - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function latencyTelemetry() { UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true); @@ -791,10 +798,10 @@ add_task( // Tests setup and teardown of the remote settings client depending on whether // quick suggest is enabled. -add_task( +add_tasks_with_rust( { // Not supported by the Rust backend. - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function setupAndTeardown() { Assert.ok( @@ -885,7 +892,7 @@ add_task( "Remote settings backend is disabled after enabling the Rust backend" ); - UrlbarPrefs.clear("quicksuggest.rustEnabled"); + UrlbarPrefs.set("quicksuggest.rustEnabled", false); Assert.ok( QuickSuggest.jsBackend.rs, "Settings client is non-null after disabling the Rust backend" @@ -898,6 +905,7 @@ add_task( // Leave the prefs in the same state as when the task started. UrlbarPrefs.clear("suggest.quicksuggest.nonsponsored"); UrlbarPrefs.clear("suggest.quicksuggest.sponsored"); + UrlbarPrefs.clear("quicksuggest.rustEnabled"); UrlbarPrefs.set("quicksuggest.enabled", true); Assert.ok( !QuickSuggest.jsBackend.rs, @@ -1349,10 +1357,10 @@ add_tasks_with_rust(async function block_timestamp() { // Makes sure remote settings data is fetched using the correct `type` based on // the value of the `quickSuggestRemoteSettingsDataType` Nimbus variable. -add_task( +add_tasks_with_rust( { // Not supported by the Rust backend. - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function remoteSettingsDataType() { UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); @@ -1509,7 +1517,7 @@ add_tasks_with_rust(async function tabToSearch() { // search heuristic makeSearchResult(context, { engineName: Services.search.defaultEngine.name, - engineIconUri: Services.search.defaultEngine.getIconURL(), + engineIconUri: await Services.search.defaultEngine.getIconURL(), heuristic: true, }), // tab to search @@ -1595,7 +1603,7 @@ add_tasks_with_rust(async function position() { // search heuristic makeSearchResult(context, { engineName: Services.search.defaultEngine.name, - engineIconUri: Services.search.defaultEngine.getIconURL(), + engineIconUri: await Services.search.defaultEngine.getIconURL(), heuristic: true, }), // best match whose backing suggestion has a `position` @@ -1659,3 +1667,35 @@ add_task(async function rustProviders() { ], }); }); + +// Tests the keyword/search-string-length threshold. Keywords/search strings +// must be at least two characters long to be matched. +add_tasks_with_rust(async function keywordLengthThreshold() { + UrlbarPrefs.set("suggest.quicksuggest.sponsored", true); + await QuickSuggestTestUtils.forceSync(); + + let tests = [ + ...ONE_CHAR_SEARCH_STRINGS.map(keyword => ({ keyword, expected: false })), + { keyword: "12", expected: true }, + { keyword: "a longer keyword", expected: true }, + ]; + + for (let { keyword, expected } of tests) { + await check_results({ + context: createContext(keyword, { + providers: [UrlbarProviderQuickSuggest.name], + isPrivate: false, + }), + matches: !expected + ? [] + : [ + makeAmpResult({ + keyword, + title: "Suggestion with 1-char keyword", + url: "http://example.com/1-char-keyword", + originalUrl: "http://example.com/1-char-keyword", + }), + ], + }); + } +}); diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js index c17f3f1655..806685eff7 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_addons.js @@ -42,7 +42,7 @@ const REMOTE_SETTINGS_RESULTS = [ icon: "https://example.com/first-addon.svg", title: "First Addon", rating: "4.7", - keywords: ["first", "1st", "two words", "a b c"], + keywords: ["first", "1st", "two words", "aa b c"], description: "Description for the First Addon", number_of_ratings: 1256, score: 0.25, @@ -353,35 +353,35 @@ add_tasks_with_rust(async function remoteSettings() { }), }, { - input: "a", + input: "aa", expected: makeExpectedResult({ suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], source: "remote-settings", }), }, { - input: "a ", + input: "aa ", expected: makeExpectedResult({ suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], source: "remote-settings", }), }, { - input: "a b", + input: "aa b", expected: makeExpectedResult({ suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], source: "remote-settings", }), }, { - input: "a b ", + input: "aa b ", expected: makeExpectedResult({ suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], source: "remote-settings", }), }, { - input: "a b c", + input: "aa b c", expected: makeExpectedResult({ suggestion: REMOTE_SETTINGS_RESULTS[0].attachment[0], source: "remote-settings", diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js index 29133a8579..79b4df2b77 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js @@ -214,9 +214,9 @@ add_tasks_with_rust(async function lowPrefixes() { // starting at "how to" instead of the first word. // // Note: The Rust implementation doesn't support this. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function lowPrefixes_howTo() { // search string -> should match diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js b/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js index e6ec61bcd4..f2e3f96c1f 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_rust_ingest.js @@ -78,11 +78,15 @@ add_task(async function firstRun() { await checkSuggestions(); - // Disable and re-enable the backend. No new ingestion should start - // immediately since this isn't the first time the backend has been enabled. + // Disable and re-enable the backend. Another ingest should start immediately + // since ingest is done every time the backend is re-enabled. UrlbarPrefs.set("quicksuggest.rustEnabled", false); UrlbarPrefs.set("quicksuggest.rustEnabled", true); - await assertNoNewIngestStarted(ingestPromise); + ({ ingestPromise } = await waitForIngestStart(ingestPromise)); + + info("Awaiting ingest promise"); + await ingestPromise; + info("Done awaiting ingest promise"); await checkSuggestions(); @@ -101,14 +105,18 @@ add_task(async function interval() { "Sanity check: Rust backend is initially disabled" ); - // Set a small interval and enable the backend. No new ingestion should start - // immediately since this isn't the first time the backend has been enabled. + // Set a small interval and enable the backend. A new ingest will immediately + // start. let intervalSecs = 1; UrlbarPrefs.set("quicksuggest.rustIngestIntervalSeconds", intervalSecs); UrlbarPrefs.set("quicksuggest.rustEnabled", true); - await assertNoNewIngestStarted(ingestPromise); + ({ ingestPromise } = await waitForIngestStart(ingestPromise)); - // Wait for a few ingests to happen. + info("Awaiting ingest promise"); + await ingestPromise; + info("Done awaiting ingest promise"); + + // Wait for a few ingests to happen due to the timer firing. for (let i = 0; i < 3; i++) { info("Preparing for ingest at index " + i); @@ -193,17 +201,6 @@ async function waitForIngestStart(oldIngestPromise) { return { ingestPromise: newIngestPromise }; } -async function assertNoNewIngestStarted(oldIngestPromise) { - for (let i = 0; i < 3; i++) { - await TestUtils.waitForTick(); - } - Assert.equal( - QuickSuggest.rustBackend.ingestPromise, - oldIngestPromise, - "No new ingest started" - ); -} - async function checkSuggestions(expected = [REMOTE_SETTINGS_SUGGESTION]) { let actual = await QuickSuggest.rustBackend.query("amp"); Assert.deepEqual( diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js b/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js index 28801904a1..cd794f435b 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js @@ -34,6 +34,15 @@ add_setup(async () => { // `lastFetchTimeMs` is expected to be `fetchDelayAfterComingOnlineMs`, we can // be sure the value actually came from `fetchDelayAfterComingOnlineMs`. QuickSuggest.weather._test_fetchDelayAfterComingOnlineMs = 53; + + // When `add_tasks_with_rust()` disables the Rust backend and forces sync, the + // JS backend will sync `Weather` with remote settings. Since keywords are + // present in remote settings at that point (we added them above), `Weather` + // will then start fetching. The fetch may or may not be done before our test + // task starts. To make sure it's done, queue another fetch and await it. + registerAddTasksWithRustSetup(async () => { + await QuickSuggest.weather._test_fetch(); + }); }); // The feature should be properly uninitialized when it's disabled and then @@ -52,17 +61,15 @@ add_tasks_with_rust(async function disableAndEnable_suggestPref() { async function doBasicDisableAndEnableTest(pref) { // Sanity check initial state. - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); // Disable the feature. It should be immediately uninitialized. UrlbarPrefs.set(pref, false); assertDisabled({ message: "After disabling", - pendingFetchCount: 0, }); // No suggestion should be returned for a search. @@ -83,19 +90,17 @@ async function doBasicDisableAndEnableTest(pref) { // Re-enable the feature. It should be immediately initialized and a fetch // should start. info("Re-enable the feature"); - let fetchPromise = QuickSuggest.weather.waitForFetches(); + let fetchPromise = waitForNewWeatherFetch(); UrlbarPrefs.set(pref, true); - assertEnabled({ + await assertEnabled({ message: "Immediately after re-enabling", hasSuggestion: false, - pendingFetchCount: 1, }); await fetchPromise; - assertEnabled({ + await assertEnabled({ message: "After awaiting fetch", hasSuggestion: true, - pendingFetchCount: 0, }); Assert.equal( @@ -126,16 +131,15 @@ async function doBasicDisableAndEnableTest(pref) { // This task is only appropriate for the JS backend, not Rust, since fetching is // always active with Rust. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function keywordsNotDefined() { // Sanity check initial state. - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); // Set RS data without any keywords. Fetching should immediately stop. @@ -147,7 +151,6 @@ add_task( ]); assertDisabled({ message: "After setting RS data without keywords", - pendingFetchCount: 0, }); // No suggestion should be returned for a search. @@ -162,24 +165,22 @@ add_task( // Set keywords. Fetching should immediately start. info("Setting keywords"); - let fetchPromise = QuickSuggest.weather.waitForFetches(); + let fetchPromise = waitForNewWeatherFetch(); await QuickSuggestTestUtils.setRemoteSettingsRecords([ { type: "weather", weather: MerinoTestUtils.WEATHER_RS_DATA, }, ]); - assertEnabled({ + await assertEnabled({ message: "Immediately after setting keywords", hasSuggestion: false, - pendingFetchCount: 1, }); await fetchPromise; - assertEnabled({ + await assertEnabled({ message: "After awaiting fetch", hasSuggestion: true, - pendingFetchCount: 0, }); Assert.equal( @@ -211,27 +212,24 @@ add_task( // it should be discarded since the feature is disabled. add_tasks_with_rust(async function disableAndEnable_immediate1() { // Sanity check initial state. - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); // Disable the feature. It should be immediately uninitialized. UrlbarPrefs.set("weather.featureGate", false); assertDisabled({ message: "After disabling", - pendingFetchCount: 0, }); // Re-enable the feature. It should be immediately initialized and a fetch // should start. - let fetchPromise = QuickSuggest.weather.waitForFetches(); + let fetchPromise = waitForNewWeatherFetch(); UrlbarPrefs.set("weather.featureGate", true); - assertEnabled({ + await assertEnabled({ message: "Immediately after re-enabling", hasSuggestion: false, - pendingFetchCount: 1, }); // Disable it again. The fetch will remain ongoing since pending fetches @@ -239,7 +237,6 @@ add_tasks_with_rust(async function disableAndEnable_immediate1() { UrlbarPrefs.set("weather.featureGate", false); assertDisabled({ message: "After disabling again", - pendingFetchCount: 1, }); // Wait for the fetch to finish. @@ -249,21 +246,19 @@ add_tasks_with_rust(async function disableAndEnable_immediate1() { // uninitialized. assertDisabled({ message: "After awaiting fetch", - pendingFetchCount: 0, }); // Clean up by re-enabling the feature for the remaining tasks. - fetchPromise = QuickSuggest.weather.waitForFetches(); + fetchPromise = waitForNewWeatherFetch(); UrlbarPrefs.set("weather.featureGate", true); await fetchPromise; // Wait for keywords to be re-synced from remote settings. await QuickSuggestTestUtils.forceSync(); - assertEnabled({ + await assertEnabled({ message: "On cleanup", hasSuggestion: true, - pendingFetchCount: 0, }); }); @@ -279,26 +274,23 @@ add_tasks_with_rust(async function disableAndEnable_immediate1() { // from step 2 should be discarded. add_tasks_with_rust(async function disableAndEnable_immediate2() { // Sanity check initial state. - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); // Disable the feature. It should be immediately uninitialized. UrlbarPrefs.set("weather.featureGate", false); assertDisabled({ message: "After disabling", - pendingFetchCount: 0, }); // Re-enable the feature. It should be immediately initialized and a fetch // should start. UrlbarPrefs.set("weather.featureGate", true); - assertEnabled({ + await assertEnabled({ message: "Immediately after re-enabling", hasSuggestion: false, - pendingFetchCount: 1, }); // Disable it again. The fetch will remain ongoing since pending fetches @@ -306,25 +298,21 @@ add_tasks_with_rust(async function disableAndEnable_immediate2() { UrlbarPrefs.set("weather.featureGate", false); assertDisabled({ message: "After disabling again", - pendingFetchCount: 1, }); - // Re-enable it. A new fetch should start, so now there will be two pending - // fetches. - let fetchPromise = QuickSuggest.weather.waitForFetches(); + // Re-enable it. A new fetch should start. + let fetchPromise = waitForNewWeatherFetch(); UrlbarPrefs.set("weather.featureGate", true); - assertEnabled({ + await assertEnabled({ message: "Immediately after re-enabling again", hasSuggestion: false, - pendingFetchCount: 2, }); - // Wait for both fetches to finish. + // Wait for it to finish. await fetchPromise; - assertEnabled({ + await assertEnabled({ message: "Immediately after re-enabling again", hasSuggestion: true, - pendingFetchCount: 0, }); // Wait for keywords to be re-synced from remote settings. @@ -334,10 +322,9 @@ add_tasks_with_rust(async function disableAndEnable_immediate2() { // A fetch that doesn't return a suggestion should cause the last-fetched // suggestion to be discarded. add_tasks_with_rust(async function noSuggestion() { - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); let histograms = MerinoTestUtils.getAndClearHistograms({ @@ -350,10 +337,9 @@ add_tasks_with_rust(async function noSuggestion() { await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "After fetch", hasSuggestion: false, - pendingFetchCount: 0, }); Assert.equal( QuickSuggest.weather._test_merino.lastFetchStatus, @@ -381,19 +367,17 @@ add_tasks_with_rust(async function noSuggestion() { // Clean up by forcing another fetch so the suggestion is non-null for the // remaining tasks. await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "On cleanup", hasSuggestion: true, - pendingFetchCount: 0, }); }); // A network error should cause the last-fetched suggestion to be discarded. add_tasks_with_rust(async function networkError() { - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); let histograms = MerinoTestUtils.getAndClearHistograms({ @@ -411,10 +395,9 @@ add_tasks_with_rust(async function networkError() { QuickSuggest.weather._test_setTimeoutMs(-1); - assertEnabled({ + await assertEnabled({ message: "After fetch", hasSuggestion: false, - pendingFetchCount: 0, }); Assert.equal( QuickSuggest.weather._test_merino.lastFetchStatus, @@ -440,19 +423,17 @@ add_tasks_with_rust(async function networkError() { // Clean up by forcing another fetch so the suggestion is non-null for the // remaining tasks. await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "On cleanup", hasSuggestion: true, - pendingFetchCount: 0, }); }); // An HTTP error should cause the last-fetched suggestion to be discarded. add_tasks_with_rust(async function httpError() { - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); let histograms = MerinoTestUtils.getAndClearHistograms({ @@ -463,10 +444,9 @@ add_tasks_with_rust(async function httpError() { MerinoTestUtils.server.response = { status: 500 }; await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "After fetch", hasSuggestion: false, - pendingFetchCount: 0, }); Assert.equal( QuickSuggest.weather._test_merino.lastFetchStatus, @@ -494,20 +474,18 @@ add_tasks_with_rust(async function httpError() { MerinoTestUtils.server.reset(); MerinoTestUtils.server.response.body.suggestions = [WEATHER_SUGGESTION]; await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "On cleanup", hasSuggestion: true, - pendingFetchCount: 0, }); }); // A fetch that doesn't return a suggestion due to a client timeout should cause // the last-fetched suggestion to be discarded. add_tasks_with_rust(async function clientTimeout() { - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); let histograms = MerinoTestUtils.getAndClearHistograms({ @@ -528,10 +506,9 @@ add_tasks_with_rust(async function clientTimeout() { await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "After fetch", hasSuggestion: false, - pendingFetchCount: 0, }); Assert.equal( QuickSuggest.weather._test_merino.lastFetchStatus, @@ -573,10 +550,9 @@ add_tasks_with_rust(async function clientTimeout() { // Clean up by forcing another fetch so the suggestion is non-null for the // remaining tasks. await QuickSuggest.weather._test_fetch(); - assertEnabled({ + await assertEnabled({ message: "On cleanup", hasSuggestion: true, - pendingFetchCount: 0, }); }); @@ -662,10 +638,9 @@ async function doLocaleTest({ shouldRunTask, osUnit, unitsByLocale }) { return; } - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); // Sanity check initial locale info. @@ -716,10 +691,9 @@ async function doLocaleTest({ shouldRunTask, osUnit, unitsByLocale }) { // Blocks a result and makes sure the weather pref is disabled. add_tasks_with_rust(async function block() { // Sanity check initial state. - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); Assert.ok( UrlbarPrefs.get("suggest.weather"), @@ -775,17 +749,16 @@ add_tasks_with_rust(async function block() { }); // Re-enable the pref and clean up. - let fetchPromise = QuickSuggest.weather.waitForFetches(); + let fetchPromise = waitForNewWeatherFetch(); UrlbarPrefs.set("suggest.weather", true); await fetchPromise; // Wait for keywords to be re-synced from remote settings. await QuickSuggestTestUtils.forceSync(); - assertEnabled({ + await assertEnabled({ message: "On cleanup", hasSuggestion: true, - pendingFetchCount: 0, }); }); @@ -849,15 +822,11 @@ async function doWakeTest({ // Advance the clock and simulate wake. info("Sending wake notification"); + let fetchPromise = waitForNewWeatherFetch(); let nowOnWake = nowOnStart + sleepIntervalMs; dateNowStub.returns(nowOnWake); QuickSuggest.weather.observe(null, "wake_notification", ""); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "After wake, next fetch should not have immediately started" - ); Assert.equal( QuickSuggest.weather._test_lastFetchTimeMs, nowOnStart, @@ -891,18 +860,13 @@ async function doWakeTest({ // Wait for the fetch. If the wake didn't trigger it, then the caller should // have passed in a `sleepIntervalMs` that will make it start soon. info("Waiting for fetch after wake"); - await QuickSuggest.weather.waitForFetches(); + await fetchPromise; Assert.equal( QuickSuggest.weather._test_fetchTimerMs, QuickSuggest.weather._test_fetchIntervalMs, "After post-wake fetch, timer period should remain full fetch interval" ); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "After post-wake fetch, no more fetches should be pending" - ); dateNowStub.restore(); } @@ -957,11 +921,6 @@ async function doOnlineTestWithSuggestion({ topic, dataValues }) { info("Sending notification: " + JSON.stringify({ topic, data })); QuickSuggest.weather.observe(null, topic, data); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "Fetch should not have started" - ); Assert.equal( QuickSuggest.weather._test_fetchTimer, timer, @@ -1032,11 +991,6 @@ async function doOnlineTestWithNullSuggestion({ !QuickSuggest.weather.suggestion, "Suggestion should remain null" ); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "Fetch should not have started" - ); Assert.equal( QuickSuggest.weather._test_fetchTimer, timer, @@ -1055,13 +1009,9 @@ async function doOnlineTestWithNullSuggestion({ Assert.ok(!QuickSuggest.weather.suggestion, "Suggestion should be null"); info("Sending notification: " + JSON.stringify({ topic, data })); + let fetchPromise = waitForNewWeatherFetch(); QuickSuggest.weather.observe(null, topic, data); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "Fetch should not have started yet" - ); Assert.notEqual( QuickSuggest.weather._test_fetchTimer, 0, @@ -1081,13 +1031,8 @@ async function doOnlineTestWithNullSuggestion({ timer = QuickSuggest.weather._test_fetchTimer; info("Waiting for fetch after notification"); - await QuickSuggest.weather.waitForFetches(); + await fetchPromise; - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "Fetch should not be pending" - ); Assert.notEqual( QuickSuggest.weather._test_fetchTimer, 0, @@ -1164,20 +1109,23 @@ async function doManyNotificationsTest(notifications) { MerinoTestUtils.WEATHER_SUGGESTION, ]; + let { fetchPromise: oldFetchPromise } = QuickSuggest.weather; + let fetchPromise = waitForNewWeatherFetch(); + // Send the notifications. for (let [topic, data] of notifications) { info("Sending notification: " + JSON.stringify({ topic, data })); QuickSuggest.weather.observe(null, topic, data); + Assert.equal( + QuickSuggest.weather.fetchPromise, + oldFetchPromise, + "No new fetch should have started yet" + ); } info("Waiting for fetch after notifications"); - await QuickSuggest.weather.waitForFetches(); + await fetchPromise; - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 0, - "Fetch should not be pending" - ); Assert.notEqual( QuickSuggest.weather._test_fetchTimer, 0, @@ -1241,7 +1189,7 @@ add_tasks_with_rust(async function vpn() { // Simulate the link status changing. Since the mock link service still // indicates a VPN is detected, the suggestion should remain null. - let fetchPromise = QuickSuggest.weather.waitForFetches(); + let fetchPromise = waitForNewWeatherFetch(); QuickSuggest.weather.observe(null, "network:link-status-changed", "changed"); await fetchPromise; Assert.ok(!QuickSuggest.weather.suggestion, "Suggestion should remain null"); @@ -1251,7 +1199,7 @@ add_tasks_with_rust(async function vpn() { Ci.nsINetworkLinkService.NONE_DETECTED; // Simulate the link status changing again. The suggestion should be fetched. - fetchPromise = QuickSuggest.weather.waitForFetches(); + fetchPromise = waitForNewWeatherFetch(); QuickSuggest.weather.observe(null, "network:link-status-changed", "changed"); await fetchPromise; Assert.ok(QuickSuggest.weather.suggestion, "Suggestion should be fetched"); @@ -1264,10 +1212,9 @@ add_tasks_with_rust(async function vpn() { // weather record. add_tasks_with_rust(async function nimbusOverride() { // Sanity check initial state. - assertEnabled({ + await assertEnabled({ message: "Sanity check initial state", hasSuggestion: true, - pendingFetchCount: 0, }); let defaultResult = makeWeatherResult(); @@ -1349,7 +1296,7 @@ add_tasks_with_rust(async function nimbusOverride() { }); }); -function assertEnabled({ message, hasSuggestion, pendingFetchCount }) { +async function assertEnabled({ message, hasSuggestion }) { info("Asserting feature is enabled"); if (message) { info(message); @@ -1360,20 +1307,20 @@ function assertEnabled({ message, hasSuggestion, pendingFetchCount }) { hasSuggestion, "Suggestion is null or non-null as expected" ); + Assert.ok(QuickSuggest.weather._test_merino, "Merino client is non-null"); + + await TestUtils.waitForCondition( + () => QuickSuggest.weather._test_fetchTimer, + "Waiting for fetch timer to become non-zero" + ); Assert.notEqual( QuickSuggest.weather._test_fetchTimer, 0, "Fetch timer is non-zero" ); - Assert.ok(QuickSuggest.weather._test_merino, "Merino client is non-null"); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - pendingFetchCount, - "Expected pending fetch count" - ); } -function assertDisabled({ message, pendingFetchCount }) { +function assertDisabled({ message }) { info("Asserting feature is disabled"); if (message) { info(message); @@ -1394,9 +1341,4 @@ function assertDisabled({ message, pendingFetchCount }) { null, "Merino client is null" ); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - pendingFetchCount, - "Expected pending fetch count" - ); } diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js b/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js index efa5922c3e..559e0cc1fa 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js @@ -23,6 +23,15 @@ add_setup(async () => { prefs: [["suggest.quicksuggest.nonsponsored", true]], }); await MerinoTestUtils.initWeather(); + + // When `add_tasks_with_rust()` disables the Rust backend and forces sync, the + // JS backend will sync `Weather` with remote settings. Since keywords are + // present in remote settings at that point (we added them above), `Weather` + // will then start fetching. The fetch may or may not be done before our test + // task starts. To make sure it's done, queue another fetch and await it. + registerAddTasksWithRustSetup(async () => { + await QuickSuggest.weather._test_fetch(); + }); }); // * Settings data: none @@ -89,9 +98,9 @@ add_tasks_with_rust(async function () { // // JS backend only. The Rust component expects settings data to contain // min_keyword_length. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function () { await doKeywordsTest({ @@ -128,9 +137,9 @@ add_task( // // JS backend only. The Rust component doesn't treat minKeywordLength == 0 as a // special case. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function () { await doKeywordsTest({ @@ -296,9 +305,9 @@ add_tasks_with_rust(async function () { // // JS backend only. The Rust component expects settings data to contain // min_keyword_length. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function () { await doKeywordsTest({ @@ -338,9 +347,9 @@ add_task( // // JS backend only. The Rust component doesn't treat minKeywordLength == 0 as a // special case. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function () { await doKeywordsTest({ @@ -578,9 +587,9 @@ add_tasks_with_rust(async function () { // TODO bug 1879209: This doesn't work with the Rust backend because if // min_keyword_length isn't specified on ingest, the Rust database will retain // the last known good min_keyword_length, which interferes with this task. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function () { await doKeywordsTest({ @@ -618,9 +627,9 @@ add_task( // TODO bug 1879209: This doesn't work with the Rust backend because if // min_keyword_length isn't specified on ingest, the Rust database will retain // the last known good min_keyword_length, which interferes with this task. -add_task( +add_tasks_with_rust( { - skip_if: () => UrlbarPrefs.get("quickSuggestRustEnabled"), + skip_if_rust_enabled: true, }, async function () { await doKeywordsTest({ @@ -817,7 +826,7 @@ async function doKeywordsTest({ !UrlbarPrefs.get("quickSuggestRustEnabled") && (nimbusValues?.weatherKeywords || settingsData?.keywords) ) { - fetchPromise = QuickSuggest.weather.waitForFetches(); + fetchPromise = waitForNewWeatherFetch(); } let nimbusCleanup; @@ -838,7 +847,6 @@ async function doKeywordsTest({ if (fetchPromise) { info("Waiting for fetch"); - assertFetchingStarted({ pendingFetchCount: 1 }); await fetchPromise; info("Got fetch"); } @@ -872,10 +880,10 @@ async function doKeywordsTest({ await nimbusCleanup?.(); - fetchPromise = null; if (!QuickSuggest.weather.suggestion) { - fetchPromise = QuickSuggest.weather.waitForFetches(); + fetchPromise = waitForNewWeatherFetch(); } + await QuickSuggestTestUtils.setRemoteSettingsRecords([ { type: "weather", @@ -905,28 +913,8 @@ async function doMatchingQuickSuggestTest(pref, isSponsored) { let keyword = "test"; let attachment = isSponsored - ? { - id: 1, - url: "http://example.com/amp", - title: "AMP Suggestion", - keywords: [keyword], - click_url: "http://example.com/amp-click", - impression_url: "http://example.com/amp-impression", - advertiser: "Amp", - iab_category: "22 - Shopping", - icon: "1234", - } - : { - id: 2, - url: "http://example.com/wikipedia", - title: "Wikipedia Suggestion", - keywords: [keyword], - click_url: "http://example.com/wikipedia-click", - impression_url: "http://example.com/wikipedia-impression", - advertiser: "Wikipedia", - iab_category: "5 - Education", - icon: "1234", - }; + ? QuickSuggestTestUtils.ampRemoteSettings({ keywords: [keyword] }) + : QuickSuggestTestUtils.wikipediaRemoteSettings({ keywords: [keyword] }); // Add a remote settings result to quick suggest. let oldPrefValue = UrlbarPrefs.get(pref); @@ -943,27 +931,6 @@ async function doMatchingQuickSuggestTest(pref, isSponsored) { ]); // First do a search to verify the quick suggest result matches the keyword. - let payload; - if (!UrlbarPrefs.get("quickSuggestRustEnabled")) { - payload = { - source: "remote-settings", - provider: "AdmWikipedia", - sponsoredImpressionUrl: attachment.impression_url, - sponsoredClickUrl: attachment.click_url, - sponsoredBlockId: attachment.id, - }; - } else { - payload = { - source: "rust", - provider: isSponsored ? "Amp" : "Wikipedia", - }; - if (isSponsored) { - payload.sponsoredImpressionUrl = attachment.impression_url; - payload.sponsoredClickUrl = attachment.click_url; - payload.sponsoredBlockId = attachment.id; - } - } - info("Doing first search for quick suggest result"); await check_results({ context: createContext(keyword, { @@ -971,35 +938,9 @@ async function doMatchingQuickSuggestTest(pref, isSponsored) { isPrivate: false, }), matches: [ - { - type: UrlbarUtils.RESULT_TYPE.URL, - source: UrlbarUtils.RESULT_SOURCE.SEARCH, - heuristic: false, - payload: { - ...payload, - telemetryType: isSponsored ? "adm_sponsored" : "adm_nonsponsored", - qsSuggestion: keyword, - title: attachment.title, - url: attachment.url, - displayUrl: attachment.url.replace(/[/]$/, ""), - originalUrl: attachment.url, - icon: null, - sponsoredAdvertiser: attachment.advertiser, - sponsoredIabCategory: attachment.iab_category, - isSponsored, - descriptionL10n: isSponsored - ? { id: "urlbar-result-action-sponsored" } - : undefined, - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, - isBlockable: true, - blockL10n: { - id: "urlbar-result-menu-dismiss-firefox-suggest", - }, - }, - }, + isSponsored + ? makeAmpResult({ keyword }) + : makeWikipediaResult({ keyword }), ], }); @@ -1398,7 +1339,7 @@ async function doIncrementTest({ !UrlbarPrefs.get("quickSuggestRustEnabled") && (nimbusValues?.weatherKeywords || settingsData?.weather?.keywords) ) { - fetchPromise = QuickSuggest.weather.waitForFetches(); + fetchPromise = waitForNewWeatherFetch(); } let nimbusCleanup; @@ -1419,7 +1360,6 @@ async function doIncrementTest({ if (fetchPromise) { info("Waiting for fetch"); - assertFetchingStarted({ pendingFetchCount: 1 }); await fetchPromise; info("Got fetch"); } @@ -1472,9 +1412,8 @@ async function doIncrementTest({ await nimbusCleanup?.(); - fetchPromise = null; if (!QuickSuggest.weather.suggestion) { - fetchPromise = QuickSuggest.weather.waitForFetches(); + fetchPromise = waitForNewWeatherFetch(); } await QuickSuggestTestUtils.setRemoteSettingsRecords([ { @@ -1485,19 +1424,3 @@ async function doIncrementTest({ UrlbarPrefs.clear("weather.minKeywordLength"); await fetchPromise; } - -function assertFetchingStarted() { - info("Asserting fetching has started"); - - Assert.notEqual( - QuickSuggest.weather._test_fetchTimer, - 0, - "Fetch timer is non-zero" - ); - Assert.ok(QuickSuggest.weather._test_merino, "Merino client is non-null"); - Assert.equal( - QuickSuggest.weather._test_pendingFetchCount, - 1, - "Expected pending fetch count" - ); -} diff --git a/browser/components/urlbar/tests/quicksuggest/unit/xpcshell.toml b/browser/components/urlbar/tests/quicksuggest/unit/xpcshell.toml index ceab478795..1f0e226684 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/xpcshell.toml +++ b/browser/components/urlbar/tests/quicksuggest/unit/xpcshell.toml @@ -49,3 +49,4 @@ skip-if = ["true"] # Bug 1880214 ["test_weather.js"] ["test_weather_keywords.js"] +skip-if = ["verify"] # Bug 1880214 - Takes a very long time due to add_tasks_with_rust() diff --git a/browser/components/urlbar/tests/unit/head.js b/browser/components/urlbar/tests/unit/head.js index 6f78608c94..da96d5704f 100644 --- a/browser/components/urlbar/tests/unit/head.js +++ b/browser/components/urlbar/tests/unit/head.js @@ -1109,26 +1109,6 @@ async function getOriginFrecency(prefix, aHost) { return rows[0].getResultByIndex(0); } -/** - * Returns the origin frecency stats. - * - * @returns {object} - * An object { count, sum, squares }. - */ -async function getOriginFrecencyStats() { - let db = await PlacesUtils.promiseDBConnection(); - let rows = await db.execute(` - SELECT - IFNULL((SELECT value FROM moz_meta WHERE key = 'origin_frecency_count'), 0), - IFNULL((SELECT value FROM moz_meta WHERE key = 'origin_frecency_sum'), 0), - IFNULL((SELECT value FROM moz_meta WHERE key = 'origin_frecency_sum_of_squares'), 0) - `); - let count = rows[0].getResultByIndex(0); - let sum = rows[0].getResultByIndex(1); - let squares = rows[0].getResultByIndex(2); - return { count, sum, squares }; -} - /** * Returns the origin autofill frecency threshold. * @@ -1136,18 +1116,7 @@ async function getOriginFrecencyStats() { * The threshold. */ async function getOriginAutofillThreshold() { - let { count, sum, squares } = await getOriginFrecencyStats(); - if (!count) { - return 0; - } - if (count == 1) { - return sum; - } - let stddevMultiplier = UrlbarPrefs.get("autoFill.stddevMultiplier"); - return ( - sum / count + - stddevMultiplier * Math.sqrt((squares - (sum * sum) / count) / count) - ); + return PlacesUtils.metadata.get("origin_frecency_threshold", 2.0); } /** diff --git a/browser/components/urlbar/tests/unit/test_UrlbarPrefs.js b/browser/components/urlbar/tests/unit/test_UrlbarPrefs.js index d30739f03e..7fb3256f6c 100644 --- a/browser/components/urlbar/tests/unit/test_UrlbarPrefs.js +++ b/browser/components/urlbar/tests/unit/test_UrlbarPrefs.js @@ -363,10 +363,10 @@ add_task(async function onNimbusChanged() { // Add an observer that throws an Error and an observer that does not define // anything to check whether the other observers can get notifications. UrlbarPrefs.addObserver({ - onPrefChanged(pref) { + onPrefChanged() { throw new Error("From onPrefChanged"); }, - onNimbusChanged(pref) { + onNimbusChanged() { throw new Error("From onNimbusChanged"); }, }); @@ -407,10 +407,10 @@ add_task(async function onPrefChanged() { // Add an observer that throws an Error and an observer that does not define // anything to check whether the other observers can get notifications. UrlbarPrefs.addObserver({ - onPrefChanged(pref) { + onPrefChanged() { throw new Error("From onPrefChanged"); }, - onNimbusChanged(pref) { + onNimbusChanged() { throw new Error("From onNimbusChanged"); }, }); diff --git a/browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.js b/browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.js index fe33228007..28cbd381f1 100644 --- a/browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.js +++ b/browser/components/urlbar/tests/unit/test_UrlbarSearchUtils.js @@ -65,14 +65,10 @@ add_task(async function onlyEnabled_option_nomatch() { let domain = engine.searchUrlDomain; let token = domain.substr(0, 1); engine.hideOneOffButton = true; - let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token, { - onlyEnabled: true, - }); + let matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token); Assert.notEqual(matchedEngines[0].searchUrlDomain, domain); engine.hideOneOffButton = false; - matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token, { - onlyEnabled: true, - }); + matchedEngines = await UrlbarSearchUtils.enginesForDomainPrefix(token); Assert.equal(matchedEngines[0].searchUrlDomain, domain); }); @@ -95,7 +91,7 @@ add_task(async function add_search_engine_match() { Assert.ok(matchedEngine); Assert.equal(matchedEngine.searchForm, "https://www.bacon.moz"); Assert.equal(matchedEngine.name, "bacon"); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); info("also type part of the public suffix"); matchedEngine = ( await UrlbarSearchUtils.enginesForDomainPrefix("bacon.m") @@ -103,7 +99,7 @@ add_task(async function add_search_engine_match() { Assert.ok(matchedEngine); Assert.equal(matchedEngine.searchForm, "https://www.bacon.moz"); Assert.equal(matchedEngine.name, "bacon"); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); }); add_task(async function match_multiple_search_engines() { @@ -134,19 +130,19 @@ add_task(async function test_aliased_search_engine_match() { Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "bacon"); Assert.ok(matchedEngine.aliases.includes("pork")); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); // Upper case matchedEngine = await UrlbarSearchUtils.engineForAlias("PORK"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "bacon"); Assert.ok(matchedEngine.aliases.includes("pork")); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); // Cap case matchedEngine = await UrlbarSearchUtils.engineForAlias("Pork"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "bacon"); Assert.ok(matchedEngine.aliases.includes("pork")); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); }); add_task(async function test_aliased_search_engine_match_upper_case_alias() { @@ -164,19 +160,19 @@ add_task(async function test_aliased_search_engine_match_upper_case_alias() { Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "patch"); Assert.ok(matchedEngine.aliases.includes("PR")); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); // Upper case matchedEngine = await UrlbarSearchUtils.engineForAlias("PR"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "patch"); Assert.ok(matchedEngine.aliases.includes("PR")); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); // Cap case matchedEngine = await UrlbarSearchUtils.engineForAlias("Pr"); Assert.ok(matchedEngine); Assert.equal(matchedEngine.name, "patch"); Assert.ok(matchedEngine.aliases.includes("PR")); - Assert.equal(matchedEngine.getIconURL(), null); + Assert.equal(await matchedEngine.getIconURL(), null); }); add_task(async function remove_search_engine_nomatch() { diff --git a/browser/components/urlbar/tests/unit/test_about_urls.js b/browser/components/urlbar/tests/unit/test_about_urls.js index 277ddb8ee1..82a8ef2750 100644 --- a/browser/components/urlbar/tests/unit/test_about_urls.js +++ b/browser/components/urlbar/tests/unit/test_about_urls.js @@ -76,8 +76,10 @@ add_task(async function aboutAboutAndAboutAddons() { heuristic: true, }), makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, uri: "about:addons", title: "about:addons", + iconUri: "page-icon:about:addons", tags: null, providerName: "AboutPages", }), @@ -93,8 +95,10 @@ add_task(async function aboutColonMatchesOnlyAboutPages() { const aboutPageNames = AboutPagesUtils.visibleAboutUrls.slice(0, 9); const aboutPageResults = aboutPageNames.map(aboutPageName => { return makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, uri: aboutPageName, title: aboutPageName, + iconUri: "page-icon:" + aboutPageName, tags: null, providerName: "AboutPages", }); @@ -165,8 +169,10 @@ add_task(async function after_general() { title: "Guide to about:addons in Firefox", }), makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, uri: "about:addons", title: "about:addons", + iconUri: "page-icon:about:addons", tags: null, providerName: "AboutPages", }), diff --git a/browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js b/browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js index 05e3a230f1..7164ae8911 100644 --- a/browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js +++ b/browser/components/urlbar/tests/unit/test_autofill_originsAndQueries.js @@ -893,8 +893,11 @@ add_autofill_task(async function bookmarkBelowThreshold() { await cleanup(); }); -// Bookmarked places should be autofilled when they *do* meet the threshold. +// Bookmarked places should be autofilled also when they meet the threshold. add_autofill_task(async function bookmarkAboveThreshold() { + // Add a visit to the URL, otherwise origin frecency will be too small, note + // it would be filled anyway as bookmarks are always filled. + await PlacesTestUtils.addVisits(["http://" + url]); // Bookmark a URL. await PlacesTestUtils.addBookmarkWithDetails({ uri: "http://" + url, @@ -957,13 +960,10 @@ add_autofill_task(async function zeroThreshold() { ); Assert.equal(placeFrecency, -1); - // Make sure the origin's frecency is 0. let originFrecency = await getOriginFrecency("http://", host); - Assert.equal(originFrecency, 0); - - // Make sure the autofill threshold is 0. + Assert.equal(originFrecency, 1, "Check expected origin's frecency"); let threshold = await getOriginAutofillThreshold(); - Assert.equal(threshold, 0); + Assert.equal(threshold, 1, "Check expected origins threshold"); let context = createContext(search, { isPrivate: false }); await check_results({ diff --git a/browser/components/urlbar/tests/unit/test_heuristic_cancel.js b/browser/components/urlbar/tests/unit/test_heuristic_cancel.js index 6f6f2fbd8a..a78004f2bd 100644 --- a/browser/components/urlbar/tests/unit/test_heuristic_cancel.js +++ b/browser/components/urlbar/tests/unit/test_heuristic_cancel.js @@ -126,7 +126,7 @@ add_task(async function timerIsCancelled() { // Then they will be queued up in a _heuristicProvidersTimer, waiting for // the results from SlowProvider. let resultsAddedPromise = new Promise(resolve => { - let observe = async (subject, topic, data) => { + let observe = async () => { Services.obs.removeObserver(observe, "results-added"); // Fire the second query to cancel the first. await controller.startQuery(secondContext); diff --git a/browser/components/urlbar/tests/unit/test_hideSponsoredHistory.js b/browser/components/urlbar/tests/unit/test_hideSponsoredHistory.js index d49aaf2fb7..79d3e3b17c 100644 --- a/browser/components/urlbar/tests/unit/test_hideSponsoredHistory.js +++ b/browser/components/urlbar/tests/unit/test_hideSponsoredHistory.js @@ -78,7 +78,7 @@ add_task(async function test() { makeSearchResult(context, { heuristic: true, engineName: engine.name, - engineIconUri: engine.getIconURL(), + engineIconUri: await engine.getIconURL(), }), ]; if (shouldAppear) { diff --git a/browser/components/urlbar/tests/unit/test_match_javascript.js b/browser/components/urlbar/tests/unit/test_match_javascript.js index 3d3eab19ba..bba0af1376 100644 --- a/browser/components/urlbar/tests/unit/test_match_javascript.js +++ b/browser/components/urlbar/tests/unit/test_match_javascript.js @@ -32,7 +32,10 @@ add_task(async function test_javascript_match() { await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); info("Match non-javascript: with plain search"); - let context = createContext("a", { isPrivate: false }); + let context = createContext("a", { + isPrivate: false, + allowAutofill: false /* avoid autofilling abc, as it's not necessary */, + }); await check_results({ context, matches: [ diff --git a/browser/components/urlbar/tests/unit/test_providerHistoryUrlHeuristic.js b/browser/components/urlbar/tests/unit/test_providerHistoryUrlHeuristic.js index 7eb62fbeea..8cf643393f 100644 --- a/browser/components/urlbar/tests/unit/test_providerHistoryUrlHeuristic.js +++ b/browser/components/urlbar/tests/unit/test_providerHistoryUrlHeuristic.js @@ -185,8 +185,10 @@ add_task(async function test_unsupported_protocol() { title: "Robots!", }), makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, uri: "about:robots", title: "about:robots", + iconUri: "page-icon:about:robots", tags: null, providerName: "AboutPages", }), diff --git a/browser/components/urlbar/tests/unit/test_providerOpenTabs.js b/browser/components/urlbar/tests/unit/test_providerOpenTabs.js index f85f547ac3..689ecf614f 100644 --- a/browser/components/urlbar/tests/unit/test_providerOpenTabs.js +++ b/browser/components/urlbar/tests/unit/test_providerOpenTabs.js @@ -3,23 +3,24 @@ "use strict"; +const userContextId1 = 3; +const userContextId2 = 5; +const url = "http://foo.mozilla.org/"; +const url2 = "http://foo2.mozilla.org/"; + add_task(async function test_openTabs() { - const userContextId1 = 3; - const userContextId2 = 5; - const url = "http://foo.mozilla.org/"; - const url2 = "http://foo2.mozilla.org/"; UrlbarProviderOpenTabs.registerOpenTab(url, userContextId1, false); UrlbarProviderOpenTabs.registerOpenTab(url, userContextId1, false); UrlbarProviderOpenTabs.registerOpenTab(url2, userContextId1, false); UrlbarProviderOpenTabs.registerOpenTab(url, userContextId2, false); Assert.deepEqual( [url, url2], - UrlbarProviderOpenTabs.getOpenTabs(userContextId1), + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(userContextId1), "Found all the expected tabs" ); Assert.deepEqual( [url], - UrlbarProviderOpenTabs.getOpenTabs(userContextId2), + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(userContextId2), "Found all the expected tabs" ); await PlacesUtils.promiseLargeCacheDBConnection(); @@ -37,13 +38,13 @@ add_task(async function test_openTabs() { await UrlbarProviderOpenTabs.unregisterOpenTab(url2, userContextId1, false); Assert.deepEqual( [url], - UrlbarProviderOpenTabs.getOpenTabs(userContextId1), + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(userContextId1), "Found all the expected tabs" ); await UrlbarProviderOpenTabs.unregisterOpenTab(url, userContextId1, false); Assert.deepEqual( [url], - UrlbarProviderOpenTabs.getOpenTabs(userContextId1), + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(userContextId1), "Found all the expected tabs" ); Assert.deepEqual( @@ -77,4 +78,86 @@ add_task(async function test_openTabs() { Assert.equal(matchCount, 2, "Found the expected number of matches"); // Sanity check that this doesn't throw. provider.cancelQuery(context); + await UrlbarProviderOpenTabs.unregisterOpenTab(url, userContextId1, false); + await UrlbarProviderOpenTabs.unregisterOpenTab(url, userContextId2, false); +}); + +add_task(async function test_openTabs_mixedtype_input() { + // Passing the userContextId as a string, rather than a number, is a fairly + // common mistake, check the API handles both properly. + Assert.deepEqual( + [], + UrlbarProviderOpenTabs.getOpenTabUrls(1), + "Found all the expected tabs" + ); + Assert.deepEqual( + [], + UrlbarProviderOpenTabs.getOpenTabUrls(2), + "Found all the expected tabs" + ); + UrlbarProviderOpenTabs.registerOpenTab(url, 1, false); + UrlbarProviderOpenTabs.registerOpenTab(url, "2", false); + Assert.deepEqual( + [url], + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(1), + "Found all the expected tabs" + ); + Assert.deepEqual( + [url], + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(2), + "Found all the expected tabs" + ); + Assert.deepEqual( + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(1), + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId("1"), + "Also check getOpenTabs adapts to the argument type" + ); + UrlbarProviderOpenTabs.unregisterOpenTab(url, "1", false); + UrlbarProviderOpenTabs.unregisterOpenTab(url, 2, false); + Assert.deepEqual( + [], + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(1), + "Found all the expected tabs" + ); + Assert.deepEqual( + [], + UrlbarProviderOpenTabs.getOpenTabUrlsForUserContextId(2), + "Found all the expected tabs" + ); +}); + +add_task(async function test_openTabs() { + Assert.equal( + 0, + UrlbarProviderOpenTabs.getOpenTabUrls().size, + "Check there's no open tabs" + ); + Assert.equal( + 0, + UrlbarProviderOpenTabs.getOpenTabUrls(true).size, + "Check there's no private open tabs" + ); + await UrlbarProviderOpenTabs.registerOpenTab(url, userContextId1, false); + await UrlbarProviderOpenTabs.registerOpenTab(url, userContextId2, false); + await UrlbarProviderOpenTabs.registerOpenTab(url2, 0, true); + Assert.equal( + 1, + UrlbarProviderOpenTabs.getOpenTabUrls().size, + "Check open tabs" + ); + Assert.deepEqual( + [userContextId1, userContextId2], + Array.from(UrlbarProviderOpenTabs.getOpenTabUrls().get(url)), + "Check the tab is in 2 userContextIds" + ); + Assert.equal( + 1, + UrlbarProviderOpenTabs.getOpenTabUrls(true).size, + "Check open private tabs" + ); + Assert.deepEqual( + [-1], + Array.from(UrlbarProviderOpenTabs.getOpenTabUrls(true).get(url2)), + "Check the tab is in the private userContextId" + ); }); diff --git a/browser/components/urlbar/tests/unit/test_providerTabToSearch.js b/browser/components/urlbar/tests/unit/test_providerTabToSearch.js index 0a8bfbead5..32bf450654 100644 --- a/browser/components/urlbar/tests/unit/test_providerTabToSearch.js +++ b/browser/components/urlbar/tests/unit/test_providerTabToSearch.js @@ -87,7 +87,7 @@ add_task(async function noAutofill() { matches: [ makeSearchResult(context, { engineName: Services.search.defaultEngine.name, - engineIconUri: Services.search.defaultEngine.getIconURL(), + engineIconUri: await Services.search.defaultEngine.getIconURL(), heuristic: true, providerName: "HeuristicFallback", }), @@ -423,7 +423,7 @@ add_task(async function test_publicSuffix() { matches: [ makeSearchResult(context, { engineName: Services.search.defaultEngine.name, - engineIconUri: Services.search.defaultEngine.getIconURL(), + engineIconUri: await Services.search.defaultEngine.getIconURL(), heuristic: true, providerName: "HeuristicFallback", }), diff --git a/browser/components/urlbar/tests/unit/test_providersManager_filtering.js b/browser/components/urlbar/tests/unit/test_providersManager_filtering.js index 094eb42437..4a1eb5c0ef 100644 --- a/browser/components/urlbar/tests/unit/test_providersManager_filtering.js +++ b/browser/components/urlbar/tests/unit/test_providersManager_filtering.js @@ -205,10 +205,10 @@ add_task(async function test_filter_queryContext() { get type() { return UrlbarUtils.PROVIDER_TYPE.PROFILE; } - isActive(context) { + isActive(_context) { return true; } - async startQuery(context, add) { + async startQuery(_context, _add) { Assert.ok(false, "Provider should no be invoked"); } } @@ -360,7 +360,7 @@ add_task(async function test_filter_priority() { super({ priority, name: `${priority}` + namePart }); this._shouldBeInvoked = shouldBeInvoked; } - async startQuery(context, add) { + async startQuery(_context, _add) { Assert.ok(this._shouldBeInvoked, `${this.name} was invoked`); } } diff --git a/browser/components/urlbar/tests/unit/test_remote_tabs.js b/browser/components/urlbar/tests/unit/test_remote_tabs.js index bb0e708162..a73721d7dc 100644 --- a/browser/components/urlbar/tests/unit/test_remote_tabs.js +++ b/browser/components/urlbar/tests/unit/test_remote_tabs.js @@ -28,7 +28,7 @@ let MockClientsEngine = { Assert.ok(guid.endsWith("desktop") || guid.endsWith("mobile")); return guid.endsWith("mobile") ? "phone" : "desktop"; }, - remoteClientExists(id) { + remoteClientExists(_id) { return true; }, getClientName(id) { @@ -662,7 +662,7 @@ add_task(async function test_duplicate_remote_tabs() { let url = "http://foo.remote.com/"; let tabs = Array(3) .fill(0) - .map((e, i) => ({ + .map(() => ({ urlHistory: [url], title: "A title", lastUsed: Math.floor(Date.now() / 1000), diff --git a/browser/components/urlbar/tests/unit/test_search_suggestions.js b/browser/components/urlbar/tests/unit/test_search_suggestions.js index dc7185149f..22f1ec7617 100644 --- a/browser/components/urlbar/tests/unit/test_search_suggestions.js +++ b/browser/components/urlbar/tests/unit/test_search_suggestions.js @@ -430,7 +430,7 @@ add_task(async function remoteSuggestionsDupeSearchString() { add_task(async function queryIsNotASubstring() { Services.prefs.setBoolPref(SUGGEST_PREF, true); - setSuggestionsFn(searchStr => { + setSuggestionsFn(() => { return ["aaa", "bbb"]; }); @@ -1550,7 +1550,7 @@ add_task(async function restrict_remote_suggestions_after_no_results() { // maxCharsForSearchSuggestions returns 0 results. We set it to 4 here to // avoid constructing a 100+ character string. Services.prefs.setIntPref("browser.urlbar.maxCharsForSearchSuggestions", 4); - setSuggestionsFn(searchStr => { + setSuggestionsFn(() => { return []; }); diff --git a/browser/components/urlbar/tests/unit/xpcshell.toml b/browser/components/urlbar/tests/unit/xpcshell.toml index 188f4390c7..b168d3b987 100644 --- a/browser/components/urlbar/tests/unit/xpcshell.toml +++ b/browser/components/urlbar/tests/unit/xpcshell.toml @@ -128,10 +128,6 @@ prefs = [ ["test_providerOmnibox.js"] ["test_providerOpenTabs.js"] -skip-if = [ - "os == 'mac' && debug", # Bug 1781972 - "os == 'win' && debug", # Bug 1781972 -] ["test_providerPlaces.js"] -- cgit v1.2.3