forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			303 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| import {
 | |
|   UrlbarProvider,
 | |
|   UrlbarUtils,
 | |
| } from "resource:///modules/UrlbarUtils.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs",
 | |
|   UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
 | |
|   UrlbarProviderTopSites: "resource:///modules/UrlbarProviderTopSites.sys.mjs",
 | |
| });
 | |
| 
 | |
| const TELEMETRY_PREFIX = "contextual.services.quicksuggest";
 | |
| 
 | |
| const TELEMETRY_SCALARS = {
 | |
|   BLOCK: `${TELEMETRY_PREFIX}.block_weather`,
 | |
|   CLICK: `${TELEMETRY_PREFIX}.click_weather`,
 | |
|   IMPRESSION: `${TELEMETRY_PREFIX}.impression_weather`,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * A provider that returns a suggested url to the user based on what
 | |
|  * they have currently typed so they can navigate directly.
 | |
|  *
 | |
|  * This provider is active only when either the Rust backend is disabled or
 | |
|  * weather keywords are defined in Nimbus. When Rust is enabled and keywords are
 | |
|  * not defined in Nimbus, the Rust component serves the initial weather
 | |
|  * suggestion and UrlbarProviderQuickSuggest handles it along with other
 | |
|  * suggestion types. Once the Rust backend is enabled by default and we no
 | |
|  * longer want to experiment with weather keywords, this provider can be removed
 | |
|  * along with the legacy telemetry it records.
 | |
|  */
 | |
| class ProviderWeather extends UrlbarProvider {
 | |
|   /**
 | |
|    * Returns the name of this provider.
 | |
|    *
 | |
|    * @returns {string} the name of this provider.
 | |
|    */
 | |
|   get name() {
 | |
|     return "Weather";
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The type of the provider.
 | |
|    *
 | |
|    * @returns {UrlbarUtils.PROVIDER_TYPE}
 | |
|    */
 | |
|   get type() {
 | |
|     return UrlbarUtils.PROVIDER_TYPE.NETWORK;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @returns {object} An object mapping from mnemonics to scalar names.
 | |
|    */
 | |
|   get TELEMETRY_SCALARS() {
 | |
|     return { ...TELEMETRY_SCALARS };
 | |
|   }
 | |
| 
 | |
|   getPriority(context) {
 | |
|     if (!context.searchString) {
 | |
|       // Zero-prefix suggestions have the same priority as top sites.
 | |
|       return lazy.UrlbarProviderTopSites.PRIORITY;
 | |
|     }
 | |
|     return super.getPriority(context);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Whether this provider should be invoked for the given context.
 | |
|    * If this method returns false, the providers manager won't start a query
 | |
|    * with this provider, to save on resources.
 | |
|    *
 | |
|    * @param {UrlbarQueryContext} queryContext The query context object
 | |
|    * @returns {boolean} Whether this provider should be invoked for the search.
 | |
|    */
 | |
|   isActive(queryContext) {
 | |
|     this.#resultFromLastQuery = null;
 | |
| 
 | |
|     // When Rust is enabled and keywords are not defined in Nimbus, weather
 | |
|     // results are created by the quick suggest provider, not this one.
 | |
|     if (
 | |
|       lazy.UrlbarPrefs.get("quickSuggestRustEnabled") &&
 | |
|       !lazy.QuickSuggest.weather?.keywords
 | |
|     ) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // If the sources don't include search or the user used a restriction
 | |
|     // character other than search, don't allow any suggestions.
 | |
|     if (
 | |
|       !queryContext.sources.includes(UrlbarUtils.RESULT_SOURCE.SEARCH) ||
 | |
|       (queryContext.restrictSource &&
 | |
|         queryContext.restrictSource != UrlbarUtils.RESULT_SOURCE.SEARCH)
 | |
|     ) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       queryContext.isPrivate ||
 | |
|       queryContext.searchMode ||
 | |
|       // `QuickSuggest.weather` will be undefined if `QuickSuggest` hasn't been
 | |
|       // initialized.
 | |
|       !lazy.QuickSuggest.weather?.suggestion
 | |
|     ) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     let { keywords } = lazy.QuickSuggest.weather;
 | |
|     if (!keywords) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return keywords.has(queryContext.trimmedLowerCaseSearchString);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Starts querying. Extended classes should return a Promise resolved when the
 | |
|    * provider is done searching AND returning results.
 | |
|    *
 | |
|    * @param {UrlbarQueryContext} queryContext The query context object
 | |
|    * @param {Function} addCallback Callback invoked by the provider to add a new
 | |
|    *        result. A UrlbarResult should be passed to it.
 | |
|    * @returns {Promise}
 | |
|    */
 | |
|   async startQuery(queryContext, addCallback) {
 | |
|     let { weather } = lazy.QuickSuggest;
 | |
|     if (!weather.suggestion) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let result = weather.makeResult(
 | |
|       queryContext,
 | |
|       weather.suggestion,
 | |
|       queryContext.searchString
 | |
|     );
 | |
|     if (result) {
 | |
|       result.payload.source = weather.suggestion.source;
 | |
|       result.payload.provider = weather.suggestion.provider;
 | |
|       addCallback(this, result);
 | |
|       this.#resultFromLastQuery = result;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   getResultCommands(result) {
 | |
|     return lazy.QuickSuggest.weather.getResultCommands(result);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * This is called only for dynamic result types, when the urlbar view updates
 | |
|    * the view of one of the results of the provider.  It should return an object
 | |
|    * describing the view update.
 | |
|    *
 | |
|    * @param {UrlbarResult} result
 | |
|    *   The result whose view will be updated.
 | |
|    * @returns {object} An object describing the view update.
 | |
|    */
 | |
|   getViewUpdate(result) {
 | |
|     return lazy.QuickSuggest.weather.getViewUpdate(result);
 | |
|   }
 | |
| 
 | |
|   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;
 | |
|     }
 | |
| 
 | |
|     // Impression and clicked telemetry are both recorded on engagement. We
 | |
|     // define "impression" to mean a weather result was present in the view when
 | |
|     // any result was picked.
 | |
|     if (state == "engagement" && queryContext) {
 | |
|       // Get the result that's visible in the view. `details.result` is the
 | |
|       // engaged result, if any; if it's from this provider, then that's the
 | |
|       // visible result. Otherwise fall back to #getVisibleResultFromLastQuery.
 | |
|       let { result } = details;
 | |
|       if (result?.providerName != this.name) {
 | |
|         result = this.#getVisibleResultFromLastQuery(controller.view);
 | |
|       }
 | |
| 
 | |
|       if (result) {
 | |
|         this.#recordEngagementTelemetry(
 | |
|           result,
 | |
|           controller.input.isPrivate,
 | |
|           details.result == result ? details.selType : ""
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Handle commands.
 | |
|     if (details.result?.providerName == this.name) {
 | |
|       this.#handlePossibleCommand(
 | |
|         controller.view,
 | |
|         details.result,
 | |
|         details.selType
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this.#resultFromLastQuery = null;
 | |
|   }
 | |
| 
 | |
|   #getVisibleResultFromLastQuery(view) {
 | |
|     let result = this.#resultFromLastQuery;
 | |
| 
 | |
|     if (
 | |
|       result?.rowIndex >= 0 &&
 | |
|       view?.visibleResults?.[result.rowIndex] == result
 | |
|     ) {
 | |
|       // The result was visible.
 | |
|       return result;
 | |
|     }
 | |
| 
 | |
|     // Find a visible result.
 | |
|     return view?.visibleResults?.find(r => r.providerName == this.name);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Records engagement telemetry. This should be called only at the end of an
 | |
|    * engagement when a weather result is present or when a weather result is
 | |
|    * dismissed.
 | |
|    *
 | |
|    * @param {UrlbarResult} result
 | |
|    *   The weather result that was present (and possibly picked) at the end of
 | |
|    *   the engagement or that was dismissed.
 | |
|    * @param {boolean} isPrivate
 | |
|    *   Whether the engagement is in a private context.
 | |
|    * @param {string} selType
 | |
|    *   This parameter indicates the part of the row the user picked, if any, and
 | |
|    *   should be one of the following values:
 | |
|    *
 | |
|    *   - "": The user didn't pick the row or any part of it
 | |
|    *   - "weather": The user picked the main part of the row
 | |
|    *   - "dismiss": The user dismissed the result
 | |
|    *
 | |
|    *   An empty string means the user picked some other row to end the
 | |
|    *   engagement, not the weather row. In that case only impression telemetry
 | |
|    *   will be recorded.
 | |
|    *
 | |
|    *   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
 | |
|    *   `onLegacyEngagement()`; see `TelemetryEvent.typeFromElement()`.
 | |
|    */
 | |
|   #recordEngagementTelemetry(result, isPrivate, selType) {
 | |
|     // Indexes recorded in quick suggest telemetry are 1-based, so add 1 to the
 | |
|     // 0-based `result.rowIndex`.
 | |
|     let telemetryResultIndex = result.rowIndex + 1;
 | |
| 
 | |
|     // impression scalars
 | |
|     Services.telemetry.keyedScalarAdd(
 | |
|       TELEMETRY_SCALARS.IMPRESSION,
 | |
|       telemetryResultIndex,
 | |
|       1
 | |
|     );
 | |
| 
 | |
|     // scalars related to clicking the result and other elements in its row
 | |
|     let clickScalars = [];
 | |
|     let eventObject;
 | |
|     switch (selType) {
 | |
|       case "weather":
 | |
|         clickScalars.push(TELEMETRY_SCALARS.CLICK);
 | |
|         eventObject = "click";
 | |
|         break;
 | |
|       case "dismiss":
 | |
|         clickScalars.push(TELEMETRY_SCALARS.BLOCK);
 | |
|         eventObject = "block";
 | |
|         break;
 | |
|       default:
 | |
|         if (selType) {
 | |
|           eventObject = "other";
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     for (let scalar of clickScalars) {
 | |
|       Services.telemetry.keyedScalarAdd(scalar, telemetryResultIndex, 1);
 | |
|     }
 | |
| 
 | |
|     // engagement event
 | |
|     Services.telemetry.recordEvent(
 | |
|       lazy.QuickSuggest.TELEMETRY_EVENT_CATEGORY,
 | |
|       "engagement",
 | |
|       eventObject || "impression_only",
 | |
|       "",
 | |
|       {
 | |
|         match_type: "firefox-suggest",
 | |
|         position: String(telemetryResultIndex),
 | |
|         suggestion_type: "weather",
 | |
|         source: result.payload.source,
 | |
|       }
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   #handlePossibleCommand(view, result, selType) {
 | |
|     lazy.QuickSuggest.weather.handleCommand(view, result, selType);
 | |
|   }
 | |
| 
 | |
|   // The result we added during the most recent query.
 | |
|   #resultFromLastQuery = null;
 | |
| }
 | |
| 
 | |
| export var UrlbarProviderWeather = new ProviderWeather();
 | 
