tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

UrlbarSearchOneOffs.sys.mjs (11086B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { SearchOneOffs } from "moz-src:///browser/components/search/SearchOneOffs.sys.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 const lazy = XPCOMUtils.declareLazy({
      9  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     10  UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
     11 });
     12 
     13 /**
     14 * @import {LegacySearchOneOffButton} from "moz-src:///browser/components/search/SearchOneOffs.sys.mjs"
     15 * @import {UrlbarView} from "moz-src:///browser/components/urlbar/UrlbarView.sys.mjs"
     16 */
     17 
     18 /**
     19 * The one-off search buttons in the urlbar.
     20 */
     21 export class UrlbarSearchOneOffs extends SearchOneOffs {
     22  /**
     23   * Constructor.
     24   *
     25   * @param {UrlbarView} view
     26   *   The parent UrlbarView.
     27   */
     28  constructor(view) {
     29    super(view.panel.querySelector(".search-one-offs"));
     30    this.view = view;
     31    this.input = view.input;
     32    lazy.UrlbarPrefs.addObserver(this);
     33    // Override the SearchOneOffs.sys.mjs value for the Address Bar.
     34    this.disableOneOffsHorizontalKeyNavigation = true;
     35  }
     36 
     37  /**
     38   * Returns the local search mode one-off buttons.
     39   *
     40   * @returns {Array}
     41   *   The local one-off buttons.
     42   */
     43  get localButtons() {
     44    return this.getSelectableButtons(false).filter(b => b.source);
     45  }
     46 
     47  /**
     48   * Invoked when Web provided search engines list changes.
     49   */
     50  updateWebEngines() {
     51    this.invalidateCache();
     52    if (this.view.isOpen) {
     53      this._rebuild();
     54    }
     55  }
     56 
     57  /**
     58   * Enables (shows) or disables (hides) the one-offs.
     59   *
     60   * @param {boolean} enable
     61   *   True to enable, false to disable.
     62   */
     63  enable(enable) {
     64    if (lazy.UrlbarPrefs.getScotchBonnetPref("scotchBonnet.disableOneOffs")) {
     65      enable = false;
     66    }
     67    if (enable) {
     68      this.telemetryOrigin = "urlbar";
     69      this.style.display = "";
     70      this.textbox = this.view.input.inputField;
     71      if (this.view.isOpen) {
     72        this._rebuild();
     73      }
     74      this.view.controller.addListener(this);
     75    } else {
     76      this.telemetryOrigin = null;
     77      this.style.display = "none";
     78      this.textbox = null;
     79      this.view.controller.removeListener(this);
     80    }
     81  }
     82 
     83  /**
     84   * Query listener method.  Delegates to the superclass.
     85   */
     86  onViewOpen() {
     87    this._on_popupshowing();
     88  }
     89 
     90  /**
     91   * Query listener method.  Delegates to the superclass.
     92   */
     93  onViewClose() {
     94    this._on_popuphidden();
     95  }
     96 
     97  /**
     98   * @returns {boolean}
     99   *   True if the one-offs are connected to a view.
    100   */
    101  get hasView() {
    102    // Return true if the one-offs are enabled.  We set style.display = "none"
    103    // when they're disabled, and we hide the container when there are no
    104    // engines to show.
    105    return this.style.display != "none" && !this.container.hidden;
    106  }
    107 
    108  /**
    109   * @returns {boolean}
    110   *   True if the view is open.
    111   */
    112  get isViewOpen() {
    113    return this.view.isOpen;
    114  }
    115 
    116  /**
    117   * The selected one-off including the search-settings button.
    118   *
    119   * @param {?LegacySearchOneOffButton} button
    120   *   The selected one-off button. Null if no one-off is selected.
    121   */
    122  set selectedButton(button) {
    123    if (this.selectedButton == button) {
    124      return;
    125    }
    126 
    127    super.selectedButton = button;
    128 
    129    let expectedSearchMode;
    130    if (button && button != this.view.oneOffSearchButtons.settingsButton) {
    131      expectedSearchMode = {
    132        engineName: button.engine?.name,
    133        source: button.source,
    134        entry: "oneoff",
    135      };
    136      this.input.searchMode = expectedSearchMode;
    137    } else if (this.input.searchMode) {
    138      // Restore the previous state. We do this only if we're in search mode, as
    139      // an optimization in the common case of cycling through normal results.
    140      this.input.restoreSearchModeState();
    141    }
    142  }
    143 
    144  get selectedButton() {
    145    return super.selectedButton;
    146  }
    147 
    148  /**
    149   * The selected index in the view or -1 if there is no selection.
    150   *
    151   * @returns {number}
    152   */
    153  get selectedViewIndex() {
    154    return this.view.selectedRowIndex;
    155  }
    156  set selectedViewIndex(val) {
    157    this.view.selectedRowIndex = val;
    158  }
    159 
    160  /**
    161   * Closes the view.
    162   */
    163  closeView() {
    164    if (this.view) {
    165      this.view.close();
    166    }
    167  }
    168 
    169  /**
    170   * Called when a one-off is clicked.
    171   *
    172   * @param {MouseEvent|KeyboardEvent} event
    173   *   The event that triggered the pick.
    174   * @param {object} searchMode
    175   *   Used by UrlbarInput.setSearchMode to enter search mode. See setSearchMode
    176   *   documentation for details.
    177   */
    178  handleSearchCommand(event, searchMode) {
    179    // The settings button and adding engines are a special case and executed
    180    // immediately.
    181    if (
    182      this.selectedButton == this.view.oneOffSearchButtons.settingsButton ||
    183      this.selectedButton.classList.contains(
    184        "searchbar-engine-one-off-add-engine"
    185      )
    186    ) {
    187      this.input.controller.engagementEvent.discard();
    188      this.selectedButton.doCommand();
    189      this.selectedButton = null;
    190      return;
    191    }
    192 
    193    // We allow autofill in local but not remote search modes.
    194    let startQueryParams = {
    195      allowAutofill:
    196        !searchMode.engineName &&
    197        searchMode.source != lazy.UrlbarUtils.RESULT_SOURCE.SEARCH,
    198      event,
    199    };
    200 
    201    let userTypedSearchString =
    202      this.input.value && this.input.getAttribute("pageproxystate") != "valid";
    203    let engine = Services.search.getEngineByName(searchMode.engineName);
    204 
    205    let { where, params } = this._whereToOpen(event);
    206 
    207    // Some key combinations should execute a search immediately. We handle
    208    // these here, outside the switch statement.
    209    if (
    210      userTypedSearchString &&
    211      engine &&
    212      (event.shiftKey || where != "current")
    213    ) {
    214      this.input.handleNavigation({
    215        event,
    216        oneOffParams: {
    217          openWhere: where,
    218          openParams: params,
    219          engine: this.selectedButton.engine,
    220        },
    221      });
    222      this.selectedButton = null;
    223      return;
    224    }
    225 
    226    // Handle opening search mode in either the current tab or in a new tab.
    227    switch (where) {
    228      case "current": {
    229        this.input.searchMode = searchMode;
    230        this.input.startQuery(startQueryParams);
    231        break;
    232      }
    233      case "tab": {
    234        // We set this.selectedButton when switching tabs. If we entered search
    235        // mode preview here, it could be cleared when this.selectedButton calls
    236        // setSearchMode.
    237        searchMode.isPreview = false;
    238 
    239        let newTab = this.input.window.gBrowser.addTrustedTab("about:newtab");
    240        this.input.setSearchMode(searchMode, newTab.linkedBrowser);
    241        if (userTypedSearchString) {
    242          // Set the search string for the new tab.
    243          newTab.linkedBrowser.userTypedValue = this.input.value;
    244        }
    245        if (!params?.inBackground) {
    246          this.input.window.gBrowser.selectedTab = newTab;
    247          newTab.ownerGlobal.gURLBar.startQuery(startQueryParams);
    248        }
    249        break;
    250      }
    251      default: {
    252        this.input.searchMode = searchMode;
    253        this.input.startQuery(startQueryParams);
    254        this.input.select();
    255        break;
    256      }
    257    }
    258 
    259    this.selectedButton = null;
    260  }
    261 
    262  /**
    263   * Sets the tooltip for a one-off button with an engine.  This should set
    264   * either the `tooltiptext` attribute or the relevant l10n ID.
    265   *
    266   * @param {LegacySearchOneOffButton} button
    267   *   The one-off button.
    268   */
    269  setTooltipForEngineButton(button) {
    270    let aliases = button.engine.aliases;
    271    if (!aliases.length) {
    272      super.setTooltipForEngineButton(button);
    273      return;
    274    }
    275    this.document.l10n.setAttributes(
    276      button,
    277      "search-one-offs-engine-with-alias",
    278      {
    279        engineName: button.engine.name,
    280        alias: aliases[0],
    281      }
    282    );
    283  }
    284 
    285  /**
    286   * Overrides the willHide method in the superclass to account for the local
    287   * search mode buttons.
    288   *
    289   * @returns {Promise<boolean>}
    290   *   True if we will hide the one-offs when they are requested.
    291   */
    292  async willHide() {
    293    // We need to call super.willHide() even when we return false below because
    294    // it has the necessary side effect of creating this._engineInfo.
    295    let superWillHide = await super.willHide();
    296    if (
    297      lazy.UrlbarUtils.LOCAL_SEARCH_MODES.some(m =>
    298        lazy.UrlbarPrefs.get(m.pref)
    299      )
    300    ) {
    301      return false;
    302    }
    303    return superWillHide;
    304  }
    305 
    306  /**
    307   * Called when a pref tracked by UrlbarPrefs changes.
    308   *
    309   * @param {string} changedPref
    310   *   The name of the pref, relative to `browser.urlbar.` if the pref is in
    311   *   that branch.
    312   */
    313  onPrefChanged(changedPref) {
    314    // Invalidate the engine cache when the local-one-offs-related prefs change
    315    // so that the one-offs rebuild themselves the next time the view opens.
    316    if (
    317      [...lazy.UrlbarUtils.LOCAL_SEARCH_MODES.map(m => m.pref)].includes(
    318        changedPref
    319      )
    320    ) {
    321      this.invalidateCache();
    322    }
    323  }
    324 
    325  /**
    326   * Overrides _rebuildEngineList to add the local one-offs.
    327   *
    328   * @param {Array} engines
    329   *    The search engines to add.
    330   * @param {Array} addEngines
    331   *        The engines that can be added.
    332   */
    333  async _rebuildEngineList(engines, addEngines) {
    334    await super._rebuildEngineList(engines, addEngines);
    335 
    336    const messageIDs = {
    337      actions: "search-one-offs-actions",
    338      bookmarks: "search-one-offs-bookmarks",
    339      history: "search-one-offs-history",
    340      tabs: "search-one-offs-tabs",
    341    };
    342    for (let { source, pref, restrict } of lazy.UrlbarUtils
    343      .LOCAL_SEARCH_MODES) {
    344      if (!lazy.UrlbarPrefs.get(pref)) {
    345        continue;
    346      }
    347      let name = lazy.UrlbarUtils.getResultSourceName(source);
    348      let button = this.document.createXULElement("button");
    349      button.id = `urlbar-engine-one-off-item-${name}`;
    350      button.setAttribute("class", "searchbar-engine-one-off-item");
    351      button.setAttribute("tabindex", "-1");
    352      this.document.l10n.setAttributes(button, messageIDs[name], {
    353        restrict,
    354      });
    355      button.source = source;
    356      this.buttons.appendChild(button);
    357    }
    358  }
    359 
    360  /**
    361   * Overrides the superclass's click listener to handle clicks on local
    362   * one-offs in addition to engine one-offs.
    363   *
    364   * @param {MouseEvent} event
    365   *   The click event.
    366   */
    367  _on_click(event) {
    368    // Ignore right clicks.
    369    if (event.button == 2) {
    370      return;
    371    }
    372 
    373    let button = /** @type {LegacySearchOneOffButton} */ (event.originalTarget);
    374 
    375    if (!button.engine && !button.source) {
    376      return;
    377    }
    378 
    379    this.selectedButton = button;
    380    this.handleSearchCommand(event, {
    381      engineName: button.engine?.name,
    382      source: button.source,
    383      entry: "oneoff",
    384    });
    385  }
    386 
    387  /**
    388   * Overrides the superclass's contextmenu listener to handle the context menu.
    389   *
    390   * @param {event} event
    391   *   The contextmenu event.
    392   */
    393  _on_contextmenu(event) {
    394    // Prevent the context menu from appearing.
    395    event.preventDefault();
    396  }
    397 }