/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
/**
* XUL:richlistbox element.
*/
MozElements.RichListBox =
class RichListBox
extends MozElements.BaseControl {
constructor() {
super();
this.selectedItems =
new ChromeNodeList();
this._currentIndex =
null;
this._lastKeyTime = 0;
this._incrementalString =
"";
this._suppressOnSelect =
false;
this._userSelecting =
false;
this._selectTimeout =
null;
this._currentItem =
null;
this._selectionStart =
null;
this.addEventListener(
"keypress",
event => {
if (event.altKey || event.metaKey) {
return;
}
switch (event.keyCode) {
case KeyEvent.DOM_VK_UP:
this._moveByOffsetFromUserEvent(-1, event);
break;
case KeyEvent.DOM_VK_DOWN:
this._moveByOffsetFromUserEvent(1, event);
break;
case KeyEvent.DOM_VK_HOME:
this._moveByOffsetFromUserEvent(-
this.currentIndex, event);
break;
case KeyEvent.DOM_VK_END:
this._moveByOffsetFromUserEvent(
this.getRowCount() -
this.currentIndex - 1,
event
);
break;
case KeyEvent.DOM_VK_PAGE_UP:
this._moveByOffsetFromUserEvent(
this.scrollOnePage(-1), event);
break;
case KeyEvent.DOM_VK_PAGE_DOWN:
this._moveByOffsetFromUserEvent(
this.scrollOnePage(1), event);
break;
}
},
{ mozSystemGroup:
true }
);
this.addEventListener(
"keypress", event => {
if (event.target !=
this) {
return;
}
if (
event.key ==
" " &&
event.ctrlKey &&
!event.shiftKey &&
!event.altKey &&
!event.metaKey &&
this.currentItem &&
this.selType ==
"multiple"
) {
this.toggleItemSelection(
this.currentItem);
}
if (!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;
}
}
});
this.addEventListener(
"focus", () => {
if (
this.getRowCount() > 0) {
if (
this.currentIndex == -1) {
this.currentIndex =
this.getIndexOfFirstVisibleRow();
let currentItem =
this.getItemAtIndex(
this.currentIndex);
if (currentItem) {
this.selectItem(currentItem);
}
}
else {
this._fireEvent(
this.currentItem,
"DOMMenuItemActive");
}
}
this._lastKeyTime = 0;
});
this.addEventListener(
"click", event => {
// clicking into nothing should unselect multiple selections
if (event.originalTarget ==
this &&
this.selType ==
"multiple") {
this.clearSelection();
this.currentItem =
null;
}
});
this.addEventListener(
"MozSwipeGesture", event => {
// Only handle swipe gestures up and down
switch (event.direction) {
case event.DIRECTION_DOWN:
this.scrollTop =
this.scrollHeight;
break;
case event.DIRECTION_UP:
this.scrollTop = 0;
break;
}
});
}
connectedCallback() {
if (
this.delayConnectedCallback()) {
return;
}
this.setAttribute(
"allowevents",
"true");
this._refreshSelection();
}
// nsIDOMXULSelectControlElement
set selectedItem(val) {
this.selectItem(val);
}
get selectedItem() {
return this.selectedItems.length ?
this.selectedItems[0] :
null;
}
// nsIDOMXULSelectControlElement
set selectedIndex(val) {
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;
}
}
get selectedIndex() {
if (
this.selectedItems.length) {
return this.getIndexOfItem(
this.selectedItems[0]);
}
return -1;
}
// nsIDOMXULSelectControlElement
set value(val) {
var kids =
this.getElementsByAttribute(
"value", val);
if (kids && kids.item(0)) {
this.selectItem(kids[0]);
}
}
get value() {
if (
this.selectedItems.length) {
return this.selectedItem.value;
}
return null;
}
// nsIDOMXULSelectControlElement
get itemCount() {
return this.itemChildren.length;
}
// nsIDOMXULSelectControlElement
set selType(val) {
this.setAttribute(
"seltype", val);
}
get selType() {
return this.getAttribute(
"seltype") ||
"";
}
// nsIDOMXULSelectControlElement
set currentItem(val) {
if (
this._currentItem == val) {
return;
}
if (
this._currentItem) {
this._currentItem.current =
false;
if (!val && !
this.suppressMenuItemEvent) {
// An item is losing focus and there is no new item to focus.
// Notify a11y that there is no focused item.
this._fireEvent(
this._currentItem,
"DOMMenuItemInactive");
}
}
this._currentItem = val;
if (val) {
val.current =
true;
if (!
this.suppressMenuItemEvent) {
// Notify a11y that this item got focus.
this._fireEvent(val,
"DOMMenuItemActive");
}
}
}
get currentItem() {
return this._currentItem;
}
// nsIDOMXULSelectControlElement
set currentIndex(val) {
if (val >= 0) {
this.currentItem =
this.getItemAtIndex(val);
}
else {
this.currentItem =
null;
}
}
get currentIndex() {
return this.currentItem ?
this.getIndexOfItem(
this.currentItem) : -1;
}
// nsIDOMXULSelectControlElement
get selectedCount() {
return this.selectedItems.length;
}
get itemChildren() {
let children = Array.from(
this.children).filter(
node => node.localName ==
"richlistitem"
);
return children;
}
set suppressOnSelect(val) {
this.setAttribute(
"suppressonselect", val);
}
get suppressOnSelect() {
return this.getAttribute(
"suppressonselect") ==
"true";
}
set _selectDelay(val) {
this.setAttribute(
"_selectDelay", val);
}
get _selectDelay() {
return this.getAttribute(
"_selectDelay") || 50;
}
_fireOnSelect() {
// 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;
}
// remember the current item and all selected items with IDs
var state =
this.currentItem ?
this.currentItem.id :
"";
if (
this.selType ==
"multiple" &&
this.selectedCount) {
let getId =
function getId(aItem) {
return aItem.id;
};
state +=
" " + [...
this.selectedItems].filter(getId).map(getId).join(
" ");
}
if (state) {
this.setAttribute(
"last-selected", state);
}
else {
this.removeAttribute(
"last-selected");
}
// preserve the index just in case no IDs are available
if (
this.currentIndex > -1) {
this._currentIndex =
this.currentIndex + 1;
}
var event = document.createEvent(
"Events");
event.initEvent(
"select",
true,
true);
this.dispatchEvent(event);
// always call this (allows a commandupdater without controller)
document.commandDispatcher.updateCommands(
"richlistbox-select");
}
getNextItem(aStartItem, aDelta) {
while (aStartItem) {
aStartItem = aStartItem.nextSibling;
if (
aStartItem &&
aStartItem.localName ==
"richlistitem" &&
(!
this._userSelecting ||
this.canUserSelect(aStartItem))
) {
--aDelta;
if (aDelta == 0) {
return aStartItem;
}
}
}
return null;
}
getPreviousItem(aStartItem, aDelta) {
while (aStartItem) {
aStartItem = aStartItem.previousSibling;
if (
aStartItem &&
aStartItem.localName ==
"richlistitem" &&
(!
this._userSelecting ||
this.canUserSelect(aStartItem))
) {
--aDelta;
if (aDelta == 0) {
return aStartItem;
}
}
}
return null;
}
appendItem(aLabel, aValue) {
var item =
this.ownerDocument.createXULElement(
"richlistitem");
item.setAttribute(
"value", aValue);
var label =
this.ownerDocument.createXULElement(
"label");
label.setAttribute(
"value", aLabel);
label.setAttribute(
"flex",
"1");
label.setAttribute(
"crop",
"end");
item.appendChild(label);
this.appendChild(item);
return item;
}
// nsIDOMXULSelectControlElement
getIndexOfItem(aItem) {
// 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.itemChildren.indexOf(aItem);
}
// nsIDOMXULSelectControlElement
getItemAtIndex(aIndex) {
if (
this._selecting &&
this._selecting.index == aIndex) {
return this._selecting.item;
}
return this.itemChildren[aIndex] ||
null;
}
// nsIDOMXULMultiSelectControlElement
addItemToSelection(aItem) {
if (
this.selType !=
"multiple" &&
this.selectedCount) {
return;
}
if (aItem.selected) {
return;
}
this.selectedItems.append(aItem);
aItem.selected =
true;
this._fireOnSelect();
}
// nsIDOMXULMultiSelectControlElement
removeItemFromSelection(aItem) {
if (!aItem.selected) {
return;
}
this.selectedItems.remove(aItem);
aItem.selected =
false;
this._fireOnSelect();
}
// nsIDOMXULMultiSelectControlElement
toggleItemSelection(aItem) {
if (aItem.selected) {
this.removeItemFromSelection(aItem);
}
else {
this.addItemToSelection(aItem);
}
}
// nsIDOMXULMultiSelectControlElement
selectItem(aItem) {
if (!aItem || aItem.disabled) {
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();
}
// nsIDOMXULMultiSelectControlElement
selectItemRange(aStartItem, aEndItem) {
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();
}
// nsIDOMXULMultiSelectControlElement
selectAll() {
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();
}
// nsIDOMXULMultiSelectControlElement
clearSelection() {
if (
this.selectedItems) {
while (
this.selectedItems.length) {
let item =
this.selectedItems[0];
item.selected =
false;
this.selectedItems.remove(item);
}
}
this._selectionStart =
null;
this._fireOnSelect();
}
// nsIDOMXULMultiSelectControlElement
getSelectedItem(aIndex) {
return aIndex <
this.selectedItems.length
?
this.selectedItems[aIndex]
:
null;
}
ensureIndexIsVisible(aIndex) {
return this.ensureElementIsVisible(
this.getItemAtIndex(aIndex));
}
ensureElementIsVisible(aElement, aAlignToTop) {
if (!aElement) {
return;
}
// These calculations assume that there is no padding on the
// "richlistbox" element, although there might be a margin.
var targetRect = aElement.getBoundingClientRect();
var scrollRect =
this.getBoundingClientRect();
var offset = targetRect.top - scrollRect.top;
if (!aAlignToTop && offset >= 0) {
// scrollRect.bottom wouldn't take a horizontal scroll bar into account
let scrollRectBottom = scrollRect.top +
this.clientHeight;
offset = targetRect.bottom - scrollRectBottom;
if (offset <= 0) {
return;
}
}
this.scrollTop += offset;
}
getIndexOfFirstVisibleRow() {
var children =
this.itemChildren;
for (
var ix = 0; ix < children.length; ix++) {
if (
this._isItemVisible(children[ix])) {
return ix;
}
}
return -1;
}
getRowCount() {
return this.itemChildren.length;
}
scrollOnePage(aDirection) {
var children =
this.itemChildren;
if (!children.length) {
return 0;
}
// If nothing is selected, we just select the first element
// at the extreme we're moving away from
if (!
this.currentItem) {
return aDirection == -1 ? children.length : 0;
}
// If the current item is visible, scroll by one page so that
// the new current item is at approximately the same position as
// the existing current item.
let height =
this.getBoundingClientRect().height;
if (
this._isItemVisible(
this.currentItem)) {
this.scrollBy(0, height * aDirection);
}
// Figure out, how many items fully fit into the view port
// (including the currently selected one), and determine
// the index of the first one lying (partially) outside
let currentItemRect =
this.currentItem.getBoundingClientRect();
var startBorder = currentItemRect.y;
if (aDirection == -1) {
startBorder += currentItemRect.height;
}
var index =
this.currentIndex;
for (
var ix = index; 0 <= ix && ix < children.length; ix += aDirection) {
let childRect = children[ix].getBoundingClientRect();
if (childRect.height == 0) {
continue;
// hidden children have a y of 0
}
var endBorder = childRect.y + (aDirection == -1 ? childRect.height : 0);
if ((endBorder - startBorder) * aDirection > height) {
break;
// we've reached the desired distance
}
index = ix;
}
return index !=
this.currentIndex
? index -
this.currentIndex
: aDirection;
}
_refreshSelection() {
// when this method is called, we know that either the currentItem
// and selectedItems we have are null (ctor) or a reference to an
// element no longer in the DOM (template).
// first look for the last-selected attribute
var state =
this.getAttribute(
"last-selected");
if (state) {
var ids = state.split(
" ");
var suppressSelect =
this._suppressOnSelect;
this._suppressOnSelect =
true;
this.clearSelection();
for (let i = 1; i < ids.length; i++) {
var selectedItem = document.getElementById(ids[i]);
if (selectedItem) {
this.addItemToSelection(selectedItem);
}
}
var currentItem = document.getElementById(ids[0]);
if (!currentItem &&
this._currentIndex) {
currentItem =
this.getItemAtIndex(
Math.min(
this._currentIndex - 1,
this.getRowCount())
);
}
if (currentItem) {
this.currentItem = currentItem;
if (
this.selType !=
"multiple" &&
this.selectedCount == 0) {
this.selectedItem = currentItem;
}
if (
this.getBoundingClientRect().height) {
this.ensureElementIsVisible(currentItem);
}
else {
// XXX hack around a bug in ensureElementIsVisible as it will
// scroll beyond the last element, bug 493645.
this.ensureElementIsVisible(currentItem.previousElementSibling);
}
}
this._suppressOnSelect = suppressSelect;
// XXX actually it's just a refresh, but at least
// the Extensions manager expects this:
this._fireOnSelect();
return;
}
// try to restore the selected items according to their IDs
// (applies after a template rebuild, if last-selected was not set)
if (
this.selectedItems) {
let itemIds = [];
for (let i =
this.selectedCount - 1; i >= 0; i--) {
let selectedItem =
this.selectedItems[i];
itemIds.push(selectedItem.id);
this.selectedItems.remove(selectedItem);
}
for (let i = 0; i < itemIds.length; i++) {
let selectedItem = document.getElementById(itemIds[i]);
if (selectedItem) {
this.selectedItems.append(selectedItem);
}
}
}
if (
this.currentItem &&
this.currentItem.id) {
this.currentItem = document.getElementById(
this.currentItem.id);
}
else {
this.currentItem =
null;
}
// if we have no previously current item or if the above check fails to
// find the previous nodes (which causes it to clear selection)
if (!
this.currentItem &&
this.selectedCount == 0) {
this.currentIndex =
this._currentIndex ?
this._currentIndex - 1 : 0;
// cf. listbox constructor:
// select items according to their attributes
var children =
this.itemChildren;
for (let i = 0; i < children.length; ++i) {
if (children[i].getAttribute(
"selected") ==
"true") {
this.selectedItems.append(children[i]);
}
}
}
if (
this.selType !=
"multiple" &&
this.selectedCount == 0) {
this.selectedItem =
this.currentItem;
}
}
_isItemVisible(aItem) {
if (!aItem) {
return false;
}
var y =
this.getBoundingClientRect().y;
// Partially visible items are also considered visible
let itemRect = aItem.getBoundingClientRect();
return (
itemRect.y + itemRect.height > y && itemRect.y < y +
this.clientHeight
);
}
moveByOffset(aOffset, aIsSelecting, aIsSelectingRange, aEvent) {
if ((aIsSelectingRange || !aIsSelecting) &&
this.selType !=
"multiple") {
return;
}
var newIndex =
this.currentIndex + aOffset;
if (newIndex < 0) {
newIndex = 0;
}
var numItems =
this.getRowCount();
if (newIndex > numItems - 1) {
newIndex = numItems - 1;
}
var newItem =
this.getItemAtIndex(newIndex);
// make sure that the item is actually visible/selectable
if (
this._userSelecting && newItem && !
this.canUserSelect(newItem)) {
newItem =
aOffset > 0
?
this.getNextItem(newItem, 1) ||
this.getPreviousItem(newItem, 1)
:
this.getPreviousItem(newItem, 1) ||
this.getNextItem(newItem, 1);
}
if (newItem) {
let hadFocus =
this.currentItem.contains(document.activeElement);
this.ensureIndexIsVisible(
this.getIndexOfItem(newItem));
if (aIsSelectingRange) {
this.selectItemRange(
null, newItem);
}
else if (aIsSelecting) {
this.selectItem(newItem);
}
if (hadFocus) {
let flags =
Services.focus[
aEvent.type.startsWith(
"key") ?
"FLAG_BYKEY" :
"FLAG_BYJS"
];
Services.focus.moveFocus(
window,
newItem,
Services.focus.MOVEFOCUS_FIRST,
flags
);
}
this.currentItem = newItem;
}
}
_moveByOffsetFromUserEvent(aOffset, aEvent) {
if (!aEvent.defaultPrevented) {
this._userSelecting =
true;
this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey, aEvent);
this._userSelecting =
false;
aEvent.preventDefault();
}
}
canUserSelect(aItem) {
if (aItem.disabled) {
return false;
}
var style = document.defaultView.getComputedStyle(aItem);
return (
style.display !=
"none" &&
style.visibility ==
"visible" &&
style.MozUserInput !=
"none"
);
}
_selectTimeoutHandler(aMe) {
aMe._fireOnSelect();
aMe._selectTimeout =
null;
}
timedSelect(aItem, aTimeout) {
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
);
}
}
/**
* For backwards-compatibility and for convenience.
* Use ensureElementIsVisible instead
*/
ensureSelectedElementIsVisible() {
return this.ensureElementIsVisible(
this.selectedItem);
}
_fireEvent(aTarget, aName) {
let event = document.createEvent(
"Events");
event.initEvent(aName,
true,
true);
aTarget.dispatchEvent(event);
}
};
MozXULElement.implementCustomInterface(MozElements.RichListBox, [
Ci.nsIDOMXULSelectControlElement,
Ci.nsIDOMXULMultiSelectControlElement,
]);
customElements.define(
"richlistbox", MozElements.RichListBox);
/**
* XUL:richlistitem element.
*/
MozElements.MozRichlistitem =
class MozRichlistitem
extends (
MozElements.BaseText
) {
constructor() {
super();
this.selectedByMouseOver =
false;
/**
* If there is no modifier key, we select on mousedown, not
* click, so that drags work correctly.
*/
this.addEventListener(
"mousedown", event => {
var control =
this.control;
if (!control ||
this.disabled || control.disabled) {
return;
}
if (
(!event.ctrlKey ||
(AppConstants.platform ==
"macosx" && event.button == 2)) &&
!event.shiftKey &&
!event.metaKey
) {
if (!
this.selected) {
control.selectItem(
this);
}
control.currentItem =
this;
}
});
/**
* On a click (up+down on the same item), deselect everything
* except this item.
*/
this.addEventListener(
"click", event => {
if (event.button != 0) {
return;
}
var control =
this.control;
if (!control ||
this.disabled || 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;
});
}
connectedCallback() {
this._updateInnerControlsForSelection(
this.selected);
}
/**
* nsIDOMXULSelectControlItemElement
*/
get label() {
const XUL_NS =
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
return Array.from(
this.getElementsByTagNameNS(XUL_NS,
"label"),
label => label.value
).join(
" ");
}
set searchLabel(val) {
if (val !==
null) {
this.setAttribute(
"searchlabel", val);
}
// fall back to the label property (default value)
else {
this.removeAttribute(
"searchlabel");
}
}
get searchLabel() {
return this.hasAttribute(
"searchlabel")
?
this.getAttribute(
"searchlabel")
:
this.label;
}
/**
* nsIDOMXULSelectControlItemElement
*/
set value(val) {
this.setAttribute(
"value", val);
}
get value() {
return this.getAttribute(
"value") ||
"";
}
/**
* nsIDOMXULSelectControlItemElement
*/
set selected(val) {
if (val) {
this.setAttribute(
"selected",
"true");
}
else {
this.removeAttribute(
"selected");
}
this._updateInnerControlsForSelection(val);
}
get selected() {
return this.getAttribute(
"selected") ==
"true";
}
/**
* nsIDOMXULSelectControlItemElement
*/
get control() {
var parent =
this.parentNode;
while (parent) {
if (parent.localName ==
"richlistbox") {
return parent;
}
parent = parent.parentNode;
}
return null;
}
set current(val) {
if (val) {
this.setAttribute(
"current",
"true");
}
else {
this.removeAttribute(
"current");
}
}
get current() {
return this.getAttribute(
"current") ==
"true";
}
_updateInnerControlsForSelection(selected) {
for (let control of
this.querySelectorAll(
"button,menulist")) {
if (!selected && control.tabIndex == 0) {
control.tabIndex = -1;
}
else if (selected && control.tabIndex == -1) {
control.tabIndex = 0;
}
}
}
};
MozXULElement.implementCustomInterface(MozElements.MozRichlistitem, [
Ci.nsIDOMXULSelectControlItemElement,
]);
customElements.define(
"richlistitem", MozElements.MozRichlistitem);
}