summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs399
1 files changed, 399 insertions, 0 deletions
diff --git a/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs b/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs
new file mode 100644
index 0000000000..4a862f441e
--- /dev/null
+++ b/browser/components/urlbar/UrlbarSearchOneOffs.sys.mjs
@@ -0,0 +1,399 @@
+/* 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",
+ 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.jsm value for the Address Bar.
+ this.disableOneOffsHorizontalKeyNavigation = true;
+ this._webEngines = [];
+ }
+
+ /**
+ * 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 (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();
+ }
+
+ /**
+ * 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, a xul:button, 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * The selected index in the view or -1 if there is no selection.
+ *
+ * @returns {number}
+ */
+ get selectedViewIndex() {
+ return this.view.selectedElementIndex;
+ }
+ set selectedViewIndex(val) {
+ this.view.selectedElementIndex = val;
+ }
+
+ /**
+ * Closes the view.
+ */
+ closeView() {
+ if (this.view) {
+ this.view.close();
+ }
+ }
+
+ /**
+ * Called when a one-off is clicked. This is not called for the settings
+ * button.
+ *
+ * @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;
+ }
+
+ // 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.
+ */
+ _rebuildEngineList(engines, addEngines) {
+ 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 (!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();
+ }
+}