mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			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();
 |