Bug 1476611 - Part 2 - Flatten the "richlistbox" bindings. r=bgrins
MozReview-Commit-ID: FrXKW1T7wYd
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -74,17 +74,16 @@ toolkit.jar:
content/global/bindings/datetimepopup.xml (widgets/datetimepopup.xml)
content/global/bindings/datetimebox.xml (widgets/datetimebox.xml)
content/global/bindings/datetimebox.css (widgets/datetimebox.css)
* content/global/bindings/dialog.xml (widgets/dialog.xml)
content/global/bindings/editor.xml (widgets/editor.xml)
* content/global/bindings/findbar.xml (widgets/findbar.xml)
content/global/bindings/general.xml (widgets/general.xml)
content/global/bindings/groupbox.xml (widgets/groupbox.xml)
- content/global/bindings/listbox.xml (widgets/listbox.xml)
content/global/bindings/menu.xml (widgets/menu.xml)
content/global/bindings/menulist.xml (widgets/menulist.xml)
content/global/bindings/notification.xml (widgets/notification.xml)
content/global/bindings/numberbox.xml (widgets/numberbox.xml)
content/global/bindings/popup.xml (widgets/popup.xml)
content/global/bindings/progressmeter.xml (widgets/progressmeter.xml)
content/global/bindings/radio.xml (widgets/radio.xml)
content/global/bindings/remote-browser.xml (widgets/remote-browser.xml)
deleted file mode 100644
--- a/toolkit/content/widgets/listbox.xml
+++ /dev/null
@@ -1,663 +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/. -->
-
-<!-- This files relies on these specific Chrome/XBL globals -->
-<!-- globals ChromeNodeList -->
-
-<bindings id="listboxBindings"
- xmlns="http://www.mozilla.org/xbl"
- xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- xmlns:xbl="http://www.mozilla.org/xbl">
-
- <!--
- Interface binding that is base for bindings of xul:listbox and
- xul:richlistbox elements. This binding assumes that successors bindings
- will implement the following properties and methods:
-
- /** Return the number of items */
- readonly itemCount
-
- /** Return index of given item
- * @param aItem - given item element */
- getIndexOfItem(aItem)
-
- /** Return item at given index
- * @param aIndex - index of item element */
- getItemAtIndex(aIndex)
-
- /** Return count of item elements */
- getRowCount()
-
- /** Return true if item of given index is visible
- * @param aIndex - index of item element
- *
- * @note XXX: this method should be removed after bug 364612 is fixed
- */
- ensureIndexIsVisible(aIndex)
-
- /** Return true if item element is visible
- * @param aElement - given item element */
- ensureElementIsVisible(aElement)
-
- /** Scroll list control to make visible item of given index
- * @param aIndex - index of item element
- *
- * @note XXX: this method should be removed after bug 364612 is fixed
- */
- scrollToIndex(aIndex)
-
- /** Create item element and append it to the end of listbox
- * @param aLabel - label of new item element
- * @param aValue - value of new item element */
- appendItem(aLabel, aValue)
-
- /** Scroll up/down one page
- * @param aDirection - specifies scrolling direction, should be either -1 or 1
- * @return the number of elements the selection scrolled
- */
- scrollOnePage(aDirection)
-
- /** Fire "select" event */
- _fireOnSelect()
- -->
- <binding id="listbox-base"
- extends="chrome://global/content/bindings/general.xml#basecontrol">
-
- <implementation implements="nsIDOMXULMultiSelectControlElement">
- <field name="_lastKeyTime">0</field>
- <field name="_incrementalString">""</field>
-
- <!-- nsIDOMXULSelectControlElement -->
- <property name="selectedItem"
- onset="this.selectItem(val);">
- <getter>
- <![CDATA[
- return this.selectedItems.length > 0 ? this.selectedItems[0] : null;
- ]]>
- </getter>
- </property>
-
- <property name="selectedIndex">
- <getter>
- <![CDATA[
- if (this.selectedItems.length > 0)
- return this.getIndexOfItem(this.selectedItems[0]);
- return -1;
- ]]>
- </getter>
- <setter>
- <![CDATA[
- if (val >= 0) {
- // This is a micro-optimization so that a call to getIndexOfItem or
- // getItemAtIndex caused by _fireOnSelect (especially for derived
- // widgets) won't loop the children.
- this._selecting = {
- item: this.getItemAtIndex(val),
- index: val
- };
- this.selectItem(this._selecting.item);
- delete this._selecting;
- } else {
- this.clearSelection();
- this.currentItem = null;
- }
- ]]>
- </setter>
- </property>
-
- <property name="value">
- <getter>
- <![CDATA[
- if (this.selectedItems.length > 0)
- return this.selectedItem.value;
- return null;
- ]]>
- </getter>
- <setter>
- <![CDATA[
- var kids = this.getElementsByAttribute("value", val);
- if (kids && kids.item(0))
- this.selectItem(kids[0]);
- return val;
- ]]>
- </setter>
- </property>
-
- <!-- nsIDOMXULMultiSelectControlElement -->
- <property name="selType"
- onget="return this.getAttribute('seltype');"
- onset="this.setAttribute('seltype', val); return val;"/>
-
- <property name="currentItem" onget="return this._currentItem;">
- <setter>
- if (this._currentItem == val)
- return val;
-
- if (this._currentItem)
- this._currentItem.current = false;
- this._currentItem = val;
-
- if (val)
- val.current = true;
-
- return val;
- </setter>
- </property>
-
- <property name="currentIndex">
- <getter>
- return this.currentItem ? this.getIndexOfItem(this.currentItem) : -1;
- </getter>
- <setter>
- <![CDATA[
- if (val >= 0)
- this.currentItem = this.getItemAtIndex(val);
- else
- this.currentItem = null;
- ]]>
- </setter>
- </property>
-
- <field name="selectedItems">new ChromeNodeList()</field>
-
- <method name="addItemToSelection">
- <parameter name="aItem"/>
- <body>
- <![CDATA[
- if (this.selType != "multiple" && this.selectedCount)
- return;
-
- if (aItem.selected)
- return;
-
- this.selectedItems.append(aItem);
- aItem.selected = true;
-
- this._fireOnSelect();
- ]]>
- </body>
- </method>
-
- <method name="removeItemFromSelection">
- <parameter name="aItem"/>
- <body>
- <![CDATA[
- if (!aItem.selected)
- return;
-
- this.selectedItems.remove(aItem);
- aItem.selected = false;
- this._fireOnSelect();
- ]]>
- </body>
- </method>
-
- <method name="toggleItemSelection">
- <parameter name="aItem"/>
- <body>
- <![CDATA[
- if (aItem.selected)
- this.removeItemFromSelection(aItem);
- else
- this.addItemToSelection(aItem);
- ]]>
- </body>
- </method>
-
- <method name="selectItem">
- <parameter name="aItem"/>
- <body>
- <![CDATA[
- if (!aItem)
- return;
-
- if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem)
- return;
-
- this._selectionStart = null;
-
- var suppress = this._suppressOnSelect;
- this._suppressOnSelect = true;
-
- this.clearSelection();
- this.addItemToSelection(aItem);
- this.currentItem = aItem;
-
- this._suppressOnSelect = suppress;
- this._fireOnSelect();
- ]]>
- </body>
- </method>
-
- <method name="selectItemRange">
- <parameter name="aStartItem"/>
- <parameter name="aEndItem"/>
- <body>
- <![CDATA[
- if (this.selType != "multiple")
- return;
-
- if (!aStartItem)
- aStartItem = this._selectionStart ?
- this._selectionStart : this.currentItem;
-
- if (!aStartItem)
- aStartItem = aEndItem;
-
- var suppressSelect = this._suppressOnSelect;
- this._suppressOnSelect = true;
-
- this._selectionStart = aStartItem;
-
- var currentItem;
- var startIndex = this.getIndexOfItem(aStartItem);
- var endIndex = this.getIndexOfItem(aEndItem);
- if (endIndex < startIndex) {
- currentItem = aEndItem;
- aEndItem = aStartItem;
- aStartItem = currentItem;
- } else {
- currentItem = aStartItem;
- }
-
- while (currentItem) {
- this.addItemToSelection(currentItem);
- if (currentItem == aEndItem) {
- currentItem = this.getNextItem(currentItem, 1);
- break;
- }
- currentItem = this.getNextItem(currentItem, 1);
- }
-
- // Clear around new selection
- // Don't use clearSelection() because it causes a lot of noise
- // with respect to selection removed notifications used by the
- // accessibility API support.
- var userSelecting = this._userSelecting;
- this._userSelecting = false; // that's US automatically unselecting
- for (; currentItem; currentItem = this.getNextItem(currentItem, 1))
- this.removeItemFromSelection(currentItem);
-
- for (currentItem = this.getItemAtIndex(0); currentItem != aStartItem;
- currentItem = this.getNextItem(currentItem, 1))
- this.removeItemFromSelection(currentItem);
- this._userSelecting = userSelecting;
-
- this._suppressOnSelect = suppressSelect;
-
- this._fireOnSelect();
- ]]>
- </body>
- </method>
-
- <method name="selectAll">
- <body>
- this._selectionStart = null;
-
- var suppress = this._suppressOnSelect;
- this._suppressOnSelect = true;
-
- var item = this.getItemAtIndex(0);
- while (item) {
- this.addItemToSelection(item);
- item = this.getNextItem(item, 1);
- }
-
- this._suppressOnSelect = suppress;
- this._fireOnSelect();
- </body>
- </method>
-
- <method name="invertSelection">
- <body>
- this._selectionStart = null;
-
- var suppress = this._suppressOnSelect;
- this._suppressOnSelect = true;
-
- var item = this.getItemAtIndex(0);
- while (item) {
- if (item.selected)
- this.removeItemFromSelection(item);
- else
- this.addItemToSelection(item);
- item = this.getNextItem(item, 1);
- }
-
- this._suppressOnSelect = suppress;
- this._fireOnSelect();
- </body>
- </method>
-
- <method name="clearSelection">
- <body>
- <![CDATA[
- if (this.selectedItems) {
- while (this.selectedItems.length > 0) {
- let item = this.selectedItems[0];
- item.selected = false;
- this.selectedItems.remove(item);
- }
- }
-
- this._selectionStart = null;
- this._fireOnSelect();
- ]]>
- </body>
- </method>
-
- <property name="selectedCount" readonly="true"
- onget="return this.selectedItems.length;"/>
-
- <method name="getSelectedItem">
- <parameter name="aIndex"/>
- <body>
- <![CDATA[
- return aIndex < this.selectedItems.length ?
- this.selectedItems[aIndex] : null;
- ]]>
- </body>
- </method>
-
- <!-- Other public members -->
- <property name="disableKeyNavigation"
- onget="return this.hasAttribute('disableKeyNavigation');">
- <setter>
- if (val)
- this.setAttribute("disableKeyNavigation", "true");
- else
- this.removeAttribute("disableKeyNavigation");
- return val;
- </setter>
- </property>
-
- <property name="suppressOnSelect"
- onget="return this.getAttribute('suppressonselect') == 'true';"
- onset="this.setAttribute('suppressonselect', val);"/>
-
- <property name="_selectDelay"
- onset="this.setAttribute('_selectDelay', val);"
- onget="return this.getAttribute('_selectDelay') || 50;"/>
-
- <method name="timedSelect">
- <parameter name="aItem"/>
- <parameter name="aTimeout"/>
- <body>
- <![CDATA[
- var suppress = this._suppressOnSelect;
- if (aTimeout != -1)
- this._suppressOnSelect = true;
-
- this.selectItem(aItem);
-
- this._suppressOnSelect = suppress;
-
- if (aTimeout != -1) {
- if (this._selectTimeout)
- window.clearTimeout(this._selectTimeout);
- this._selectTimeout =
- window.setTimeout(this._selectTimeoutHandler, aTimeout, this);
- }
- ]]>
- </body>
- </method>
-
- <!-- Private -->
- <method name="_moveByOffsetFromUserEvent">
- <parameter name="aOffset"/>
- <parameter name="aEvent"/>
- <body>
- <![CDATA[
- if (!aEvent.defaultPrevented) {
- this._userSelecting = true;
- this._mayReverse = true;
- this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey);
- this._userSelecting = false;
- this._mayReverse = false;
- aEvent.preventDefault();
- }
- ]]>
- </body>
- </method>
-
- <method name="_canUserSelect">
- <parameter name="aItem"/>
- <body>
- <![CDATA[
- var style = document.defaultView.getComputedStyle(aItem);
- return style.display != "none" && style.visibility == "visible";
- ]]>
- </body>
- </method>
-
- <method name="_selectTimeoutHandler">
- <parameter name="aMe"/>
- <body>
- aMe._fireOnSelect();
- aMe._selectTimeout = null;
- </body>
- </method>
-
- <field name="_suppressOnSelect">false</field>
- <field name="_userSelecting">false</field>
- <field name="_mayReverse">false</field>
- <field name="_selectTimeout">null</field>
- <field name="_currentItem">null</field>
- <field name="_selectionStart">null</field>
- </implementation>
-
- <handlers>
- <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
- action="this._moveByOffsetFromUserEvent(-1, event);"
- group="system"/>
- <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
- action="this._moveByOffsetFromUserEvent(1, event);"
- group="system"/>
- <handler event="keypress" keycode="VK_HOME" modifiers="control shift any"
- group="system">
- <![CDATA[
- this._mayReverse = true;
- this._moveByOffsetFromUserEvent(-this.currentIndex, event);
- this._mayReverse = false;
- ]]>
- </handler>
- <handler event="keypress" keycode="VK_END" modifiers="control shift any"
- group="system">
- <![CDATA[
- this._mayReverse = true;
- this._moveByOffsetFromUserEvent(this.getRowCount() - this.currentIndex - 1, event);
- this._mayReverse = false;
- ]]>
- </handler>
- <handler event="keypress" keycode="VK_PAGE_UP" modifiers="control shift any"
- group="system">
- <![CDATA[
- this._mayReverse = true;
- this._moveByOffsetFromUserEvent(this.scrollOnePage(-1), event);
- this._mayReverse = false;
- ]]>
- </handler>
- <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="control shift any"
- group="system">
- <![CDATA[
- this._mayReverse = true;
- this._moveByOffsetFromUserEvent(this.scrollOnePage(1), event);
- this._mayReverse = false;
- ]]>
- </handler>
- <handler event="keypress" key=" " modifiers="control" phase="target">
- <![CDATA[
- if (this.currentItem && this.selType == "multiple")
- this.toggleItemSelection(this.currentItem);
- ]]>
- </handler>
- <handler event="focus">
- <![CDATA[
- if (this.getRowCount() > 0) {
- if (this.currentIndex == -1) {
- this.currentIndex = this.getIndexOfFirstVisibleRow();
- } else {
- this.currentItem._fireEvent("DOMMenuItemActive");
- }
- }
- this._lastKeyTime = 0;
- ]]>
- </handler>
- <handler event="keypress" phase="target">
- <![CDATA[
- if (this.disableKeyNavigation || !event.charCode ||
- event.altKey || event.ctrlKey || event.metaKey)
- return;
-
- if (event.timeStamp - this._lastKeyTime > 1000)
- this._incrementalString = "";
-
- var key = String.fromCharCode(event.charCode).toLowerCase();
- this._incrementalString += key;
- this._lastKeyTime = event.timeStamp;
-
- // If all letters in the incremental string are the same, just
- // try to match the first one
- var incrementalString = /^(.)\1+$/.test(this._incrementalString) ?
- RegExp.$1 : this._incrementalString;
- var length = incrementalString.length;
-
- var rowCount = this.getRowCount();
- var l = this.selectedItems.length;
- var start = l > 0 ? this.getIndexOfItem(this.selectedItems[l - 1]) : -1;
- // start from the first element if none was selected or from the one
- // following the selected one if it's a new or a repeated-letter search
- if (start == -1 || length == 1)
- start++;
-
- for (var i = 0; i < rowCount; i++) {
- var k = (start + i) % rowCount;
- var listitem = this.getItemAtIndex(k);
- if (!this._canUserSelect(listitem))
- continue;
- // allow richlistitems to specify the string being searched for
- var searchText = "searchLabel" in listitem ? listitem.searchLabel :
- listitem.getAttribute("label"); // (see also bug 250123)
- searchText = searchText.substring(0, length).toLowerCase();
- if (searchText == incrementalString) {
- this.ensureIndexIsVisible(k);
- this.timedSelect(listitem, this._selectDelay);
- break;
- }
- }
- ]]>
- </handler>
- </handlers>
- </binding>
-
- <binding id="listitem"
- extends="chrome://global/content/bindings/general.xml#basetext">
- <implementation implements="nsIDOMXULSelectControlItemElement">
- <property name="current" onget="return this.getAttribute('current') == 'true';">
- <setter><![CDATA[
- if (val)
- this.setAttribute("current", "true");
- else
- this.removeAttribute("current");
-
- let control = this.control;
- if (!control || !control.suppressMenuItemEvent) {
- this._fireEvent(val ? "DOMMenuItemActive" : "DOMMenuItemInactive");
- }
-
- return val;
- ]]></setter>
- </property>
-
- <!-- ///////////////// nsIDOMXULSelectControlItemElement ///////////////// -->
-
- <property name="value" onget="return this.getAttribute('value');"
- onset="this.setAttribute('value', val); return val;"/>
-
- <property name="selected" onget="return this.getAttribute('selected') == 'true';">
- <setter><![CDATA[
- if (val)
- this.setAttribute("selected", "true");
- else
- this.removeAttribute("selected");
-
- return val;
- ]]></setter>
- </property>
-
- <property name="control">
- <getter><![CDATA[
- var parent = this.parentNode;
- while (parent) {
- if (parent instanceof Ci.nsIDOMXULSelectControlElement)
- return parent;
- parent = parent.parentNode;
- }
- return null;
- ]]></getter>
- </property>
-
- <method name="_fireEvent">
- <parameter name="name"/>
- <body>
- <![CDATA[
- var event = document.createEvent("Events");
- event.initEvent(name, true, true);
- this.dispatchEvent(event);
- ]]>
- </body>
- </method>
- </implementation>
- <handlers>
- <!-- If there is no modifier key, we select on mousedown, not
- click, so that drags work correctly. -->
- <handler event="mousedown">
- <![CDATA[
- var control = this.control;
- if (!control || control.disabled)
- return;
- if ((!event.ctrlKey || (/Mac/.test(navigator.platform) && event.button == 2)) &&
- !event.shiftKey && !event.metaKey) {
- if (!this.selected) {
- control.selectItem(this);
- }
- control.currentItem = this;
- }
- ]]>
- </handler>
-
- <!-- On a click (up+down on the same item), deselect everything
- except this item. -->
- <handler event="click" button="0">
- <![CDATA[
- var control = this.control;
- if (!control || control.disabled)
- return;
- control._userSelecting = true;
- if (control.selType != "multiple") {
- control.selectItem(this);
- } else if (event.ctrlKey || event.metaKey) {
- control.toggleItemSelection(this);
- control.currentItem = this;
- } else if (event.shiftKey) {
- control.selectItemRange(null, this);
- control.currentItem = this;
- } else {
- /* We want to deselect all the selected items except what was
- clicked, UNLESS it was a right-click. We have to do this
- in click rather than mousedown so that you can drag a
- selected group of items */
-
- // use selectItemRange instead of selectItem, because this
- // doesn't de- and reselect this item if it is selected
- control.selectItemRange(this, this);
- }
- control._userSelecting = false;
- ]]>
- </handler>
- </handlers>
- </binding>
-</bindings>
--- a/toolkit/content/widgets/richlistbox.xml
+++ b/toolkit/content/widgets/richlistbox.xml
@@ -1,40 +1,42 @@
<?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/. -->
+<!-- This file relies on these specific Chrome/XBL globals -->
+<!-- globals ChromeNodeList -->
+
<bindings id="richlistboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="richlistbox"
- extends="chrome://global/content/bindings/listbox.xml#listbox-base">
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
<content>
<children includes="listheader"/>
<xul:scrollbox allowevents="true" orient="vertical" anonid="main-box"
flex="1" style="overflow: auto;" xbl:inherits="dir,pack">
<children/>
</xul:scrollbox>
</content>
- <implementation>
+ <implementation implements="nsIDOMXULMultiSelectControlElement">
<field name="_scrollbox">
document.getAnonymousElementByAttribute(this, "anonid", "main-box");
</field>
<constructor>
<![CDATA[
this._refreshSelection();
]]>
</constructor>
- <!-- Overriding baselistbox -->
<method name="_fireOnSelect">
<body>
<![CDATA[
// make sure not to modify last-selected when suppressing select events
// (otherwise we'll lose the selection when a template gets rebuilt)
if (this._suppressOnSelect || this.suppressOnSelect)
return;
@@ -129,44 +131,354 @@
item.appendChild(label);
this.appendChild(item);
return item;
</body>
</method>
+ <!-- nsIDOMXULSelectControlElement -->
+ <property name="selectedItem"
+ onset="this.selectItem(val);">
+ <getter>
+ <![CDATA[
+ return this.selectedItems.length > 0 ? this.selectedItems[0] : null;
+ ]]>
+ </getter>
+ </property>
+
+ <!-- nsIDOMXULSelectControlElement -->
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ if (this.selectedItems.length > 0)
+ return this.getIndexOfItem(this.selectedItems[0]);
+ return -1;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val >= 0) {
+ // This is a micro-optimization so that a call to getIndexOfItem or
+ // getItemAtIndex caused by _fireOnSelect (especially for derived
+ // widgets) won't loop the children.
+ this._selecting = {
+ item: this.getItemAtIndex(val),
+ index: val
+ };
+ this.selectItem(this._selecting.item);
+ delete this._selecting;
+ } else {
+ this.clearSelection();
+ this.currentItem = null;
+ }
+ ]]>
+ </setter>
+ </property>
+
+ <!-- nsIDOMXULSelectControlElement -->
+ <property name="value">
+ <getter>
+ <![CDATA[
+ if (this.selectedItems.length > 0)
+ return this.selectedItem.value;
+ return null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var kids = this.getElementsByAttribute("value", val);
+ if (kids && kids.item(0))
+ this.selectItem(kids[0]);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <!-- nsIDOMXULSelectControlElement -->
<property name="itemCount" readonly="true"
onget="return this.children.length"/>
+ <!-- nsIDOMXULSelectControlElement -->
<method name="getIndexOfItem">
<parameter name="aItem"/>
<body>
<![CDATA[
// don't search the children, if we're looking for none of them
if (aItem == null)
return -1;
if (this._selecting && this._selecting.item == aItem)
return this._selecting.index;
return this.children.indexOf(aItem);
]]>
</body>
</method>
+ <!-- nsIDOMXULSelectControlElement -->
<method name="getItemAtIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
if (this._selecting && this._selecting.index == aIndex)
return this._selecting.item;
return this.children[aIndex] || null;
]]>
</body>
</method>
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <property name="selType"
+ onget="return this.getAttribute('seltype');"
+ onset="this.setAttribute('seltype', val); return val;"/>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <property name="currentItem" onget="return this._currentItem;">
+ <setter>
+ if (this._currentItem == val)
+ return val;
+
+ if (this._currentItem)
+ this._currentItem.current = false;
+ this._currentItem = val;
+
+ if (val)
+ val.current = true;
+
+ return val;
+ </setter>
+ </property>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <property name="currentIndex">
+ <getter>
+ return this.currentItem ? this.getIndexOfItem(this.currentItem) : -1;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val >= 0)
+ this.currentItem = this.getItemAtIndex(val);
+ else
+ this.currentItem = null;
+ ]]>
+ </setter>
+ </property>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <field name="selectedItems">new ChromeNodeList()</field>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="addItemToSelection">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (this.selType != "multiple" && this.selectedCount)
+ return;
+
+ if (aItem.selected)
+ return;
+
+ this.selectedItems.append(aItem);
+ aItem.selected = true;
+
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="removeItemFromSelection">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (!aItem.selected)
+ return;
+
+ this.selectedItems.remove(aItem);
+ aItem.selected = false;
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="toggleItemSelection">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (aItem.selected)
+ this.removeItemFromSelection(aItem);
+ else
+ this.addItemToSelection(aItem);
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="selectItem">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (!aItem)
+ return;
+
+ if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem)
+ return;
+
+ this._selectionStart = null;
+
+ var suppress = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ this.clearSelection();
+ this.addItemToSelection(aItem);
+ this.currentItem = aItem;
+
+ this._suppressOnSelect = suppress;
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="selectItemRange">
+ <parameter name="aStartItem"/>
+ <parameter name="aEndItem"/>
+ <body>
+ <![CDATA[
+ if (this.selType != "multiple")
+ return;
+
+ if (!aStartItem)
+ aStartItem = this._selectionStart ?
+ this._selectionStart : this.currentItem;
+
+ if (!aStartItem)
+ aStartItem = aEndItem;
+
+ var suppressSelect = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ this._selectionStart = aStartItem;
+
+ var currentItem;
+ var startIndex = this.getIndexOfItem(aStartItem);
+ var endIndex = this.getIndexOfItem(aEndItem);
+ if (endIndex < startIndex) {
+ currentItem = aEndItem;
+ aEndItem = aStartItem;
+ aStartItem = currentItem;
+ } else {
+ currentItem = aStartItem;
+ }
+
+ while (currentItem) {
+ this.addItemToSelection(currentItem);
+ if (currentItem == aEndItem) {
+ currentItem = this.getNextItem(currentItem, 1);
+ break;
+ }
+ currentItem = this.getNextItem(currentItem, 1);
+ }
+
+ // Clear around new selection
+ // Don't use clearSelection() because it causes a lot of noise
+ // with respect to selection removed notifications used by the
+ // accessibility API support.
+ var userSelecting = this._userSelecting;
+ this._userSelecting = false; // that's US automatically unselecting
+ for (; currentItem; currentItem = this.getNextItem(currentItem, 1))
+ this.removeItemFromSelection(currentItem);
+
+ for (currentItem = this.getItemAtIndex(0); currentItem != aStartItem;
+ currentItem = this.getNextItem(currentItem, 1))
+ this.removeItemFromSelection(currentItem);
+ this._userSelecting = userSelecting;
+
+ this._suppressOnSelect = suppressSelect;
+
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="selectAll">
+ <body>
+ this._selectionStart = null;
+
+ var suppress = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ var item = this.getItemAtIndex(0);
+ while (item) {
+ this.addItemToSelection(item);
+ item = this.getNextItem(item, 1);
+ }
+
+ this._suppressOnSelect = suppress;
+ this._fireOnSelect();
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="invertSelection">
+ <body>
+ this._selectionStart = null;
+
+ var suppress = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ var item = this.getItemAtIndex(0);
+ while (item) {
+ if (item.selected)
+ this.removeItemFromSelection(item);
+ else
+ this.addItemToSelection(item);
+ item = this.getNextItem(item, 1);
+ }
+
+ this._suppressOnSelect = suppress;
+ this._fireOnSelect();
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="clearSelection">
+ <body>
+ <![CDATA[
+ if (this.selectedItems) {
+ while (this.selectedItems.length > 0) {
+ let item = this.selectedItems[0];
+ item.selected = false;
+ this.selectedItems.remove(item);
+ }
+ }
+
+ this._selectionStart = null;
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <property name="selectedCount" readonly="true"
+ onget="return this.selectedItems.length;"/>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <method name="getSelectedItem">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return aIndex < this.selectedItems.length ?
+ this.selectedItems[aIndex] : null;
+ ]]>
+ </body>
+ </method>
+
<method name="ensureIndexIsVisible">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
]]>
</body>
</method>
@@ -250,17 +562,16 @@
index = ix;
}
return index != this.currentIndex ? index - this.currentIndex : aDirection;
]]>
</body>
</method>
- <!-- richlistbox specific -->
<property name="children" readonly="true">
<getter>
<![CDATA[
let iface = Ci.nsIDOMXULSelectControlItemElement;
let children = Array.from(this.childNodes)
.filter(node => node instanceof iface);
if (this.dir == "reverse" && this._mayReverse) {
children.reverse();
@@ -369,30 +680,224 @@
// Partially visible items are also considered visible
return (aItem.boxObject.y + aItem.boxObject.height > y) &&
(aItem.boxObject.y < y + this._scrollbox.boxObject.height);
]]>
</body>
</method>
+ <property name="disableKeyNavigation"
+ onget="return this.hasAttribute('disableKeyNavigation');">
+ <setter>
+ if (val)
+ this.setAttribute("disableKeyNavigation", "true");
+ else
+ this.removeAttribute("disableKeyNavigation");
+ return val;
+ </setter>
+ </property>
+
+ <property name="suppressOnSelect"
+ onget="return this.getAttribute('suppressonselect') == 'true';"
+ onset="this.setAttribute('suppressonselect', val);"/>
+
+ <property name="_selectDelay"
+ onset="this.setAttribute('_selectDelay', val);"
+ onget="return this.getAttribute('_selectDelay') || 50;"/>
+
+ <method name="_moveByOffsetFromUserEvent">
+ <parameter name="aOffset"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (!aEvent.defaultPrevented) {
+ this._userSelecting = true;
+ this._mayReverse = true;
+ this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey);
+ this._userSelecting = false;
+ this._mayReverse = false;
+ aEvent.preventDefault();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_canUserSelect">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ var style = document.defaultView.getComputedStyle(aItem);
+ return style.display != "none" && style.visibility == "visible";
+ ]]>
+ </body>
+ </method>
+
+ <method name="_selectTimeoutHandler">
+ <parameter name="aMe"/>
+ <body>
+ aMe._fireOnSelect();
+ aMe._selectTimeout = null;
+ </body>
+ </method>
+
+ <method name="timedSelect">
+ <parameter name="aItem"/>
+ <parameter name="aTimeout"/>
+ <body>
+ <![CDATA[
+ var suppress = this._suppressOnSelect;
+ if (aTimeout != -1)
+ this._suppressOnSelect = true;
+
+ this.selectItem(aItem);
+
+ this._suppressOnSelect = suppress;
+
+ if (aTimeout != -1) {
+ if (this._selectTimeout)
+ window.clearTimeout(this._selectTimeout);
+ this._selectTimeout =
+ window.setTimeout(this._selectTimeoutHandler, aTimeout, this);
+ }
+ ]]>
+ </body>
+ </method>
+
<field name="_currentIndex">null</field>
+ <field name="_lastKeyTime">0</field>
+ <field name="_incrementalString">""</field>
+ <field name="_suppressOnSelect">false</field>
+ <field name="_userSelecting">false</field>
+ <field name="_mayReverse">false</field>
+ <field name="_selectTimeout">null</field>
+ <field name="_currentItem">null</field>
+ <field name="_selectionStart">null</field>
<!-- For backwards-compatibility and for convenience.
Use ensureElementIsVisible instead -->
<method name="ensureSelectedElementIsVisible">
<body>
<![CDATA[
return this.ensureElementIsVisible(this.selectedItem);
]]>
</body>
</method>
</implementation>
<handlers>
+ <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
+ action="this._moveByOffsetFromUserEvent(-1, event);"
+ group="system"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
+ action="this._moveByOffsetFromUserEvent(1, event);"
+ group="system"/>
+
+ <handler event="keypress" keycode="VK_HOME" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(-this.currentIndex, event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+
+ <handler event="keypress" keycode="VK_END" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(this.getRowCount() - this.currentIndex - 1, event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(this.scrollOnePage(-1), event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(this.scrollOnePage(1), event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+
+ <handler event="keypress" key=" " modifiers="control" phase="target">
+ <![CDATA[
+ if (this.currentItem && this.selType == "multiple")
+ this.toggleItemSelection(this.currentItem);
+ ]]>
+ </handler>
+
+ <handler event="focus">
+ <![CDATA[
+ if (this.getRowCount() > 0) {
+ if (this.currentIndex == -1) {
+ this.currentIndex = this.getIndexOfFirstVisibleRow();
+ } else {
+ this.currentItem._fireEvent("DOMMenuItemActive");
+ }
+ }
+ this._lastKeyTime = 0;
+ ]]>
+ </handler>
+
+ <handler event="keypress" phase="target">
+ <![CDATA[
+ if (this.disableKeyNavigation || !event.charCode ||
+ event.altKey || event.ctrlKey || event.metaKey)
+ return;
+
+ if (event.timeStamp - this._lastKeyTime > 1000)
+ this._incrementalString = "";
+
+ var key = String.fromCharCode(event.charCode).toLowerCase();
+ this._incrementalString += key;
+ this._lastKeyTime = event.timeStamp;
+
+ // If all letters in the incremental string are the same, just
+ // try to match the first one
+ var incrementalString = /^(.)\1+$/.test(this._incrementalString) ?
+ RegExp.$1 : this._incrementalString;
+ var length = incrementalString.length;
+
+ var rowCount = this.getRowCount();
+ var l = this.selectedItems.length;
+ var start = l > 0 ? this.getIndexOfItem(this.selectedItems[l - 1]) : -1;
+ // start from the first element if none was selected or from the one
+ // following the selected one if it's a new or a repeated-letter search
+ if (start == -1 || length == 1)
+ start++;
+
+ for (var i = 0; i < rowCount; i++) {
+ var k = (start + i) % rowCount;
+ var listitem = this.getItemAtIndex(k);
+ if (!this._canUserSelect(listitem))
+ continue;
+ // allow richlistitems to specify the string being searched for
+ var searchText = "searchLabel" in listitem ? listitem.searchLabel :
+ listitem.getAttribute("label"); // (see also bug 250123)
+ searchText = searchText.substring(0, length).toLowerCase();
+ if (searchText == incrementalString) {
+ this.ensureIndexIsVisible(k);
+ this.timedSelect(listitem, this._selectDelay);
+ break;
+ }
+ }
+ ]]>
+ </handler>
+
<handler event="click">
<![CDATA[
// clicking into nothing should unselect
if (event.originalTarget == this._scrollbox) {
this.clearSelection();
this.currentItem = null;
}
]]>
@@ -410,18 +915,18 @@
break;
}
]]>
</handler>
</handlers>
</binding>
<binding id="richlistitem"
- extends="chrome://global/content/bindings/listbox.xml#listitem">
- <implementation>
+ extends="chrome://global/content/bindings/general.xml#basetext">
+ <implementation implements="nsIDOMXULSelectControlItemElement">
<field name="selectedByMouseOver">false</field>
<destructor>
<![CDATA[
var control = this.control;
if (!control)
return;
// When we are destructed and we are current or selected, unselect ourselves
@@ -433,16 +938,17 @@
control.removeItemFromSelection(this);
control._suppressOnSelect = suppressSelect;
}
if (this.current)
control.currentItem = null;
]]>
</destructor>
+ <!-- nsIDOMXULSelectControlItemElement -->
<property name="label" readonly="true">
<!-- Setter purposely not implemented; the getter returns a
concatentation of label text to expose via accessibility APIs -->
<getter>
<![CDATA[
const XULNS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return Array.map(this.getElementsByTagNameNS(XULNS, "label"),
@@ -465,11 +971,116 @@
this.setAttribute("searchlabel", val);
else
// fall back to the label property (default value)
this.removeAttribute("searchlabel");
return val;
]]>
</setter>
</property>
+
+ <!-- nsIDOMXULSelectControlItemElement -->
+ <property name="value" onget="return this.getAttribute('value');"
+ onset="this.setAttribute('value', val); return val;"/>
+
+ <!-- nsIDOMXULSelectControlItemElement -->
+ <property name="selected" onget="return this.getAttribute('selected') == 'true';">
+ <setter><![CDATA[
+ if (val)
+ this.setAttribute("selected", "true");
+ else
+ this.removeAttribute("selected");
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- nsIDOMXULSelectControlItemElement -->
+ <property name="control">
+ <getter><![CDATA[
+ var parent = this.parentNode;
+ while (parent) {
+ if (parent instanceof Ci.nsIDOMXULSelectControlElement)
+ return parent;
+ parent = parent.parentNode;
+ }
+ return null;
+ ]]></getter>
+ </property>
+
+ <property name="current" onget="return this.getAttribute('current') == 'true';">
+ <setter><![CDATA[
+ if (val)
+ this.setAttribute("current", "true");
+ else
+ this.removeAttribute("current");
+
+ let control = this.control;
+ if (!control || !control.suppressMenuItemEvent) {
+ this._fireEvent(val ? "DOMMenuItemActive" : "DOMMenuItemInactive");
+ }
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="_fireEvent">
+ <parameter name="name"/>
+ <body>
+ <![CDATA[
+ var event = document.createEvent("Events");
+ event.initEvent(name, true, true);
+ this.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
</implementation>
+
+ <handlers>
+ <!-- If there is no modifier key, we select on mousedown, not
+ click, so that drags work correctly. -->
+ <handler event="mousedown">
+ <![CDATA[
+ var control = this.control;
+ if (!control || control.disabled)
+ return;
+ if ((!event.ctrlKey || (/Mac/.test(navigator.platform) && event.button == 2)) &&
+ !event.shiftKey && !event.metaKey) {
+ if (!this.selected) {
+ control.selectItem(this);
+ }
+ control.currentItem = this;
+ }
+ ]]>
+ </handler>
+
+ <!-- On a click (up+down on the same item), deselect everything
+ except this item. -->
+ <handler event="click" button="0">
+ <![CDATA[
+ var control = this.control;
+ if (!control || control.disabled)
+ return;
+ control._userSelecting = true;
+ if (control.selType != "multiple") {
+ control.selectItem(this);
+ } else if (event.ctrlKey || event.metaKey) {
+ control.toggleItemSelection(this);
+ control.currentItem = this;
+ } else if (event.shiftKey) {
+ control.selectItemRange(null, this);
+ control.currentItem = this;
+ } else {
+ /* We want to deselect all the selected items except what was
+ clicked, UNLESS it was a right-click. We have to do this
+ in click rather than mousedown so that you can drag a
+ selected group of items */
+
+ // use selectItemRange instead of selectItem, because this
+ // doesn't de- and reselect this item if it is selected
+ control.selectItemRange(this, this);
+ }
+ control._userSelecting = false;
+ ]]>
+ </handler>
+ </handlers>
</binding>
</bindings>