/* 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/. */
"use strict";
/* globals XULCommandEvent */
// This is loaded into chrome windows with the subscript loader. Wrap in // a block to prevent accidentally leaking globals onto `window`.
{ const lazy = {};
this.destroy = this.destroy.bind(this); this._setupEventListeners();
let searchbar = this; this.observer = {
observe(aEngine, aTopic) { if (aTopic == "browser-search-engine-modified") { // Make sure the engine list is refetched next time it's needed
searchbar._engines = null;
// Update the popup header and update the display after any modification.
searchbar._textbox.popup.updateHeader();
searchbar.updateDisplay();
}
},
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
};
// Don't go further if in Customize mode. if (this.parentNode.parentNode.localName == "toolbarpaletteitem") { return;
}
// Ensure we get persisted widths back, if we've been in the palette:
let storedWidth = Services.xulStore.getValue(
document.documentURI, this.parentNode.id, "width"
); if (storedWidth) { this.parentNode.setAttribute("width", storedWidth); this.parentNode.style.width = storedWidth + "px";
}
(window.delayedStartupPromise || Promise.resolve()).then(() => {
window.requestIdleCallback(() => {
Services.search
.init()
.then(() => { // Bail out if the binding's been destroyed if (!this._initialized) { return;
}
// Ensure the popup header is updated if the user has somehow // managed to open the popup before the search service has finished // initializing. this._textbox.popup.updateHeader(); // Refresh the display (updating icon, etc) this.updateDisplay();
BrowserSearch.updateOpenSearchBadge();
})
.catch(status =>
console.error( "Cannot initialize search service, bailing out:",
status
)
);
});
});
// Wait until the popupshowing event to avoid forcing immediate // attachment of the search-one-offs binding. this.textbox.popup.addEventListener( "popupshowing",
() => {
let oneOffButtons = this.textbox.popup.oneOffButtons; // Some accessibility tests create their own <searchbar> that doesn't // use the popup binding below, so null-check oneOffButtons. if (oneOffButtons) {
oneOffButtons.telemetryOrigin = "searchbar"; // Set .textbox first, since the popup setter will cause // a _rebuild call that uses it.
oneOffButtons.textbox = this.textbox;
oneOffButtons.popup = this.textbox.popup;
}
},
{ capture: true, once: true }
);
set currentEngine(val) { if (PrivateBrowsingUtils.isWindowPrivate(window)) {
Services.search.setDefaultPrivate(
val,
Ci.nsISearchService.CHANGE_REASON_USER_SEARCHBAR
);
} else {
Services.search.setDefault(
val,
Ci.nsISearchService.CHANGE_REASON_USER_SEARCHBAR
);
}
}
get currentEngine() {
let currentEngine; if (PrivateBrowsingUtils.isWindowPrivate(window)) {
currentEngine = Services.search.defaultPrivateEngine;
} else {
currentEngine = Services.search.defaultEngine;
} // Return a dummy engine if there is no currentEngine return currentEngine || { name: "", uri: null };
}
/** * textbox is used by sanitize.js to clear the undo history when * clearing form information. * * @returns {HTMLInputElement}
*/
get textbox() { returnthis._textbox;
}
set value(val) { this._textbox.value = val;
}
get value() { returnthis._textbox.value;
}
destroy() { if (this._initialized) { this._initialized = false;
window.removeEventListener("unload", this.destroy);
// Make sure to break the cycle from _textbox to us. Otherwise we leak // the world. But make sure it's actually pointing to us. // Also make sure the textbox has ever been constructed, otherwise the // _textbox getter will cause the textbox constructor to run, add an // observer, and leak the world too. if ( this._textbox && this._textbox.mController && this._textbox.mController.input && this._textbox.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) { this._textbox.mController.input = null;
}
}
openSuggestionsPanel(aShowOnlySettingsIfEmpty) { if (this._textbox.open) { return;
}
this._textbox.showHistoryPopup();
let searchIcon = document.querySelector(".searchbar-search-button");
searchIcon.setAttribute("aria-expanded", "true");
if (this._textbox.value) { // showHistoryPopup does a startSearch("") call, ensure the // controller handles the text from the input box instead: this._textbox.mController.handleText();
} elseif (aShowOnlySettingsIfEmpty) { this.setAttribute("showonlysettings", "true");
}
}
async selectEngine(aEvent, isNextEngine) { // Stop event bubbling now, because the rest of this method is async.
aEvent.preventDefault();
aEvent.stopPropagation();
// Find the new index.
let engines = await this.getEngines();
let currentName = this.currentEngine.name;
let newIndex = -1;
let lastIndex = engines.length - 1; for (let i = lastIndex; i >= 0; --i) { if (engines[i].name == currentName) { // Check bounds to cycle through the list of engines continuously. if (!isNextEngine && i == 0) {
newIndex = lastIndex;
} elseif (isNextEngine && i == lastIndex) {
newIndex = 0;
} else {
newIndex = i + (isNextEngine ? 1 : -1);
} break;
}
}
if (selectedIndex == -1) {
isOneOff = this.textbox.popup.oneOffButtons.eventTargetIsAOneOff(aEvent);
}
if (aWhere === "tab" && !!aParams.inBackground) { // Keep the focus in the search bar.
aParams.avoidBrowserFocus = true;
} elseif (
aWhere !== "window" &&
aEvent.keyCode === KeyEvent.DOM_VK_RETURN
) { // Move the focus to the selected browser when keyup the Enter.
aParams.avoidBrowserFocus = true; this._needBrowserFocusAtEnterKeyUp = true;
}
// This is a one-off search only if oneOffRecorded is true. this.doSearch(textValue, aWhere, aEngine, aParams, isOneOff);
}
doSearch(aData, aWhere, aEngine, aParams, isOneOff = false) {
let textBox = this._textbox;
let engine = aEngine || this.currentEngine;
// Save the current value in the form history if (
aData &&
!PrivateBrowsingUtils.isWindowPrivate(window) &&
lazy.FormHistory.enabled &&
aData.length <=
lazy.SearchSuggestionController.SEARCH_HISTORY_MAX_VALUE_LENGTH
) {
lazy.FormHistory.update({
op: "bump",
fieldname: textBox.getAttribute("autocompletesearchparam"),
value: aData,
source: engine.name,
}).catch(error =>
console.error("Saving search to form history failed:", error)
);
}
let submission = engine.getSubmission(aData, null, "searchbar");
// If we hit here, we come either from a one-off, a plain search or a suggestion. const details = {
isOneOff,
isSuggestion: !isOneOff && this.telemetrySelectedIndex != -1,
};
// Record when the user uses the search bar
Services.prefs.setStringPref( "browser.search.widget.lastUsed", new Date().toISOString()
);
// null parameter below specifies HTML response for search
let params = {
postData: submission.postData,
globalHistoryOptions: {
triggeringSearchEngine: engine.name,
},
}; if (aParams) { for (let key in aParams) {
params[key] = aParams[key];
}
}
openTrustedLinkIn(submission.uri.spec, aWhere, params);
}
/** * Returns information on where a search results page should be loaded: in the * current tab or a new tab. * * @param {event} aEvent * The event that triggered the page load. * @param {boolean} [aForceNewTab] * True to force the load in a new tab. * @returns {object} An object { where, params }. `where` is a string: * "current" or "tab". `params` is an object further describing how * the page should be loaded.
*/
_whereToOpen(aEvent, aForceNewTab = false) {
let where = "current";
let params = {}; const newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
// Open ctrl/cmd clicks on one-off buttons in a new background tab. if (aEvent?.originalTarget.classList.contains("search-go-button")) {
where = lazy.BrowserUtils.whereToOpenLink(aEvent, false, true); if (
newTabPref &&
!aEvent.altKey &&
!aEvent.getModifierState("AltGraph") &&
where == "current" &&
!gBrowser.selectedTab.isEmpty
) {
where = "tab";
}
} elseif (aForceNewTab) {
where = "tab"; if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) {
params = {
inBackground: true,
};
}
} else { if (
(KeyboardEvent.isInstance(aEvent) &&
(aEvent.altKey || aEvent.getModifierState("AltGraph"))) ^
newTabPref &&
!gBrowser.selectedTab.isEmpty
) {
where = "tab";
} if (
MouseEvent.isInstance(aEvent) &&
(aEvent.button == 1 || aEvent.getModifierState("Accel"))
) {
where = "tab";
params = {
inBackground: true,
};
}
}
return { where, params };
}
/** * Opens the search form of the provided engine or the current engine * if no engine was provided. * * @param {event} aEvent * The event causing the searchForm to be opened. * @param {nsISearchEngine} [aEngine] * The search engine or undefined to use the current engine. * @param {string} where * Where the search form should be opened. * @param {object} [params] * Parameters for URILoadingHelper.openLinkIn.
*/
openSearchFormWhere(aEvent, aEngine, where, params = {}) {
let engine = aEngine || this.currentEngine;
let searchForm = engine.searchForm;
if (where === "tab" && !!params.inBackground) { // Keep the focus in the search bar.
params.avoidBrowserFocus = true;
} elseif (
where !== "window" &&
aEvent.keyCode === KeyEvent.DOM_VK_RETURN
) { // Move the focus to the selected browser when keyup the Enter.
params.avoidBrowserFocus = true; this._needBrowserFocusAtEnterKeyUp = true;
}
openTrustedLinkIn(searchForm, where, params);
}
disconnectedCallback() { this.destroy(); while (this.firstChild) { this.firstChild.remove();
}
}
/** * Determines if we should select all the text in the searchbar based on the * searchbar state, and whether the selection is empty.
*/
_maybeSelectAll() { if (
!this._preventClickSelectsAll &&
document.activeElement == this._textbox && this._textbox.selectionStart == this._textbox.selectionEnd
) { this.select();
}
}
this.addEventListener( "blur",
() => { // Reset the flag since we can't capture enter keyup event if the event happens // after moving the focus. this._needBrowserFocusAtEnterKeyUp = false;
// If the input field is still focused then a different window has // received focus, ignore the next focus event. this._ignoreFocus = document.activeElement == this._textbox;
}, true
);
this.addEventListener( "focus",
() => { // Speculatively connect to the current engine's search URI (and // suggest URI, if different) to reduce request latency this.currentEngine.speculativeConnect({
window,
originAttributes: gBrowser.contentPrincipal.originAttributes,
});
if (this._ignoreFocus) { // This window has been re-focused, don't show the suggestions this._ignoreFocus = false; return;
}
// Don't open the suggestions if there is no text in the textbox. if (!this._textbox.value) { return;
}
// Don't open the suggestions if the mouse was used to focus the // textbox, that will be taken care of in the click handler. if (
Services.focus.getLastFocusMethod(window) &
Services.focus.FLAG_BYMOUSE
) { return;
}
this.openSuggestionsPanel();
}, true
);
this.addEventListener("mousedown", event => { this._preventClickSelectsAll = this._textbox.focused; // Ignore right clicks if (event.button != 0) { return;
}
// Ignore clicks on the search go button. if (event.originalTarget.classList.contains("search-go-button")) { return;
}
// Ignore clicks on menu items in the input's context menu. if (event.originalTarget.localName == "menuitem") { return;
}
let isIconClick = event.originalTarget.classList.contains( "searchbar-search-button"
);
// Hide popup when icon is clicked while popup is open if (isIconClick && this.textbox.popup.popupOpen) { this.textbox.popup.closePopup();
let searchIcon = document.querySelector(".searchbar-search-button");
searchIcon.setAttribute("aria-expanded", "false");
} elseif (isIconClick || this._textbox.value) { // Open the suggestions whenever clicking on the search icon or if there // is text in the textbox. this.openSuggestionsPanel(true);
}
});
}
this.textbox.addEventListener("dragover", event => {
let types = event.dataTransfer.types; if (
types.includes("text/plain") ||
types.includes("text/x-moz-text-internal")
) {
event.preventDefault();
}
});
this.textbox.addEventListener("drop", event => {
let dataTransfer = event.dataTransfer;
let data = dataTransfer.getData("text/plain"); if (!data) {
data = dataTransfer.getData("text/x-moz-text-internal");
} if (data) {
event.preventDefault(); this.textbox.value = data; this.openSuggestionsPanel();
}
});
this.textbox.addEventListener("contextmenu", event => { if (!this._menupopup) { this._buildContextMenu();
}
this._textbox.closePopup();
// Make sure the context menu isn't opened via keyboard shortcut. Check for text selection // before updating the state of any menu items. if (event.button) { this._maybeSelectAll();
}
// Update disabled state of menu items for (let item of this._menupopup.querySelectorAll("menuitem[cmd]")) {
let command = item.getAttribute("cmd");
let controller =
document.commandDispatcher.getControllerForCommand(command);
item.disabled = !controller.isCommandEnabled(command);
}
let pasteEnabled = document.commandDispatcher
.getControllerForCommand("cmd_paste")
.isCommandEnabled("cmd_paste"); this._pasteAndSearchMenuItem.disabled = !pasteEnabled;
// This overrides the searchParam property in autocomplete.xml. We're // hijacking this property as a vehicle for delivering the privacy // information about the window into the guts of nsSearchSuggestions. // Note that the setter is the same as the parent. We were not sure whether // we can override just the getter. If that proves to be the case, the setter // can be removed.
Object.defineProperty(this.textbox, "searchParam", {
get() { return ( this.getAttribute("autocompletesearchparam") +
(PrivateBrowsingUtils.isWindowPrivate(window) ? "|private" : "")
);
},
set(val) { this.setAttribute("autocompletesearchparam", val);
},
});
// This is implemented so that when textbox.value is set directly (e.g., // by tests), the one-off query is updated. this.textbox.onBeforeValueSet = aValue => { if (this.textbox.popup._oneOffButtons) { this.textbox.popup.oneOffButtons.query = aValue;
} return aValue;
};
// Returns true if the event is handled by us, false otherwise. this.textbox.onBeforeHandleKeyDown = aEvent => { if (aEvent.getModifierState("Accel")) { if (
aEvent.keyCode == KeyEvent.DOM_VK_DOWN ||
aEvent.keyCode == KeyEvent.DOM_VK_UP
) { this.selectEngine(aEvent, aEvent.keyCode == KeyEvent.DOM_VK_DOWN); returntrue;
} returnfalse;
}
let popup = this.textbox.popup;
let searchIcon = document.querySelector(".searchbar-search-button");
searchIcon.setAttribute("aria-expanded", popup.popupOpen); if (popup.popupOpen) {
let suggestionsHidden =
popup.richlistbox.getAttribute("collapsed") == "true";
let numItems = suggestionsHidden ? 0 : popup.matchCount; return popup.oneOffButtons.handleKeyDown(aEvent, numItems, true);
} elseif (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) { if (this.textbox.editor.canUndo) { this.textbox.editor.undoAll();
} else { this.textbox.select();
} returntrue;
} returnfalse;
};
// This method overrides the autocomplete binding's openPopup (essentially // duplicating the logic from the autocomplete popup binding's // openAutocompletePopup method), modifying it so that the popup is aligned with // the inner textbox, but sized to not extend beyond the search bar border. this.textbox.openPopup = () => { // Entering customization mode after the search bar had focus causes // the popup to appear again, due to focus returning after the // hamburger panel closes. Don't open in that spurious event. if (document.documentElement.hasAttribute("customizing")) { return;
}
let popup = this.textbox.popup;
let searchIcon = document.querySelector(".searchbar-search-button"); if (!popup.mPopupOpen) { // Initially the panel used for the searchbar (PopupSearchAutoComplete // in browser.xhtml) is hidden to avoid impacting startup / new // window performance. The base binding's openPopup would normally // call the overriden openAutocompletePopup in // browser-search-autocomplete-result-popup binding to unhide the popup, // but since we're overriding openPopup we need to unhide the panel // ourselves.
popup.hidden = false;
// Don't roll up on mouse click in the anchor for the search UI. if (popup.id == "PopupSearchAutoComplete") {
popup.setAttribute("norolluponanchor", "true");
}
popup.mInput = this.textbox; // clear any previous selection, see bugs 400671 and 488357
popup.selectedIndex = -1;
// Ensure the panel has a meaningful initial size and doesn't grow // unconditionally.
let { width } = window.windowUtils.getBoundsWithoutFlushing(this); if (popup.oneOffButtons) { // We have a min-width rule on search-panel-one-offs to show at // least 4 buttons, so take that into account here.
width = Math.max(width, popup.oneOffButtons.buttonWidth * 4);
}
this.textbox.handleEnter = event => { // Toggle the open state of the add-engine menu button if it's // selected. We're using handleEnter for this instead of listening // for the command event because a command event isn't fired. if ( this.textbox.selectedButton && this.textbox.selectedButton.getAttribute("anonid") == "addengine-menu-button"
) { this.textbox.selectedButton.open = !this.textbox.selectedButton.open; returntrue;
} // Ignore blank search unless add search engine or // settings button is selected, see bugs 1894910 and 1903608. if (
!this.textbox.value &&
!( this.textbox.selectedButton?.getAttribute("id") == "searchbar-anon-search-settings" || this.textbox.selectedButton?.classList.contains( "searchbar-engine-one-off-add-engine"
)
)
) { if (event.shiftKey) {
let engine = this.textbox.selectedButton?.engine;
let { where, params } = this._whereToOpen(event); this.openSearchFormWhere(event, engine, where, params);
} returntrue;
} // Otherwise, "call super": do what the autocomplete binding's // handleEnter implementation does. returnthis.textbox.mController.handleEnter(false, event || null);
};
let engine;
let oneOff = this.textbox.selectedButton; if (oneOff) { if (!oneOff.engine) {
oneOff.doCommand(); return;
}
engine = oneOff.engine;
} if (this.textbox.popupSelectedIndex != -1) { this.telemetrySelectedIndex = this.textbox.popupSelectedIndex; this.textbox.popupSelectedIndex = -1;
} this.handleSearchCommand(event, engine);
};
this.textbox.onbeforeinput = event => { if (event.data && this._needBrowserFocusAtEnterKeyUp) { // Ignore char key input while processing enter key.
event.preventDefault();
}
};
this.textbox.onkeyup = () => { // Pressing Enter key while pressing Meta key, and next, even when // releasing Enter key before releasing Meta key, the keyup event is not // fired. Therefore, if Enter keydown is detecting, continue the post // processing for Enter key when any keyup event is detected. if (this._needBrowserFocusAtEnterKeyUp) { this._needBrowserFocusAtEnterKeyUp = false;
gBrowser.selectedBrowser.focus();
}
};
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.