Revision control

Copy as Markdown

Other Tools

<?xml version="1.0"?>
<!-- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % searchBarDTD SYSTEM "chrome://communicator/locale/search/searchbar.dtd">
%searchBarDTD;
<!ENTITY % textcontextDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd">
%textcontextDTD;
<!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd">
%navigatorDTD;
]>
<bindings id="SearchBindings"
<binding id="searchbar">
<resources>
</resources>
<content>
anonid="searchbar-stringbundle"/>
<!--
There is a dependency between "maxrows" attribute and
"SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing
one of them requires changing the other one.
-->
<xul:textbox class="searchbar-textbox"
anonid="searchbar-textbox"
type="autocomplete"
inputtype="search"
flex="1"
autocompletepopup="_child"
autocompletesearch="search-autocomplete"
autocompletesearchparam="searchbar-history"
maxrows="10"
completeselectedindex="true"
showcommentcolumn="true"
tabscrolling="true"
xbl:inherits="disabled">
<xul:box>
<xul:toolbarbutton class="plain searchbar-engine-button"
type="menu"
anonid="searchbar-engine-button"
xbl:inherits="image">
<xul:menupopup class="searchbar-popup"
anonid="searchbar-popup">
<xul:menuseparator/>
<xul:menuitem class="open-engine-manager"
anonid="open-engine-manager"
label="&cmd_engineManager.label;"
oncommand="OpenSearchEngineManager();"/>
</xul:menupopup>
</xul:toolbarbutton>
</xul:box>
<xul:hbox class="search-go-container">
<xul:image class="search-go-button"
anonid="search-go-button"
onclick="handleSearchCommand(event);"
tooltiptext="&searchEndCap.label;"/>
</xul:hbox>
<xul:panel anonid="searchPopupAutoComplete"
type="autocomplete"
noautofocus="true"/>
</xul:textbox>
</content>
<implementation implements="nsIObserver, nsIBrowserSearchInitObserver, nsISearchInstallCallback">
<constructor><![CDATA[
if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
return;
if (this.usePrivateBrowsing)
this._textbox.searchParam += "|private";
Services.obs.addObserver(this, "browser-search-engine-modified");
Services.obs.addObserver(this, "browser-search-service");
this._initialized = true;
Services.search.init(this);
]]></constructor>
<destructor><![CDATA[
if (this._initialized) {
this._initialized = false;
Services.obs.removeObserver(this, "browser-search-engine-modified");
Services.obs.removeObserver(this, "browser-search-service");
}
// 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._textboxInitialized && this._textbox.mController.input == this)
this._textbox.mController.input = null;
]]></destructor>
<field name="_stringBundle">document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-stringbundle");</field>
<field name="_textboxInitialized">false</field>
<field name="_textbox">document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-textbox");</field>
<field name="_popup">document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-popup");</field>
<field name="searchButton">document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-engine-button");</field>
<field name="usePrivateBrowsing" readonly="true">
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext)
.usePrivateBrowsing
</field>
<field name="_initialized">false</field>
<field name="_engines">null</field>
<field name="_needToBuildPopup">true</field>
<field name="FormHistory" readonly="true"><![CDATA[
(ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {}))
.FormHistory;
]]>
</field>
<property name="engines" readonly="true">
<getter><![CDATA[
if (!this._engines)
this._engines = Services.search.getVisibleEngines();
return this._engines;
]]></getter>
</property>
<property name="currentEngine">
<setter><![CDATA[
Services.search.currentEngine = val;
Services.obs.notifyObservers(null, "browser-search-engine-modified",
"engine-current");
return val;
]]></setter>
<getter><![CDATA[
var currentEngine = Services.search.currentEngine;
// Return a dummy engine if there is no currentEngine
return currentEngine || {name: "", uri: null};
]]></getter>
</property>
<!-- textbox is used by sanitize.js to clear the undo history when
clearing form information. -->
<property name="textbox" readonly="true"
onget="return this._textbox;"/>
<property name="value" onget="return this._textbox.value;"
onset="return this._textbox.value = val;"/>
<method name="focus">
<body><![CDATA[
this._textbox.focus();
]]></body>
</method>
<method name="select">
<body><![CDATA[
this._textbox.select();
]]></body>
</method>
<method name="onInitComplete">
<parameter name="aStatus"/>
<body><![CDATA[
if (!this._initialized)
return;
if (Components.isSuccessCode(aStatus)) {
// Refresh the display (updating icon, etc)
this.updateDisplay();
} else {
Cu.reportError("Cannot initialize search service, bailing out: " + aStatus);
}
]]></body>
</method>
<method name="onSuccess">
<parameter name="aEngine"/>
<body><![CDATA[
this.currentEngine = aEngine;
]]></body>
</method>
<method name="onError">
<parameter name="aErrorCode"/>
<body><![CDATA[
]]></body>
</method>
<method name="observe">
<parameter name="aEngine"/>
<parameter name="aTopic"/>
<parameter name="aVerb"/>
<body><![CDATA[
if (aTopic == "browser-search-engine-modified" ||
(aTopic == "browser-search-service" && aVerb == "init-complete")) {
switch (aVerb) {
case "engine-removed":
this.offerNewEngine(aEngine);
break;
case "engine-added":
this.hideNewEngine(aEngine);
break;
case "engine-current":
// The current engine was changed. Rebuilding the menu appears to
// confuse its idea of whether it should be open when it's just
// been clicked, so we force it to close now.
this._popup.hidePopup();
break;
case "engine-changed":
// An engine was removed (or hidden) or added, or an icon was
// changed. Do nothing special.
}
// Make sure the engine list is refetched next time it's needed
this._engines = null;
// Rebuild the popup and update the display after any modification.
this.rebuildPopup();
this.updateDisplay();
}
]]></body>
</method>
<!-- There are two seaprate lists of search engines, whose uses intersect
in this file. The search service (nsIBrowserSearchService and
nsSearchService.js) maintains a list of Engine objects which is used to
populate the searchbox list of available engines and to perform queries.
That list is accessed here via this.SearchService, and it's that sort of
Engine that is passed to this binding's observer as aEngine.
In addition, navigator.js fills two lists of autodetected search engines
(browser.engines and browser.hiddenEngines) as properties of
mCurrentBrowser. Those lists contain unnamed JS objects of the form
{ uri:, title:, icon: }, and that's what the searchbar uses to determine
whether to show any "Add <EngineName>" menu items in the drop-down.
The two types of engines are currently related by their identifying
titles (the Engine object's 'name'), although that may change; see bug
335102. -->
<!-- If the engine that was just removed from the searchbox list was
autodetected on this page, move it to each browser's active list so it
will be offered to be added again. -->
<method name="offerNewEngine">
<parameter name="aEngine"/>
<body><![CDATA[
for (var browser of getBrowser().browsers) {
if (browser.hiddenEngines) {
// XXX This will need to be changed when engines are identified by
// URL rather than title; see bug 335102.
var removeTitle = aEngine.wrappedJSObject.name;
for (var i = 0; i < browser.hiddenEngines.length; i++) {
if (browser.hiddenEngines[i].title == removeTitle) {
if (!browser.engines)
browser.engines = [];
browser.engines.push(browser.hiddenEngines[i]);
browser.hiddenEngines.splice(i, 1);
break;
}
}
}
}
this.updateSearchButton();
]]></body>
</method>
<!-- If the engine that was just added to the searchbox list was
autodetected on this page, move it to each browser's hidden list so it is
no longer offered to be added. -->
<method name="hideNewEngine">
<parameter name="aEngine"/>
<body><![CDATA[
for (var browser of getBrowser().browsers) {
if (browser.engines) {
// XXX This will need to be changed when engines are identified by
// URL rather than title; see bug 335102.
var removeTitle = aEngine.wrappedJSObject.name;
for (var i = 0; i < browser.engines.length; i++) {
if (browser.engines[i].title == removeTitle) {
if (!browser.hiddenEngines)
browser.hiddenEngines = [];
browser.hiddenEngines.push(browser.engines[i]);
browser.engines.splice(i, 1);
break;
}
}
}
}
this.updateSearchButton();
]]></body>
</method>
<method name="updateSearchButton">
<body><![CDATA[
var engines = getBrowser().mCurrentBrowser.engines;
if (engines && engines.length)
this.searchButton.setAttribute("addengines", "true");
else
this.searchButton.removeAttribute("addengines");
]]></body>
</method>
<method name="updateDisplay">
<body><![CDATA[
var uri = this.currentEngine.iconURI;
this.setAttribute("image", uri ? uri.spec : "");
var name = this.currentEngine.name;
var text = this._stringBundle.getFormattedString("searchtip", [name]);
this._textbox.placeholder = name;
this._textbox.tooltipText = text;
]]></body>
</method>
<!-- Rebuilds the dynamic portion of the popup menu (i.e., the menu items
for new search engines that can be added to the available list). This
is called each time the popup is shown.
-->
<method name="rebuildPopupDynamic">
<body><![CDATA[
// We might not have added the main popup items yet, do that first
// if needed.
if (this._needToBuildPopup)
this.rebuildPopup();
var popup = this._popup;
// Clear any addengine menuitems, including addengine-item entries and
// the addengine-separator. Work backward to avoid invalidating the
// indexes as items are removed.
var items = popup.childNodes;
for (var i = items.length - 1; i >= 0; i--) {
if (items[i].classList.contains("addengine-item") ||
items[i].classList.contains("addengine-separator"))
items[i].remove();
}
var addengines = getBrowser().mCurrentBrowser.engines;
if (addengines && addengines.length > 0) {
const kXULNS =
// Find the (first) separator in the remaining menu, or the first item
// if no separators are present.
var insertLocation = popup.firstChild;
while (insertLocation.nextSibling &&
insertLocation.localName != "menuseparator") {
insertLocation = insertLocation.nextSibling;
}
if (insertLocation.localName != "menuseparator")
insertLocation = popup.firstChild;
var separator = document.createElementNS(kXULNS, "menuseparator");
separator.setAttribute("class", "addengine-separator");
popup.insertBefore(separator, insertLocation);
// Insert the "add this engine" items.
for (var i = 0; i < addengines.length; i++) {
var engineInfo = addengines[i];
var labelStr =
this._stringBundle.getFormattedString("cmd_addFoundEngine",
[engineInfo.title]);
var menuitem = document.createElementNS(kXULNS, "menuitem");
menuitem.setAttribute("class", "menuitem-iconic addengine-item");
menuitem.setAttribute("label", labelStr);
menuitem.setAttribute("tooltiptext", engineInfo.uri);
menuitem.setAttribute("uri", engineInfo.uri);
if (engineInfo.icon)
menuitem.setAttribute("image", engineInfo.icon);
menuitem.setAttribute("title", engineInfo.title);
popup.insertBefore(menuitem, insertLocation);
}
}
]]></body>
</method>
<!-- Rebuilds the list of visible search engines in the menu. Does not remove
or update any dynamic entries (i.e., "Add this engine" items) nor the
Manage Engines item. This is called by the observer when the list of
visible engines, or the currently selected engine, has changed.
-->
<method name="rebuildPopup">
<body><![CDATA[
var popup = this._popup;
// Clear the popup, down to the first separator
while (popup.firstChild && popup.firstChild.localName != "menuseparator")
popup.firstChild.remove();
const kXULNS =
var engines = this.engines;
for (var i = engines.length - 1; i >= 0; --i) {
var menuitem = document.createElementNS(kXULNS, "menuitem");
var name = engines[i].name;
menuitem.setAttribute("label", name);
menuitem.setAttribute("class", "menuitem-iconic searchbar-engine-menuitem menuitem-with-favicon");
// Since this menu is rebuilt by the observer method whenever a new
// engine is selected, the "selected" attribute does not need to be
// explicitly cleared anywhere.
if (engines[i] == this.currentEngine)
menuitem.setAttribute("selected", "true");
var tooltip = this._stringBundle.getFormattedString("searchtip", [name]);
menuitem.setAttribute("tooltiptext", tooltip);
if (engines[i].iconURI)
menuitem.setAttribute("image", engines[i].iconURI.spec);
popup.insertBefore(menuitem, popup.firstChild);
menuitem.engine = engines[i];
}
this._needToBuildPopup = false;
]]></body>
</method>
<method name="selectEngine">
<parameter name="aEvent"/>
<parameter name="isNextEngine"/>
<body><![CDATA[
// Find the new index
var newIndex = this.engines.indexOf(this.currentEngine);
newIndex += isNextEngine ? 1 : -1;
if (newIndex >= 0 && newIndex < this.engines.length)
this.currentEngine = this.engines[newIndex];
aEvent.preventDefault();
aEvent.stopPropagation();
]]></body>
</method>
<method name="handleSearchCommand">
<parameter name="aEvent"/>
<body><![CDATA[
var textBox = this._textbox;
var textValue = textBox.value;
var where = "current";
if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
if (aEvent.button == 2)
return;
where = whereToOpenLink(aEvent, false, true);
}
else {
var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
if ((aEvent && aEvent.altKey) ^ newTabPref)
where = "tabfocused";
}
this.doSearch(textValue, where);
]]></body>
</method>
<method name="doSearch">
<parameter name="aData"/>
<parameter name="aWhere"/>
<body><![CDATA[
var textBox = this._textbox;
// Save the current value in the form history.
if (aData && !this.usePrivateBrowsing && this.FormHistory.enabled) {
this.FormHistory.update(
{ op: "bump",
fieldname: textBox.getAttribute("autocompletesearchparam"),
value: aData },
{ handleError(aError) {
Cu.reportError("Saving search to form history failed: " + aError.message);
}});
}
// null parameter below specifies HTML response for search
var submission = this.currentEngine.getSubmission(aData);
openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
]]></body>
</method>
</implementation>
<handlers>
<handler event="command"><![CDATA[
const target = event.originalTarget;
if (target.engine) {
this.currentEngine = target.engine;
} else if (target.classList.contains("addengine-item")) {
// We only detect OpenSearch files
var type = Ci.nsISearchEngine.DATA_XML;
// Select the installed engine if the installation succeeds
Services.search.addEngine(target.getAttribute("uri"), type,
target.getAttribute("image"), false,
this);
}
else
return;
this.focus();
this.select();
]]></handler>
<handler event="popupshowing" action="this.rebuildPopupDynamic();"/>
<handler event="DOMMouseScroll"
phase="capturing"
modifiers="accel"
action="this.selectEngine(event, (event.detail > 0));"/>
<handler event="focus"><![CDATA[
// Speculatively connect to the current engine's search URI (and
// suggest URI, if different) to reduce request latency.
this.currentEngine.speculativeConnect({window: window});
]]></handler>
</handlers>
</binding>
<binding id="search-textbox"
<implementation implements="nsIObserver">
<constructor><![CDATA[
var bindingParent = document.getBindingParent(this);
if (bindingParent && bindingParent.parentNode.parentNode.localName ==
"toolbarpaletteitem")
return;
if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll"))
this.setAttribute("clickSelectsAll", true);
// Add items to context menu and attach controller to handle them
this.controllers.appendController(this.searchbarController);
// bindingParent is not set in the sidebar because there is no
// searchbar created in it.
if (bindingParent) {
bindingParent._textboxInitialized = true;
}
// Add observer for suggest preference
Services.prefs.addObserver("browser.search.suggest.enabled", this);
this._inputBox.setAttribute("suggestchecked", this._suggestEnabled);
]]></constructor>
<destructor><![CDATA[
Services.prefs.removeObserver("browser.search.suggest.enabled", this);
// Because XBL and the customize toolbar code interacts poorly,
// there may not be anything to remove here
try {
this.controllers.removeController(this.searchbarController);
} catch (ex) { }
]]></destructor>
<field name="_inputBox">
document.getAnonymousElementByAttribute(this, "anonid", "textbox-input-box");
</field>
<field name="_suggestEnabled">
Services.prefs.getBoolPref("browser.search.suggest.enabled");
</field>
<method name="observe">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aData"/>
<body><![CDATA[
if (aTopic == "nsPref:changed") {
this._suggestEnabled =
Services.prefs.getBoolPref("browser.search.suggest.enabled");
this._inputBox.setAttribute("suggestchecked", this._suggestEnabled);
}
]]></body>
</method>
<field name="FormHistory" readonly="true"><![CDATA[
(ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {}))
.FormHistory;
]]>
</field>
<!-- nsIController -->
<field name="searchbarController" readonly="true"><![CDATA[({
supportsCommand: function(aCommand) {
switch (aCommand) {
case "cmd_pasteAndSearch":
case "cmd_clearhistory":
case "cmd_togglesuggest":
return true;
}
return false;
},
isCommandEnabled: function(aCommand) {
switch (aCommand) {
case "cmd_pasteAndSearch":
return document.commandDispatcher
.getControllerForCommand("cmd_paste")
.isCommandEnabled("cmd_paste");
case "cmd_clearhistory":
case "cmd_togglesuggest":
return true;
}
return false;
},
doCommand: function (aCommand) {
switch (aCommand) {
case "cmd_pasteAndSearch":
this.select();
goDoCommand("cmd_paste");
this.onTextEntered();
break;
case "cmd_clearhistory":
this.FormHistory.update(
{ op : "remove", fieldname : "searchbar-history" },
null);
this.value = "";
break;
case "cmd_togglesuggest":
// The pref observer will update _suggestEnabled and the menu
// checkmark.
Services.prefs.setBoolPref("browser.search.suggest.enabled",
!this._suggestEnabled);
break;
default:
// do nothing with unrecognized command
}
}.bind(this)
})]]></field>
</implementation>
<handlers>
<handler event="dragover">
<![CDATA[
var types = event.dataTransfer.types;
if (types.includes("text/plain") || types.includes("text/x-moz-text-internal"))
event.preventDefault();
]]>
</handler>
<handler event="drop">
<![CDATA[
var dataTransfer = event.dataTransfer;
var data = dataTransfer.getData("text/plain");
if (!data)
data = dataTransfer.getData("text/x-moz-text-internal");
if (data) {
event.preventDefault();
this.value = data;
this.onTextEntered();
}
]]>
</handler>
</handlers>
</binding>
<binding id="searchbar-textbox"
<implementation>
<method name="openSearch">
<body>
<![CDATA[
// Don't open search popup if history popup is open
if (!this.popupOpen) {
document.getBindingParent(this).searchButton.open = true;
return false;
}
return true;
]]>
</body>
</method>
<!-- override |onTextEntered| in autocomplete.xml -->
<method name="onTextEntered">
<parameter name="aEvent"/>
<body><![CDATA[
var evt = aEvent || this.mEnterEvent;
document.getBindingParent(this).handleSearchCommand(evt);
this.mEnterEvent = null;
]]></body>
</method>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_UP" modifiers="accel"
phase="capturing"
action="document.getBindingParent(this).selectEngine(event, false);"/>
<handler event="keypress" keycode="VK_DOWN" modifiers="accel"
phase="capturing"
action="document.getBindingParent(this).selectEngine(event, true);"/>
<handler event="keypress" keycode="VK_DOWN" modifiers="alt"
phase="capturing"
action="return this.openSearch();"/>
<handler event="keypress" keycode="VK_UP" modifiers="alt"
phase="capturing"
action="return this.openSearch();"/>
<handler event="keypress" keycode="VK_F4" phase="capturing">
<![CDATA[
return (AppConstants.platform == "macosx") || this.openSearch()
]]></handler>
</handlers>
</binding>
<binding id="input-box-search" extends="chrome://global/content/bindings/textbox.xml#input-box">
<content context="_child">
<children/>
<xul:menupopup anonid="input-box-contextmenu"
class="textbox-contextmenu"
onpopupshowing="var input =
this.parentNode.getElementsByAttribute('anonid', 'input')[0];
if (document.commandDispatcher.focusedElement != input)
input.focus();
this.parentNode._doPopupItemEnabling(this);"
oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if (cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
<xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
<xul:menuseparator/>
<xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
<xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
<xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
<xul:menuitem label="&pasteSearchCmd.label;" accesskey="&pasteSearchCmd.accesskey;" cmd="cmd_pasteAndSearch"/>
<xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
<xul:menuseparator/>
<xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
<xul:menuseparator/>
<xul:menuitem label="&clearHistoryCmd.label;" accesskey="&clearHistoryCmd.accesskey;" cmd="cmd_clearhistory"/>
<xul:menuitem label="&showSuggestionsCmd.label;"
accesskey="&showSuggestionsCmd.accesskey;"
anonid="toggle-suggest-item"
type="checkbox"
autocheck="false"
xbl:inherits="checked=suggestchecked"
cmd="cmd_togglesuggest"/>
</xul:menupopup>
</content>
</binding>
</bindings>