From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- browser/components/urlbar/.eslintrc.js | 2 - browser/components/urlbar/UrlbarController.sys.mjs | 121 ++++-- browser/components/urlbar/UrlbarInput.sys.mjs | 29 +- browser/components/urlbar/UrlbarPrefs.sys.mjs | 20 +- .../urlbar/UrlbarProviderAboutPages.sys.mjs | 4 +- .../urlbar/UrlbarProviderAutofill.sys.mjs | 2 +- .../urlbar/UrlbarProviderCalculator.sys.mjs | 2 +- .../urlbar/UrlbarProviderClipboard.sys.mjs | 5 +- .../urlbar/UrlbarProviderContextualSearch.sys.mjs | 2 +- .../urlbar/UrlbarProviderInputHistory.sys.mjs | 4 +- .../urlbar/UrlbarProviderInterventions.sys.mjs | 8 +- .../urlbar/UrlbarProviderOmnibox.sys.mjs | 2 +- .../components/urlbar/UrlbarProviderPlaces.sys.mjs | 2 +- .../urlbar/UrlbarProviderQuickActions.sys.mjs | 4 +- .../urlbar/UrlbarProviderQuickSuggest.sys.mjs | 12 +- ...lbarProviderQuickSuggestContextualOptIn.sys.mjs | 2 +- .../urlbar/UrlbarProviderRecentSearches.sys.mjs | 2 +- .../urlbar/UrlbarProviderSearchSuggestions.sys.mjs | 2 +- .../urlbar/UrlbarProviderSearchTips.sys.mjs | 2 +- .../urlbar/UrlbarProviderTabToSearch.sys.mjs | 4 +- .../urlbar/UrlbarProviderTokenAliasEngines.sys.mjs | 2 +- .../urlbar/UrlbarProviderTopSites.sys.mjs | 10 +- .../urlbar/UrlbarProviderUnitConversion.sys.mjs | 2 +- .../urlbar/UrlbarProviderWeather.sys.mjs | 6 +- .../urlbar/UrlbarProvidersManager.sys.mjs | 8 +- browser/components/urlbar/UrlbarUtils.sys.mjs | 32 +- browser/components/urlbar/UrlbarView.sys.mjs | 10 + .../urlbar/docs/dynamic-result-types.rst | 6 +- browser/components/urlbar/metrics.yaml | 38 +- browser/components/urlbar/pings.yaml | 16 + .../urlbar/private/AddonSuggestions.sys.mjs | 10 +- .../components/urlbar/private/AdmWikipedia.sys.mjs | 5 +- .../urlbar/private/MDNSuggestions.sys.mjs | 10 +- .../urlbar/private/SuggestBackendRust.sys.mjs | 9 +- .../urlbar/private/YelpSuggestions.sys.mjs | 10 +- .../urlbar/tests/UrlbarTestUtils.sys.mjs | 17 +- .../urlbar/tests/browser-tips/browser_picks.js | 10 +- .../tests/browser-tips/browser_searchTips.js | 2 +- .../browser-tips/browser_searchTips_interaction.js | 2 +- .../components/urlbar/tests/browser/browser.toml | 14 +- .../tests/browser/browser_UrlbarInput_overflow.js | 2 +- .../tests/browser/browser_aboutHomeLoading.js | 4 +- .../browser_acknowledgeFeedbackAndDismissal.js | 4 +- .../tests/browser/browser_copy_during_load.js | 2 +- .../urlbar/tests/browser/browser_dynamicResults.js | 4 +- .../urlbar/tests/browser/browser_engagement.js | 29 +- .../browser_less_common_selection_manipulations.js | 288 ++++++++++++++ .../tests/browser/browser_locationBarCommand.js | 2 +- .../browser_primary_selection_safe_on_new_tab.js | 2 +- .../urlbar/tests/browser/browser_raceWithTabs.js | 4 +- .../urlbar/tests/browser/browser_result_menu.js | 12 +- .../urlbar/tests/browser/browser_stop_pending.js | 4 +- .../browser_urlbar_telemetry_tabtosearch.js | 4 +- .../tests/engagementTelemetry/browser/browser.toml | 8 +- .../browser_glean_telemetry_abandonment_type.js | 8 +- .../browser_glean_telemetry_engagement_tips.js | 2 +- .../browser_glean_telemetry_engagement_type.js | 48 ++- .../browser/browser_glean_telemetry_exposure.js | 2 +- .../browser_glean_telemetry_potential_exposure.js | 438 +++++++++++++++++++++ .../browser/browser_glean_telemetry_reenter.js | 79 ++++ .../tests/engagementTelemetry/browser/head.js | 8 +- .../quicksuggest/QuickSuggestTestUtils.sys.mjs | 17 +- .../quicksuggest/browser/browser_quicksuggest.js | 37 ++ .../browser/browser_quicksuggest_addons.js | 7 +- .../browser/browser_quicksuggest_block.js | 5 - .../browser/browser_quicksuggest_mdn.js | 68 ++-- .../browser/browser_quicksuggest_pocket.js | 20 +- .../browser/browser_quicksuggest_yelp.js | 5 + .../browser/browser_telemetry_dynamicWikipedia.js | 5 - .../browser/browser_telemetry_gleanEmptyStrings.js | 5 - .../browser_telemetry_impressionEdgeCases.js | 11 +- .../browser/browser_telemetry_nonsponsored.js | 5 - .../browser/browser_telemetry_sponsored.js | 5 - .../urlbar/tests/quicksuggest/browser/head.js | 41 +- .../urlbar/tests/quicksuggest/unit/head.js | 10 +- .../unit/test_quicksuggest_impressionCaps.js | 2 +- .../unit/test_quicksuggest_merinoSessions.js | 2 +- .../urlbar/tests/quicksuggest/unit/test_weather.js | 2 +- .../components/urlbar/tests/unit/test_exposure.js | 11 +- .../components/urlbar/tests/unit/test_l10nCache.js | 2 +- 80 files changed, 1337 insertions(+), 327 deletions(-) create mode 100644 browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js create mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_potential_exposure.js create mode 100644 browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_reenter.js (limited to 'browser/components/urlbar') diff --git a/browser/components/urlbar/.eslintrc.js b/browser/components/urlbar/.eslintrc.js index 8ead689bcc..aac2436d20 100644 --- a/browser/components/urlbar/.eslintrc.js +++ b/browser/components/urlbar/.eslintrc.js @@ -5,8 +5,6 @@ "use strict"; module.exports = { - extends: ["plugin:mozilla/require-jsdoc"], - rules: { "mozilla/var-only-at-top-level": "error", "no-unused-expressions": "error", diff --git a/browser/components/urlbar/UrlbarController.sys.mjs b/browser/components/urlbar/UrlbarController.sys.mjs index 9bfc3a645d..7e4d0ff1c5 100644 --- a/browser/components/urlbar/UrlbarController.sys.mjs +++ b/browser/components/urlbar/UrlbarController.sys.mjs @@ -133,6 +133,7 @@ export class UrlbarController { // notifications related to the previous query. this.notify(NOTIFICATIONS.QUERY_STARTED, queryContext); await this.manager.startQuery(queryContext, this); + // If the query has been cancelled, onQueryFinished was notified already. // Note this._lastQueryContextWrapper may have changed in the meanwhile. if ( @@ -144,6 +145,16 @@ export class UrlbarController { this.manager.cancelQuery(queryContext); this.notify(NOTIFICATIONS.QUERY_FINISHED, queryContext); } + + // Record a potential exposure if the current search string matches one of + // the registered keywords. + if (!queryContext.isPrivate) { + let searchStr = queryContext.trimmedLowerCaseSearchString; + if (lazy.UrlbarPrefs.get("potentialExposureKeywords").has(searchStr)) { + this.engagementEvent.addPotentialExposure(searchStr); + } + } + return queryContext; } @@ -335,7 +346,7 @@ export class UrlbarController { } event.preventDefault(); break; - case KeyEvent.DOM_VK_TAB: + case KeyEvent.DOM_VK_TAB: { // It's always possible to tab through results when the urlbar was // focused with the mouse or has a search string, or when the view // already has a selection. @@ -368,6 +379,7 @@ export class UrlbarController { event.preventDefault(); } break; + } case KeyEvent.DOM_VK_PAGE_DOWN: case KeyEvent.DOM_VK_PAGE_UP: if (event.ctrlKey) { @@ -592,8 +604,8 @@ export class UrlbarController { /** * Triggers a "dismiss" engagement for the selected result if one is selected * and it's not the heuristic. Providers that can respond to dismissals of - * their results should implement `onEngagement()`, handle the dismissal, and - * call `controller.removeResult()`. + * their results should implement `onLegacyEngagement()`, handle the + * dismissal, and call `controller.removeResult()`. * * @param {Event} event * The event that triggered dismissal. @@ -783,13 +795,6 @@ class TelemetryEvent { interactionType: this._getStartInteractionType(event, searchString), searchString, }; - - this._controller.manager.notifyEngagementChange( - "start", - queryContext, - {}, - this._controller - ); } /** @@ -821,17 +826,31 @@ class TelemetryEvent { * @param {DOMElement} [details.element] The picked view element. */ record(event, details) { + // Prevent re-entering `record()`. This can happen because + // `#internalRecord()` will notify an engagement to the provider, that may + // execute an action blurring the input field. Then both an engagement + // and an abandonment would be recorded for the same session. + // Nulling out `_startEventInfo` doesn't save us in this case, because it + // happens after `#internalRecord()`, and `isSessionOngoing` must be + // calculated inside it. + if (this.#handlingRecord) { + return; + } + // This should never throw, or it may break the urlbar. try { - this._internalRecord(event, details); + this.#handlingRecord = true; + this.#internalRecord(event, details); } catch (ex) { console.error("Could not record event: ", ex); } finally { + this.#handlingRecord = false; + // Reset the start event info except for engagements that do not end the // search session. In that case, the view stays open and further // engagements are possible and should be recorded when they occur. // (`details.isSessionOngoing` is not a param; rather, it's set by - // `_internalRecord()`.) + // `#internalRecord()`.) if (!details.isSessionOngoing) { this._startEventInfo = null; this._discarded = false; @@ -839,19 +858,10 @@ class TelemetryEvent { } } - _internalRecord(event, details) { + #internalRecord(event, details) { const startEventInfo = this._startEventInfo; if (!this._category || !startEventInfo) { - if (this._discarded && this._category && details?.selType !== "dismiss") { - let { queryContext } = this._controller._lastQueryContextWrapper || {}; - this._controller.manager.notifyEngagementChange( - "discard", - queryContext, - {}, - this._controller - ); - } return; } if ( @@ -938,6 +948,10 @@ class TelemetryEvent { } ); + if (!details.isSessionOngoing) { + this.#recordEndOfSessionTelemetry(details.searchString); + } + if (skipLegacyTelemetry) { this._controller.manager.notifyEngagementChange( method, @@ -1091,23 +1105,6 @@ class TelemetryEvent { return; } - // First check to see if we can record an exposure event - 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( `${method} event: ${JSON.stringify(eventInfo)}` ); @@ -1115,6 +1112,38 @@ class TelemetryEvent { Glean.urlbar[method].record(eventInfo); } + #recordEndOfSessionTelemetry(searchString) { + // exposures + 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); + this.#exposureResultTypes.clear(); + } + this.#tentativeExposureResultTypes.clear(); + + // potential exposures + if (this.#potentialExposureKeywords.size) { + let normalizedSearchString = searchString.trim().toLowerCase(); + for (let keyword of this.#potentialExposureKeywords) { + let data = { + keyword, + terminal: keyword == normalizedSearchString, + }; + this._controller.logger.debug( + `potential_exposure event: ${JSON.stringify(data)}` + ); + Glean.urlbar.potentialExposure.record(data); + } + GleanPings.urlbarPotentialExposure.submit(); + this.#potentialExposureKeywords.clear(); + } + } + /** * 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 @@ -1164,6 +1193,16 @@ class TelemetryEvent { this.#tentativeExposureResultTypes.clear(); } + /** + * Registers a potential exposure in the current urlbar session. + * + * @param {string} keyword + * The keyword that was matched. + */ + addPotentialExposure(keyword) { + this.#potentialExposureKeywords.add(keyword); + } + #getInteractionType( method, startEventInfo, @@ -1350,8 +1389,12 @@ class TelemetryEvent { } } + // Used to avoid re-entering `record()`. + #handlingRecord = false; + #previousSearchWordsSet = null; #exposureResultTypes = new Set(); #tentativeExposureResultTypes = new Set(); + #potentialExposureKeywords = new Set(); } diff --git a/browser/components/urlbar/UrlbarInput.sys.mjs b/browser/components/urlbar/UrlbarInput.sys.mjs index 96fc7b9301..a96e862cff 100644 --- a/browser/components/urlbar/UrlbarInput.sys.mjs +++ b/browser/components/urlbar/UrlbarInput.sys.mjs @@ -1364,7 +1364,7 @@ export class UrlbarInput { // The value setter clobbers the actiontype attribute, so we need this // helper to restore it afterwards. const setValueAndRestoreActionType = (value, allowTrim) => { - this._setValue(value, allowTrim); + this._setValue(value, { allowTrim }); switch (result.type) { case lazy.UrlbarUtils.RESULT_TYPE.TAB_SWITCH: @@ -1555,7 +1555,7 @@ export class UrlbarInput { !this.value.endsWith(" ") ) { this._autofillPlaceholder = null; - this._setValue(this.window.gBrowser.userTypedValue, false); + this._setValue(this.window.gBrowser.userTypedValue); } return false; @@ -1940,7 +1940,7 @@ export class UrlbarInput { } set value(val) { - this._setValue(val, true); + this._setValue(val, { allowTrim: true }); } get lastSearchString() { @@ -2107,7 +2107,7 @@ export class UrlbarInput { this.searchMode = searchMode; let value = result.payload.query?.trimStart() || ""; - this._setValue(value, false); + this._setValue(value); if (startQuery) { this.startQuery({ allowAutofill: false }); @@ -2253,10 +2253,6 @@ export class UrlbarInput { "--urlbar-height", px(getBoundsWithoutFlushing(this.textbox).height) ); - this.textbox.style.setProperty( - "--urlbar-toolbar-height", - px(getBoundsWithoutFlushing(this._toolbar).height) - ); this.setAttribute("breakout", "true"); this.textbox.parentNode.setAttribute("breakout", "true"); @@ -2266,7 +2262,16 @@ export class UrlbarInput { }); } - _setValue(val, allowTrim) { + /** + * Sets the input field value. + * + * @param {string} val The new value to set. + * @param {object} [options] Options for setting. + * @param {boolean} [options.allowTrim] Whether the value can be trimmed. + * + * @returns {string} The set value. + */ + _setValue(val, { allowTrim = false } = {}) { // Don't expose internal about:reader URLs to the user. let originalUrl = lazy.ReaderMode.getOriginalUrlObjectForDisplay(val); if (originalUrl) { @@ -2730,7 +2735,7 @@ export class UrlbarInput { }) { // The autofilled value may be a URL that includes a scheme at the // beginning. Do not allow it to be trimmed. - this._setValue(value, false); + this._setValue(value); this.inputField.setSelectionRange(selectionStart, selectionEnd); this._autofillPlaceholder = { value, @@ -3152,8 +3157,8 @@ export class UrlbarInput { this.select(); this.window.goDoCommand("cmd_paste"); this.setResultForCurrentValue(null); - this.controller.clearLastQueryContextCache(); this.handleCommand(); + this.controller.clearLastQueryContextCache(); this._suppressStartQuery = false; }); @@ -3504,7 +3509,7 @@ export class UrlbarInput { } if (untrim) { this._focusUntrimmedValue = this._untrimmedValue; - this._setValue(this._focusUntrimmedValue, false); + this._setValue(this._focusUntrimmedValue); } } diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs index 022d0b1c7c..cd8a6b0f4c 100644 --- a/browser/components/urlbar/UrlbarPrefs.sys.mjs +++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs @@ -65,7 +65,7 @@ const PREF_URLBAR_DEFAULTS = new Map([ ["autoFill.stddevMultiplier", [0.0, "float"]], // Feature gate pref for clipboard suggestions in the urlbar. - ["clipboard.featureGate", true], + ["clipboard.featureGate", false], // Whether to show a link for using the search functionality provided by the // active view if the the view utilizes OpenSearch. @@ -1507,12 +1507,28 @@ class Preferences { return this.shouldHandOffToSearchModePrefs.some( prefName => !this.get(prefName) ); - case "autoFillAdaptiveHistoryUseCountThreshold": + case "autoFillAdaptiveHistoryUseCountThreshold": { const nimbusValue = this._nimbus.autoFillAdaptiveHistoryUseCountThreshold; return nimbusValue === undefined ? this.get("autoFill.adaptiveHistory.useCountThreshold") : parseFloat(nimbusValue); + } + case "potentialExposureKeywords": { + // Get the keywords array from Nimbus or prefs and convert it to a Set. + // If the value comes from Nimbus, it will already be an array. If it + // comes from prefs, it should be a stringified array. + let value = this._readPref(pref); + if (typeof value == "string") { + try { + value = JSON.parse(value); + } catch (e) {} + } + if (!Array.isArray(value)) { + value = null; + } + return new Set(value); + } } return this._readPref(pref); } diff --git a/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs b/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs index 62f85b2348..be607a80d5 100644 --- a/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderAboutPages.sys.mjs @@ -49,7 +49,7 @@ class ProviderAboutPages extends UrlbarProvider { * @returns {boolean} Whether this provider should be invoked for the search. */ isActive(queryContext) { - return queryContext.trimmedSearchString.toLowerCase().startsWith("about:"); + return queryContext.trimmedLowerCaseSearchString.startsWith("about:"); } /** @@ -61,7 +61,7 @@ class ProviderAboutPages extends UrlbarProvider { * result. A UrlbarResult should be passed to it. */ startQuery(queryContext, addCallback) { - let searchString = queryContext.trimmedSearchString.toLowerCase(); + let searchString = queryContext.trimmedLowerCaseSearchString; for (const aboutUrl of lazy.AboutPagesUtils.visibleAboutUrls) { if (aboutUrl.startsWith(searchString)) { let result = new lazy.UrlbarResult( diff --git a/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs b/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs index 32e605206e..7470df0fea 100644 --- a/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs @@ -653,7 +653,7 @@ class ProviderAutofill extends UrlbarProvider { queryType: QUERYTYPE.AUTOFILL_ADAPTIVE, // `fullSearchString` is the value the user typed including a prefix if // they typed one. `searchString` has been stripped of the prefix. - fullSearchString: queryContext.searchString.toLowerCase(), + fullSearchString: queryContext.lowerCaseSearchString, searchString: this._searchString, strippedPrefix: this._strippedPrefix, useCountThreshold: lazy.UrlbarPrefs.get( diff --git a/browser/components/urlbar/UrlbarProviderCalculator.sys.mjs b/browser/components/urlbar/UrlbarProviderCalculator.sys.mjs index a55531167c..3f0ffed299 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) { + onLegacyEngagement(state, queryContext, details) { let { result } = details; if (result?.providerName == this.name) { lazy.ClipboardHelper.copyString(result.payload.value); diff --git a/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs b/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs index 5337e610cc..1dc5bb9b86 100644 --- a/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs @@ -143,10 +143,7 @@ class ProviderClipboard extends UrlbarProvider { addCallback(this, result); } - onEngagement(state, queryContext, details, controller) { - if (!["engagement", "abandonment"].includes(state)) { - return; - } + onLegacyEngagement(state, queryContext, details, controller) { const visibleResults = controller.view?.visibleResults ?? []; for (const result of visibleResults) { if ( diff --git a/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs b/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs index 63c94ee8f3..5714f11e72 100644 --- a/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs @@ -246,7 +246,7 @@ class ProviderContextualSearch extends UrlbarProvider { }; } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName == this.name) { this.#pickResult(result, controller.browserWindow); diff --git a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs index f929a1c003..17b6a4c9b0 100644 --- a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs @@ -200,7 +200,7 @@ class ProviderInputHistory extends UrlbarProvider { } } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName != this.name) { return; @@ -236,7 +236,7 @@ class ProviderInputHistory extends UrlbarProvider { SQL_ADAPTIVE_QUERY, { parent: lazy.PlacesUtils.tagsFolderId, - search_string: queryContext.searchString.toLowerCase(), + search_string: queryContext.lowerCaseSearchString, matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE, searchBehavior: lazy.UrlbarPrefs.get("defaultBehavior"), userContextId: lazy.UrlbarPrefs.get("switchTabs.searchAllContainers") diff --git a/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs b/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs index 08b4ea36b7..68b9c1665d 100644 --- a/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderInterventions.sys.mjs @@ -703,7 +703,7 @@ class ProviderInterventions extends UrlbarProvider { } } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; // `selType` is "tip" when the tip's main button is picked. Ignore clicks on @@ -714,10 +714,8 @@ class ProviderInterventions extends UrlbarProvider { this.#pickResult(result, controller.browserWindow); } - if (["engagement", "abandonment"].includes(state)) { - for (let tip of this.tipsShownInCurrentEngagement) { - Services.telemetry.keyedScalarAdd("urlbar.tips", `${tip}-shown`, 1); - } + for (let tip of this.tipsShownInCurrentEngagement) { + Services.telemetry.keyedScalarAdd("urlbar.tips", `${tip}-shown`, 1); } this.tipsShownInCurrentEngagement.clear(); } diff --git a/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs b/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs index 351e8ff60b..362f683027 100644 --- a/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderOmnibox.sys.mjs @@ -178,7 +178,7 @@ class ProviderOmnibox extends UrlbarProvider { ); } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName != this.name) { return; diff --git a/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs b/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs index 650acd1730..c94ebee80a 100644 --- a/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs @@ -1517,7 +1517,7 @@ class ProviderPlaces extends UrlbarProvider { search.notifyResult(false); } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName != this.name) { return; diff --git a/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs index f199b6b892..29370cbaaf 100644 --- a/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs @@ -95,7 +95,7 @@ class ProviderQuickActions extends UrlbarProvider { */ async startQuery(queryContext, addCallback) { await lazy.QuickActionsLoaderDefault.ensureLoaded(); - let input = queryContext.trimmedSearchString.toLowerCase(); + let input = queryContext.trimmedLowerCaseSearchString; if ( !queryContext.searchMode && @@ -241,7 +241,7 @@ class ProviderQuickActions extends UrlbarProvider { } } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { // Ignore engagements on other results that didn't end the session. if (details.result?.providerName != this.name && details.isSessionOngoing) { return; diff --git a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs index 78e254616e..fbc8cc8c3f 100644 --- a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs @@ -229,7 +229,7 @@ class ProviderQuickSuggest extends UrlbarProvider { } } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { // Ignore engagements on other results that didn't end the session. if (details.result?.providerName != this.name && details.isSessionOngoing) { return; @@ -237,7 +237,7 @@ class ProviderQuickSuggest extends UrlbarProvider { // Reset the Merino session ID when a session ends. By design for the user's // privacy, we don't keep it around between engagements. - if (state != "start" && !details.isSessionOngoing) { + if (!details.isSessionOngoing) { this.#merino?.resetSession(); } @@ -486,8 +486,8 @@ class ProviderQuickSuggest extends UrlbarProvider { * end of the engagement or that was dismissed. Null if no quick suggest * result was present. * @param {object} details - * The `details` object that was passed to `onEngagement()`. It must look - * like this: `{ selType, selIndex }` + * The `details` object that was passed to `onLegacyEngagement()`. It must + * look like this: `{ selType, selIndex }` */ #recordEngagement(queryContext, result, details) { let resultSelType = ""; @@ -781,8 +781,8 @@ class ProviderQuickSuggest extends UrlbarProvider { * 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. * @param {object} options.details - * The `details` object that was passed to `onEngagement()`. It must look - * like this: `{ selType, selIndex }` + * The `details` object that was passed to `onLegacyEngagement()`. It must + * look like this: `{ selType, selIndex }` */ #recordNavSuggestionTelemetry({ queryContext, diff --git a/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs index 48006d09c0..b3c322ffa1 100644 --- a/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickSuggestContextualOptIn.sys.mjs @@ -188,7 +188,7 @@ class ProviderQuickSuggestContextualOptIn extends UrlbarProvider { row.ownerGlobal.A11yUtils.announce({ raw: alertText }); } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName != this.name) { return; diff --git a/browser/components/urlbar/UrlbarProviderRecentSearches.sys.mjs b/browser/components/urlbar/UrlbarProviderRecentSearches.sys.mjs index ceeba729d4..1565013440 100644 --- a/browser/components/urlbar/UrlbarProviderRecentSearches.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderRecentSearches.sys.mjs @@ -63,7 +63,7 @@ class ProviderRecentSearches extends UrlbarProvider { return 1; } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName != this.name) { return; diff --git a/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs b/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs index 8cb3532d94..e3d13feb56 100644 --- a/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderSearchSuggestions.sys.mjs @@ -352,7 +352,7 @@ class ProviderSearchSuggestions extends UrlbarProvider { return undefined; } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { let { result } = details; if (result?.providerName != this.name) { return; diff --git a/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs b/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs index b19528619c..a7a23a3228 100644 --- a/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs @@ -273,7 +273,7 @@ class ProviderSearchTips extends UrlbarProvider { lazy.UrlbarPrefs.set(`tipShownCount.${tip}`, MAX_SHOWN_COUNT); } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { // Ignore engagements on other results that didn't end the session. let { result } = details; if (result?.providerName != this.name && details.isSessionOngoing) { diff --git a/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs b/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs index 9aabef3d19..0cce6481b1 100644 --- a/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs @@ -194,7 +194,7 @@ class ProviderTabToSearch extends UrlbarProvider { * Called when a result from the provider is selected. "Selected" refers to * the user highlighing the result with the arrow keys/Tab, before it is * picked. onSelection is also called when a user clicks a result. In the - * event of a click, onSelection is called just before onEngagement. + * event of a click, onSelection is called just before onLegacyEngagement. * * @param {UrlbarResult} result * The result that was selected. @@ -226,7 +226,7 @@ class ProviderTabToSearch extends UrlbarProvider { } } - onEngagement(state, queryContext, details) { + onLegacyEngagement(state, queryContext, details) { let { result, element } = details; if ( result?.providerName == this.name && diff --git a/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs b/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs index b3a91bcbe4..db9e8df382 100644 --- a/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderTokenAliasEngines.sys.mjs @@ -173,7 +173,7 @@ class ProviderTokenAliasEngines extends UrlbarProvider { } async _getAutofillResult(queryContext) { - let lowerCaseSearchString = queryContext.searchString.toLowerCase(); + let { lowerCaseSearchString } = queryContext; // The user is typing a specific engine. We should show a heuristic result. for (let { engine, tokenAliases } of this._engines) { diff --git a/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs b/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs index e9d968f20f..a046de37d4 100644 --- a/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderTopSites.sys.mjs @@ -193,7 +193,7 @@ class ProviderTopSites extends UrlbarProvider { return site; }); - // Store Sponsored Top Sites so we can use it in `onEngagement` + // Store Sponsored Top Sites so we can use it in `onLegacyEngagement` if (sponsoredSites.length) { this.sponsoredSites = sponsoredSites; } @@ -333,12 +333,8 @@ class ProviderTopSites extends UrlbarProvider { } } - onEngagement(state, queryContext) { - if ( - !queryContext.isPrivate && - this.sponsoredSites && - ["engagement", "abandonment"].includes(state) - ) { + onLegacyEngagement(state, queryContext) { + if (!queryContext.isPrivate && this.sponsoredSites) { for (let site of this.sponsoredSites) { Services.telemetry.keyedScalarAdd( SCALAR_CATEGORY_TOPSITES, diff --git a/browser/components/urlbar/UrlbarProviderUnitConversion.sys.mjs b/browser/components/urlbar/UrlbarProviderUnitConversion.sys.mjs index 98c4d025e4..a5ad28d2aa 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) { + onLegacyEngagement(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 24342fecab..8e9b6b8f3e 100644 --- a/browser/components/urlbar/UrlbarProviderWeather.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderWeather.sys.mjs @@ -115,7 +115,7 @@ class ProviderWeather extends UrlbarProvider { return false; } - return keywords.has(queryContext.searchString.trim().toLocaleLowerCase()); + return keywords.has(queryContext.trimmedLowerCaseSearchString); } /** @@ -163,7 +163,7 @@ class ProviderWeather extends UrlbarProvider { return lazy.QuickSuggest.weather.getViewUpdate(result); } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { // Ignore engagements on other results that didn't end the session. if (details.result?.providerName != this.name && details.isSessionOngoing) { return; @@ -243,7 +243,7 @@ class ProviderWeather extends UrlbarProvider { * A non-empty string means the user picked the weather row or some part of * it, and both impression and click telemetry will be recorded. The * non-empty-string values come from the `details.selType` passed in to - * `onEngagement()`; see `TelemetryEvent.typeFromElement()`. + * `onLegacyEngagement()`; see `TelemetryEvent.typeFromElement()`. */ #recordEngagementTelemetry(result, isPrivate, selType) { // Indexes recorded in quick suggest telemetry are 1-based, so add 1 to the diff --git a/browser/components/urlbar/UrlbarProvidersManager.sys.mjs b/browser/components/urlbar/UrlbarProvidersManager.sys.mjs index 609b0735e1..ac70e03e1b 100644 --- a/browser/components/urlbar/UrlbarProvidersManager.sys.mjs +++ b/browser/components/urlbar/UrlbarProvidersManager.sys.mjs @@ -334,11 +334,11 @@ class ProvidersManager { /** * Notifies all providers when the user starts and ends an engagement with the - * urlbar. For details on parameters, see UrlbarProvider.onEngagement(). + * urlbar. For details on parameters, see + * UrlbarProvider.onLegacyEngagement(). * * @param {string} state - * The state of the engagement, one of: start, engagement, abandonment, - * discard + * The state of the engagement, one of: engagement, abandonment * @param {UrlbarQueryContext} queryContext * The engagement's query context, if available. * @param {object} details @@ -349,7 +349,7 @@ class ProvidersManager { notifyEngagementChange(state, queryContext, details = {}, controller) { for (let provider of this.providers) { provider.tryMethod( - "onEngagement", + "onLegacyEngagement", state, queryContext, details, diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs index 2bbb5d1ab0..9fca8426a3 100644 --- a/browser/components/urlbar/UrlbarUtils.sys.mjs +++ b/browser/components/urlbar/UrlbarUtils.sys.mjs @@ -854,7 +854,7 @@ export var UrlbarUtils = { * @returns {string} The modified paste data. */ stripUnsafeProtocolOnPaste(pasteData) { - while (true) { + for (;;) { let scheme = ""; try { scheme = Services.io.extractScheme(pasteData); @@ -1831,6 +1831,9 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = { isBlockable: { type: "boolean", }, + isManageable: { + type: "boolean", + }, isPinned: { type: "boolean", }, @@ -2175,6 +2178,8 @@ export class UrlbarQueryContext { this.pendingHeuristicProviders = new Set(); this.deferUserSelectionProviders = new Set(); this.trimmedSearchString = this.searchString.trim(); + this.lowerCaseSearchString = this.searchString.toLowerCase(); + this.trimmedLowerCaseSearchString = this.trimmedSearchString.toLowerCase(); this.userContextId = lazy.UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable( options.userContextId, @@ -2431,21 +2436,12 @@ export class UrlbarProvider { * @param {string} _state * The state of the engagement, one of the following strings: * - * start - * A new query has started in the urlbar. * engagement * The user picked a result in the urlbar or used paste-and-go. * abandonment * The urlbar was blurred (i.e., lost focus). - * discard - * This doesn't correspond to a user action, but it means that the - * urlbar has discarded the engagement for some reason, and the - * `onEngagement` implementation should ignore it. - * * @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". + * The engagement's query context. * @param {object} _details * This object is non-empty only when `state` is "engagement" or * "abandonment", and it describes the search string and engaged result. @@ -2479,7 +2475,7 @@ export class UrlbarProvider { * @param {UrlbarController} _controller * The associated controller. */ - onEngagement(_state, _queryContext, _details, _controller) {} + onLegacyEngagement(_state, _queryContext, _details, _controller) {} /** * Called before a result from the provider is selected. See `onSelection` @@ -2497,8 +2493,8 @@ export class UrlbarProvider { * Called when a result from the provider is selected. "Selected" refers to * the user highlighing the result with the arrow keys/Tab, before it is * picked. onSelection is also called when a user clicks a result. In the - * event of a click, onSelection is called just before onEngagement. Note that - * this is called when heuristic results are pre-selected. + * event of a click, onSelection is called just before onLegacyEngagement. + * Note that this is called when heuristic results are pre-selected. * * @param {UrlbarResult} _result * The result that was selected. @@ -2581,8 +2577,8 @@ export class UrlbarProvider { /** * Gets the list of commands that should be shown in the result menu for a * given result from the provider. All commands returned by this method should - * be handled by implementing `onEngagement()` with the possible exception of - * commands automatically handled by the urlbar, like "help". + * be handled by implementing `onLegacyEngagement()` with the possible + * exception of commands automatically handled by the urlbar, like "help". * * @param {UrlbarResult} _result * The menu will be shown for this result. @@ -2594,8 +2590,8 @@ export class UrlbarProvider { * {string} name * The name of the command. Must be specified unless `children` is * present. When a command is picked, its name will be passed as - * `details.selType` to `onEngagement()`. The special name "separator" - * will create a menu separator. + * `details.selType` to `onLegacyEngagement()`. The special name + * "separator" will create a menu separator. * {object} l10n * An l10n object for the command's label: `{ id, args }` * Must be specified unless `name` is "separator". diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs index b5fe1e1955..3d6ea46781 100644 --- a/browser/components/urlbar/UrlbarView.sys.mjs +++ b/browser/components/urlbar/UrlbarView.sys.mjs @@ -48,6 +48,7 @@ const ZERO_PREFIX_SCALAR_EXPOSURE = "urlbar.zeroprefix.exposure"; const RESULT_MENU_COMMANDS = { DISMISS: "dismiss", HELP: "help", + MANAGE: "manage", }; const getBoundsWithoutFlushing = element => @@ -3139,6 +3140,15 @@ export class UrlbarView { }, }); } + if (result.payload.isManageable) { + commands.push({ + name: RESULT_MENU_COMMANDS.MANAGE, + l10n: { + id: "urlbar-result-menu-manage-firefox-suggest", + }, + }); + } + let rv = commands.length ? commands : null; this.#resultMenuCommands.set(result, rv); return rv; diff --git a/browser/components/urlbar/docs/dynamic-result-types.rst b/browser/components/urlbar/docs/dynamic-result-types.rst index f72c5e4a13..2c81c1656f 100644 --- a/browser/components/urlbar/docs/dynamic-result-types.rst +++ b/browser/components/urlbar/docs/dynamic-result-types.rst @@ -152,8 +152,8 @@ aren't relevant to dynamic result types, and you should choose values appropriate to your use case. If any elements created in the view for your results can be picked with the -keyboard or mouse, then be sure to implement your provider's ``onEngagement`` -method. +keyboard or mouse, then be sure to implement your provider's +``onLegacyEngagement`` method. For help on implementing providers in general, see the address bar's `Architecture Overview`__. @@ -616,7 +616,7 @@ URL Navigation If a result's payload includes a string ``url`` property and a boolean ``shouldNavigate: true`` property, then picking the result will navigate to the -URL. The ``onEngagement`` method of the result's provider will still be called +URL. The ``onLegacyEngagement`` method of the result's provider will still be called before navigation. Text Highlighting diff --git a/browser/components/urlbar/metrics.yaml b/browser/components/urlbar/metrics.yaml index 95337d84eb..173ee08a10 100644 --- a/browser/components/urlbar/metrics.yaml +++ b/browser/components/urlbar/metrics.yaml @@ -110,7 +110,6 @@ urlbar: `intervention_unknown`, `intervention_update`, `keyword`, - `merino_adm_nonsponsored`, `merino_adm_sponsored`, `merino_amo`, `merino_top_picks`, @@ -159,6 +158,7 @@ urlbar: notification_emails: - fx-search-telemetry@mozilla.com expires: never + engagement: type: event description: Recorded when the user executes an action on a result. @@ -242,7 +242,6 @@ urlbar: `intervention_unknown`, `intervention_update`, `keyword`, - `merino_adm_nonsponsored`, `merino_adm_sponsored`, `merino_amo`, `merino_top_picks`, @@ -363,7 +362,6 @@ urlbar: `intervention_unknown`, `intervention_update`, `keyword`, - `merino_adm_nonsponsored`, `merino_adm_sponsored`, `merino_amo`, `merino_top_picks`, @@ -432,6 +430,40 @@ urlbar: - fx-search-telemetry@mozilla.com expires: never + potential_exposure: + type: event + description: > + This event is recorded in the `urlbar-potential-exposure` ping, which is + submitted at the end of urlbar sessions during which the user typed a + keyword defined by the Nimbus variable `potentialExposureKeywords`. A + "session" begins when the user focuses the urlbar and ends with an + engagement or abandonment. The ping will contain one event per unique + keyword that is typed during the session. This ping is not submitted for + sessions in private windows. + extra_keys: + keyword: + type: string + description: > + The matched keyword. + terminal: + type: boolean + description: > + Whether the matched keyword was present at the end of the urlbar + session. If true, the session ended with the keyword. If false, the + keyword was typed at some point during the session but the session + did not end with it. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881875 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881875 + data_sensitivity: + - stored_content + notification_emails: + - fx-search-telemetry@mozilla.com + expires: never + send_in_pings: + - urlbar-potential-exposure + quick_suggest_contextual_opt_in: type: event description: > diff --git a/browser/components/urlbar/pings.yaml b/browser/components/urlbar/pings.yaml index 8153b62863..4c46f16909 100644 --- a/browser/components/urlbar/pings.yaml +++ b/browser/components/urlbar/pings.yaml @@ -19,3 +19,19 @@ quick-suggest: - https://bugzilla.mozilla.org/show_bug.cgi?id=1854755 notification_emails: - najiang@mozilla.com + +urlbar-potential-exposure: + description: | + This ping is submitted at the end of urlbar sessions during which the user + typed a keyword defined by the Nimbus variable `potentialExposureKeywords`. + A "session" begins when the user focuses the urlbar and ends with an + engagement or abandonment. The ping will contain one + `urlbar.potential_exposure` event per unique keyword that is typed during + the session. This ping is not submitted for sessions in private windows. + include_client_id: false + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881875 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1881875 + notification_emails: + - fx-search-telemetry@mozilla.com diff --git a/browser/components/urlbar/private/AddonSuggestions.sys.mjs b/browser/components/urlbar/private/AddonSuggestions.sys.mjs index 23311cec1c..ace82e41d3 100644 --- a/browser/components/urlbar/private/AddonSuggestions.sys.mjs +++ b/browser/components/urlbar/private/AddonSuggestions.sys.mjs @@ -21,7 +21,7 @@ const UTM_PARAMS = { }; const RESULT_MENU_COMMAND = { - HELP: "help", + MANAGE: "manage", NOT_INTERESTED: "not_interested", NOT_RELEVANT: "not_relevant", SHOW_LESS_FREQUENTLY: "show_less_frequently", @@ -212,9 +212,9 @@ export class AddonSuggestions 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", }, } ); @@ -224,8 +224,8 @@ export class AddonSuggestions 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": diff --git a/browser/components/urlbar/private/AdmWikipedia.sys.mjs b/browser/components/urlbar/private/AdmWikipedia.sys.mjs index 3ab5bad09f..596e15df4c 100644 --- a/browser/components/urlbar/private/AdmWikipedia.sys.mjs +++ b/browser/components/urlbar/private/AdmWikipedia.sys.mjs @@ -190,14 +190,11 @@ export class AdmWikipedia extends BaseFeature { sponsoredBlockId: suggestion.block_id, sponsoredAdvertiser: suggestion.advertiser, sponsoredIabCategory: suggestion.iab_category, - helpUrl: lazy.QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, }; let result = new lazy.UrlbarResult( diff --git a/browser/components/urlbar/private/MDNSuggestions.sys.mjs b/browser/components/urlbar/private/MDNSuggestions.sys.mjs index c9e7da18af..3efedbd12a 100644 --- a/browser/components/urlbar/private/MDNSuggestions.sys.mjs +++ b/browser/components/urlbar/private/MDNSuggestions.sys.mjs @@ -15,7 +15,7 @@ ChromeUtils.defineESModuleGetters(lazy, { }); const RESULT_MENU_COMMAND = { - HELP: "help", + MANAGE: "manage", NOT_INTERESTED: "not_interested", NOT_RELEVANT: "not_relevant", }; @@ -157,9 +157,9 @@ export class MDNSuggestions 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", }, }, ]; @@ -167,8 +167,8 @@ export class MDNSuggestions 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": diff --git a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs index 2d96e7540f..3993149757 100644 --- a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs +++ b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs @@ -136,11 +136,12 @@ export class SuggestBackendRust extends BaseFeature { suggestion.provider = type; suggestion.is_sponsored = type == "Amp" || type == "Yelp"; if (Array.isArray(suggestion.icon)) { - suggestion.icon_blob = new Blob( - [new Uint8Array(suggestion.icon)], - type == "Yelp" ? { type: "image/svg+xml" } : null - ); + suggestion.icon_blob = new Blob([new Uint8Array(suggestion.icon)], { + type: suggestion.iconMimetype ?? "", + }); + delete suggestion.icon; + delete suggestion.iconMimetype; } } diff --git a/browser/components/urlbar/private/YelpSuggestions.sys.mjs b/browser/components/urlbar/private/YelpSuggestions.sys.mjs index 4cf454c71d..e2a2803bd7 100644 --- a/browser/components/urlbar/private/YelpSuggestions.sys.mjs +++ b/browser/components/urlbar/private/YelpSuggestions.sys.mjs @@ -15,8 +15,8 @@ ChromeUtils.defineESModuleGetters(lazy, { }); 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", @@ -168,9 +168,9 @@ export class YelpSuggestions 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", }, } ); @@ -180,8 +180,8 @@ export class YelpSuggestions extends BaseFeature { handleCommand(view, result, selType, searchString) { 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; case RESULT_MENU_COMMAND.INACCURATE_LOCATION: // Currently the only way we record this feedback is in the Glean diff --git a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs index cfc9ecb3d8..f576f4ca19 100644 --- a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs +++ b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs @@ -158,7 +158,7 @@ export var UrlbarTestUtils = { lazy.UrlbarPrefs.get("trimURLs") && value != lazy.BrowserUIUtils.trimURL(value) ) { - window.gURLBar._setValue(value, false); + window.gURLBar._setValue(value); fireInputEvent = true; } else { window.gURLBar.value = value; @@ -1315,10 +1315,7 @@ export var UrlbarTestUtils = { // Set most of the string directly instead of going through sendString, // so that we don't make life unnecessarily hard for consumers by // possibly starting multiple searches. - win.gURLBar._setValue( - text.substr(0, text.length - 1), - false /* allowTrim = */ - ); + win.gURLBar._setValue(text.substr(0, text.length - 1)); } this.EventUtils.sendString(text.substr(-1, 1), win); }, @@ -1490,7 +1487,7 @@ class TestProvider extends UrlbarProvider { * @param {Function} [options.onSelection] * If given, a function that will be called when * {@link UrlbarView.#selectElement} method is called. - * @param {Function} [options.onEngagement] + * @param {Function} [options.onLegacyEngagement] * If given, a function that will be called when engagement. * @param {Function} [options.delayResultsPromise] * If given, we'll await on this before returning results. @@ -1503,7 +1500,7 @@ class TestProvider extends UrlbarProvider { addTimeout = 0, onCancel = null, onSelection = null, - onEngagement = null, + onLegacyEngagement = null, delayResultsPromise = null, } = {}) { if (delayResultsPromise && addTimeout) { @@ -1520,7 +1517,7 @@ class TestProvider extends UrlbarProvider { this._type = type; this._onCancel = onCancel; this._onSelection = onSelection; - this._onEngagement = onEngagement; + this._onLegacyEngagement = onLegacyEngagement; // As this has been a common source of mistakes, auto-upgrade the provider // type to heuristic if any result is heuristic. @@ -1574,8 +1571,8 @@ class TestProvider extends UrlbarProvider { this._onSelection?.(result, element); } - onEngagement(state, queryContext, details, controller) { - this._onEngagement?.(state, queryContext, details, controller); + onLegacyEngagement(state, queryContext, details, controller) { + this._onLegacyEngagement?.(state, queryContext, details, controller); } } diff --git a/browser/components/urlbar/tests/browser-tips/browser_picks.js b/browser/components/urlbar/tests/browser-tips/browser_picks.js index ba0ff69357..c9d725dfb5 100644 --- a/browser/components/urlbar/tests/browser-tips/browser_picks.js +++ b/browser/components/urlbar/tests/browser-tips/browser_picks.js @@ -117,8 +117,8 @@ async function doTest({ click, buttonUrl = undefined, helpUrl = undefined }) { }); UrlbarProvidersManager.registerProvider(provider); - let onEngagementPromise = new Promise( - resolve => (provider.onEngagement = resolve) + let onLegacyEngagementPromise = new Promise( + resolve => (provider.onLegacyEngagement = resolve) ); // Do a search to show our tip result. @@ -142,8 +142,8 @@ async function doTest({ click, buttonUrl = undefined, helpUrl = undefined }) { ); } - // Now pick the target and wait for provider.onEngagement to be called and - // the URL to load if necessary. + // Now pick the target and wait for provider.onLegacyEngagement to be called + // and the URL to load if necessary. let loadPromise; if (buttonUrl || helpUrl) { loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); @@ -160,7 +160,7 @@ async function doTest({ click, buttonUrl = undefined, helpUrl = undefined }) { EventUtils.synthesizeKey("KEY_Enter"); } }); - await onEngagementPromise; + await onLegacyEngagementPromise; await loadPromise; // Check telemetry. diff --git a/browser/components/urlbar/tests/browser-tips/browser_searchTips.js b/browser/components/urlbar/tests/browser-tips/browser_searchTips.js index a82a2d658b..8c98e27993 100644 --- a/browser/components/urlbar/tests/browser-tips/browser_searchTips.js +++ b/browser/components/urlbar/tests/browser-tips/browser_searchTips.js @@ -18,7 +18,7 @@ ChromeUtils.defineESModuleGetters(this, { "resource:///modules/UrlbarProviderSearchTips.sys.mjs", }); -// These should match the same consts in UrlbarProviderSearchTips.jsm. +// These should match the same consts in UrlbarProviderSearchTips.sys.mjs. const MAX_SHOWN_COUNT = 4; const LAST_UPDATE_THRESHOLD_MS = 24 * 60 * 60 * 1000; 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 72d05cf632..6c0550a2df 100644 --- a/browser/components/urlbar/tests/browser-tips/browser_searchTips_interaction.js +++ b/browser/components/urlbar/tests/browser-tips/browser_searchTips_interaction.js @@ -25,7 +25,7 @@ XPCOMUtils.defineLazyServiceGetter( "nsIClipboardHelper" ); -// These should match the same consts in UrlbarProviderSearchTips.jsm. +// These should match the same consts in UrlbarProviderSearchTips.sys.mjs. const MAX_SHOWN_COUNT = 4; const LAST_UPDATE_THRESHOLD_MS = 24 * 60 * 60 * 1000; diff --git a/browser/components/urlbar/tests/browser/browser.toml b/browser/components/urlbar/tests/browser/browser.toml index b9934aa838..44b964e5ca 100644 --- a/browser/components/urlbar/tests/browser/browser.toml +++ b/browser/components/urlbar/tests/browser/browser.toml @@ -4,7 +4,11 @@ support-files = [ "head.js", "head-common.js", ] - +skip-if = [ + "os == 'linux' && os_version == '18.04' && asan", # long running manifest + "os == 'linux' && os_version == '18.04' && tsan", # long running manifest + "win11_2009 && asan", # long running manifest +] prefs = [ "browser.bookmarks.testing.skipDefaultBookmarksImport=true", "browser.urlbar.trending.featureGate=false", @@ -280,6 +284,8 @@ support-files = [ ["browser_keyword_select_and_type.js"] +["browser_less_common_selection_manipulations.js"] + ["browser_loadRace.js"] ["browser_locationBarCommand.js"] @@ -398,9 +404,6 @@ https_first_disabled = true ["browser_revert.js"] -["browser_search_continuation.js"] -support-files = ["search-engines", "../../../search/test/browser/trendingSuggestionEngine.sjs"] - ["browser_searchFunction.js"] ["browser_searchHistoryLimit.js"] @@ -490,6 +493,9 @@ support-files = [ ["browser_search_bookmarks_from_bookmarks_menu.js"] +["browser_search_continuation.js"] +support-files = ["search-engines", "../../../search/test/browser/trendingSuggestionEngine.sjs"] + ["browser_search_history_from_history_panel.js"] ["browser_selectStaleResults.js"] diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js index f191cae321..d01734959a 100644 --- a/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js +++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_overflow.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -async function testVal(aExpected, overflowSide = "") { +async function testVal(aExpected, overflowSide = null) { info(`Testing ${aExpected}`); try { gURLBar.setURI(makeURI(aExpected)); diff --git a/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js b/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js index 427a7419c8..bb710c7065 100644 --- a/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js +++ b/browser/components/urlbar/tests/browser/browser_aboutHomeLoading.js @@ -98,7 +98,7 @@ add_task(async function clearURLBarAfterManuallyLoadingAboutHome() { () => {} ); // This opens about:newtab: - BrowserOpenTab(); + BrowserCommands.openTab(); let tab = await promiseTabOpenedAndSwitchedTo; is(gURLBar.value, "", "URL bar should be empty"); is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null"); @@ -132,7 +132,7 @@ add_task(async function dontTemporarilyShowAboutHome() { let win = OpenBrowserWindow(); await windowOpenedPromise; let promiseTabSwitch = BrowserTestUtils.switchTab(win.gBrowser, () => {}); - win.BrowserOpenTab(); + win.BrowserCommands.openTab(); await promiseTabSwitch; currentBrowser = win.gBrowser.selectedBrowser; is(win.gBrowser.visibleTabs.length, 2, "2 tabs opened"); diff --git a/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js b/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js index 8c4b05501e..54f40a85ee 100644 --- a/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js +++ b/browser/components/urlbar/tests/browser/browser_acknowledgeFeedbackAndDismissal.js @@ -389,11 +389,11 @@ class TestProvider extends UrlbarTestUtils.TestProvider { ]; } - onEngagement(state, queryContext, details, controller) { + onLegacyEngagement(state, queryContext, details, controller) { if (details.result?.providerName == this.name) { let { selType } = details; - info(`onEngagement called, selType=` + selType); + info(`onLegacyEngagement called, selType=` + selType); if (!this.commandCount.hasOwnProperty(selType)) { this.commandCount[selType] = 0; 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 3eaa53bcda..e1d352a171 100644 --- a/browser/components/urlbar/tests/browser/browser_copy_during_load.js +++ b/browser/components/urlbar/tests/browser/browser_copy_during_load.js @@ -45,7 +45,7 @@ add_task(async function () { null, true ); - BrowserStop(); + BrowserCommands.stop(); await browserStoppedPromise; }); }); diff --git a/browser/components/urlbar/tests/browser/browser_dynamicResults.js b/browser/components/urlbar/tests/browser/browser_dynamicResults.js index aad15e0145..2ba1b7ab5f 100644 --- a/browser/components/urlbar/tests/browser/browser_dynamicResults.js +++ b/browser/components/urlbar/tests/browser/browser_dynamicResults.js @@ -511,7 +511,7 @@ add_task(async function shouldNavigate() { await UrlbarTestUtils.promisePopupClose(window, () => EventUtils.synthesizeKey("KEY_Enter") ); - // Verify that onEngagement was still called. + // Verify that onLegacyEngagement was still called. let [result, pickedElement] = await pickPromise; Assert.equal(result, row.result, "Picked result"); Assert.equal(pickedElement, element, "Picked element"); @@ -904,7 +904,7 @@ class TestProvider extends UrlbarTestUtils.TestProvider { }; } - onEngagement(state, queryContext, details, _controller) { + onLegacyEngagement(state, queryContext, details, _controller) { if (this._pickPromiseResolve) { let { result, element } = details; this._pickPromiseResolve([result, element]); diff --git a/browser/components/urlbar/tests/browser/browser_engagement.js b/browser/components/urlbar/tests/browser/browser_engagement.js index b1998b6f55..fbc321e322 100644 --- a/browser/components/urlbar/tests/browser/browser_engagement.js +++ b/browser/components/urlbar/tests/browser/browser_engagement.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Tests the UrlbarProvider.onEngagement() method. +// Tests the UrlbarProvider.onLegacyEngagement() method. "use strict"; @@ -110,32 +110,21 @@ async function doTest({ let provider = new TestProvider(); UrlbarProvidersManager.registerProvider(provider); - let startPromise = provider.promiseEngagement(); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window: win, value: "test", fireInputEvent: true, }); - let [state, queryContext, details, controller] = await startPromise; - Assert.equal( - controller.input.isPrivate, - expectedIsPrivate, - "Start isPrivate" - ); - Assert.equal(state, "start", "Start state"); - - // `queryContext` isn't always defined for `start`, and `onEngagement` - // shouldn't rely on it being defined on start, but there's no good reason to - // assert that it's not defined here. - - // Similarly, `details` is never defined for `start`, but there's no good - // reason to assert that it's not defined. - let endPromise = provider.promiseEngagement(); let { result, element } = (await endEngagement()) ?? {}; - [state, queryContext, details, controller] = await endPromise; + let [state, queryContext, details, controller] = await endPromise; + + Assert.ok( + ["engagement", "abandonment"].includes(state), + "State should be either 'engagement' or 'abandonment'" + ); Assert.equal(controller.input.isPrivate, expectedIsPrivate, "End isPrivate"); Assert.equal(state, expectedEndState, "End state"); Assert.ok(queryContext, "End queryContext"); @@ -179,7 +168,7 @@ async function doTest({ } /** - * Test provider that resolves promises when onEngagement is called. + * Test provider that resolves promises when onLegacyEngagement is called. */ class TestProvider extends UrlbarTestUtils.TestProvider { _resolves = []; @@ -197,7 +186,7 @@ class TestProvider extends UrlbarTestUtils.TestProvider { }); } - onEngagement(...args) { + onLegacyEngagement(...args) { let resolve = this._resolves.shift(); if (resolve) { resolve(args); diff --git a/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js b/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js new file mode 100644 index 0000000000..2ad6ee0e07 --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js @@ -0,0 +1,288 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests less common mouse/keyboard manipulations of the address bar input + * field selection, for example: + * - Home/Del + * - Shift+Right/Left + * - Drag selection + * - Double-click on word + * + * All the tests set up some initial conditions, and check it. Then optionally + * they can manipulate the selection further, and check the results again. + * We want to ensure the final selection is the expected one, even if in the + * future we change our trimming strategy for the input field value. + */ + +const tests = [ + { + description: "Test HOME starting from full selection", + openPanel() { + EventUtils.synthesizeKey("l", { accelKey: true }); + }, + get selection() { + return [0, gURLBar.value.length]; + }, + manipulate() { + // Cursor must move to the first visible character, regardless of any + // "untrimming" we could be doing. + this._visibleValue = gURLBar.value; + if (AppConstants.platform == "macosx") { + EventUtils.synthesizeKey("KEY_ArrowLeft", { metaKey: true }); + } else { + EventUtils.synthesizeKey("KEY_Home"); + } + }, + get modifiedSelection() { + let start = gURLBar.value.indexOf(this._visibleValue); + return [start, start]; + }, + }, + { + description: "Test END starting from full selection", + openPanel() { + EventUtils.synthesizeKey("l", { accelKey: true }); + }, + get selection() { + return [0, gURLBar.value.length]; + }, + manipulate() { + if (AppConstants.platform == "macosx") { + EventUtils.synthesizeKey("KEY_ArrowRight", { metaKey: true }); + } else { + EventUtils.synthesizeKey("KEY_End", {}); + } + }, + get modifiedSelection() { + return [gURLBar.value.length, gURLBar.value.length]; + }, + }, + { + description: "Test SHIFT+LEFT starting from full selection", + openPanel() { + EventUtils.synthesizeKey("l", { accelKey: true }); + }, + get selection() { + return [0, gURLBar.value.length]; + }, + manipulate() { + EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }); + }, + get modifiedSelection() { + return [0, gURLBar.value.length - 1]; + }, + }, + { + description: "Test SHIFT+RIGHT starting from full selection", + openPanel() { + EventUtils.synthesizeKey("l", { accelKey: true }); + }, + get selection() { + return [0, gURLBar.value.length]; + }, + manipulate() { + EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }); + }, + get modifiedSelection() { + return [0, gURLBar.value.length]; + }, + }, + { + description: "Test Drag Selection from the first character", + async openPanel() { + this._expectedSelectedText = gURLBar.value.substring(0, 5); + await selectWithMouseDrag( + getTextWidth(gURLBar.value[0]) / 2 - 1, + getTextWidth(gURLBar.value.substring(0, 5)) + ); + }, + get selection() { + return [ + 0, + gURLBar.value.indexOf(this._expectedSelectedText) + + this._expectedSelectedText.length, + ]; + }, + }, + { + description: "Test Drag Selection from the last character", + async openPanel() { + this._expectedSelectedText = gURLBar.value.substring(-5); + await selectWithMouseDrag( + getTextWidth(gURLBar.value) + 1, + getTextWidth(this._expectedSelectedText) + ); + }, + get selection() { + return [ + gURLBar.value.indexOf(this._expectedSelectedText), + gURLBar.value.length, + ]; + }, + }, + { + description: "Test Drag Selection in the middle of the string", + async openPanel() { + this._expectedSelectedText = gURLBar.value.substring(5, 10); + await selectWithMouseDrag( + getTextWidth(gURLBar.value.substring(0, 5)), + getTextWidth(gURLBar.value.substring(0, 10)) + ); + }, + get selection() { + let start = gURLBar.value.indexOf(this._expectedSelectedText); + return [start, start + this._expectedSelectedText.length]; + }, + }, + { + description: "Test Double-click on word", + async openPanel() { + let wordBoundaryIndex = gURLBar.value.search(/\btest/); + this._expectedSelectedText = "test"; + await selectWithDoubleClick( + getTextWidth(gURLBar.value.substring(0, wordBoundaryIndex)) + ); + }, + get selection() { + let start = gURLBar.value.indexOf(this._expectedSelectedText); + return [start, start + this._expectedSelectedText.length]; + }, + }, + { + description: "Click at the right of the text", + openPanel() { + EventUtils.synthesizeKey("l", { accelKey: true }); + }, + get selection() { + return [0, gURLBar.value.length]; + }, + manipulate() { + let rect = gURLBar.inputField.getBoundingClientRect(); + EventUtils.synthesizeMouse( + gURLBar.inputField, + getTextWidth(gURLBar.value) + 10, + rect.height / 2, + {} + ); + }, + get modifiedSelection() { + return [gURLBar.value.length, gURLBar.value.length]; + }, + }, +]; + +add_setup(async function () { + gURLBar.inputField.style.font = "14px monospace"; + registerCleanupFunction(() => { + gURLBar.inputField.style.font = null; + }); +}); + +add_task(async function https() { + await doTest("https://example.com/test/some/page.htm"); +}); + +add_task(async function http() { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await doTest("http://example.com/test/other/page.htm"); +}); + +async function doTest(url) { + await BrowserTestUtils.withNewTab(url, async () => { + for (let test of tests) { + gURLBar.blur(); + info(test.description); + await UrlbarTestUtils.promisePopupOpen(window, async () => { + await test.openPanel(); + }); + info( + `Selected text is <${gURLBar.value.substring( + gURLBar.selectionStart, + gURLBar.selectionEnd + )}>` + ); + Assert.deepEqual( + test.selection, + [gURLBar.selectionStart, gURLBar.selectionEnd], + "Check selection" + ); + + if (test.manipulate) { + await test.manipulate(); + info( + `Selected text is <${gURLBar.value.substring( + gURLBar.selectionStart, + gURLBar.selectionEnd + )}>` + ); + Assert.deepEqual( + test.modifiedSelection, + [gURLBar.selectionStart, gURLBar.selectionEnd], + "Check selection after manipulation" + ); + } + } + }); +} + +function getTextWidth(inputText) { + const canvas = + getTextWidth.canvas || + (getTextWidth.canvas = document.createElement("canvas")); + let context = canvas.getContext("2d"); + context.font = window + .getComputedStyle(gURLBar.inputField) + .getPropertyValue("font"); + return context.measureText(inputText).width; +} + +function selectWithMouseDrag(fromX, toX) { + let target = gURLBar.inputField; + let rect = target.getBoundingClientRect(); + let promise = BrowserTestUtils.waitForEvent(target, "mouseup"); + EventUtils.synthesizeMouse( + target, + fromX, + rect.height / 2, + { type: "mousemove" }, + target.ownerGlobal + ); + EventUtils.synthesizeMouse( + target, + fromX, + rect.height / 2, + { type: "mousedown" }, + target.ownerGlobal + ); + EventUtils.synthesizeMouse( + target, + toX, + rect.height / 2, + { type: "mousemove" }, + target.ownerGlobal + ); + EventUtils.synthesizeMouse( + target, + toX, + rect.height / 2, + { type: "mouseup" }, + target.ownerGlobal + ); + return promise; +} + +function selectWithDoubleClick(offsetX) { + let target = gURLBar.inputField; + let rect = target.getBoundingClientRect(); + let promise = BrowserTestUtils.waitForEvent(target, "dblclick"); + EventUtils.synthesizeMouse(target, offsetX, rect.height / 2, { + clickCount: 1, + }); + EventUtils.synthesizeMouse(target, offsetX, rect.height / 2, { + clickCount: 2, + }); + return promise; +} diff --git a/browser/components/urlbar/tests/browser/browser_locationBarCommand.js b/browser/components/urlbar/tests/browser/browser_locationBarCommand.js index 84c45e586a..92409f979f 100644 --- a/browser/components/urlbar/tests/browser/browser_locationBarCommand.js +++ b/browser/components/urlbar/tests/browser/browser_locationBarCommand.js @@ -276,7 +276,7 @@ async function typeAndCommand(eventType, details = {}) { async function triggerCommand(eventType, details = {}) { Assert.equal( await UrlbarTestUtils.promiseUserContextId(window), - gBrowser.selectedTab.getAttribute("usercontextid"), + gBrowser.selectedTab.getAttribute("usercontextid") || "", "userContextId must be the same as the originating tab" ); diff --git a/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js b/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js index 2f8e871bfe..66a8ed3a41 100644 --- a/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js +++ b/browser/components/urlbar/tests/browser/browser_primary_selection_safe_on_new_tab.js @@ -42,7 +42,7 @@ add_task(async function () { // tab button. let userInput = window.windowUtils.setHandlingUserInput(true); try { - BrowserOpenTab(); + BrowserCommands.openTab(); } finally { userInput.destruct(); } diff --git a/browser/components/urlbar/tests/browser/browser_raceWithTabs.js b/browser/components/urlbar/tests/browser/browser_raceWithTabs.js index 17560ea101..821aa0f0ee 100644 --- a/browser/components/urlbar/tests/browser/browser_raceWithTabs.js +++ b/browser/components/urlbar/tests/browser/browser_raceWithTabs.js @@ -41,7 +41,7 @@ add_task(async function hitEnterLoadInRightTab() { gBrowser.tabContainer, "TabOpen" ); - BrowserOpenTab(); + BrowserCommands.openTab(); let oldTab = (await oldTabOpenPromise).target; let oldTabLoadedPromise = BrowserTestUtils.browserLoaded( oldTab.linkedBrowser, @@ -60,7 +60,7 @@ add_task(async function hitEnterLoadInRightTab() { EventUtils.sendKey("return"); info("Immediately open a second tab"); - BrowserOpenTab(); + BrowserCommands.openTab(); let newTab = (await tabOpenPromise).target; info("Created new tab; waiting for tabs to load"); diff --git a/browser/components/urlbar/tests/browser/browser_result_menu.js b/browser/components/urlbar/tests/browser/browser_result_menu.js index ccbe247598..f00b92fa63 100644 --- a/browser/components/urlbar/tests/browser/browser_result_menu.js +++ b/browser/components/urlbar/tests/browser/browser_result_menu.js @@ -201,10 +201,10 @@ add_task(async function firefoxSuggest() { ], }); - // Implement the provider's `onEngagement()` so it removes the result. - let onEngagementCallCount = 0; - provider.onEngagement = (state, queryContext, details, controller) => { - onEngagementCallCount++; + // Implement the provider's `onLegacyEngagement()` so it removes the result. + let onLegacyEngagementCallCount = 0; + provider.onLegacyEngagement = (state, queryContext, details, controller) => { + onLegacyEngagementCallCount++; controller.removeResult(details.result); }; @@ -245,9 +245,9 @@ add_task(async function firefoxSuggest() { }); Assert.greater( - onEngagementCallCount, + onLegacyEngagementCallCount, 0, - "onEngagement() should have been called" + "onLegacyEngagement() should have been called" ); Assert.equal( UrlbarTestUtils.getResultCount(window), diff --git a/browser/components/urlbar/tests/browser/browser_stop_pending.js b/browser/components/urlbar/tests/browser/browser_stop_pending.js index 50f5dfdeec..938a57dc28 100644 --- a/browser/components/urlbar/tests/browser/browser_stop_pending.js +++ b/browser/components/urlbar/tests/browser/browser_stop_pending.js @@ -125,7 +125,7 @@ add_task(async function () { null, true ); - BrowserStop(); + BrowserCommands.stop(); await browserStoppedPromise; is( @@ -207,7 +207,7 @@ add_task(async function () { null, true ); - BrowserStop(); + BrowserCommands.stop(); await browserStoppedPromise; is( diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js index 318b29ad19..b2591a0c14 100644 --- a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js +++ b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_tabtosearch.js @@ -344,8 +344,8 @@ async function impressions_test(isOnboarding) { 5 ); - // See javadoc for UrlbarProviderTabToSearch.onEngagement for discussion - // about retained results. + // See javadoc for UrlbarProviderTabToSearch.onLegacyEngagement for + // discussion about retained results. info("Reopen the result set with retained results. Record impression."); await UrlbarTestUtils.promisePopupOpen(window, () => { EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml b/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml index cf6bc80318..a72f2d9b8d 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser.toml @@ -26,8 +26,6 @@ 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"] @@ -36,6 +34,8 @@ prefs = ["browser.bookmarks.testing.skipDefaultBookmarksImport=true"] ["browser_glean_telemetry_abandonment_tips.js"] +["browser_glean_telemetry_abandonment_type.js"] + ["browser_glean_telemetry_engagement_edge_cases.js"] ["browser_glean_telemetry_engagement_groups.js"] @@ -66,4 +66,8 @@ skip-if = ["verify"] # Bug 1852375 - MerinoTestUtils.initWeather() doesn't play ["browser_glean_telemetry_exposure_edge_cases.js"] +["browser_glean_telemetry_potential_exposure.js"] + ["browser_glean_telemetry_record_preferences.js"] + +["browser_glean_telemetry_reenter.js"] 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 index 99145d7cc3..b8a16bd10c 100644 --- 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 @@ -19,7 +19,7 @@ function checkUrlbarFocus(win, focusState) { // URL bar records the correct abandonment telemetry with abandonment type // "tab_swtich". add_task(async function tabSwitchFocusedToFocused() { - await doTest(async browser => { + await doTest(async () => { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "test search", @@ -45,7 +45,7 @@ add_task(async function tabSwitchFocusedToFocused() { // URL bar loses focus logs abandonment telemetry with abandonment type // "blur". add_task(async function tabSwitchFocusedToUnfocused() { - await doTest(async browser => { + await doTest(async () => { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "test search", @@ -65,7 +65,7 @@ add_task(async function tabSwitchFocusedToUnfocused() { // 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 => { + await doTest(async () => { checkUrlbarFocus(window, false); let promiseTabOpened = BrowserTestUtils.waitForEvent( @@ -91,7 +91,7 @@ add_task(async function tabSwitchUnFocusedToFocused() { // 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 => { + await doTest(async () => { checkUrlbarFocus(window, false); let tab2 = await BrowserTestUtils.openNewForegroundTab(window.gBrowser); 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 ff31bdc52a..053d307088 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 @@ -63,7 +63,7 @@ add_task(async function selected_result_tip() { ), ], priority: 1, - onEngagement: () => { + onLegacyEngagement: () => { deferred.resolve(); }, }); 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 6b1dedbce2..59c4460e52 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 @@ -101,11 +101,32 @@ add_task(async function engagement_type_dismiss() { }); add_task(async function engagement_type_help() { - const cleanupQuickSuggest = await ensureQuickSuggestInit(); + const url = "https://example.com/"; + const helpUrl = "https://example.com/help"; + let provider = new UrlbarTestUtils.TestProvider({ + priority: Infinity, + results: [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + { + url, + isBlockable: true, + blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest" }, + helpUrl, + helpL10n: { + id: "urlbar-result-menu-learn-more-about-firefox-suggest", + }, + } + ), + ], + }); + UrlbarProvidersManager.registerProvider(provider); await doTest(async () => { - await openPopup("sponsored"); - await selectRowByURL("https://example.com/sponsored"); + await openPopup("test"); + await selectRowByURL(url); + const onTabOpened = BrowserTestUtils.waitForNewTab(gBrowser); UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "L"); const tab = await onTabOpened; @@ -114,5 +135,26 @@ add_task(async function engagement_type_help() { assertEngagementTelemetry([{ engagement_type: "help" }]); }); + UrlbarProvidersManager.unregisterProvider(provider); +}); + +add_task(async function engagement_type_manage() { + const cleanupQuickSuggest = await ensureQuickSuggestInit(); + + await doTest(async () => { + await openPopup("sponsored"); + await selectRowByURL("https://example.com/sponsored"); + + const onManagePageLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + "about:preferences#search" + ); + UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "M"); + await onManagePageLoaded; + + assertEngagementTelemetry([{ engagement_type: "manage" }]); + }); + await cleanupQuickSuggest(); }); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js index 07e8b9b360..ef2ec623bc 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_exposure.js @@ -11,7 +11,7 @@ add_setup(async function () { await initExposureTest(); }); -add_task(async function exposureSponsoredOnEngagement() { +add_task(async function exposureSponsoredOnLegacyEngagement() { await doExposureTest({ prefs: [ ["browser.urlbar.exposureResults", suggestResultType("adm_sponsored")], diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_potential_exposure.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_potential_exposure.js new file mode 100644 index 0000000000..275e3968eb --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_potential_exposure.js @@ -0,0 +1,438 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Tests the `urlbar-potential-exposure` ping. + +const WAIT_FOR_PING_TIMEOUT_MS = 1000; + +// Avoid timeouts in verify mode, especially on Mac. +requestLongerTimeout(3); + +add_setup(async function test_setup() { + Services.fog.testResetFOG(); + + // Add a mock engine so we don't hit the network. + await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); + + registerCleanupFunction(() => { + Services.fog.testResetFOG(); + }); +}); + +add_task(async function oneKeyword_noMatch_1() { + await doTest({ + keywords: ["example"], + searchStrings: ["exam"], + expectedEvents: [], + }); +}); + +add_task(async function oneKeyword_noMatch_2() { + await doTest({ + keywords: ["exam"], + searchStrings: ["example"], + expectedEvents: [], + }); +}); + +add_task(async function oneKeyword_oneMatch_terminal_1() { + await doTest({ + keywords: ["example"], + searchStrings: ["example"], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function oneKeyword_oneMatch_terminal_2() { + await doTest({ + keywords: ["example"], + searchStrings: ["exam", "example"], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function oneKeyword_oneMatch_nonterminal_1() { + await doTest({ + keywords: ["example"], + searchStrings: ["example", "exam"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function oneKeyword_oneMatch_nonterminal_2() { + await doTest({ + keywords: ["example"], + searchStrings: ["ex", "example", "exam"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_terminal_1() { + await doTest({ + keywords: ["example"], + searchStrings: ["example", "example"], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_terminal_2() { + await doTest({ + keywords: ["example"], + searchStrings: ["example", "exampl", "example"], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_terminal_3() { + await doTest({ + keywords: ["example"], + searchStrings: ["exam", "example", "example"], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_terminal_4() { + await doTest({ + keywords: ["example"], + searchStrings: ["exam", "example", "exampl", "example"], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_nonterminal_1() { + await doTest({ + keywords: ["example"], + searchStrings: ["example", "example", "exampl"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_nonterminal_2() { + await doTest({ + keywords: ["example"], + searchStrings: ["exam", "example", "example", "exampl"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_nonterminal_3() { + await doTest({ + keywords: ["example"], + searchStrings: ["example", "exam", "example", "exampl"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function oneKeyword_dupeMatches_nonterminal_4() { + await doTest({ + keywords: ["example"], + searchStrings: ["exam", "example", "exampl", "example", "exampl"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function manyKeywords_noMatch() { + await doTest({ + keywords: ["foo", "bar", "baz"], + searchStrings: ["example"], + expectedEvents: [], + }); +}); + +add_task(async function manyKeywords_oneMatch_terminal_1() { + await doTest({ + keywords: ["foo", "bar", "baz"], + searchStrings: ["bar"], + expectedEvents: [{ extra: { keyword: "bar", terminal: true } }], + }); +}); + +add_task(async function manyKeywords_oneMatch_terminal_2() { + await doTest({ + keywords: ["foo", "bar", "baz"], + searchStrings: ["example", "bar"], + expectedEvents: [{ extra: { keyword: "bar", terminal: true } }], + }); +}); + +add_task(async function manyKeywords_oneMatch_nonterminal_1() { + await doTest({ + keywords: ["foo", "bar", "baz"], + searchStrings: ["bar", "example"], + expectedEvents: [{ extra: { keyword: "bar", terminal: false } }], + }); +}); + +add_task(async function manyKeywords_oneMatch_nonterminal_2() { + await doTest({ + keywords: ["foo", "bar", "baz"], + searchStrings: ["exam", "bar", "example"], + expectedEvents: [{ extra: { keyword: "bar", terminal: false } }], + }); +}); + +add_task(async function manyKeywords_manyMatches_terminal_1() { + let keywords = ["foo", "bar", "baz"]; + await doTest({ + keywords, + searchStrings: keywords, + expectedEvents: keywords.map((keyword, i) => ({ + extra: { keyword, terminal: i == keywords.length - 1 }, + })), + }); +}); + +add_task(async function manyKeywords_manyMatches_terminal_2() { + let keywords = ["foo", "bar", "baz"]; + await doTest({ + keywords, + searchStrings: ["exam", "foo", "exampl", "bar", "example", "baz"], + expectedEvents: keywords.map((keyword, i) => ({ + extra: { keyword, terminal: i == keywords.length - 1 }, + })), + }); +}); + +add_task(async function manyKeywords_manyMatches_nonterminal_1() { + let keywords = ["foo", "bar", "baz"]; + await doTest({ + keywords, + searchStrings: ["foo", "bar", "baz", "example"], + expectedEvents: keywords.map(keyword => ({ + extra: { keyword, terminal: false }, + })), + }); +}); + +add_task(async function manyKeywords_manyMatches_nonterminal_2() { + let keywords = ["foo", "bar", "baz"]; + await doTest({ + keywords, + searchStrings: ["exam", "foo", "exampl", "bar", "example", "baz", "exam"], + expectedEvents: keywords.map(keyword => ({ + extra: { keyword, terminal: false }, + })), + }); +}); + +add_task(async function manyKeywords_dupeMatches_terminal() { + let keywords = ["foo", "bar", "baz"]; + let searchStrings = [...keywords, ...keywords]; + await doTest({ + keywords, + searchStrings, + expectedEvents: keywords.map((keyword, i) => ({ + extra: { keyword, terminal: i == keywords.length - 1 }, + })), + }); +}); + +add_task(async function manyKeywords_dupeMatches_nonterminal() { + let keywords = ["foo", "bar", "baz"]; + let searchStrings = [...keywords, ...keywords, "example"]; + await doTest({ + keywords, + searchStrings, + expectedEvents: keywords.map(keyword => ({ + extra: { keyword, terminal: false }, + })), + }); +}); + +add_task(async function searchStringNormalization_terminal() { + await doTest({ + keywords: ["example"], + searchStrings: [" ExaMPLe "], + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); +}); + +add_task(async function searchStringNormalization_nonterminal() { + await doTest({ + keywords: ["example"], + searchStrings: [" ExaMPLe ", "foo"], + expectedEvents: [{ extra: { keyword: "example", terminal: false } }], + }); +}); + +add_task(async function multiWordKeyword() { + await doTest({ + keywords: ["this has multiple words"], + searchStrings: ["this has multiple words"], + expectedEvents: [ + { extra: { keyword: "this has multiple words", terminal: true } }, + ], + }); +}); + +// Smoke test that ends a session with an engagement instead of an abandonment +// as other tasks in this file do. +add_task(async function engagement() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + await doTest({ + keywords: ["example"], + searchStrings: ["example"], + endSession: () => + // Hit the Enter key on the heuristic search result. + UrlbarTestUtils.promisePopupClose(window, () => + EventUtils.synthesizeKey("KEY_Enter") + ), + expectedEvents: [{ extra: { keyword: "example", terminal: true } }], + }); + }); +}); + +// Smoke test that uses Nimbus to set keywords instead of a pref as other tasks +// in this file do. +add_task(async function nimbus() { + let keywords = ["foo", "bar", "baz"]; + await doTest({ + useNimbus: true, + keywords, + searchStrings: keywords, + expectedEvents: keywords.map((keyword, i) => ({ + extra: { keyword, terminal: i == keywords.length - 1 }, + })), + }); +}); + +// The ping should not be submitted for sessions in private windows. +add_task(async function privateWindow() { + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + await doTest({ + win: privateWin, + keywords: ["example"], + searchStrings: ["example"], + expectedEvents: [], + }); + await BrowserTestUtils.closeWindow(privateWin); +}); + +add_task(async function invalidPotentialExposureKeywords_pref() { + await doTest({ + keywords: "not an array of keywords", + searchStrings: ["example", "not an array of keywords"], + expectedEvents: [], + }); +}); + +add_task(async function invalidPotentialExposureKeywords_nimbus() { + await doTest({ + useNimbus: true, + keywords: "not an array of keywords", + searchStrings: ["example", "not an array of keywords"], + expectedEvents: [], + }); +}); + +async function doTest({ + keywords, + searchStrings, + expectedEvents, + endSession = null, + useNimbus = false, + win = window, +}) { + endSession ||= () => + UrlbarTestUtils.promisePopupClose(win, () => win.gURLBar.blur()); + + let nimbusCleanup; + let keywordsJson = JSON.stringify(keywords); + if (useNimbus) { + nimbusCleanup = await UrlbarTestUtils.initNimbusFeature({ + potentialExposureKeywords: keywordsJson, + }); + } else { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.potentialExposureKeywords", keywordsJson]], + }); + } + + let pingPromise = waitForPing(); + + for (let value of searchStrings) { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + value, + window: win, + }); + } + await endSession(); + + // Wait `WAIT_FOR_PING_TIMEOUT_MS` for the ping to be submitted before + // reporting a timeout. Note that some tasks do not expect a ping to be + // submitted, and they rely on this timeout behavior. + info("Awaiting ping promise"); + let events = null; + events = await Promise.race([ + pingPromise, + new Promise(resolve => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + if (!events) { + info("Timed out waiting for ping"); + } + resolve([]); + }, WAIT_FOR_PING_TIMEOUT_MS) + ), + ]); + + assertEvents(events, expectedEvents); + + if (nimbusCleanup) { + await nimbusCleanup(); + } else { + await SpecialPowers.popPrefEnv(); + } + Services.fog.testResetFOG(); +} + +function waitForPing() { + return new Promise(resolve => { + GleanPings.urlbarPotentialExposure.testBeforeNextSubmit(() => { + let events = Glean.urlbar.potentialExposure.testGetValue(); + info("testBeforeNextSubmit got events: " + JSON.stringify(events)); + resolve(events); + }); + }); +} + +function assertEvents(actual, expected) { + info("Comparing events: " + JSON.stringify({ actual, expected })); + + // Add some expected boilerplate properties to the expected events so that + // callers don't have to but so that we still check them. + expected = expected.map(e => ({ + category: "urlbar", + name: "potential_exposure", + // `testGetValue()` stringifies booleans for some reason. Let callers + // specify booleans since booleans are correct, and stringify them here. + ...stringifyBooleans(e), + })); + + // Filter out properties from the actual events that aren't defined in the + // expected events. Ignore unimportant properties like timestamps. + actual = actual.map((a, i) => + Object.fromEntries( + Object.entries(a).filter(([key]) => expected[i]?.hasOwnProperty(key)) + ) + ); + + Assert.deepEqual(actual, expected, "Checking expected Glean events"); +} + +function stringifyBooleans(obj) { + let newObj = {}; + for (let [key, value] of Object.entries(obj)) { + if (value && typeof value == "object") { + newObj[key] = stringifyBooleans(value); + } else if (typeof value == "boolean") { + newObj[key] = String(value); + } else { + newObj[key] = value; + } + } + return newObj; +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_reenter.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_reenter.js new file mode 100644 index 0000000000..51bdc84870 --- /dev/null +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_reenter.js @@ -0,0 +1,79 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test we don't re-enter record() (and record both an engagement and an +// abandonment) when handling an engagement blurs the input field. + +const TEST_URL = "https://example.com/"; + +add_task(async function () { + await setup(); + let deferred = Promise.withResolvers(); + const provider = new UrlbarTestUtils.TestProvider({ + results: [ + new UrlbarResult( + UrlbarUtils.RESULT_TYPE.URL, + UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + { + url: TEST_URL, + helpUrl: "https://example.com/help", + helpL10n: { + id: "urlbar-result-menu-tip-get-help", + }, + } + ), + ], + priority: 999, + onLegacyEngagement: () => { + info("Blur the address bar during the onLegacyEngagement notification"); + gURLBar.blur(); + // Run at the next tick to be sure spurious events would have happened. + TestUtils.waitForTick().then(() => { + deferred.resolve(); + }); + }, + }); + UrlbarProvidersManager.registerProvider(provider); + // This should cover at least engagement and abandonment. + let engagementSpy = sinon.spy(provider, "onLegacyEngagement"); + + let beforeRecordCall = false, + recordReentered = false; + let recordStub = sinon + .stub(gURLBar.controller.engagementEvent, "record") + .callsFake((...args) => { + recordReentered = beforeRecordCall; + beforeRecordCall = true; + recordStub.wrappedMethod.apply(gURLBar.controller.engagementEvent, args); + beforeRecordCall = false; + }); + + registerCleanupFunction(() => { + sinon.restore(); + UrlbarProvidersManager.unregisterProvider(provider); + }); + + await doTest(async () => { + await openPopup("example"); + await selectRowByURL(TEST_URL); + EventUtils.synthesizeKey("VK_RETURN"); + await deferred.promise; + + assertEngagementTelemetry([{ engagement_type: "enter" }]); + assertAbandonmentTelemetry([]); + + Assert.ok(recordReentered, "`record()` was re-entered"); + Assert.equal( + engagementSpy.callCount, + 1, + "`onLegacyEngagement` was invoked twice" + ); + Assert.equal( + engagementSpy.args[0][0], + "engagement", + "`engagement` notified" + ); + }); +}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js index 4317a50930..1373cc7e27 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js @@ -10,6 +10,7 @@ Services.scriptloader.loadSubScript( ChromeUtils.defineESModuleGetters(this, { QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs", + sinon: "resource://testing-common/Sinon.sys.mjs", }); const lazy = {}; @@ -210,12 +211,7 @@ async function doTest(testFn) { await QuickSuggest.blockedSuggestions.clear(); await QuickSuggest.blockedSuggestions._test_readyPromise; await updateTopSites(() => true); - - try { - await BrowserTestUtils.withNewTab(gBrowser, testFn); - } catch (e) { - console.error(e); - } + await BrowserTestUtils.withNewTab(gBrowser, testFn); } async function initGroupTest() { diff --git a/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs b/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs index 2ba9dce8be..1002b4e231 100644 --- a/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs +++ b/browser/components/urlbar/tests/quicksuggest/QuickSuggestTestUtils.sys.mjs @@ -490,6 +490,8 @@ class _QuickSuggestTestUtils { * Whether the result is expected to be sponsored. * @param {boolean} [options.isBestMatch] * Whether the result is expected to be a best match. + * @param {boolean} [options.isManageable] + * Whether the result is expected to show Manage result menu item. * @returns {result} * The quick suggest result. */ @@ -500,6 +502,7 @@ class _QuickSuggestTestUtils { index = -1, isSponsored = true, isBestMatch = false, + isManageable = true, } = {}) { this.Assert.ok( url || originalUrl, @@ -574,11 +577,19 @@ class _QuickSuggestTestUtils { } this.Assert.equal( - result.payload.helpUrl, - lazy.QuickSuggest.HELP_URL, - "Result helpURL" + result.payload.isManageable, + isManageable, + "Result isManageable" ); + if (!isManageable) { + this.Assert.equal( + result.payload.helpUrl, + lazy.QuickSuggest.HELP_URL, + "Result helpURL" + ); + } + this.Assert.ok( row._buttons.get("menu"), "The menu button should be present" diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js index 130afe8c53..98f6ba6117 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js @@ -164,3 +164,40 @@ add_tasks_with_rust( await cleanUpNimbus(); } ); + +// Tests the "Manage" result menu for sponsored suggestion. +add_tasks_with_rust(async function resultMenu_manage_sponsored() { + await BrowserTestUtils.withNewTab({ gBrowser }, async browser => { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "fra", + }); + + const managePage = "about:preferences#search"; + let onManagePageLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + managePage + ); + // Click the command. + await UrlbarTestUtils.openResultMenuAndClickItem(window, "manage", { + resultIndex: 1, + }); + await onManagePageLoaded; + Assert.equal( + browser.currentURI.spec, + managePage, + "The manage page is loaded" + ); + + await UrlbarTestUtils.promisePopupClose(window); + }); +}); + +// Tests the "Manage" result menu for non-sponsored suggestion. +add_tasks_with_rust(async function resultMenu_manage_nonSponsored() { + await doManageTest({ + input: "nonspon", + index: 1, + }); +}); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js index b09345aa54..f34b479134 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_addons.js @@ -245,10 +245,15 @@ add_task(async function resultMenu_notInterested() { }); // Tests the "Not relevant" result menu dismissal command. -add_task(async function notRelevant() { +add_task(async function resultMenu_notRelevant() { await doDismissTest("not_relevant", false); }); +// Tests the "Manage" result menu. +add_task(async function resultMenu_manage() { + await doManageTest({ input: "only match the Merino suggestion", index: 1 }); +}); + // Tests the row/group label. add_task(async function rowLabel() { await UrlbarTestUtils.promiseAutocompleteResultPopup({ diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js index c400cf72f6..3fa91e5a32 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_block.js @@ -5,11 +5,6 @@ "use strict"; -ChromeUtils.defineESModuleGetters(this, { - CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.sys.mjs", -}); - const { TELEMETRY_SCALARS } = UrlbarProviderQuickSuggest; const { TIMESTAMP_TEMPLATE } = QuickSuggest; diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js index b7da7533c4..33bd37703d 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_mdn.js @@ -21,6 +21,9 @@ const REMOTE_SETTINGS_DATA = [ }, ]; +// Avoid timeouts in verify mode. They're especially common on Mac. +requestLongerTimeout(5); + add_setup(async function () { await QuickSuggestTestUtils.ensureQuickSuggestInit({ remoteSettingsRecords: REMOTE_SETTINGS_DATA, @@ -28,35 +31,37 @@ add_setup(async function () { }); add_tasks_with_rust(async function basic() { - const suggestion = REMOTE_SETTINGS_DATA[0].attachment[0]; - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: suggestion.keywords[0], - }); - Assert.equal(UrlbarTestUtils.getResultCount(window), 2); - - const { element, result } = await UrlbarTestUtils.getDetailsOfResultAt( - window, - 1 - ); - Assert.equal( - result.providerName, - UrlbarProviderQuickSuggest.name, - "The result should be from the expected provider" - ); - Assert.equal( - result.payload.provider, - UrlbarPrefs.get("quickSuggestRustEnabled") ? "Mdn" : "MDNSuggestions" - ); + await BrowserTestUtils.withNewTab("about:blank", async () => { + const suggestion = REMOTE_SETTINGS_DATA[0].attachment[0]; + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: suggestion.keywords[0], + }); + Assert.equal(UrlbarTestUtils.getResultCount(window), 2); + + const { element, result } = await UrlbarTestUtils.getDetailsOfResultAt( + window, + 1 + ); + Assert.equal( + result.providerName, + UrlbarProviderQuickSuggest.name, + "The result should be from the expected provider" + ); + Assert.equal( + result.payload.provider, + UrlbarPrefs.get("quickSuggestRustEnabled") ? "Mdn" : "MDNSuggestions" + ); - const onLoad = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - result.payload.url - ); - EventUtils.synthesizeMouseAtCenter(element.row, {}); - await onLoad; - Assert.ok(true, "Expected page is loaded"); + const onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + result.payload.url + ); + EventUtils.synthesizeMouseAtCenter(element.row, {}); + await onLoad; + Assert.ok(true, "Expected page is loaded"); + }); await PlacesUtils.history.clear(); }); @@ -111,7 +116,7 @@ add_tasks_with_rust(async function resultMenu_notInterested() { }); // Tests the "Not relevant" result menu dismissal command. -add_tasks_with_rust(async function notRelevant() { +add_tasks_with_rust(async function resultMenu_notRelevant() { await doDismissTest("not_relevant"); Assert.equal(UrlbarPrefs.get("suggest.mdn"), true); @@ -123,6 +128,11 @@ add_tasks_with_rust(async function notRelevant() { await QuickSuggest.blockedSuggestions.clear(); }); +// Tests the "Manage" result menu. +add_tasks_with_rust(async function resultMenu_manage() { + await doManageTest({ input: "array", index: 1 }); +}); + async function doDismissTest(command) { const keyword = REMOTE_SETTINGS_DATA[0].attachment[0].keywords[0]; // Do a search. diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js index 0064b6a297..a40a35893b 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_pocket.js @@ -4,12 +4,6 @@ "use strict"; // Browser tests for Pocket suggestions. -// -// TODO: Make this work with Rust enabled. Right now, running this test with -// Rust hits the following error on ingest, which prevents ingest from finishing -// successfully: -// -// 0:03.17 INFO Console message: [JavaScript Error: "1698289045697 urlbar ERROR QuickSuggest.SuggestBackendRust :: Ingest error: Error executing SQL: FOREIGN KEY constraint failed" {file: "resource://gre/modules/Log.sys.mjs" line: 722}] // The expected index of the Pocket suggestion. const EXPECTED_RESULT_INDEX = 1; @@ -30,6 +24,8 @@ const REMOTE_SETTINGS_DATA = [ }, ]; +requestLongerTimeout(5); + add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [ @@ -47,7 +43,7 @@ add_setup(async function () { }); }); -add_task(async function basic() { +add_tasks_with_rust(async function basic() { await BrowserTestUtils.withNewTab("about:blank", async () => { // Do a search. await UrlbarTestUtils.promiseAutocompleteResultPopup({ @@ -96,7 +92,7 @@ add_task(async function basic() { }); // Tests the "Show less frequently" command. -add_task(async function resultMenu_showLessFrequently() { +add_tasks_with_rust(async function resultMenu_showLessFrequently() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.pocket.featureGate", true], @@ -235,7 +231,7 @@ async function doShowLessFrequently({ input, expected, keepViewOpen = false }) { } // Tests the "Not interested" result menu dismissal command. -add_task(async function resultMenu_notInterested() { +add_tasks_with_rust(async function resultMenu_notInterested() { await doDismissTest("not_interested"); // Re-enable suggestions and wait until PocketSuggestions syncs them from @@ -245,7 +241,7 @@ add_task(async function resultMenu_notInterested() { }); // Tests the "Not relevant" result menu dismissal command. -add_task(async function notRelevant() { +add_tasks_with_rust(async function notRelevant() { await doDismissTest("not_relevant"); }); @@ -361,7 +357,7 @@ async function doDismissTest(command) { } // Tests row labels. -add_task(async function rowLabel() { +add_tasks_with_rust(async function rowLabel() { const testCases = [ // high confidence keyword best match { @@ -389,7 +385,7 @@ add_task(async function rowLabel() { }); // Tests visibility of "Show less frequently" menu. -add_task(async function showLessFrequentlyMenuVisibility() { +add_tasks_with_rust(async function showLessFrequentlyMenuVisibility() { const testCases = [ // high confidence keyword best match { diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_yelp.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_yelp.js index b7c2bdc25c..7197946171 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_yelp.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest_yelp.js @@ -401,6 +401,11 @@ async function doDismiss({ menu, assert }) { await UrlbarTestUtils.promisePopupClose(window); } +// Tests the "Manage" result menu. +add_task(async function resultMenu_manage() { + await doManageTest({ input: "ramen", index: 1 }); +}); + // Tests the row/group label. add_task(async function rowLabel() { let tests = [ diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js index 001c54458c..71c289e0ef 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js @@ -7,11 +7,6 @@ "use strict"; -ChromeUtils.defineESModuleGetters(this, { - CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.sys.mjs", -}); - const { TELEMETRY_SCALARS } = UrlbarProviderQuickSuggest; const MERINO_SUGGESTION = { diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_gleanEmptyStrings.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_gleanEmptyStrings.js index 00cbe6c4e1..2c75b63a71 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_gleanEmptyStrings.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_gleanEmptyStrings.js @@ -7,11 +7,6 @@ "use strict"; -ChromeUtils.defineESModuleGetters(this, { - CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.sys.mjs", -}); - const { TELEMETRY_SCALARS } = UrlbarProviderQuickSuggest; const MERINO_RESULT = { 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 821c5cf470..eab48faaaf 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_impressionEdgeCases.js @@ -8,8 +8,6 @@ "use strict"; ChromeUtils.defineESModuleGetters(this, { - CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.sys.mjs", UrlbarView: "resource:///modules/UrlbarView.sys.mjs", sinon: "resource://testing-common/Sinon.sys.mjs", }); @@ -376,8 +374,11 @@ async function doEngagementWithoutAddingResultToView( let getPriorityStub = sandbox.stub(UrlbarProviderQuickSuggest, "getPriority"); getPriorityStub.returns(Infinity); - // Spy on `UrlbarProviderQuickSuggest.onEngagement()`. - let onEngagementSpy = sandbox.spy(UrlbarProviderQuickSuggest, "onEngagement"); + // Spy on `UrlbarProviderQuickSuggest.onLegacyEngagement()`. + let onLegacyEngagementSpy = sandbox.spy( + UrlbarProviderQuickSuggest, + "onLegacyEngagement" + ); let sandboxCleanup = () => { getPriorityStub?.restore(); @@ -454,7 +455,7 @@ async function doEngagementWithoutAddingResultToView( }); await loadPromise; - let engagementCalls = onEngagementSpy.getCalls().filter(call => { + let engagementCalls = onLegacyEngagementSpy.getCalls().filter(call => { let state = call.args[0]; return state == "engagement"; }); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js index 9a1aa06c02..f541801bae 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_nonsponsored.js @@ -7,11 +7,6 @@ "use strict"; -ChromeUtils.defineESModuleGetters(this, { - CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.sys.mjs", -}); - const { TELEMETRY_SCALARS } = UrlbarProviderQuickSuggest; const REMOTE_SETTINGS_RESULT = { diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js index 7c477e8af7..b11a491c92 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_sponsored.js @@ -7,11 +7,6 @@ "use strict"; -ChromeUtils.defineESModuleGetters(this, { - CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.sys.mjs", -}); - const { TELEMETRY_SCALARS } = UrlbarProviderQuickSuggest; const REMOTE_SETTINGS_RESULT = { diff --git a/browser/components/urlbar/tests/quicksuggest/browser/head.js b/browser/components/urlbar/tests/quicksuggest/browser/head.js index cc5f449e94..a1bf0feabe 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/head.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/head.js @@ -12,7 +12,7 @@ Services.scriptloader.loadSubScript( ChromeUtils.defineESModuleGetters(this, { CONTEXTUAL_SERVICES_PING_TYPES: - "resource:///modules/PartnerLinkAttribution.jsm", + "resource:///modules/PartnerLinkAttribution.sys.mjs", QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs", TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", UrlbarProviderQuickSuggest: @@ -522,6 +522,45 @@ async function doCommandTest({ info("Finished command test: " + JSON.stringify({ commandOrArray })); } +/* + * Do test the "Manage" result menu item. + * + * @param {object} options + * Options + * @param {number} options.index + * The index of the suggestion that will be checked in the results list. + * @param {number} options.input + * The input value on the urlbar. + */ +async function doManageTest({ index, input }) { + await BrowserTestUtils.withNewTab({ gBrowser }, async browser => { + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: input, + }); + + const managePage = "about:preferences#search"; + let onManagePageLoaded = BrowserTestUtils.browserLoaded( + browser, + false, + managePage + ); + // Click the command. + await UrlbarTestUtils.openResultMenuAndClickItem(window, "manage", { + resultIndex: index, + }); + await onManagePageLoaded; + + Assert.equal( + browser.currentURI.spec, + managePage, + "The manage page is loaded" + ); + + await UrlbarTestUtils.promisePopupClose(window); + }); +} + /** * Gets a row in the view, which is assumed to be open, and asserts that it's a * particular quick suggest row. If it is, the row is returned. If it's not, diff --git a/browser/components/urlbar/tests/quicksuggest/unit/head.js b/browser/components/urlbar/tests/quicksuggest/unit/head.js index 73bedf468e..5808e06bdf 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/head.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/head.js @@ -182,14 +182,11 @@ function makeWikipediaResult({ qsSuggestion: keyword, sponsoredAdvertiser: "Wikipedia", sponsoredIabCategory: "5 - Education", - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, telemetryType: "adm_nonsponsored", }, }; @@ -256,14 +253,11 @@ function makeAmpResult({ sponsoredBlockId: blockId, sponsoredAdvertiser: advertiser, sponsoredIabCategory: iabCategory, - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, telemetryType: "adm_sponsored", descriptionL10n: { id: "urlbar-result-action-sponsored" }, }, diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js index 1c00cb5320..ecb7c3dd09 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_impressionCaps.js @@ -3884,7 +3884,7 @@ async function checkSearch({ name, searchString, expectedResults }) { removeResult() {}, }, }); - UrlbarProviderQuickSuggest.onEngagement( + UrlbarProviderQuickSuggest.onLegacyEngagement( "engagement", context, { diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js index 61b1b9186f..c98fc5b6b4 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merinoSessions.js @@ -149,7 +149,7 @@ add_task(async function canceledQueries() { }); function endEngagement({ controller, context = null, state = "engagement" }) { - UrlbarProviderQuickSuggest.onEngagement( + UrlbarProviderQuickSuggest.onLegacyEngagement( state, context || createContext("endEngagement", { diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js b/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js index cd794f435b..8479b97210 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_weather.js @@ -723,7 +723,7 @@ add_tasks_with_rust(async function block() { let result = context.results[0]; let provider = UrlbarProvidersManager.getProvider(result.providerName); Assert.ok(provider, "Sanity check: Result provider found"); - provider.onEngagement( + provider.onLegacyEngagement( "engagement", context, { diff --git a/browser/components/urlbar/tests/unit/test_exposure.js b/browser/components/urlbar/tests/unit/test_exposure.js index e3ce0b8479..3e63e668d7 100644 --- a/browser/components/urlbar/tests/unit/test_exposure.js +++ b/browser/components/urlbar/tests/unit/test_exposure.js @@ -3,7 +3,6 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ ChromeUtils.defineESModuleGetters(this, { - QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs", UrlbarProviderQuickSuggest: "resource:///modules/UrlbarProviderQuickSuggest.sys.mjs", }); @@ -177,14 +176,11 @@ function makeAmpResult({ sponsoredBlockId: blockId, sponsoredAdvertiser: advertiser, sponsoredIabCategory: iabCategory, - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, telemetryType: "adm_sponsored", descriptionL10n: { id: "urlbar-result-action-sponsored" }, }, @@ -240,14 +236,11 @@ function makeWikipediaResult({ qsSuggestion: keyword, sponsoredAdvertiser: "Wikipedia", sponsoredIabCategory: "5 - Education", - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, telemetryType: "adm_nonsponsored", }, }; diff --git a/browser/components/urlbar/tests/unit/test_l10nCache.js b/browser/components/urlbar/tests/unit/test_l10nCache.js index e92c75fa01..bd93cc50d6 100644 --- a/browser/components/urlbar/tests/unit/test_l10nCache.js +++ b/browser/components/urlbar/tests/unit/test_l10nCache.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -// Tests L10nCache in UrlbarUtils.jsm. +// Tests L10nCache in UrlbarUtils.sys.mjs. "use strict"; -- cgit v1.2.3