mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
adb59720b0
--HG-- extra : rebase_source : b79131c2937a3c64db3a7b2dbf7940df9fc8e45d
882 lines
27 KiB
XML
882 lines
27 KiB
XML
<?xml version="1.0" encoding="Windows-1252" ?>
|
|
<!-- 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 % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
|
|
%browserDTD;
|
|
]>
|
|
|
|
<bindings
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
|
|
<implementation implements="nsIObserver">
|
|
<constructor>
|
|
<![CDATA[
|
|
this._mayFormat = Services.prefs.getBoolPref("browser.urlbar.formatting.enabled");
|
|
this._mayTrimURLs = Services.prefs.getBoolPref("browser.urlbar.trimURLs");
|
|
this._maySelectAll = Services.prefs.getBoolPref("browser.urlbar.doubleClickSelectsAll");
|
|
|
|
Services.prefs.addObserver("browser.urlbar.formatting.enabled", this, false);
|
|
Services.prefs.addObserver("browser.urlbar.trimURLs", this, false);
|
|
Services.prefs.addObserver("browser.urlbar.doubleClickSelectsAll", this, false);
|
|
|
|
this.inputField.controllers.insertControllerAt(0, this._copyCutValueController);
|
|
|
|
this.minResultsForPopup = 0;
|
|
this.popup._input = this;
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
Services.prefs.removeObserver("browser.urlbar.formatting.enabled", this);
|
|
Services.prefs.removeObserver("browser.urlbar.trimURLs", this);
|
|
Services.prefs.removeObserver("browser.urlbar.doubleClickSelectsAll", this);
|
|
]]>
|
|
</destructor>
|
|
|
|
<field name="_mayFormat"/>
|
|
<field name="_mayTrimURLs"/>
|
|
<field name="_maySelectAll"/>
|
|
<field name="_lastKnownGoodURL"/>
|
|
|
|
<method name="openPopup">
|
|
<body>
|
|
<![CDATA[
|
|
this.popup.openAutocompletePopup(this, null);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="closePopup">
|
|
<body>
|
|
<![CDATA[
|
|
this.popup.closePopup(this, null);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- URI Display: Domain Highlighting -->
|
|
|
|
<method name="_clearFormatting">
|
|
<body>
|
|
<![CDATA[
|
|
let controller = this.editor.selectionController;
|
|
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
|
selection.removeAllRanges();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="formatValue">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this._mayFormat || this.isEditing)
|
|
return;
|
|
|
|
let controller = this.editor.selectionController;
|
|
let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
|
|
selection.removeAllRanges();
|
|
|
|
let textNode = this.editor.rootElement.firstChild;
|
|
let value = textNode.textContent;
|
|
|
|
let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
|
|
if (protocol &&
|
|
["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
|
|
return;
|
|
let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
|
|
if (!matchedURL)
|
|
return;
|
|
|
|
let [, preDomain, domain] = matchedURL;
|
|
let baseDomain = domain;
|
|
let subDomain = "";
|
|
// getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
|
|
if (domain[0] != "[") {
|
|
try {
|
|
baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
|
|
if (!domain.endsWith(baseDomain)) {
|
|
// getBaseDomainFromHost converts its resultant to ACE.
|
|
let IDNService = Cc["@mozilla.org/network/idn-service;1"]
|
|
.getService(Ci.nsIIDNService);
|
|
baseDomain = IDNService.convertACEtoUTF8(baseDomain);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
if (baseDomain != domain) {
|
|
subDomain = domain.slice(0, -baseDomain.length);
|
|
}
|
|
|
|
let rangeLength = preDomain.length + subDomain.length;
|
|
if (rangeLength) {
|
|
let range = document.createRange();
|
|
range.setStart(textNode, 0);
|
|
range.setEnd(textNode, rangeLength);
|
|
selection.addRange(range);
|
|
}
|
|
|
|
let startRest = preDomain.length + domain.length;
|
|
if (startRest < value.length) {
|
|
let range = document.createRange();
|
|
range.setStart(textNode, startRest);
|
|
range.setEnd(textNode, value.length);
|
|
selection.addRange(range);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- URI Display: Scheme and Trailing Slash Triming -->
|
|
|
|
<method name="_trimURL">
|
|
<parameter name="aURL"/>
|
|
<body>
|
|
<![CDATA[
|
|
// This function must not modify the given URL such that calling
|
|
// nsIURIFixup::createFixupURI with the rfdesult will produce a different URI.
|
|
return aURL /* remove single trailing slash for http/https/ftp URLs */
|
|
.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
|
|
/* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
|
|
.replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_getSelectedValueForClipboard">
|
|
<body>
|
|
<![CDATA[
|
|
// Grab the actual input field's value, not our value, which could include moz-action:
|
|
let inputVal = this.inputField.value;
|
|
let selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
|
|
|
|
// If the selection doesn't start at the beginning or doesn't span the full domain or
|
|
// the URL bar is modified, nothing else to do here.
|
|
if (this.selectionStart > 0 || this.valueIsTyped)
|
|
return selectedVal;
|
|
|
|
// The selection doesn't span the full domain if it doesn't contain a slash and is
|
|
// followed by some character other than a slash.
|
|
if (!selectedVal.contains("/")) {
|
|
let remainder = inputVal.replace(selectedVal, "");
|
|
if (remainder != "" && remainder[0] != "/")
|
|
return selectedVal;
|
|
}
|
|
|
|
let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
|
|
|
|
let uri;
|
|
try {
|
|
uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
|
|
} catch (e) {}
|
|
if (!uri)
|
|
return selectedVal;
|
|
|
|
// Only copy exposable URIs
|
|
try {
|
|
uri = uriFixup.createExposableURI(uri);
|
|
} catch (ex) {}
|
|
|
|
// If the entire URL is selected, just use the actual loaded URI.
|
|
if (inputVal == selectedVal) {
|
|
// ... but only if isn't a javascript: or data: URI, since those
|
|
// are hard to read when encoded
|
|
if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
|
|
// Parentheses are known to confuse third-party applications (bug 458565).
|
|
selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
|
|
}
|
|
|
|
return selectedVal;
|
|
}
|
|
|
|
// Just the beginning of the URL is selected, check for a trimmed value
|
|
let spec = uri.spec;
|
|
let trimmedSpec = this._trimURL(spec);
|
|
if (spec != trimmedSpec) {
|
|
// Prepend the portion that trimURL removed from the beginning.
|
|
// This assumes trimURL will only truncate the URL at
|
|
// the beginning or end (or both).
|
|
let trimmedSegments = spec.split(trimmedSpec);
|
|
selectedVal = trimmedSegments[0] + selectedVal;
|
|
}
|
|
|
|
return selectedVal;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<field name="_copyCutValueController">
|
|
<![CDATA[
|
|
({
|
|
urlbar: this,
|
|
doCommand: function(aCommand) {
|
|
let urlbar = this.urlbar;
|
|
let val = urlbar._getSelectedValueForClipboard();
|
|
if (!val)
|
|
return;
|
|
|
|
if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
|
|
let start = urlbar.selectionStart;
|
|
let end = urlbar.selectionEnd;
|
|
urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
|
|
urlbar.inputField.value.substring(end);
|
|
urlbar.selectionStart = urlbar.selectionEnd = start;
|
|
}
|
|
|
|
Cc["@mozilla.org/widget/clipboardhelper;1"]
|
|
.getService(Ci.nsIClipboardHelper)
|
|
.copyString(val, document);
|
|
},
|
|
|
|
supportsCommand: function(aCommand) {
|
|
switch (aCommand) {
|
|
case "cmd_copy":
|
|
case "cmd_cut":
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
isCommandEnabled: function(aCommand) {
|
|
let urlbar = this.urlbar;
|
|
return this.supportsCommand(aCommand) &&
|
|
(aCommand != "cmd_cut" || !urlbar.readOnly) &&
|
|
urlbar.selectionStart < urlbar.selectionEnd;
|
|
},
|
|
|
|
onEvent: function(aEventName) {}
|
|
})
|
|
]]>
|
|
</field>
|
|
|
|
<method name="trimValue">
|
|
<parameter name="aURL"/>
|
|
<body>
|
|
<![CDATA[
|
|
return (this._mayTrimURLs) ? this._trimURL(aURL) : aURL;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- URI Editing -->
|
|
|
|
<property name="isEditing" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
return Elements.urlbarState.hasAttribute("editing");
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="beginEditing">
|
|
<parameter name="aShouldDismiss"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (this.isEditing)
|
|
return;
|
|
|
|
Elements.urlbarState.setAttribute("editing", true);
|
|
this._lastKnownGoodURL = this.value;
|
|
|
|
if (!this.focused)
|
|
this.focus();
|
|
|
|
this._clearFormatting();
|
|
this.select();
|
|
|
|
if (aShouldDismiss)
|
|
ContextUI.dismissTabs();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="endEditing">
|
|
<parameter name="aShouldRevert"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.isEditing)
|
|
return;
|
|
|
|
Elements.urlbarState.removeAttribute("editing");
|
|
this.closePopup();
|
|
this.formatValue();
|
|
|
|
if (this.focused)
|
|
this.blur();
|
|
|
|
if (aShouldRevert)
|
|
this.value = this._lastKnownGoodURL;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- URI Submission -->
|
|
|
|
<method name="_canonizeURL">
|
|
<parameter name="aURL"/>
|
|
<parameter name="aTriggeringEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!aURL)
|
|
return "";
|
|
|
|
// Only add the suffix when the URL bar value isn't already "URL-like",
|
|
// and only if we get a keyboard event, to match user expectations.
|
|
if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aURL)) {
|
|
let accel = aTriggeringEvent.ctrlKey;
|
|
let shift = aTriggeringEvent.shiftKey;
|
|
let suffix = "";
|
|
|
|
switch (true) {
|
|
case (accel && shift):
|
|
suffix = ".org/";
|
|
break;
|
|
case (shift):
|
|
suffix = ".net/";
|
|
break;
|
|
case (accel):
|
|
try {
|
|
suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
|
|
if (suffix.charAt(suffix.length - 1) != "/")
|
|
suffix += "/";
|
|
} catch(e) {
|
|
suffix = ".com/";
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (suffix) {
|
|
// trim leading/trailing spaces (bug 233205)
|
|
aURL = aURL.trim();
|
|
|
|
// Tack www. and suffix on. If user has appended directories, insert
|
|
// suffix before them (bug 279035). Be careful not to get two slashes.
|
|
let firstSlash = aURL.indexOf("/");
|
|
if (firstSlash >= 0) {
|
|
aURL = aURL.substring(0, firstSlash) + suffix + aURL.substring(firstSlash + 1);
|
|
} else {
|
|
aURL = aURL + suffix;
|
|
}
|
|
aURL = "http://www." + aURL;
|
|
}
|
|
}
|
|
|
|
return aURL;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="submitURL">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
// If the address was typed in by a user, tidy it up
|
|
if (aEvent instanceof KeyEvent)
|
|
this.value = this._canonizeURL(this.value, aEvent);
|
|
|
|
this.endEditing();
|
|
BrowserUI.goToURI(this.value);
|
|
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="submitSearch">
|
|
<parameter name="anEngineName"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.endEditing();
|
|
BrowserUI.doOpenSearch(anEngineName);
|
|
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- nsIObserver -->
|
|
|
|
<method name="observe">
|
|
<parameter name="aSubject"/>
|
|
<parameter name="aTopic"/>
|
|
<parameter name="aData"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aTopic != "nsPref:changed")
|
|
return;
|
|
|
|
switch (aData) {
|
|
case "browser.urlbar.formatting.enabled":
|
|
this._mayFormat = Services.prefs.getBoolPref(aData);
|
|
if (!this._mayFormat) this._clearFormatting();
|
|
break;
|
|
case "browser.urlbar.trimURLs":
|
|
this._mayTrimURLs = Services.prefs.getBoolPref(aData);
|
|
break;
|
|
case "browser.urlbar.doubleClickSelectsAll":
|
|
this._maySelectAll = Services.prefs.getBoolPref(aData);
|
|
break;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<!-- Entering editing mode -->
|
|
|
|
<handler event="focus" phase="capturing">
|
|
<![CDATA[
|
|
this.beginEditing();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="input" phase="capturing">
|
|
<![CDATA[
|
|
// Ensures that paste-and-go actually brings the URL bar into editing mode
|
|
// and displays the half-height autocomplete popup.
|
|
this.beginEditing();
|
|
this.openPopup();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="click" phase="capturing">
|
|
<![CDATA[
|
|
this.beginEditing(true);
|
|
]]>
|
|
</handler>
|
|
|
|
<!-- Editing mode behaviors -->
|
|
|
|
<handler event="dblclick" phase="capturing">
|
|
<![CDATA[
|
|
if (this._maySelectAll) this.select();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="contextmenu" phase="capturing">
|
|
<![CDATA[
|
|
let box = this.inputField.parentNode;
|
|
box.showContextMenu(this, event, true);
|
|
]]>
|
|
</handler>
|
|
|
|
<!-- Leaving editing mode -->
|
|
|
|
<handler event="blur" phase="capturing">
|
|
<![CDATA[
|
|
this.endEditing();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="keypress" phase="capturing" keycode="VK_RETURN"
|
|
modifiers="accel shift any">
|
|
<![CDATA[
|
|
if (this.popup.submitSelected())
|
|
return;
|
|
|
|
if (this.submitURL(event))
|
|
return;
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="keypress" phase="capturing" keycode="VK_UP">
|
|
<![CDATA[
|
|
if (!this.popup.hasSelection) {
|
|
// Treat the first up as a down key to trick handleKeyNavigation() to start
|
|
// keyboard navigation on autocomplete popup.
|
|
this.mController.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
|
|
event.preventDefault();
|
|
}
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="urlbar-autocomplete">
|
|
<content class="meta-section-container">
|
|
<xul:vbox class="meta-section" anonid="results-container" flex="1">
|
|
<xul:label class="meta-section-title"
|
|
value="&autocompleteResultsHeader.label;"/>
|
|
<richgrid anonid="results" rows="3" flex="1"
|
|
seltype="single" deferlayout="true"/>
|
|
</xul:vbox>
|
|
|
|
<xul:vbox class="meta-section" flex="1">
|
|
<xul:label anonid="searches-header" class="meta-section-title"/>
|
|
<richgrid anonid="searches" rows="3" flex="1"
|
|
seltype="single" deferlayout="true"/>
|
|
</xul:vbox>
|
|
</content>
|
|
|
|
<implementation implements="nsIAutoCompletePopup, nsIObserver">
|
|
<constructor>
|
|
<![CDATA[
|
|
this.hidden = true;
|
|
|
|
Services.obs.addObserver(this, "browser-search-engine-modified", false);
|
|
|
|
this._results.controller = this;
|
|
this._searches.controller = this;
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
Services.obs.removeObserver(this, "browser-search-engine-modified");
|
|
]]>
|
|
</destructor>
|
|
|
|
<!-- nsIAutocompleteInput -->
|
|
|
|
<field name="_input">null</field>
|
|
|
|
<property name="input" readonly="true" onget="return this._input;"/>
|
|
<property name="matchCount" readonly="true" onget="return this.input.controller.matchCount;"/>
|
|
<property name="popupOpen" readonly="true" onget="return !this.hidden"/>
|
|
<property name="overrideValue" readonly="true" onget="return null;"/>
|
|
|
|
<property name="selectedItem">
|
|
<getter>
|
|
<![CDATA[
|
|
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
return this._isGridBound(this._results) ? this._results.selectedItem : null;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="selectedIndex">
|
|
<getter>
|
|
<![CDATA[
|
|
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
return this._isGridBound(this._results) ? this._results.selectedIndex : -1;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="hasSelection">
|
|
<getter>
|
|
<![CDATA[
|
|
if (!this._isGridBound(this._results) ||
|
|
!this._isGridBound(this._searches))
|
|
return false;
|
|
|
|
return (this._results.selectedIndex >= 0 ||
|
|
this._searches.selectedIndex >= 0);
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="openAutocompletePopup">
|
|
<parameter name="aInput"/>
|
|
<parameter name="aElement"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (this.popupOpen)
|
|
return;
|
|
|
|
ContextUI.dismissContextAppbar();
|
|
|
|
this._input = aInput;
|
|
this._grid = this._results;
|
|
|
|
this.clearSelection();
|
|
this.invalidate();
|
|
|
|
this._results.arrangeItemsNow();
|
|
this._searches.arrangeItemsNow();
|
|
|
|
this.hidden = false;
|
|
Elements.urlbarState.setAttribute("autocomplete", "true");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="closePopup">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.popupOpen)
|
|
return;
|
|
|
|
this.input.controller.stopSearch();
|
|
this.hidden = true;
|
|
Elements.urlbarState.removeAttribute("autocomplete");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Updating grid content -->
|
|
|
|
<field name="_grid">null</field>
|
|
|
|
<field name="_results" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results');</field>
|
|
<field name="_resultsContainer" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'results-container');</field>
|
|
|
|
<field name="_searchesHeader" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches-header');</field>
|
|
<field name="_searches" readonly="true">document.getAnonymousElementByAttribute(this, 'anonid', 'searches');</field>
|
|
|
|
<property name="_otherGrid" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
if (this._grid == null)
|
|
return null;
|
|
|
|
return (this._grid == this._results) ? this._searches : this._results;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="_isGridBound">
|
|
<parameter name="aGrid"/>
|
|
<body>
|
|
<![CDATA[
|
|
return aGrid && aGrid.isBound;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="invalidate">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.popupOpen)
|
|
return;
|
|
|
|
this.updateResults();
|
|
this.updateSearchEngineHeader();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Updating grid content: results -->
|
|
|
|
<method name="updateResults">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this._isGridBound(this._results))
|
|
return;
|
|
|
|
if (!this.input)
|
|
return;
|
|
|
|
let haveNoResults = (this.matchCount == 0);
|
|
this._resultsContainer.hidden = haveNoResults;
|
|
|
|
if (haveNoResults) {
|
|
this._results.clearAll();
|
|
return;
|
|
}
|
|
|
|
let controller = this.input.controller;
|
|
let lastMatch = this.matchCount - 1;
|
|
let iterCount = Math.max(this._results.itemCount, this.matchCount);
|
|
|
|
// Swap out existing items for new search hit results
|
|
for (let i = 0; i < iterCount; i++) {
|
|
if (i > lastMatch) {
|
|
let lastItem = this._results.itemCount - 1;
|
|
this._results.removeItemAt(lastItem, true);
|
|
continue;
|
|
}
|
|
|
|
let value = controller.getValueAt(i);
|
|
let label = controller.getCommentAt(i) || value;
|
|
let iconURI = controller.getImageAt(i);
|
|
|
|
let item = this._results.getItemAtIndex(i);
|
|
if (item == null) {
|
|
item = this._results.appendItem(label, value, true);
|
|
item.setAttribute("autocomplete", "true");
|
|
} else {
|
|
item.setAttribute("label", label);
|
|
item.setAttribute("value", value);
|
|
}
|
|
|
|
item.setAttribute("iconURI", iconURI);
|
|
}
|
|
|
|
this._results.arrangeItems();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Updating grid content: search engines -->
|
|
|
|
<field name="_engines">[]</field>
|
|
|
|
<method name="_initSearchEngines">
|
|
<body>
|
|
<![CDATA[
|
|
Services.search.init(this.updateSearchEngineGrid.bind(this));
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateSearchEngineGrid">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this._isGridBound(this._searches))
|
|
return;
|
|
|
|
this._engines = Services.search.getVisibleEngines();
|
|
|
|
while (this._searches.itemCount > 0)
|
|
this._searches.removeItemAt(0, true);
|
|
|
|
this._engines.forEach(function (anEngine) {
|
|
let item = this._searches.appendItem(anEngine.name, anEngine.name, true);
|
|
item.setAttribute("autocomplete", "true");
|
|
item.setAttribute("search", "true");
|
|
|
|
let iconURI = anEngine.iconURI ? anEngine.iconURI.spec : "";
|
|
item.setAttribute("iconURI", iconURI);
|
|
}.bind(this));
|
|
|
|
this._searches.arrangeItems();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateSearchEngineHeader">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this._isGridBound(this._searches))
|
|
return;
|
|
|
|
let searchString = this.input.controller.searchString;
|
|
let label = Strings.browser.formatStringFromName(
|
|
"opensearch.search.header", [searchString], 1);
|
|
|
|
this._searchesHeader.value = label;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Selecting results -->
|
|
|
|
<method name="selectBy">
|
|
<parameter name="aReverse"/>
|
|
<parameter name="aPage"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this._isGridBound(this._results) ||
|
|
!this._isGridBound(this._searches))
|
|
return;
|
|
|
|
// Move between grids if we're at the edge of one.
|
|
if ((this._grid.isSelectionAtEnd && !aReverse) ||
|
|
(this._grid.isSelectionAtStart && aReverse)) {
|
|
let index = !aReverse ? 0 : this._otherGrid.itemCount - 1;
|
|
this._otherGrid.selectedIndex = index;
|
|
} else {
|
|
this._grid.offsetSelection(aReverse ? -1 : 1);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="clearSelection">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._isGridBound(this._results))
|
|
this._results.clearSelection();
|
|
|
|
if (this._isGridBound(this._searches))
|
|
this._searches.clearSelection();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Submitting selected results -->
|
|
|
|
<method name="submitSelected">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._isGridBound(this._results) &&
|
|
this._results.selectedIndex >= 0) {
|
|
let url = this.input.controller.getValueAt(this._results.selectedIndex);
|
|
this.input.value = url;
|
|
return this.input.submitURL();
|
|
}
|
|
|
|
if (this._isGridBound(this._searches) &&
|
|
this._searches.selectedIndex >= 0) {
|
|
let engine = this._engines[this._searches.selectedIndex];
|
|
return this.input.submitSearch(engine.name);
|
|
}
|
|
|
|
return false;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="handleItemClick">
|
|
<parameter name="aItem"/>
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.submitSelected();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- nsIObserver -->
|
|
|
|
<method name="observe">
|
|
<parameter name="aSubject"/>
|
|
<parameter name="aTopic"/>
|
|
<parameter name="aData"/>
|
|
<body>
|
|
<![CDATA[
|
|
switch (aTopic) {
|
|
case "browser-search-engine-modified":
|
|
this.updateSearchEngineGrid();
|
|
break;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="contentgenerated">
|
|
<![CDATA[
|
|
let grid = event.originalTarget;
|
|
|
|
if (grid == this._searches)
|
|
this._initSearchEngines();
|
|
|
|
if (grid == this._results)
|
|
this.updateResults();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="select">
|
|
<![CDATA[
|
|
let grid = event.originalTarget;
|
|
|
|
// If a selection was made on a different grid,
|
|
// remove selection from the current grid.
|
|
if (grid != this._grid) {
|
|
this._grid.clearSelection();
|
|
this._grid = this._otherGrid;
|
|
}
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
</bindings>
|