summaryrefslogtreecommitdiffstats
path: root/base/content/autocomplete.xml
diff options
context:
space:
mode:
authorThomas Groman <tgroman@nuegia.net>2020-04-20 20:49:37 -0700
committerThomas Groman <tgroman@nuegia.net>2020-04-20 20:49:37 -0700
commitf9cab004186edb425a9b88ad649726605080a17c (patch)
treee2dae51d3144e83d097a12e7a1499e3ea93f90be /base/content/autocomplete.xml
parentf428692de8b59ab89a66502c079e1823dfda8aeb (diff)
downloadwebbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.gz
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.lz
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.xz
webbrowser-f9cab004186edb425a9b88ad649726605080a17c.zip
move browser to webbrowser/
Diffstat (limited to 'base/content/autocomplete.xml')
-rw-r--r--base/content/autocomplete.xml2128
1 files changed, 0 insertions, 2128 deletions
diff --git a/base/content/autocomplete.xml b/base/content/autocomplete.xml
deleted file mode 100644
index bd09284..0000000
--- a/base/content/autocomplete.xml
+++ /dev/null
@@ -1,2128 +0,0 @@
-<?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/. -->
-
-<bindings id="privateAutocompleteBindings"
- xmlns="http://www.mozilla.org/xbl"
- xmlns:html="http://www.w3.org/1999/xhtml"
- xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- xmlns:xbl="http://www.mozilla.org/xbl">
-
- <binding id="private-autocomplete" role="xul:combobox"
- extends="chrome://global/content/bindings/textbox.xml#textbox">
- <resources>
- <stylesheet src="chrome://browser/content/autocomplete.css"/>
- <stylesheet src="chrome://browser/skin/autocomplete.css"/>
- </resources>
-
- <content sizetopopup="pref">
- <xul:hbox class="private-autocomplete-textbox-container" flex="1" xbl:inherits="focused">
- <children includes="image|deck|stack|box">
- <xul:image class="private-autocomplete-icon" allowevents="true"/>
- </children>
-
- <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
- <children/>
- <html:input anonid="input" class="private-autocomplete-textbox textbox-input"
- allowevents="true"
- xbl:inherits="tooltiptext=inputtooltiptext,value,type=inputtype,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
- </xul:hbox>
- <children includes="hbox"/>
- </xul:hbox>
-
- <xul:dropmarker anonid="historydropmarker" class="private-autocomplete-history-dropmarker"
- allowevents="true"
- xbl:inherits="open,enablehistory,parentfocused=focused"/>
-
- <xul:popupset anonid="popupset" class="private-autocomplete-result-popupset"/>
-
- <children includes="toolbarbutton"/>
- </content>
-
- <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
- <field name="mController">null</field>
- <field name="mSearchNames">null</field>
- <field name="mIgnoreInput">false</field>
- <field name="mEnterEvent">null</field>
-
- <field name="_searchBeginHandler">null</field>
- <field name="_searchCompleteHandler">null</field>
- <field name="_textEnteredHandler">null</field>
- <field name="_textRevertedHandler">null</field>
-
- <constructor><![CDATA[
- this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"].
- getService(Components.interfaces.nsIAutoCompleteController);
-
- this._searchBeginHandler = this.initEventHandler("searchbegin");
- this._searchCompleteHandler = this.initEventHandler("searchcomplete");
- this._textEnteredHandler = this.initEventHandler("textentered");
- this._textRevertedHandler = this.initEventHandler("textreverted");
-
- // For security reasons delay searches on pasted values.
- this.inputField.controllers.insertControllerAt(0, this._pasteController);
- ]]></constructor>
-
- <destructor><![CDATA[
- this.inputField.controllers.removeController(this._pasteController);
- ]]></destructor>
-
- <!-- =================== nsIAutoCompleteInput =================== -->
-
- <field name="popup"><![CDATA[
- // Wrap in a block so that the let statements don't
- // create properties on 'this' (bug 635252).
- {
- let popup = null;
- let popupId = this.getAttribute("autocompletepopup");
- if (popupId)
- popup = document.getElementById(popupId);
- if (!popup) {
- popup = document.createElement("panel");
- popup.setAttribute("type", "autocomplete");
- popup.setAttribute("noautofocus", "true");
-
- let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
- popupset.appendChild(popup);
- }
- popup.mInput = this;
- popup;
- }
- ]]></field>
-
- <property name="controller" onget="return this.mController;" readonly="true"/>
-
- <property name="popupOpen"
- onget="return this.popup.popupOpen;"
- onset="if (val) this.openPopup(); else this.closePopup();"/>
-
- <property name="disableAutoComplete"
- onset="this.setAttribute('disableautocomplete', val); return val;"
- onget="return this.getAttribute('disableautocomplete') == 'true';"/>
-
- <property name="completeDefaultIndex"
- onset="this.setAttribute('completedefaultindex', val); return val;"
- onget="return this.getAttribute('completedefaultindex') == 'true';"/>
-
- <property name="completeSelectedIndex"
- onset="this.setAttribute('completeselectedindex', val); return val;"
- onget="return this.getAttribute('completeselectedindex') == 'true';"/>
-
- <property name="forceComplete"
- onset="this.setAttribute('forcecomplete', val); return val;"
- onget="return this.getAttribute('forcecomplete') == 'true';"/>
-
- <property name="minResultsForPopup"
- onset="this.setAttribute('minresultsforpopup', val); return val;"
- onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
-
- <property name="showCommentColumn"
- onset="this.setAttribute('showcommentcolumn', val); return val;"
- onget="return this.getAttribute('showcommentcolumn') == 'true';"/>
-
- <property name="showImageColumn"
- onset="this.setAttribute('showimagecolumn', val); return val;"
- onget="return this.getAttribute('showimagecolumn') == 'true';"/>
-
- <property name="timeout"
- onset="this.setAttribute('timeout', val); return val;">
- <getter><![CDATA[
- // For security reasons delay searches on pasted values.
- if (this._valueIsPasted) {
- let t = parseInt(this.getAttribute('pastetimeout'));
- return isNaN(t) ? 1000 : t;
- }
-
- let t = parseInt(this.getAttribute('timeout'));
- return isNaN(t) ? 50 : t;
- ]]></getter>
- </property>
-
- <property name="searchParam"
- onget="return this.getAttribute('autocompletesearchparam') || '';"
- onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
-
- <property name="searchCount" readonly="true"
- onget="this.initSearchNames(); return this.mSearchNames.length;"/>
-
- <field name="shrinkDelay" readonly="true">
- parseInt(this.getAttribute("shrinkdelay")) || 0
- </field>
-
- <field name="PrivateBrowsingUtils" readonly="true">
- {
- let utils = {};
- Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", utils);
- utils.PrivateBrowsingUtils
- }
- </field>
-
- <property name="inPrivateContext" readonly="true"
- onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>
-
- <property name="noRollupOnCaretMove" readonly="true"
- onget="return this.popup.getAttribute('norolluponanchor') == 'true'"/>
-
- <!-- This is the maximum number of drop-down rows we get when we
- hit the drop marker beside fields that have it (like the URLbar).-->
- <field name="maxDropMarkerRows" readonly="true">14</field>
-
- <method name="getSearchAt">
- <parameter name="aIndex"/>
- <body><![CDATA[
- this.initSearchNames();
- return this.mSearchNames[aIndex];
- ]]></body>
- </method>
-
- <property name="textValue"
- onget="return this.value;">
- <setter><![CDATA[
- // Completing a result should simulate the user typing the result,
- // so fire an input event.
- // Trim popup selected values, but never trim results coming from
- // autofill.
- if (this.popup.selectedIndex == -1)
- this._disableTrim = true;
- this.value = val;
- this._disableTrim = false;
-
- var evt = document.createEvent("UIEvents");
- evt.initUIEvent("input", true, false, window, 0);
- this.mIgnoreInput = true;
- this.dispatchEvent(evt);
- this.mIgnoreInput = false;
- return this.value;
- ]]></setter>
- </property>
-
- <method name="selectTextRange">
- <parameter name="aStartIndex"/>
- <parameter name="aEndIndex"/>
- <body><![CDATA[
- this.inputField.setSelectionRange(aStartIndex, aEndIndex);
- ]]></body>
- </method>
-
- <method name="onSearchBegin">
- <body><![CDATA[
- if (this.popup && typeof this.popup.onSearchBegin == "function")
- this.popup.onSearchBegin();
- if (this._searchBeginHandler)
- this._searchBeginHandler();
- ]]></body>
- </method>
-
- <method name="onSearchComplete">
- <body><![CDATA[
- if (this.mController.matchCount == 0)
- this.setAttribute("nomatch", "true");
- else
- this.removeAttribute("nomatch");
-
- if (this.ignoreBlurWhileSearching && !this.focused) {
- this.handleEnter();
- this.detachController();
- }
-
- if (this._searchCompleteHandler)
- this._searchCompleteHandler();
- ]]></body>
- </method>
-
- <method name="onTextEntered">
- <body><![CDATA[
- let rv = false;
- if (this._textEnteredHandler)
- rv = this._textEnteredHandler(this.mEnterEvent);
- this.mEnterEvent = null;
- return rv;
- ]]></body>
- </method>
-
- <method name="onTextReverted">
- <body><![CDATA[
- if (this._textRevertedHandler)
- return this._textRevertedHandler();
- return false;
- ]]></body>
- </method>
-
- <!-- =================== nsIDOMXULMenuListElement =================== -->
-
- <property name="editable" readonly="true"
- onget="return true;" />
-
- <property name="crop"
- onset="this.setAttribute('crop',val); return val;"
- onget="return this.getAttribute('crop');"/>
-
- <property name="open"
- onget="return this.getAttribute('open') == 'true';">
- <setter><![CDATA[
- if (val)
- this.showHistoryPopup();
- else
- this.closePopup();
- ]]></setter>
- </property>
-
- <!-- =================== PUBLIC MEMBERS =================== -->
-
- <field name="valueIsTyped">false</field>
- <field name="_disableTrim">false</field>
- <property name="value">
- <getter><![CDATA[
- if (typeof this.onBeforeValueGet == "function") {
- var result = this.onBeforeValueGet();
- if (result)
- return result.value;
- }
- return this.inputField.value;
- ]]></getter>
- <setter><![CDATA[
- this.mIgnoreInput = true;
-
- if (typeof this.onBeforeValueSet == "function")
- val = this.onBeforeValueSet(val);
-
- if (typeof this.trimValue == "function" && !this._disableTrim)
- val = this.trimValue(val);
-
- this.valueIsTyped = false;
- this.inputField.value = val;
-
- if (typeof this.formatValue == "function")
- this.formatValue();
-
- this.mIgnoreInput = false;
- var event = document.createEvent('Events');
- event.initEvent('ValueChange', true, true);
- this.inputField.dispatchEvent(event);
- return val;
- ]]></setter>
- </property>
-
- <property name="focused" readonly="true"
- onget="return this.getAttribute('focused') == 'true';"/>
-
- <!-- maximum number of rows to display at a time -->
- <property name="maxRows"
- onset="this.setAttribute('maxrows', val); return val;"
- onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
-
- <!-- option to allow scrolling through the list via the tab key, rather than
- tab moving focus out of the textbox -->
- <property name="tabScrolling"
- onset="this.setAttribute('tabscrolling', val); return val;"
- onget="return this.getAttribute('tabscrolling') == 'true';"/>
-
- <!-- option to completely ignore any blur events while searches are
- still going on. -->
- <property name="ignoreBlurWhileSearching"
- onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
- onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
-
- <!-- disable key navigation handling in the popup results -->
- <property name="disableKeyNavigation"
- onset="this.setAttribute('disablekeynavigation', val); return val;"
- onget="return this.getAttribute('disablekeynavigation') == 'true';"/>
-
- <!-- option to highlight entries that don't have any matches -->
- <property name="highlightNonMatches"
- onset="this.setAttribute('highlightnonmatches', val); return val;"
- onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
-
- <!-- =================== PRIVATE MEMBERS =================== -->
-
- <!-- ::::::::::::: autocomplete controller ::::::::::::: -->
-
- <method name="attachController">
- <body><![CDATA[
- this.mController.input = this;
- ]]></body>
- </method>
-
- <method name="detachController">
- <body><![CDATA[
- if (this.mController.input == this)
- this.mController.input = null;
- ]]></body>
- </method>
-
- <!-- ::::::::::::: popup opening ::::::::::::: -->
-
- <method name="openPopup">
- <body><![CDATA[
- if (this.focused)
- this.popup.openAutocompletePopup(this, this);
- ]]></body>
- </method>
-
- <method name="closePopup">
- <body><![CDATA[
- this.popup.closePopup();
- ]]></body>
- </method>
-
- <method name="showHistoryPopup">
- <body><![CDATA[
- // history dropmarker pushed state
- function cleanup(popup) {
- popup.removeEventListener("popupshowing", onShow, false);
- }
- function onShow(event) {
- var popup = event.target, input = popup.input;
- cleanup(popup);
- input.setAttribute("open", "true");
- function onHide() {
- input.removeAttribute("open");
- popup.removeEventListener("popuphiding", onHide, false);
- }
- popup.addEventListener("popuphiding", onHide, false);
- }
- this.popup.addEventListener("popupshowing", onShow, false);
- setTimeout(cleanup, 1000, this.popup);
-
- // Store our "normal" maxRows on the popup, so that it can reset the
- // value when the popup is hidden.
- this.popup._normalMaxRows = this.maxRows;
-
- // Increase our maxRows temporarily, since we want the dropdown to
- // be bigger in this case. The popup's popupshowing/popuphiding
- // handlers will take care of resetting this.
- this.maxRows = this.maxDropMarkerRows;
-
- // Ensure that we have focus.
- if (!this.focused)
- this.focus();
- this.attachController();
- this.mController.startSearch("");
- ]]></body>
- </method>
-
- <method name="toggleHistoryPopup">
- <body><![CDATA[
- // If this method is called on the same event tick as the popup gets
- // hidden, do nothing to avoid re-opening the popup when the drop
- // marker is clicked while the popup is still open.
- if (!this.popup.isPopupHidingTick && !this.popup.popupOpen)
- this.showHistoryPopup();
- else
- this.closePopup();
- ]]></body>
- </method>
-
- <!-- ::::::::::::: event dispatching ::::::::::::: -->
-
- <method name="initEventHandler">
- <parameter name="aEventType"/>
- <body><![CDATA[
- let handlerString = this.getAttribute("on" + aEventType);
- if (handlerString) {
- return (new Function("eventType", "param", handlerString)).bind(this, aEventType);
- }
- return null;
- ]]></body>
- </method>
-
- <!-- ::::::::::::: key handling ::::::::::::: -->
-
- <field name="_selectionDetails">null</field>
- <method name="onKeyPress">
- <parameter name="aEvent"/>
- <body><![CDATA[
- return this.handleKeyPress(aEvent);
- ]]></body>
- </method>
-
- <method name="handleKeyPress">
- <parameter name="aEvent"/>
- <body><![CDATA[
- if (aEvent.target.localName != "textbox")
- return true; // Let child buttons of autocomplete take input
-
- //XXXpch this is so bogus...
- if (aEvent.defaultPrevented)
- return false;
-
- var cancel = false;
-
- let { AppConstants } =
- Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
- // Catch any keys that could potentially move the caret. Ctrl can be
- // used in combination with these keys on Windows and Linux; and Alt
- // can be used on OS X, so make sure the unused one isn't used.
- let metaKey = AppConstants.platform == "macosx" ? aEvent.ctrlKey : aEvent.altKey;
- if (!this.disableKeyNavigation && !metaKey) {
- switch (aEvent.keyCode) {
- case KeyEvent.DOM_VK_LEFT:
- case KeyEvent.DOM_VK_RIGHT:
- case KeyEvent.DOM_VK_HOME:
- cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
- break;
- }
- }
-
- // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
- if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) {
- switch (aEvent.keyCode) {
- case KeyEvent.DOM_VK_TAB:
- if (this.tabScrolling && this.popup.popupOpen)
- cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
- KeyEvent.DOM_VK_UP :
- KeyEvent.DOM_VK_DOWN);
- else if (this.forceComplete && this.mController.matchCount >= 1)
- this.mController.handleTab();
- break;
- case KeyEvent.DOM_VK_UP:
- case KeyEvent.DOM_VK_DOWN:
- case KeyEvent.DOM_VK_PAGE_UP:
- case KeyEvent.DOM_VK_PAGE_DOWN:
- cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
- break;
- }
- }
-
- // Handle keys we know aren't part of a shortcut, even with Alt or
- // Ctrl.
- switch (aEvent.keyCode) {
- case KeyEvent.DOM_VK_ESCAPE:
- cancel = this.mController.handleEscape();
- break;
- case KeyEvent.DOM_VK_RETURN:
- if (AppConstants.platform == "macosx") {
- // Prevent the default action, since it will beep on Mac
- if (aEvent.metaKey)
- aEvent.preventDefault();
- }
- this.mEnterEvent = aEvent;
- if (this.mController.selection) {
- this._selectionDetails = {
- index: this.mController.selection.currentIndex,
- kind: "key"
- };
- }
- cancel = this.handleEnter();
- break;
- case KeyEvent.DOM_VK_DELETE:
- if (AppConstants.platform == "macosx" && !aEvent.shiftKey) {
- break;
- }
- cancel = this.handleDelete();
- break;
- case KeyEvent.DOM_VK_BACK_SPACE:
- if (AppConstants.platform == "macosx" && aEvent.shiftKey) {
- cancel = this.handleDelete();
- }
- break;
- case KeyEvent.DOM_VK_DOWN:
- case KeyEvent.DOM_VK_UP:
- if (aEvent.altKey)
- this.toggleHistoryPopup();
- break;
- case KeyEvent.DOM_VK_F4:
- if (AppConstants.platform != "macosx") {
- this.toggleHistoryPopup();
- }
- break;
- }
-
- if (cancel) {
- aEvent.stopPropagation();
- aEvent.preventDefault();
- }
-
- return true;
- ]]></body>
- </method>
-
- <method name="handleEnter">
- <body><![CDATA[
- return this.mController.handleEnter(false);
- ]]></body>
- </method>
-
- <method name="handleDelete">
- <body><![CDATA[
- return this.mController.handleDelete();
- ]]></body>
- </method>
-
- <!-- ::::::::::::: miscellaneous ::::::::::::: -->
-
- <method name="initSearchNames">
- <body><![CDATA[
- if (!this.mSearchNames) {
- var names = this.getAttribute("autocompletesearch");
- if (!names)
- this.mSearchNames = [];
- else
- this.mSearchNames = names.split(" ");
- }
- ]]></body>
- </method>
-
- <method name="_focus">
- <!-- doesn't reset this.mController -->
- <body><![CDATA[
- this._dontBlur = true;
- this.focus();
- this._dontBlur = false;
- ]]></body>
- </method>
-
- <method name="resetActionType">
- <body><![CDATA[
- if (this.mIgnoreInput)
- return;
- this.removeAttribute("actiontype");
- ]]></body>
- </method>
-
- <field name="_valueIsPasted">false</field>
- <field name="_pasteController"><![CDATA[
- ({
- _autocomplete: this,
- _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
- supportsCommand: aCommand => aCommand == "cmd_paste",
- doCommand: function(aCommand) {
- this._autocomplete._valueIsPasted = true;
- this._autocomplete.editor.paste(this._kGlobalClipboard);
- this._autocomplete._valueIsPasted = false;
- },
- isCommandEnabled: function(aCommand) {
- return this._autocomplete.editor.isSelectionEditable &&
- this._autocomplete.editor.canPaste(this._kGlobalClipboard);
- },
- onEvent: function() {}
- })
- ]]></field>
-
- <method name="onInput">
- <parameter name="aEvent"/>
- <body><![CDATA[
- if (!this.mIgnoreInput && this.mController.input == this) {
- this.valueIsTyped = true;
- this.mController.handleText();
- }
- this.resetActionType();
- ]]></body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="input"><![CDATA[
- this.onInput(event);
- ]]></handler>
-
- <handler event="keypress" phase="capturing"
- action="return this.onKeyPress(event);"/>
-
- <handler event="compositionstart" phase="capturing"
- action="if (this.mController.input == this) this.mController.handleStartComposition();"/>
-
- <handler event="compositionend" phase="capturing"
- action="if (this.mController.input == this) this.mController.handleEndComposition();"/>
-
- <handler event="focus" phase="capturing"
- action="this.attachController();"/>
-
- <handler event="blur" phase="capturing"><![CDATA[
- if (!this._dontBlur) {
- if (this.forceComplete && this.mController.matchCount >= 1) {
- // mousemove sets selected index. Don't blindly use that selected
- // index in this blur handler since if the popup is open you can
- // easily "select" another match just by moving the mouse over it.
- let filledVal = this.value.replace(/.+ >> /, "").toLowerCase();
- let selectedVal = null;
- if (this.popup.selectedIndex >= 0) {
- selectedVal = this.mController.getFinalCompleteValueAt(
- this.popup.selectedIndex);
- }
- if (selectedVal && filledVal != selectedVal.toLowerCase()) {
- for (let i = 0; i < this.mController.matchCount; i++) {
- let matchVal = this.mController.getFinalCompleteValueAt(i);
- if (matchVal.toLowerCase() == filledVal) {
- this.popup.selectedIndex = i;
- break;
- }
- }
- }
- this.mController.handleEnter(false);
- }
- if (!this.ignoreBlurWhileSearching)
- this.detachController();
- }
- ]]></handler>
- </handlers>
- </binding>
-
- <binding id="private-autocomplete-result-popup" extends="chrome://browser/content/autocomplete.xml#private-autocomplete-base-popup">
- <resources>
- <stylesheet src="chrome://browser/content/autocomplete.css"/>
- <stylesheet src="chrome://global/skin/tree.css"/>
- <stylesheet src="chrome://browser/skin/autocomplete.css"/>
- </resources>
-
- <content ignorekeys="true" level="top" consumeoutsideclicks="never">
- <xul:tree anonid="tree" class="private-autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single">
- <xul:treecols anonid="treecols">
- <xul:treecol id="treecolAutoCompleteValue" class="private-autocomplete-treecol" flex="1" overflow="true"/>
- </xul:treecols>
- <xul:treechildren class="private-autocomplete-treebody"/>
- </xul:tree>
- </content>
-
- <implementation>
- <field name="mShowCommentColumn">false</field>
- <field name="mShowImageColumn">false</field>
-
- <property name="showCommentColumn"
- onget="return this.mShowCommentColumn;">
- <setter>
- <![CDATA[
- if (!val && this.mShowCommentColumn) {
- // reset the flex on the value column and remove the comment column
- document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1);
- this.removeColumn("treecolAutoCompleteComment");
- } else if (val && !this.mShowCommentColumn) {
- // reset the flex on the value column and add the comment column
- document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2);
- this.addColumn({id: "treecolAutoCompleteComment", flex: 1});
- }
- this.mShowCommentColumn = val;
- return val;
- ]]>
- </setter>
- </property>
-
- <property name="showImageColumn"
- onget="return this.mShowImageColumn;">
- <setter>
- <![CDATA[
- if (!val && this.mShowImageColumn) {
- // remove the image column
- this.removeColumn("treecolAutoCompleteImage");
- } else if (val && !this.mShowImageColumn) {
- // add the image column
- this.addColumn({id: "treecolAutoCompleteImage", flex: 1});
- }
- this.mShowImageColumn = val;
- return val;
- ]]>
- </setter>
- </property>
-
-
- <method name="addColumn">
- <parameter name="aAttrs"/>
- <body>
- <![CDATA[
- var col = document.createElement("treecol");
- col.setAttribute("class", "private-autocomplete-treecol");
- for (var name in aAttrs)
- col.setAttribute(name, aAttrs[name]);
- this.treecols.appendChild(col);
- return col;
- ]]>
- </body>
- </method>
-
- <method name="removeColumn">
- <parameter name="aColId"/>
- <body>
- <![CDATA[
- return this.treecols.removeChild(document.getElementById(aColId));
- ]]>
- </body>
- </method>
-
- <property name="selectedIndex"
- onget="return this.tree.currentIndex;">
- <setter>
- <![CDATA[
- this.tree.view.selection.select(val);
- if (this.tree.treeBoxObject.height > 0)
- this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val);
- // Fire select event on xul:tree so that accessibility API
- // support layer can fire appropriate accessibility events.
- var event = document.createEvent('Events');
- event.initEvent("select", true, true);
- this.tree.dispatchEvent(event);
- return val;
- ]]></setter>
- </property>
-
- <method name="adjustHeight">
- <body>
- <![CDATA[
- // detect the desired height of the tree
- var bx = this.tree.treeBoxObject;
- var view = this.tree.view;
- if (!view)
- return;
- var rows = this.maxRows;
- if (!view.rowCount || (rows && view.rowCount < rows))
- rows = view.rowCount;
-
- var height = rows * bx.rowHeight;
-
- if (height == 0) {
- this.tree.setAttribute("collapsed", "true");
- } else {
- if (this.tree.hasAttribute("collapsed"))
- this.tree.removeAttribute("collapsed");
-
- this.tree.setAttribute("height", height);
- }
- this.tree.setAttribute("hidescrollbar", view.rowCount <= rows);
- ]]>
- </body>
- </method>
-
- <method name="openAutocompletePopup">
- <parameter name="aInput"/>
- <parameter name="aElement"/>
- <body><![CDATA[
- // until we have "baseBinding", (see bug #373652) this allows
- // us to override openAutocompletePopup(), but still call
- // the method on the base class
- this._openAutocompletePopup(aInput, aElement);
- ]]></body>
- </method>
-
- <method name="_openAutocompletePopup">
- <parameter name="aInput"/>
- <parameter name="aElement"/>
- <body><![CDATA[
- if (!this.mPopupOpen) {
- this.mInput = aInput;
- this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView);
- this.invalidate();
-
- this.showCommentColumn = this.mInput.showCommentColumn;
- this.showImageColumn = this.mInput.showImageColumn;
-
- var rect = aElement.getBoundingClientRect();
- var nav = aElement.ownerDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
- .getInterface(Components.interfaces.nsIWebNavigation);
- var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell);
- var docViewer = docShell.contentViewer;
- var width = (rect.right - rect.left) * docViewer.fullZoom;
- this.setAttribute("width", width > 100 ? width : 100);
-
- // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840
- var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction;
- this.style.direction = popupDirection;
-
- this.openPopup(aElement, "after_start", 0, 0, false, false);
- }
- ]]></body>
- </method>
-
- <method name="invalidate">
- <body><![CDATA[
- this.adjustHeight();
- this.tree.treeBoxObject.invalidate();
- ]]></body>
- </method>
-
- <method name="selectBy">
- <parameter name="aReverse"/>
- <parameter name="aPage"/>
- <body><![CDATA[
- try {
- var amount = aPage ? 5 : 1;
- this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount-1);
- if (this.selectedIndex == -1) {
- this.input._focus();
- }
- } catch (ex) {
- // do nothing - occasionally timer-related js errors happen here
- // e.g. "this.selectedIndex has no properties", when you type fast and hit a
- // navigation key before this popup has opened
- }
- ]]></body>
- </method>
-
- <!-- =================== PUBLIC MEMBERS =================== -->
-
- <field name="tree">
- document.getAnonymousElementByAttribute(this, "anonid", "tree");
- </field>
-
- <field name="treecols">
- document.getAnonymousElementByAttribute(this, "anonid", "treecols");
- </field>
-
- <property name="view"
- onget="return this.mView;">
- <setter><![CDATA[
- // We must do this by hand because the tree binding may not be ready yet
- this.mView = val;
- this.tree.boxObject.view = val;
- ]]></setter>
- </property>
-
- </implementation>
- </binding>
-
- <binding id="private-autocomplete-base-popup" role="none"
-extends="chrome://global/content/bindings/popup.xml#popup">
- <implementation implements="nsIAutoCompletePopup">
- <field name="mInput">null</field>
- <field name="mPopupOpen">false</field>
- <field name="mIsPopupHidingTick">false</field>
-
- <!-- =================== nsIAutoCompletePopup =================== -->
-
- <property name="input" readonly="true"
- onget="return this.mInput"/>
-
- <property name="overrideValue" readonly="true"
- onget="return null;"/>
-
- <property name="popupOpen" readonly="true"
- onget="return this.mPopupOpen;"/>
-
- <property name="isPopupHidingTick" readonly="true"
- onget="return this.mIsPopupHidingTick;"/>
-
- <method name="closePopup">
- <body>
- <![CDATA[
- if (this.mPopupOpen) {
- this.hidePopup();
- this.removeAttribute("width");
- }
- ]]>
- </body>
- </method>
-
- <!-- This is the default number of rows that we give the autocomplete
- popup when the textbox doesn't have a "maxrows" attribute
- for us to use. -->
- <field name="defaultMaxRows" readonly="true">6</field>
-
- <!-- In some cases (e.g. when the input's dropmarker button is clicked),
- the input wants to display a popup with more rows. In that case, it
- should increase its maxRows property and store the "normal" maxRows
- in this field. When the popup is hidden, we restore the input's
- maxRows to the value stored in this field.
-
- This field is set to -1 between uses so that we can tell when it's
- been set by the input and when we need to set it in the popupshowing
- handler. -->
- <field name="_normalMaxRows">-1</field>
-
- <property name="maxRows" readonly="true">
- <getter>
- <![CDATA[
- return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
- ]]>
- </getter>
- </property>
-
- <method name="getNextIndex">
- <parameter name="aReverse"/>
- <parameter name="aAmount"/>
- <parameter name="aIndex"/>
- <parameter name="aMaxRow"/>
- <body><![CDATA[
- if (aMaxRow < 0)
- return -1;
-
- var newIdx = aIndex + (aReverse?-1:1)*aAmount;
- if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow)
- newIdx = aMaxRow;
- else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0)
- newIdx = 0;
-
- if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow)
- aIndex = -1;
- else
- aIndex = newIdx;
-
- return aIndex;
- ]]></body>
- </method>
-
- <method name="onPopupClick">
- <parameter name="aEvent"/>
- <body><![CDATA[
- var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
- controller.handleEnter(true);
- ]]></body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="popupshowing"><![CDATA[
- // If normalMaxRows wasn't already set by the input, then set it here
- // so that we restore the correct number when the popup is hidden.
-
- // Null-check this.mInput; see bug 1017914
- if (this._normalMaxRows < 0 && this.mInput) {
- this._normalMaxRows = this.mInput.maxRows;
- }
-
- // Set an attribute for styling the popup based on the input.
- let inputID = "";
- if (this.mInput && this.mInput.ownerDocument &&
- this.mInput.ownerDocument.documentURIObject.schemeIs("chrome")) {
- inputID = this.mInput.id;
- // Take care of elements with no id that are inside xbl bindings
- if (!inputID) {
- let bindingParent = this.mInput.ownerDocument.getBindingParent(this.mInput);
- if (bindingParent) {
- inputID = bindingParent.id;
- }
- }
- }
- this.setAttribute("autocompleteinput", inputID);
-
- this.mPopupOpen = true;
- ]]></handler>
-
- <handler event="popuphiding"><![CDATA[
- var isListActive = true;
- if (this.selectedIndex == -1)
- isListActive = false;
- var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
- controller.stopSearch();
-
- this.removeAttribute("autocompleteinput");
- this.mPopupOpen = false;
-
- // Prevent opening popup from historydropmarker mousedown handler
- // on the same event tick the popup is hidden by the same mousedown
- // event.
- this.mIsPopupHidingTick = true;
- setTimeout(() => {
- this.mIsPopupHidingTick = false;
- }, 0);
-
- // Reset the maxRows property to the cached "normal" value, and reset
- // _normalMaxRows so that we can detect whether it was set by the input
- // when the popupshowing handler runs.
-
- // Null-check this.mInput; see bug 1017914
- if (this.mInput)
- this.mInput.maxRows = this._normalMaxRows;
- this._normalMaxRows = -1;
- // If the list was being navigated and then closed, make sure
- // we fire accessible focus event back to textbox
-
- // Null-check this.mInput; see bug 1017914
- if (isListActive && this.mInput) {
- this.mInput.mIgnoreFocus = true;
- this.mInput._focus();
- this.mInput.mIgnoreFocus = false;
- }
- ]]></handler>
- </handlers>
- </binding>
-
- <binding id="private-autocomplete-rich-result-popup" extends="chrome://browser/content/autocomplete.xml#private-autocomplete-base-popup">
- <resources>
- <stylesheet src="chrome://browser/content/autocomplete.css"/>
- <stylesheet src="chrome://browser/skin/autocomplete.css"/>
- </resources>
-
- <content ignorekeys="true" level="top" consumeoutsideclicks="never">
- <xul:richlistbox anonid="richlistbox" class="private-autocomplete-richlistbox" flex="1"/>
- <xul:hbox>
- <children/>
- </xul:hbox>
- </content>
-
- <implementation implements="nsIAutoCompletePopup">
- <field name="_currentIndex">0</field>
- <field name="_rowHeight">0</field>
- <field name="_rlbAnimated">false</field>
-
- <!-- =================== nsIAutoCompletePopup =================== -->
-
- <property name="selectedIndex"
- onget="return this.richlistbox.selectedIndex;">
- <setter>
- <![CDATA[
- this.richlistbox.selectedIndex = val;
-
- // when clearing the selection (val == -1, so selectedItem will be
- // null), we want to scroll back to the top. see bug #406194
- this.richlistbox.ensureElementIsVisible(
- this.richlistbox.selectedItem || this.richlistbox.firstChild);
-
- return val;
- ]]>
- </setter>
- </property>
-
- <method name="onSearchBegin">
- <body><![CDATA[
- this.richlistbox.mouseSelectedIndex = -1;
- ]]></body>
- </method>
-
- <method name="openAutocompletePopup">
- <parameter name="aInput"/>
- <parameter name="aElement"/>
- <body>
- <![CDATA[
- // until we have "baseBinding", (see bug #373652) this allows
- // us to override openAutocompletePopup(), but still call
- // the method on the base class
- this._openAutocompletePopup(aInput, aElement);
- ]]>
- </body>
- </method>
-
- <method name="_openAutocompletePopup">
- <parameter name="aInput"/>
- <parameter name="aElement"/>
- <body>
- <![CDATA[
- if (!this.mPopupOpen) {
- this.mInput = aInput;
- // clear any previous selection, see bugs 400671 and 488357
- this.selectedIndex = -1;
-
- var width = aElement.getBoundingClientRect().width;
- this.setAttribute("width", width > 100 ? width : 100);
- // invalidate() depends on the width attribute
- this._invalidate();
-
- this.openPopup(aElement, "after_start", 0, 0, false, false);
- }
- ]]>
- </body>
- </method>
-
- <method name="invalidate">
- <parameter name="reason"/>
- <body>
- <![CDATA[
- // Don't bother doing work if we're not even showing
- if (!this.mPopupOpen)
- return;
-
- this._invalidate(reason);
- ]]>
- </body>
- </method>
-
- <method name="_invalidate">
- <parameter name="reason"/>
- <body>
- <![CDATA[
- // collapsed if no matches
- this.richlistbox.collapsed = (this._matchCount == 0);
-
- // Update the richlistbox height.
- if (this._adjustHeightTimeout) {
- clearTimeout(this._adjustHeightTimeout);
- }
- if (this._shrinkTimeout) {
- clearTimeout(this._shrinkTimeout);
- }
- this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0);
-
- this._currentIndex = 0;
- if (this._appendResultTimeout) {
- clearTimeout(this._appendResultTimeout);
- }
- this._appendCurrentResult(reason);
- ]]>
- </body>
- </method>
-
- <property name="maxResults" readonly="true">
- <getter>
- <![CDATA[
- // this is how many richlistitems will be kept around
- // (note, this getter may be overridden)
- return 20;
- ]]>
- </getter>
- </property>
-
- <property name="_matchCount" readonly="true">
- <getter>
- <![CDATA[
- return Math.min(this.mInput.controller.matchCount, this.maxResults);
- ]]>
- </getter>
- </property>
-
- <method name="_collapseUnusedItems">
- <body>
- <![CDATA[
- let existingItemsCount = this.richlistbox.childNodes.length;
- for (let i = this._matchCount; i < existingItemsCount; ++i) {
- this.richlistbox.childNodes[i].collapsed = true;
- }
- ]]>
- </body>
- </method>
-
- <method name="adjustHeight">
- <body>
- <![CDATA[
- // Figure out how many rows to show
- let rows = this.richlistbox.childNodes;
- let numRows = Math.min(this._matchCount, this.maxRows, rows.length);
-
- this.removeAttribute("height");
-
- // Default the height to 0 if we have no rows to show
- let height = 0;
- if (numRows) {
- if (!this._rowHeight) {
- let firstRowRect = rows[0].getBoundingClientRect();
- this._rowHeight = firstRowRect.height;
-
- let transition =
- window.getComputedStyle(this.richlistbox).transitionProperty;
- this._rlbAnimated = transition && transition != "none";
-
- // Set a fixed max-height to avoid flicker when growing the panel.
- this.richlistbox.style.maxHeight = (this._rowHeight * this.maxRows) + "px";
- }
-
- // Calculate the height to have the first row to last row shown
- height = this._rowHeight * numRows;
- }
-
- let animate = this._rlbAnimated &&
- this.getAttribute("dontanimate") != "true";
- let currentHeight = this.richlistbox.getBoundingClientRect().height;
- if (height > currentHeight) {
- // Grow immediately.
- if (animate) {
- this.richlistbox.removeAttribute("height");
- this.richlistbox.style.height = height + "px";
- } else {
- this.richlistbox.style.removeProperty("height");
- this.richlistbox.height = height;
- }
- } else {
- // Delay shrinking to avoid flicker.
- this._shrinkTimeout = setTimeout(() => {
- this._collapseUnusedItems();
- if (animate) {
- this.richlistbox.removeAttribute("height");
- this.richlistbox.style.height = height + "px";
- } else {
- this.richlistbox.style.removeProperty("height");
- this.richlistbox.height = height;
- }
- }, this.mInput.shrinkDelay);
- }
- ]]>
- </body>
- </method>
-
- <method name="_appendCurrentResult">
- <parameter name="invalidateReason"/>
- <body>
- <![CDATA[
- var controller = this.mInput.controller;
- var matchCount = this._matchCount;
- var existingItemsCount = this.richlistbox.childNodes.length;
-
- // Process maxRows per chunk to improve performance and user experience
- for (let i = 0; i < this.maxRows; i++) {
- if (this._currentIndex >= matchCount)
- break;
-
- var item;
-
- // trim the leading/trailing whitespace
- var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
-
- let url = controller.getValueAt(this._currentIndex);
-
- if (this._currentIndex < existingItemsCount) {
- // re-use the existing item
- item = this.richlistbox.childNodes[this._currentIndex];
-
- // Completely reuse the existing richlistitem for invalidation
- // due to new results, but only when: the item is the same, *OR*
- // we are about to replace the currently mouse-selected item, to
- // avoid surprising the user.
- let iface = Components.interfaces.nsIAutoCompletePopup;
- if (item.getAttribute("text") == trimmedSearchString &&
- invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
- (item.getAttribute("url") == url ||
- this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
- item.collapsed = false;
- this._currentIndex++;
- continue;
- }
- }
- else {
- // need to create a new item
- item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
- }
-
- // set these attributes before we set the class
- // so that we can use them from the constructor
- let iconURI = controller.getImageAt(this._currentIndex);
- item.setAttribute("image", iconURI);
- item.setAttribute("url", url);
- item.setAttribute("title", controller.getCommentAt(this._currentIndex));
- item.setAttribute("type", controller.getStyleAt(this._currentIndex));
- item.setAttribute("text", trimmedSearchString);
-
- if (this._currentIndex < existingItemsCount) {
- // re-use the existing item
- item._adjustAcItem();
- item.collapsed = false;
- }
- else {
- // set the class at the end so we can use the attributes
- // in the xbl constructor
- item.className = "private-autocomplete-richlistitem";
- this.richlistbox.appendChild(item);
- }
-
- this._currentIndex++;
- }
-
- if (typeof this.onResultsAdded == "function")
- this.onResultsAdded();
-
- if (this._currentIndex < matchCount) {
- // yield after each batch of items so that typing the url bar is
- // responsive
- this._appendResultTimeout = setTimeout(() => this._appendCurrentResult(), 0);
- }
- ]]>
- </body>
- </method>
-
- <method name="selectBy">
- <parameter name="aReverse"/>
- <parameter name="aPage"/>
- <body>
- <![CDATA[
- try {
- var amount = aPage ? 5 : 1;
-
- // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
- this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1);
- if (this.selectedIndex == -1) {
- this.input._focus();
- }
- } catch (ex) {
- // do nothing - occasionally timer-related js errors happen here
- // e.g. "this.selectedIndex has no properties", when you type fast and hit a
- // navigation key before this popup has opened
- }
- ]]>
- </body>
- </method>
-
- <field name="richlistbox">
- document.getAnonymousElementByAttribute(this, "anonid", "richlistbox");
- </field>
-
- <property name="view"
- onget="return this.mInput.controller;"
- onset="return val;"/>
-
- </implementation>
- </binding>
-
- <binding id="private-autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
- <content>
- <xul:hbox align="center" class="ac-title-box">
- <xul:image xbl:inherits="src=image" class="ac-site-icon"/>
- <xul:hbox anonid="title-box" class="ac-title" flex="1"
- onunderflow="_doUnderflow('_title');"
- onoverflow="_doOverflow('_title');">
- <xul:description anonid="title" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
- </xul:hbox>
- <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected"
- class="ac-ellipsis-after ac-comment"/>
- <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true">
- <xul:image class="ac-result-type-tag"/>
- <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/>
- <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
- </xul:hbox>
- <xul:image anonid="type-image" class="ac-type-icon" xbl:inherits="selected"/>
- </xul:hbox>
- <xul:hbox align="center" class="ac-url-box">
- <xul:spacer class="ac-site-icon"/>
- <xul:image class="ac-action-icon"/>
- <xul:hbox anonid="url-box" class="ac-url" flex="1"
- onunderflow="_doUnderflow('_url');"
- onoverflow="_doOverflow('_url');">
- <xul:description anonid="url" class="ac-normal-text ac-url-text"
- xbl:inherits="selected type"/>
- <xul:description anonid="action" class="ac-normal-text ac-action-text"
- xbl:inherits="selected type"/>
- </xul:hbox>
- <xul:label anonid="url-overflow-ellipsis" xbl:inherits="selected"
- class="ac-ellipsis-after ac-url-text"/>
- <xul:spacer class="ac-type-icon"/>
- </xul:hbox>
- </content>
- <implementation implements="nsIDOMXULSelectControlItemElement">
- <constructor>
- <![CDATA[
- let ellipsis = "\u2026";
- try {
- ellipsis = Components.classes["@mozilla.org/preferences-service;1"].
- getService(Components.interfaces.nsIPrefBranch).
- getComplexValue("intl.ellipsis",
- Components.interfaces.nsIPrefLocalizedString).data;
- } catch (ex) {
- // Do nothing.. we already have a default
- }
-
- this._urlOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "url-overflow-ellipsis");
- this._titleOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "title-overflow-ellipsis");
-
- this._urlOverflowEllipsis.value = ellipsis;
- this._titleOverflowEllipsis.value = ellipsis;
-
- this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
-
- this._urlBox = document.getAnonymousElementByAttribute(this, "anonid", "url-box");
- this._url = document.getAnonymousElementByAttribute(this, "anonid", "url");
- this._action = document.getAnonymousElementByAttribute(this, "anonid", "action");
-
- this._titleBox = document.getAnonymousElementByAttribute(this, "anonid", "title-box");
- this._title = document.getAnonymousElementByAttribute(this, "anonid", "title");
-
- this._extraBox = document.getAnonymousElementByAttribute(this, "anonid", "extra-box");
- this._extra = document.getAnonymousElementByAttribute(this, "anonid", "extra");
-
- this._adjustAcItem();
- ]]>
- </constructor>
-
- <property name="label" readonly="true">
- <getter>
- <![CDATA[
- // This property is a string that is read aloud by screen readers,
- // so it must not contain anything that should not be user-facing.
-
- let parts = [
- this.getAttribute("title"),
- this.getAttribute("displayurl"),
- ];
- let label = parts.filter(str => str).join(" ")
-
- // allow consumers that have extended popups to override
- // the label values for the richlistitems
- let panel = this.parentNode.parentNode;
- if (panel.createResultLabel) {
- return panel.createResultLabel(this, label);
- }
-
- return label;
- ]]>
- </getter>
- </property>
-
- <property name="_stringBundle">
- <getter><![CDATA[
- if (!this.__stringBundle) {
- this.__stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
- }
- return this.__stringBundle;
- ]]></getter>
- </property>
-
- <field name="_boundaryCutoff">null</field>
-
- <property name="boundaryCutoff" readonly="true">
- <getter>
- <![CDATA[
- if (!this._boundaryCutoff) {
- this._boundaryCutoff =
- Components.classes["@mozilla.org/preferences-service;1"].
- getService(Components.interfaces.nsIPrefBranch).
- getIntPref("toolkit.autocomplete.richBoundaryCutoff");
- }
- return this._boundaryCutoff;
- ]]>
- </getter>
- </property>
-
- <method name="_getBoundaryIndices">
- <parameter name="aText"/>
- <parameter name="aSearchTokens"/>
- <body>
- <![CDATA[
- // Short circuit for empty search ([""] == "")
- if (aSearchTokens == "")
- return [0, aText.length];
-
- // Find which regions of text match the search terms
- let regions = [];
- for (let search of Array.prototype.slice.call(aSearchTokens)) {
- let matchIndex = -1;
- let searchLen = search.length;
-
- // Find all matches of the search terms, but stop early for perf
- let lowerText = aText.substr(0, this.boundaryCutoff).toLowerCase();
- while ((matchIndex = lowerText.indexOf(search, matchIndex + 1)) >= 0) {
- regions.push([matchIndex, matchIndex + searchLen]);
- }
- }
-
- // Sort the regions by start position then end position
- regions = regions.sort((a, b) => {
- let start = a[0] - b[0];
- return (start == 0) ? a[1] - b[1] : start;
- });
-
- // Generate the boundary indices from each region
- let start = 0;
- let end = 0;
- let boundaries = [];
- let len = regions.length;
- for (let i = 0; i < len; i++) {
- // We have a new boundary if the start of the next is past the end
- let region = regions[i];
- if (region[0] > end) {
- // First index is the beginning of match
- boundaries.push(start);
- // Second index is the beginning of non-match
- boundaries.push(end);
-
- // Track the new region now that we've stored the previous one
- start = region[0];
- }
-
- // Push back the end index for the current or new region
- end = Math.max(end, region[1]);
- }
-
- // Add the last region
- boundaries.push(start);
- boundaries.push(end);
-
- // Put on the end boundary if necessary
- if (end < aText.length)
- boundaries.push(aText.length);
-
- // Skip the first item because it's always 0
- return boundaries.slice(1);
- ]]>
- </body>
- </method>
-
- <method name="_getSearchTokens">
- <parameter name="aSearch"/>
- <body>
- <![CDATA[
- let search = aSearch.toLowerCase();
- return search.split(/\s+/);
- ]]>
- </body>
- </method>
-
- <method name="_setUpDescription">
- <parameter name="aDescriptionElement"/>
- <parameter name="aText"/>
- <parameter name="aNoEmphasis"/>
- <body>
- <![CDATA[
- // Get rid of all previous text
- while (aDescriptionElement.hasChildNodes())
- aDescriptionElement.removeChild(aDescriptionElement.firstChild);
-
- // If aNoEmphasis is specified, don't add any emphasis
- if (aNoEmphasis) {
- aDescriptionElement.appendChild(document.createTextNode(aText));
- return;
- }
-
- // Get the indices that separate match and non-match text
- let search = this.getAttribute("text");
- let tokens = this._getSearchTokens(search);
- let indices = this._getBoundaryIndices(aText, tokens);
-
- let next;
- let start = 0;
- let len = indices.length;
- // Even indexed boundaries are matches, so skip the 0th if it's empty
- for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
- next = indices[i];
- let text = aText.substr(start, next - start);
- start = next;
-
- if (i % 2 == 0) {
- // Emphasize the text for even indices
- let span = aDescriptionElement.appendChild(
- document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
- span.className = "ac-emphasize-text";
- span.textContent = text;
- } else {
- // Otherwise, it's plain text
- aDescriptionElement.appendChild(document.createTextNode(text));
- }
- }
- ]]>
- </body>
- </method>
-
- <!--
- This will generate an array of emphasis pairs for use with
- _setUpEmphasisedSections(). Each pair is a tuple (array) that
- represents a block of text - containing the text of that block, and a
- boolean for whether that block should have an emphasis styling applied
- to it.
-
- These pairs are generated by parsing a localised string (aSourceString)
- with parameters, in the format that is used by
- nsIStringBundle.formatStringFromName():
-
- "textA %1$S textB textC %2$S"
-
- Or:
-
- "textA %S"
-
- Where "%1$S", "%2$S", and "%S" are intended to be replaced by provided
- replacement strings. These are specified an array of tuples
- (aReplacements), each containing the replacement text and a boolean for
- whether that text should have an emphasis styling applied. This is used
- as a 1-based array - ie, "%1$S" is replaced by the item in the first
- index of aReplacements, "%2$S" by the second, etc. "%S" will always
- match the first index.
- -->
- <method name="_generateEmphasisPairs">
- <parameter name="aSourceString"/>
- <parameter name="aReplacements"/>
- <body>
- <![CDATA[
- let pairs = [];
-
- // Split on %S, %1$S, %2$S, etc. ie:
- // "textA %S"
- // becomes ["textA ", "%S"]
- // "textA %1$S textB textC %2$S"
- // becomes ["textA ", "%1$S", " textB textC ", "%2$S"]
- let parts = aSourceString.split(/(%(?:[0-9]+\$)?S)/);
-
- for (let part of parts) {
- // The above regex will actually give us an empty string at the
- // end - we don't want that, as we don't want to later generate an
- // empty text node for it.
- if (part.length === 0)
- continue;
-
- // Determine if this token is a replacement token or a normal text
- // token. If it is a replacement token, we want to extract the
- // numerical number. However, we still want to match on "$S".
- let match = part.match(/^%(?:([0-9]+)\$)?S$/);
-
- if (match) {
- // "%S" doesn't have a numerical number in it, but will always
- // be assumed to be 1. Furthermore, the input string specifies
- // these with a 1-based index, but we want a 0-based index.
- let index = (match[1] || 1) - 1;
-
- if (index >= 0 && index < aReplacements.length) {
- pairs.push([...aReplacements[index]]);
- }
- } else {
- pairs.push([part]);
- }
- }
-
- return pairs;
- ]]>
- </body>
- </method>
-
- <!--
- _setUpEmphasisedSections() has the same use as _setUpDescription,
- except instead of taking a string and highlighting given tokens, it takes
- an array of pairs generated by _generateEmphasisPairs(). This allows
- control over emphasising based on specific blocks of text, rather than
- search for substrings.
- -->
- <method name="_setUpEmphasisedSections">
- <parameter name="aDescriptionElement"/>
- <parameter name="aTextPairs"/>
- <body>
- <![CDATA[
- // Get rid of all previous text
- while (aDescriptionElement.hasChildNodes())
- aDescriptionElement.firstChild.remove();
-
- for (let [text, emphasise] of aTextPairs) {
- if (emphasise) {
- let span = aDescriptionElement.appendChild(
- document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
- span.textContent = text;
- switch(emphasise) {
- case "match":
- span.className = "ac-emphasize-text";
- break;
- case "selected":
- span.className = "ac-selected-text";
- break;
- }
- } else {
- aDescriptionElement.appendChild(document.createTextNode(text));
- }
- }
- ]]>
- </body>
- </method>
-
- <field name="_textToSubURI">null</field>
- <method name="_unescapeUrl">
- <parameter name="url"/>
- <body>
- <![CDATA[
- if (!this._textToSubURI) {
- this._textToSubURI =
- Components.classes["@mozilla.org/intl/texttosuburi;1"]
- .getService(Components.interfaces.nsITextToSubURI);
- }
- return this._textToSubURI.unEscapeURIForUI("UTF-8", url);
- ]]>
- </body>
- </method>
-
- <method name="_adjustAcItem">
- <body>
- <![CDATA[
- let originalUrl = this.getAttribute("url");
- let title = this.getAttribute("title");
- let type = this.getAttribute("type");
-
- let displayUrl;
- let emphasiseTitle = true;
- let emphasiseUrl = true;
-
- // Hide the title's extra box by default, until we find out later if
- // we need extra stuff.
- this._extraBox.hidden = true;
- this._titleBox.flex = 1;
- this._typeImage.hidden = false;
-
- this.removeAttribute("actiontype");
- this.classList.remove("overridable-action");
-
- // The ellipses are hidden via their visibility so that they always
- // take up space and don't pop in on top of text when shown. For
- // keyword searches, however, the title ellipsis should not take up
- // space when hidden. Setting the hidden property accomplishes that.
- this._titleOverflowEllipsis.hidden = false;
-
- let types = new Set(type.split(/\s+/));
-
- // Remove types that should ultimately not be in the `type` string.
- let initialTypes = new Set(types);
- types.delete("action");
- types.delete("autofill");
- types.delete("heuristic");
- types.delete("search");
-
- type = [...types][0] || "";
-
- // If the type includes an action, set up the item appropriately.
- if (initialTypes.has("action")) {
- let action = this._parseActionUrl(originalUrl);
- this.setAttribute("actiontype", action.type);
-
- if (action.type == "switchtab") {
- this.classList.add("overridable-action");
- displayUrl = this._unescapeUrl(action.params.url);
- let desc = this._stringBundle.GetStringFromName("switchToTab");
- this._setUpDescription(this._action, desc, true);
- } else if (action.type == "remotetab") {
- displayUrl = this._unescapeUrl(action.params.url);
- let desc = action.params.deviceName;
- this._setUpDescription(this._action, desc, true);
- } else if (action.type == "searchengine") {
- emphasiseUrl = false;
-
- // The order here is not localizable, we default to appending
- // "- Search with Engine" to the search string, to be able to
- // properly generate emphasis pairs. That said, no localization
- // changed the order while it was possible, so doesn't look like
- // there's a strong need for that.
- let {engineName, searchSuggestion, searchQuery} = action.params;
- let engineStr = " - " +
- this._stringBundle.formatStringFromName("searchWithEngine",
- [engineName], 1);
-
- // Make the title by generating an array of pairs and its
- // corresponding interpolation string (e.g., "%1$S") to pass to
- // _generateEmphasisPairs.
- let pairs;
- if (searchSuggestion) {
- // Check if the search query appears in the suggestion. It may
- // not. If it does, then emphasize the query in the suggestion
- // and otherwise just include the suggestion without emphasis.
- let idx = searchSuggestion.indexOf(searchQuery);
- if (idx >= 0) {
- pairs = [
- [searchSuggestion.substring(0, idx), ""],
- [searchQuery, "match"],
- [searchSuggestion.substring(idx + searchQuery.length), ""],
- ];
- } else {
- pairs = [
- [searchSuggestion, ""],
- ];
- }
- } else {
- pairs = [
- [searchQuery, ""],
- ];
- }
- pairs.push([engineStr, "selected"]);
- let interpStr = pairs.map((pair, i) => `%${i + 1}$S`).join("");
- title = this._generateEmphasisPairs(interpStr, pairs);
-
- // If this is a default search match, we remove the image so we
- // can style it ourselves with a generic search icon.
- // We don't do this when matching an aliased search engine,
- // because the icon helps with recognising which engine will be
- // used (when using the default engine, we don't need that
- // recognition).
- if (!action.params.alias) {
- this.removeAttribute("image");
- }
- } else if (action.type == "visiturl") {
- emphasiseUrl = false;
- displayUrl = this._unescapeUrl(action.params.url);
- let sourceStr = this._stringBundle.GetStringFromName("visitURL");
- title = this._generateEmphasisPairs(sourceStr, [
- [displayUrl, "match"],
- ]);
- }
- }
-
- // Check if we have a search engine name
- if (initialTypes.has("search")) {
- emphasiseUrl = false;
-
- const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 ";
-
- let searchEngine = "";
- [title, searchEngine] = title.split(TITLE_SEARCH_ENGINE_SEPARATOR);
- displayUrl = this._stringBundle.formatStringFromName("searchWithEngine", [searchEngine], 1);
- }
-
- if (!displayUrl) {
- let input = this.parentNode.parentNode.input;
- let url = typeof(input.trimValue) == "function" ?
- input.trimValue(originalUrl) :
- originalUrl;
- displayUrl = this._unescapeUrl(url);
- }
- this.setAttribute("displayurl", displayUrl);
-
- // Check if we have an auto-fill URL
- if (initialTypes.has("autofill")) {
- emphasiseUrl = false;
-
- let sourceStr = this._stringBundle.GetStringFromName("visitURL");
- title = this._generateEmphasisPairs(sourceStr, [
- [displayUrl, "match"],
- ]);
- }
-
- // If we have a tag match, show the tags and icon
- if (type == "tag" || type == "bookmark-tag") {
- // Configure the extra box for tags display
- this._extraBox.hidden = false;
- this._extraBox.childNodes[0].hidden = false;
- this._extraBox.childNodes[1].hidden = true;
- this._extraBox.pack = "end";
- this._titleBox.flex = 1;
-
- // The title is separated from the tags by an endash
- let tags;
- [, title, tags] = title.match(/^(.+) \u2013 (.+)$/);
-
- // Each tag is split by a comma in an undefined order, so sort it
- let sortedTags = tags.split(",").sort().join(", ");
-
- // Emphasize the matching text in the tags
- this._setUpDescription(this._extra, sortedTags);
-
- // If we're suggesting bookmarks, then treat tagged matches as
- // bookmarks for the star.
- if (type == "bookmark-tag") {
- type = "bookmark";
- } else {
- this._typeImage.hidden = true;
- }
- // keyword and favicon type results for search engines
- // have an extra magnifying glass icon after them
- } else if (type == "keyword" || (initialTypes.has("search") &&
- initialTypes.has("favicon"))) {
- // Configure the extra box for keyword display
- this._extraBox.hidden = false;
- this._extraBox.childNodes[0].hidden = true;
- // The second child node is ":" and it should be hidden for non keyword types
- this._extraBox.childNodes[1].hidden = type == "keyword" ? false : true;
- this._extraBox.pack = "start";
- this._titleBox.flex = 0;
-
- // Hide the ellipsis so it doesn't take up space.
- this._titleOverflowEllipsis.hidden = true;
-
- if (type == "keyword") {
- // Put the parameters next to the title if we have any
- let search = this.getAttribute("text");
- let params = "";
- let paramsIndex = search.indexOf(" ");
- if (paramsIndex != -1)
- params = search.substr(paramsIndex + 1);
-
- // Emphasize the keyword parameters
- this._setUpDescription(this._extra, params);
-
- // Don't emphasize keyword searches in the title or url
- emphasiseUrl = false;
- emphasiseTitle = false;
- } else {
- // Don't show any description for non keyword types.
- this._setUpDescription(this._extra, "", true);
- }
- // If the result has the type favicon and a known search provider,
- // customize it the same way as a keyword result.
- type = "keyword";
- }
-
- // Give the image the icon style and a special one for the type
- this._typeImage.className = "ac-type-icon" +
- (type ? " ac-result-type-" + type : "");
-
- // Show the domain as the title if we don't have a title.
- if (title == "") {
- title = displayUrl;
- try {
- let uri = Services.io.newURI(originalUrl, null, null);
- // Not all valid URLs have a domain.
- if (uri.host)
- title = uri.host;
- } catch (e) {}
- }
-
- // Emphasize the matching search terms for the description
- if (Array.isArray(title))
- this._setUpEmphasisedSections(this._title, title);
- else
- this._setUpDescription(this._title, title, !emphasiseTitle);
-
- this._setUpDescription(this._url, displayUrl, !emphasiseUrl);
-
- // Set up overflow on a timeout because the contents of the box
- // might not have a width yet even though we just changed them
- setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis);
- setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis);
- ]]>
- </body>
- </method>
-
- <method name="_parseActionUrl">
- <parameter name="aUrl"/>
- <body><![CDATA[
- if (!aUrl.startsWith("moz-action:"))
- return null;
-
- // URL is in the format moz-action:ACTION,PARAMS
- // Where PARAMS is a JSON encoded object.
- let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
-
- let action = {
- type: type,
- };
-
- try {
- action.params = JSON.parse(params);
- for (let key in action.params) {
- action.params[key] = decodeURIComponent(action.params[key]);
- }
- } catch (e) {
- // If this failed, we assume that params is not a JSON object, and
- // is instead just a flat string. This will happen when
- // UnifiedComplete is disabled - in which case, the param is always
- // a URL.
- action.params = {
- url: params,
- }
- }
-
- return action;
- ]]></body>
- </method>
-
- <method name="_setUpOverflow">
- <parameter name="aParentBox"/>
- <parameter name="aEllipsis"/>
- <body>
- <![CDATA[
- // Hide the ellipsis incase there's just enough to not underflow
- aEllipsis.style.visibility = "hidden";
-
- // Start with the parent's width and subtract off its children
- let tooltip = [];
- let children = aParentBox.childNodes;
- let widthDiff = aParentBox.boxObject.width;
-
- for (let i = 0; i < children.length; i++) {
- // Only consider a child if it actually takes up space
- let childWidth = children[i].boxObject.width;
- if (childWidth > 0) {
- // Subtract a little less to account for subpixel rounding
- widthDiff -= childWidth - .5;
-
- // Add to the tooltip if it's not hidden and has text
- let childText = children[i].textContent;
- if (childText)
- tooltip.push(childText);
- }
- }
-
- // If the children take up more space than the parent.. overflow!
- if (widthDiff < 0) {
- // Re-show the ellipsis now that we know it's needed
- aEllipsis.style.visibility = "visible";
-
- // Separate text components with a ndash --
- aParentBox.tooltipText = tooltip.join(" \u2013 ");
- }
- ]]>
- </body>
- </method>
-
- <method name="_doUnderflow">
- <parameter name="aName"/>
- <body>
- <![CDATA[
- // Hide the ellipsis right when we know we're underflowing instead of
- // waiting for the timeout to trigger the _setUpOverflow calculations
- this[aName + "Box"].tooltipText = "";
- this[aName + "OverflowEllipsis"].style.visibility = "hidden";
- ]]>
- </body>
- </method>
-
- <method name="_doOverflow">
- <parameter name="aName"/>
- <body>
- <![CDATA[
- this._setUpOverflow(this[aName + "Box"],
- this[aName + "OverflowEllipsis"]);
- ]]>
- </body>
- </method>
-
- </implementation>
- </binding>
-
- <binding id="private-autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
- <content>
- <children includes="treecols"/>
- <xul:treerows class="private-autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1">
- <children/>
- </xul:treerows>
- </content>
- </binding>
-
- <binding id="private-autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
- <implementation>
- <field name="mLastMoveTime">Date.now()</field>
- <field name="mouseSelectedIndex">-1</field>
- </implementation>
- <handlers>
- <handler event="mouseup">
- <![CDATA[
- // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc.
- let item = event.originalTarget;
- while (item && item.localName != "richlistitem") {
- item = item.parentNode;
- }
-
- if (!item)
- return;
-
- this.parentNode.onPopupClick(event);
- ]]>
- </handler>
-
- <handler event="mousemove">
- <![CDATA[
- if (Date.now() - this.mLastMoveTime > 30) {
- let item = event.target;
- while (item && item.localName != "richlistitem") {
- item = item.parentNode;
- }
-
- if (!item)
- return;
-
- let index = this.getIndexOfItem(item);
- if (index != this.selectedIndex) {
- this.mouseSelectedIndex = this.selectedIndex = index;
- }
-
- this.mLastMoveTime = Date.now();
- }
- ]]>
- </handler>
- </handlers>
- </binding>
-
- <binding id="private-autocomplete-treebody">
- <implementation>
- <field name="mLastMoveTime">Date.now()</field>
- </implementation>
-
- <handlers>
- <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/>
-
- <handler event="mousedown"><![CDATA[
- var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
- if (rc != this.parentNode.currentIndex)
- this.parentNode.view.selection.select(rc);
- ]]></handler>
-
- <handler event="mousemove"><![CDATA[
- if (Date.now() - this.mLastMoveTime > 30) {
- var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
- if (rc != this.parentNode.currentIndex)
- this.parentNode.view.selection.select(rc);
- this.mLastMoveTime = Date.now();
- }
- ]]></handler>
- </handlers>
- </binding>
-
- <binding id="private-autocomplete-treerows">
- <content>
- <xul:hbox flex="1" class="tree-bodybox">
- <children/>
- </xul:hbox>
- <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/>
- </content>
- </binding>
-
- <binding id="private-history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
- <handlers>
- <handler event="mousedown" button="0"><![CDATA[
- document.getBindingParent(this).toggleHistoryPopup();
- ]]></handler>
- </handlers>
- </binding>
-
-</bindings>