Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  UrlbarSearchOneOffs.sys.mjs   Sprache: unbekannt

 
/* 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 { SearchOneOffs } from "resource:///modules/SearchOneOffs.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
  UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs",
  UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
});

/**
 * The one-off search buttons in the urlbar.
 */
export class UrlbarSearchOneOffs extends SearchOneOffs {
  /**
   * Constructor.
   *
   * @param {UrlbarView} view
   *   The parent UrlbarView.
   */
  constructor(view) {
    super(view.panel.querySelector(".search-one-offs"));
    this.view = view;
    this.input = view.input;
    lazy.UrlbarPrefs.addObserver(this);
    // Override the SearchOneOffs.sys.mjs value for the Address Bar.
    this.disableOneOffsHorizontalKeyNavigation = true;
    this._webEngines = [];
    this.addEventListener("rebuild", this);
  }

  /**
   * Returns the local search mode one-off buttons.
   *
   * @returns {Array}
   *   The local one-off buttons.
   */
  get localButtons() {
    return this.getSelectableButtons(false).filter(b => b.source);
  }

  /**
   * Invoked when Web provided search engines list changes.
   *
   * @param {Array} engines Array of Web provided search engines. Each engine
   *        is defined as  { icon, name, tooltip, uri }.
   */
  updateWebEngines(engines) {
    this._webEngines = engines;
    this.invalidateCache();
    if (this.view.isOpen) {
      this._rebuild();
    }
  }

  /**
   * Enables (shows) or disables (hides) the one-offs.
   *
   * @param {boolean} enable
   *   True to enable, false to disable.
   */
  enable(enable) {
    if (lazy.UrlbarPrefs.getScotchBonnetPref("scotchBonnet.disableOneOffs")) {
      enable = false;
    }
    if (enable) {
      this.telemetryOrigin = "urlbar";
      this.style.display = "";
      this.textbox = this.view.input.inputField;
      if (this.view.isOpen) {
        this._rebuild();
      }
      this.view.controller.addQueryListener(this);
    } else {
      this.telemetryOrigin = null;
      this.style.display = "none";
      this.textbox = null;
      this.view.controller.removeQueryListener(this);
    }
  }

  /**
   * Query listener method.  Delegates to the superclass.
   */
  onViewOpen() {
    this._on_popupshowing();
  }

  #queryContext;
  onQueryStarted(queryContext) {
    this.#queryContext = queryContext;
  }

  onQueryFinished(queryContext) {
    this.#buildQuickSuggestOptIn(queryContext);

    if (
      this.#quickSuggestOptInContainer &&
      !this.#quickSuggestOptInContainer.hidden
    ) {
      this.#quickSuggestOptInProvider._recordGlean("impression");
    }
  }

  #quickSuggestOptInContainer;
  get #quickSuggestOptInProvider() {
    return lazy.UrlbarProvidersManager.getProvider(
      "UrlbarProviderQuickSuggestContextualOptIn"
    );
  }

  #buildQuickSuggestOptIn(queryContext) {
    let provider = this.#quickSuggestOptInProvider;
    if (
      !provider._shouldDisplayContextualOptIn(queryContext) ||
      provider.isActive(queryContext)
    ) {
      if (this.#quickSuggestOptInContainer) {
        this.#quickSuggestOptInContainer.hidden = true;
      }
      return;
    }

    if (this.#quickSuggestOptInContainer) {
      this.#quickSuggestOptInContainer.hidden = false;
      this.#udpateQuickSuggestOptInCopy();
      return;
    }

    // The following is basically a copy of what UrlbarView generates for
    // ProviderQuickSuggestContextualOptIn's view template. Gross but good
    // enough for the experiment. Ultimately, if we decide to keep this UI at
    // the bottom, and when we replace the one-off buttons footer with a better
    // UI (e.g. search button), this can become a proper result again.
    let parser = new DOMParser();
    let doc = parser.parseFromString(
      `
<div xmlns="http://www.w3.org/1999/xhtml" class="urlbarView-quickSuggestContextualOptIn-one-off-container">
  <div class="urlbarView-row" role="presentation" type="dynamic">
    <span class="urlbarView-row-inner">
      <span class="urlbarView-dynamic-quickSuggestContextualOptIn-no-wrap urlbarView-no-wrap">
        <img class="urlbarView-dynamic-quickSuggestContextualOptIn-icon urlbarView-favicon" src="chrome://branding/content/icon32.png" />
        <span class="urlbarView-dynamic-quickSuggestContextualOptIn-text-container">
          <strong class="urlbarView-dynamic-quickSuggestContextualOptIn-title"></strong>
          <span class="urlbarView-dynamic-quickSuggestContextualOptIn-description">
            <a class="urlbarView-dynamic-quickSuggestContextualOptIn-learn_more" data-l10n-name="learn-more-link" selectable="" name="learn_more" id="urlbarView-footer-quickSuggestContextualOptIn-learn_more"></a>
          </span>
        </span>
      </span>
    </span>
    <span primary="" name="allow" class="urlbarView-button urlbarView-button-0" role="button" data-l10n-id="urlbar-firefox-suggest-contextual-opt-in-allow" id="urlbarView-footer-quickSuggestContextualOptIn-allow"></span>
    <span name="dismiss" class="urlbarView-button urlbarView-button-1" role="button" data-l10n-id="urlbar-firefox-suggest-contextual-opt-in-dismiss" id="urlbarView-footer-quickSuggestContextualOptIn-dismiss"></span>
  </div>
</div>
      `,
      "text/html"
    );
    this.#quickSuggestOptInContainer = this.document.importNode(
      doc.body.firstElementChild,
      true
    );

    // DOMParser normalizes attribute names to lowercase, so need to set this one after the fact.
    this.#quickSuggestOptInContainer.firstElementChild.setAttribute(
      "dynamicType",
      "quickSuggestContextualOptIn"
    );

    this.container.appendChild(this.#quickSuggestOptInContainer);
    this.#quickSuggestOptInContainer.addEventListener("keydown", this);
    this.#udpateQuickSuggestOptInCopy();
  }

  #udpateQuickSuggestOptInCopy() {
    let alternativeCopy = lazy.UrlbarPrefs.get(
      "quicksuggest.contextualOptIn.sayHello"
    );
    this.document.l10n.setAttributes(
      this.#quickSuggestOptInContainer.querySelector(
        ".urlbarView-dynamic-quickSuggestContextualOptIn-title"
      ),
      alternativeCopy
        ? "urlbar-firefox-suggest-contextual-opt-in-title-2"
        : "urlbar-firefox-suggest-contextual-opt-in-title-1"
    );
    this.document.l10n.setAttributes(
      this.#quickSuggestOptInContainer.querySelector(
        ".urlbarView-dynamic-quickSuggestContextualOptIn-description"
      ),
      alternativeCopy
        ? "urlbar-firefox-suggest-contextual-opt-in-description-2"
        : "urlbar-firefox-suggest-contextual-opt-in-description-1"
    );
  }

  #isQuickSuggestOptInElement(element) {
    return (
      this.#quickSuggestOptInContainer &&
      element?.compareDocumentPosition(this.#quickSuggestOptInContainer) &
        Node.DOCUMENT_POSITION_CONTAINS
    );
  }

  #handleQuickSuggestOptInCommand(element) {
    if (this.#isQuickSuggestOptInElement(element)) {
      this.#quickSuggestOptInProvider._handleCommand(
        element,
        this.view.controller,
        null,
        this.#quickSuggestOptInContainer
      );
      return true;
    }
    return false;
  }

  /**
   * Query listener method.  Delegates to the superclass.
   */
  onViewClose() {
    this._on_popuphidden();
  }

  /**
   * @returns {boolean}
   *   True if the one-offs are connected to a view.
   */
  get hasView() {
    // Return true if the one-offs are enabled.  We set style.display = "none"
    // when they're disabled, and we hide the container when there are no
    // engines to show.
    return this.style.display != "none" && !this.container.hidden;
  }

  /**
   * @returns {boolean}
   *   True if the view is open.
   */
  get isViewOpen() {
    return this.view.isOpen;
  }

  /**
   * The selected one-off including the search-settings button.
   *
   * @param {DOMElement|null} button
   *   The selected one-off button. Null if no one-off is selected.
   */
  set selectedButton(button) {
    if (this.selectedButton == button) {
      return;
    }

    if (this.#isQuickSuggestOptInElement(button)) {
      this.#quickSuggestOptInProvider.onBeforeSelection(null, button);
    }

    super.selectedButton = button;

    let expectedSearchMode;
    if (button && button != this.view.oneOffSearchButtons.settingsButton) {
      expectedSearchMode = {
        engineName: button.engine?.name,
        source: button.source,
        entry: "oneoff",
      };
      this.input.searchMode = expectedSearchMode;
    } else if (this.input.searchMode) {
      // Restore the previous state. We do this only if we're in search mode, as
      // an optimization in the common case of cycling through normal results.
      this.input.restoreSearchModeState();
    }
  }

  get selectedButton() {
    return super.selectedButton;
  }

  getSelectableButtons(aIncludeNonEngineButtons) {
    const buttons = super.getSelectableButtons(aIncludeNonEngineButtons);

    if (
      aIncludeNonEngineButtons &&
      this.#quickSuggestOptInContainer &&
      !this.#quickSuggestOptInContainer.hidden
    ) {
      buttons.push(
        ...this.#quickSuggestOptInContainer.querySelectorAll(
          "[role=button], [selectable]"
        )
      );
    }

    return buttons;
  }

  /**
   * The selected index in the view or -1 if there is no selection.
   *
   * @returns {number}
   */
  get selectedViewIndex() {
    return this.view.selectedRowIndex;
  }
  set selectedViewIndex(val) {
    this.view.selectedRowIndex = val;
  }

  /**
   * Closes the view.
   */
  closeView() {
    if (this.view) {
      this.view.close();
    }
  }

  /**
   * Called when a one-off is clicked.
   *
   * @param {event} event
   *   The event that triggered the pick.
   * @param {object} searchMode
   *   Used by UrlbarInput.setSearchMode to enter search mode. See setSearchMode
   *   documentation for details.
   */
  handleSearchCommand(event, searchMode) {
    // The settings button and adding engines are a special case and executed
    // immediately.
    if (
      this.selectedButton == this.view.oneOffSearchButtons.settingsButton ||
      this.selectedButton.classList.contains(
        "searchbar-engine-one-off-add-engine"
      )
    ) {
      this.input.controller.engagementEvent.discard();
      this.selectedButton.doCommand();
      this.selectedButton = null;
      return;
    }

    if (this.#handleQuickSuggestOptInCommand(this.selectedButton)) {
      this.input.controller.engagementEvent.discard();
      this.selectedButton = null;
      return;
    }

    // We allow autofill in local but not remote search modes.
    let startQueryParams = {
      allowAutofill:
        !searchMode.engineName &&
        searchMode.source != lazy.UrlbarUtils.RESULT_SOURCE.SEARCH,
      event,
    };

    let userTypedSearchString =
      this.input.value && this.input.getAttribute("pageproxystate") != "valid";
    let engine = Services.search.getEngineByName(searchMode.engineName);

    let { where, params } = this._whereToOpen(event);

    // Some key combinations should execute a search immediately. We handle
    // these here, outside the switch statement.
    if (
      userTypedSearchString &&
      engine &&
      (event.shiftKey || where != "current")
    ) {
      this.input.handleNavigation({
        event,
        oneOffParams: {
          openWhere: where,
          openParams: params,
          engine: this.selectedButton.engine,
        },
      });
      this.selectedButton = null;
      return;
    }

    // Handle opening search mode in either the current tab or in a new tab.
    switch (where) {
      case "current": {
        this.input.searchMode = searchMode;
        this.input.startQuery(startQueryParams);
        break;
      }
      case "tab": {
        // We set this.selectedButton when switching tabs. If we entered search
        // mode preview here, it could be cleared when this.selectedButton calls
        // setSearchMode.
        searchMode.isPreview = false;

        let newTab = this.input.window.gBrowser.addTrustedTab("about:newtab");
        this.input.setSearchMode(searchMode, newTab.linkedBrowser);
        if (userTypedSearchString) {
          // Set the search string for the new tab.
          newTab.linkedBrowser.userTypedValue = this.input.value;
        }
        if (!params?.inBackground) {
          this.input.window.gBrowser.selectedTab = newTab;
          newTab.ownerGlobal.gURLBar.startQuery(startQueryParams);
        }
        break;
      }
      default: {
        this.input.searchMode = searchMode;
        this.input.startQuery(startQueryParams);
        this.input.select();
        break;
      }
    }

    this.selectedButton = null;
  }

  /**
   * Sets the tooltip for a one-off button with an engine.  This should set
   * either the `tooltiptext` attribute or the relevant l10n ID.
   *
   * @param {element} button
   *   The one-off button.
   */
  setTooltipForEngineButton(button) {
    let aliases = button.engine.aliases;
    if (!aliases.length) {
      super.setTooltipForEngineButton(button);
      return;
    }
    this.document.l10n.setAttributes(
      button,
      "search-one-offs-engine-with-alias",
      {
        engineName: button.engine.name,
        alias: aliases[0],
      }
    );
  }

  /**
   * Overrides the willHide method in the superclass to account for the local
   * search mode buttons.
   *
   * @returns {boolean}
   *   True if we will hide the one-offs when they are requested.
   */
  async willHide() {
    // We need to call super.willHide() even when we return false below because
    // it has the necessary side effect of creating this._engineInfo.
    let superWillHide = await super.willHide();
    if (
      lazy.UrlbarUtils.LOCAL_SEARCH_MODES.some(m =>
        lazy.UrlbarPrefs.get(m.pref)
      )
    ) {
      return false;
    }
    return superWillHide;
  }

  /**
   * Called when a pref tracked by UrlbarPrefs changes.
   *
   * @param {string} changedPref
   *   The name of the pref, relative to `browser.urlbar.` if the pref is in
   *   that branch.
   */
  onPrefChanged(changedPref) {
    // Invalidate the engine cache when the local-one-offs-related prefs change
    // so that the one-offs rebuild themselves the next time the view opens.
    if (
      [...lazy.UrlbarUtils.LOCAL_SEARCH_MODES.map(m => m.pref)].includes(
        changedPref
      )
    ) {
      this.invalidateCache();
    }
  }

  /**
   * Overrides _getAddEngines to return engines that can be added.
   *
   * @returns {Array} engines
   */
  _getAddEngines() {
    return this._webEngines;
  }

  /**
   * Overrides _rebuildEngineList to add the local one-offs.
   *
   * @param {Array} engines
   *    The search engines to add.
   * @param {Array} addEngines
   *        The engines that can be added.
   */
  async _rebuildEngineList(engines, addEngines) {
    await super._rebuildEngineList(engines, addEngines);

    for (let { source, pref, restrict } of lazy.UrlbarUtils
      .LOCAL_SEARCH_MODES) {
      if (!lazy.UrlbarPrefs.get(pref)) {
        continue;
      }
      let name = lazy.UrlbarUtils.getResultSourceName(source);
      let button = this.document.createXULElement("button");
      button.id = `urlbar-engine-one-off-item-${name}`;
      button.setAttribute("class", "searchbar-engine-one-off-item");
      button.setAttribute("tabindex", "-1");
      this.document.l10n.setAttributes(button, `search-one-offs-${name}`, {
        restrict,
      });
      button.source = source;
      this.buttons.appendChild(button);
    }
  }

  /**
   * Overrides the superclass's click listener to handle clicks on local
   * one-offs in addition to engine one-offs.
   *
   * @param {event} event
   *   The click event.
   */
  _on_click(event) {
    // Ignore right clicks.
    if (event.button == 2) {
      return;
    }

    let button = event.originalTarget;

    if (this.#handleQuickSuggestOptInCommand(button)) {
      return;
    }

    if (!button.engine && !button.source) {
      return;
    }

    this.selectedButton = button;
    this.handleSearchCommand(event, {
      engineName: button.engine?.name,
      source: button.source,
      entry: "oneoff",
    });
  }

  /**
   * Overrides the superclass's contextmenu listener to handle the context menu.
   *
   * @param {event} event
   *   The contextmenu event.
   */
  _on_contextmenu(event) {
    // Prevent the context menu from appearing.
    event.preventDefault();
  }

  _on_rebuild() {
    if (this.#queryContext) {
      this.#buildQuickSuggestOptIn(this.#queryContext);
    }
  }
}

[ Dauer der Verarbeitung: 0.3 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge