This source file includes following definitions.
- m_suggestedIndex
- create
- create
- formControlType
- optionSelectedByUser
- hasPlaceholderLabelOption
- validationMessage
- valueMissing
- listBoxSelectItem
- usesMenuList
- activeSelectionStartListIndex
- activeSelectionEndListIndex
- add
- addBeforeOptionAtIndex
- remove
- value
- setValue
- suggestedValue
- setSuggestedValue
- isPresentationAttribute
- parseAttribute
- shouldShowFocusRingOnMouseFocus
- canSelectAll
- createRenderer
- selectedOptions
- options
- updateListItemSelectedStates
- childrenChanged
- optionElementChildrenChanged
- accessKeyAction
- setMultiple
- setSize
- namedItem
- item
- setOption
- setLength
- isRequiredFormControl
- nextValidIndex
- nextSelectableListIndex
- previousSelectableListIndex
- firstSelectableListIndex
- lastSelectableListIndex
- nextSelectableListIndexPageAway
- selectAll
- saveLastSelection
- setActiveSelectionAnchorIndex
- setActiveSelectionEndIndex
- updateListBoxSelection
- listBoxOnChange
- dispatchInputAndChangeEventForMenuList
- scrollToSelection
- setOptionsChangedOnRenderer
- listItems
- invalidateSelectedItems
- setRecalcListItems
- recalcListItems
- selectedIndex
- setSelectedIndex
- suggestedIndex
- setSuggestedIndex
- optionSelectionStateChanged
- selectOption
- optionToListIndex
- listToOptionIndex
- dispatchFocusEvent
- dispatchBlurEvent
- deselectItemsWithoutValidation
- saveFormControlState
- searchOptionsForValue
- restoreFormControlState
- parseMultipleAttribute
- appendFormData
- resetImpl
- platformHandleKeydownEvent
- menuListDefaultEventHandler
- updateSelectedState
- listBoxDefaultEventHandler
- defaultEventHandler
- lastSelectedListIndex
- indexOfSelectedOption
- optionCount
- optionAtIndex
- typeAheadFind
- insertedInto
- accessKeySetSelectedIndex
- length
- finishParsingChildren
- anonymousIndexedSetter
- isInteractiveContent
- supportsAutofocus
#include "config.h"
#include "core/html/HTMLSelectElement.h"
#include "HTMLNames.h"
#include "bindings/v8/ExceptionMessages.h"
#include "bindings/v8/ExceptionState.h"
#include "bindings/v8/ExceptionStatePlaceholder.h"
#include "core/accessibility/AXObjectCache.h"
#include "core/dom/Attribute.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/NodeTraversal.h"
#include "core/events/GestureEvent.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/MouseEvent.h"
#include "core/frame/LocalFrame.h"
#include "core/html/FormDataList.h"
#include "core/html/HTMLFormElement.h"
#include "core/html/HTMLOptionElement.h"
#include "core/html/forms/FormController.h"
#include "core/page/EventHandler.h"
#include "core/page/SpatialNavigation.h"
#include "core/rendering/RenderListBox.h"
#include "core/rendering/RenderMenuList.h"
#include "core/rendering/RenderTheme.h"
#include "platform/PlatformMouseEvent.h"
#include "platform/text/PlatformLocale.h"
using namespace std;
using namespace WTF::Unicode;
namespace WebCore {
using namespace HTMLNames;
static const unsigned maxSelectItems = 10000;
HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form)
: HTMLFormControlElementWithState(selectTag, document, form)
, m_typeAhead(this)
, m_size(0)
, m_lastOnChangeIndex(-1)
, m_activeSelectionAnchorIndex(-1)
, m_activeSelectionEndIndex(-1)
, m_isProcessingUserDrivenChange(false)
, m_multiple(false)
, m_activeSelectionState(false)
, m_shouldRecalcListItems(false)
, m_suggestedIndex(-1)
{
ScriptWrappable::init(this);
}
PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
{
return adoptRef(new HTMLSelectElement(document, 0));
}
PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
{
return adoptRef(new HTMLSelectElement(document, form));
}
const AtomicString& HTMLSelectElement::formControlType() const
{
DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral));
DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral));
return m_multiple ? selectMultiple : selectOne;
}
void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
{
if (!usesMenuList()) {
updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false);
setNeedsValidityCheck();
if (fireOnChangeNow)
listBoxOnChange();
return;
}
if (optionIndex == selectedIndex())
return;
selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchInputAndChangeEvent : 0) | UserDriven);
}
bool HTMLSelectElement::hasPlaceholderLabelOption() const
{
if (multiple() || size() > 1)
return false;
int listIndex = optionToListIndex(0);
ASSERT(listIndex >= 0);
if (listIndex < 0)
return false;
return !listIndex && toHTMLOptionElement(listItems()[listIndex])->value().isEmpty();
}
String HTMLSelectElement::validationMessage() const
{
if (!willValidate())
return String();
if (customError())
return customValidationMessage();
if (valueMissing())
return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForSelect);
return String();
}
bool HTMLSelectElement::valueMissing() const
{
if (!willValidate())
return false;
if (!isRequired())
return false;
int firstSelectionIndex = selectedIndex();
return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
}
void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
{
if (!multiple())
optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
else {
updateSelectedState(listIndex, allowMultiplySelections, shift);
setNeedsValidityCheck();
if (fireOnChangeNow)
listBoxOnChange();
}
}
bool HTMLSelectElement::usesMenuList() const
{
if (RenderTheme::theme().delegatesMenuListRendering())
return true;
return !m_multiple && m_size <= 1;
}
int HTMLSelectElement::activeSelectionStartListIndex() const
{
if (m_activeSelectionAnchorIndex >= 0)
return m_activeSelectionAnchorIndex;
return optionToListIndex(selectedIndex());
}
int HTMLSelectElement::activeSelectionEndListIndex() const
{
if (m_activeSelectionEndIndex >= 0)
return m_activeSelectionEndIndex;
return lastSelectedListIndex();
}
void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
{
RefPtr<HTMLElement> protectNewChild(element);
if (!element || !(isHTMLOptionElement(element) || isHTMLOptGroupElement(element) || isHTMLHRElement(element)))
return;
insertBefore(element, before, exceptionState);
setNeedsValidityCheck();
}
void HTMLSelectElement::addBeforeOptionAtIndex(HTMLElement* element, int beforeIndex, ExceptionState& exceptionState)
{
HTMLElement* beforeElement = toHTMLElement(options()->item(beforeIndex));
add(element, beforeElement, exceptionState);
}
void HTMLSelectElement::remove(int optionIndex)
{
int listIndex = optionToListIndex(optionIndex);
if (listIndex < 0)
return;
listItems()[listIndex]->remove(IGNORE_EXCEPTION);
}
String HTMLSelectElement::value() const
{
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); i++) {
if (isHTMLOptionElement(items[i]) && toHTMLOptionElement(items[i])->selected())
return toHTMLOptionElement(items[i])->value();
}
return "";
}
void HTMLSelectElement::setValue(const String &value, bool sendEvents)
{
int optionIndex = 0;
if (value.isNull()) {
optionIndex = -1;
} else {
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); i++) {
if (isHTMLOptionElement(items[i])) {
if (toHTMLOptionElement(items[i])->value() == value)
break;
optionIndex++;
}
}
if (optionIndex >= static_cast<int>(items.size()))
optionIndex = -1;
}
int previousSelectedIndex = selectedIndex();
setSelectedIndex(optionIndex);
if (sendEvents && previousSelectedIndex != selectedIndex()) {
if (usesMenuList())
dispatchInputAndChangeEventForMenuList(false);
else
listBoxOnChange();
}
}
String HTMLSelectElement::suggestedValue() const
{
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
if (isHTMLOptionElement(items[i]) && m_suggestedIndex >= 0) {
if (i == static_cast<unsigned>(m_suggestedIndex))
return toHTMLOptionElement(items[i])->value();
}
}
return "";
}
void HTMLSelectElement::setSuggestedValue(const String& value)
{
if (value.isNull()) {
setSuggestedIndex(-1);
return;
}
const Vector<HTMLElement*>& items = listItems();
unsigned optionIndex = 0;
for (unsigned i = 0; i < items.size(); ++i) {
if (isHTMLOptionElement(items[i])) {
if (toHTMLOptionElement(items[i])->value() == value) {
setSuggestedIndex(optionIndex);
return;
}
optionIndex++;
}
}
setSuggestedIndex(-1);
}
bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
{
if (name == alignAttr) {
return false;
}
return HTMLFormControlElementWithState::isPresentationAttribute(name);
}
void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
{
if (name == sizeAttr) {
int oldSize = m_size;
int size = value.toInt();
AtomicString attrSize = AtomicString::number(size);
if (attrSize != value) {
if (Attribute* sizeAttribute = ensureUniqueElementData().getAttributeItem(sizeAttr))
sizeAttribute->setValue(attrSize);
}
size = max(size, 1);
if (oldSize != size)
updateListItemSelectedStates();
m_size = size;
setNeedsValidityCheck();
if (m_size != oldSize && inActiveDocument()) {
lazyReattachIfAttached();
setRecalcListItems();
}
} else if (name == multipleAttr)
parseMultipleAttribute(value);
else if (name == accesskeyAttr) {
} else if (name == disabledAttr) {
HTMLFormControlElementWithState::parseAttribute(name, value);
if (renderer() && renderer()->isMenuList()) {
if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
if (menuList->popupIsVisible())
menuList->hidePopup();
}
}
} else
HTMLFormControlElementWithState::parseAttribute(name, value);
}
bool HTMLSelectElement::shouldShowFocusRingOnMouseFocus() const
{
return true;
}
bool HTMLSelectElement::canSelectAll() const
{
return !usesMenuList();
}
RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
{
if (usesMenuList())
return new RenderMenuList(this);
return new RenderListBox(this);
}
PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
{
updateListItemSelectedStates();
return ensureCachedHTMLCollection(SelectedOptions);
}
PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
{
return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get());
}
void HTMLSelectElement::updateListItemSelectedStates()
{
if (!m_shouldRecalcListItems)
return;
recalcListItems();
setNeedsValidityCheck();
}
void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
{
setRecalcListItems();
setNeedsValidityCheck();
m_lastOnChangeSelection.clear();
HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
}
void HTMLSelectElement::optionElementChildrenChanged()
{
setRecalcListItems();
setNeedsValidityCheck();
if (renderer()) {
if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
cache->childrenChanged(this);
}
}
void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
{
focus();
dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
}
void HTMLSelectElement::setMultiple(bool multiple)
{
bool oldMultiple = this->multiple();
int oldSelectedIndex = selectedIndex();
setAttribute(multipleAttr, multiple ? emptyAtom : nullAtom);
if (oldMultiple != this->multiple())
setSelectedIndex(oldSelectedIndex);
}
void HTMLSelectElement::setSize(int size)
{
setIntegralAttribute(sizeAttr, size);
}
Element* HTMLSelectElement::namedItem(const AtomicString& name)
{
return options()->namedItem(name);
}
Element* HTMLSelectElement::item(unsigned index)
{
return options()->item(index);
}
void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionState& exceptionState)
{
if (index > maxSelectItems - 1)
index = maxSelectItems - 1;
int diff = index - length();
RefPtr<HTMLElement> before = nullptr;
if (diff > 0) {
setLength(index, exceptionState);
} else if (diff < 0) {
before = toHTMLElement(options()->item(index+1));
remove(index);
}
if (!exceptionState.hadException()) {
add(option, before.get(), exceptionState);
if (diff >= 0 && option->selected())
optionSelectionStateChanged(option, true);
}
}
void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionState)
{
if (newLen > maxSelectItems)
newLen = maxSelectItems;
int diff = length() - newLen;
if (diff < 0) {
do {
RefPtr<Element> option = document().createElement(optionTag, false);
ASSERT(option);
add(toHTMLElement(option), 0, exceptionState);
if (exceptionState.hadException())
break;
} while (++diff);
} else {
const Vector<HTMLElement*>& items = listItems();
Vector<RefPtr<Element> > itemsToRemove;
size_t optionIndex = 0;
for (size_t i = 0; i < items.size(); ++i) {
Element* item = items[i];
if (isHTMLOptionElement(items[i]) && optionIndex++ >= newLen) {
ASSERT(item->parentNode());
itemsToRemove.append(item);
}
}
for (size_t i = 0; i < itemsToRemove.size(); ++i) {
Element* item = itemsToRemove[i].get();
if (item->parentNode())
item->parentNode()->removeChild(item, exceptionState);
}
}
setNeedsValidityCheck();
}
bool HTMLSelectElement::isRequiredFormControl() const
{
return isRequired();
}
int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
{
ASSERT(direction == -1 || direction == 1);
const Vector<HTMLElement*>& listItems = this->listItems();
int lastGoodIndex = listIndex;
int size = listItems.size();
for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
--skip;
if (!listItems[listIndex]->isDisabledFormControl() && isHTMLOptionElement(*listItems[listIndex])) {
lastGoodIndex = listIndex;
if (skip <= 0)
break;
}
}
return lastGoodIndex;
}
int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
{
return nextValidIndex(startIndex, SkipForwards, 1);
}
int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
{
if (startIndex == -1)
startIndex = listItems().size();
return nextValidIndex(startIndex, SkipBackwards, 1);
}
int HTMLSelectElement::firstSelectableListIndex() const
{
const Vector<HTMLElement*>& items = listItems();
int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
if (static_cast<size_t>(index) == items.size())
return -1;
return index;
}
int HTMLSelectElement::lastSelectableListIndex() const
{
return nextValidIndex(-1, SkipForwards, INT_MAX);
}
int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
{
const Vector<HTMLElement*>& items = listItems();
int pageSize = 0;
if (renderer()->isListBox())
pageSize = toRenderListBox(renderer())->size() - 1;
int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
return nextValidIndex(edgeIndex, direction, skipAmount);
}
void HTMLSelectElement::selectAll()
{
ASSERT(!usesMenuList());
if (!renderer() || !m_multiple)
return;
saveLastSelection();
m_activeSelectionState = true;
setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
setActiveSelectionEndIndex(previousSelectableListIndex(-1));
updateListBoxSelection(false);
listBoxOnChange();
setNeedsValidityCheck();
}
void HTMLSelectElement::saveLastSelection()
{
if (usesMenuList()) {
m_lastOnChangeIndex = selectedIndex();
return;
}
m_lastOnChangeSelection.clear();
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
m_lastOnChangeSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
}
}
void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
{
m_activeSelectionAnchorIndex = index;
m_cachedStateForActiveSelection.clear();
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
m_cachedStateForActiveSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
}
}
void HTMLSelectElement::setActiveSelectionEndIndex(int index)
{
m_activeSelectionEndIndex = index;
}
void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
{
ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
continue;
if (i >= start && i <= end)
toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
toHTMLOptionElement(element)->setSelectedState(false);
else
toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
}
scrollToSelection();
setNeedsValidityCheck();
notifyFormStateChanged();
}
void HTMLSelectElement::listBoxOnChange()
{
ASSERT(!usesMenuList() || m_multiple);
const Vector<HTMLElement*>& items = listItems();
if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
dispatchFormControlChangeEvent();
return;
}
bool fireOnChange = false;
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
bool selected = isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected();
if (selected != m_lastOnChangeSelection[i])
fireOnChange = true;
m_lastOnChangeSelection[i] = selected;
}
if (fireOnChange) {
RefPtr<HTMLSelectElement> protector(this);
dispatchInputEvent();
dispatchFormControlChangeEvent();
}
}
void HTMLSelectElement::dispatchInputAndChangeEventForMenuList(bool requiresUserGesture)
{
ASSERT(usesMenuList());
int selected = selectedIndex();
if (m_lastOnChangeIndex != selected && (!requiresUserGesture || m_isProcessingUserDrivenChange)) {
m_lastOnChangeIndex = selected;
m_isProcessingUserDrivenChange = false;
RefPtr<HTMLSelectElement> protector(this);
dispatchInputEvent();
dispatchFormControlChangeEvent();
}
}
void HTMLSelectElement::scrollToSelection()
{
if (usesMenuList())
return;
if (RenderObject* renderer = this->renderer())
toRenderListBox(renderer)->selectionChanged();
}
void HTMLSelectElement::setOptionsChangedOnRenderer()
{
if (RenderObject* renderer = this->renderer()) {
if (usesMenuList())
toRenderMenuList(renderer)->setOptionsChanged(true);
else
toRenderListBox(renderer)->setOptionsChanged(true);
}
}
const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
{
if (m_shouldRecalcListItems)
recalcListItems();
else {
#if !ASSERT_DISABLED
Vector<HTMLElement*> items = m_listItems;
recalcListItems(false);
ASSERT(items == m_listItems);
#endif
}
return m_listItems;
}
void HTMLSelectElement::invalidateSelectedItems()
{
if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
collection->invalidateCache();
}
void HTMLSelectElement::setRecalcListItems()
{
m_shouldRecalcListItems = true;
m_activeSelectionAnchorIndex = -1;
setOptionsChangedOnRenderer();
setNeedsStyleRecalc(SubtreeStyleChange);
if (!inDocument()) {
if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
collection->invalidateCache();
}
if (!inDocument())
invalidateSelectedItems();
if (renderer()) {
if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
cache->childrenChanged(this);
}
}
void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
{
m_listItems.clear();
m_shouldRecalcListItems = false;
HTMLOptionElement* foundSelected = 0;
HTMLOptionElement* firstOption = 0;
for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
if (!currentElement->isHTMLElement()) {
currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
continue;
}
HTMLElement& current = toHTMLElement(*currentElement);
if (isHTMLOptGroupElement(current)) {
m_listItems.append(¤t);
if (Element* nextElement = ElementTraversal::firstWithin(current)) {
currentElement = nextElement;
continue;
}
}
if (isHTMLOptionElement(current)) {
m_listItems.append(¤t);
if (updateSelectedStates && !m_multiple) {
HTMLOptionElement& option = toHTMLOptionElement(current);
if (!firstOption)
firstOption = &option;
if (option.selected()) {
if (foundSelected)
foundSelected->setSelectedState(false);
foundSelected = &option;
} else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
foundSelected = &option;
foundSelected->setSelectedState(true);
}
}
}
if (isHTMLHRElement(current))
m_listItems.append(¤t);
currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
}
if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
firstOption->setSelectedState(true);
}
int HTMLSelectElement::selectedIndex() const
{
unsigned index = 0;
const Vector<HTMLElement*>& items = listItems();
for (size_t i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
if (isHTMLOptionElement(*element)) {
if (toHTMLOptionElement(*element).selected())
return index;
++index;
}
}
return -1;
}
void HTMLSelectElement::setSelectedIndex(int index)
{
selectOption(index, DeselectOtherOptions);
}
int HTMLSelectElement::suggestedIndex() const
{
return m_suggestedIndex;
}
void HTMLSelectElement::setSuggestedIndex(int suggestedIndex)
{
m_suggestedIndex = suggestedIndex;
if (RenderObject* renderer = this->renderer()) {
renderer->updateFromElement();
if (renderer->isListBox())
toRenderListBox(renderer)->scrollToRevealElementAtListIndex(suggestedIndex);
}
}
void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
{
ASSERT(option->ownerSelectElement() == this);
if (optionIsSelected)
selectOption(option->index());
else if (!usesMenuList() || multiple())
selectOption(-1);
else
selectOption(nextSelectableListIndex(-1));
}
void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
{
bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
const Vector<HTMLElement*>& items = listItems();
int listIndex = optionToListIndex(optionIndex);
HTMLElement* element = 0;
if (listIndex >= 0) {
element = items[listIndex];
if (isHTMLOptionElement(*element)) {
if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
setActiveSelectionAnchorIndex(listIndex);
if (m_activeSelectionEndIndex < 0 || shouldDeselect)
setActiveSelectionEndIndex(listIndex);
toHTMLOptionElement(*element).setSelectedState(true);
}
}
if (shouldDeselect)
deselectItemsWithoutValidation(element);
if (RenderObject* renderer = this->renderer())
renderer->updateFromElement();
scrollToSelection();
if (usesMenuList()) {
m_isProcessingUserDrivenChange = flags & UserDriven;
if (flags & DispatchInputAndChangeEvent)
dispatchInputAndChangeEventForMenuList();
if (RenderObject* renderer = this->renderer()) {
if (usesMenuList())
toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
else if (renderer->isListBox())
toRenderListBox(renderer)->selectionChanged();
}
}
setNeedsValidityCheck();
notifyFormStateChanged();
}
int HTMLSelectElement::optionToListIndex(int optionIndex) const
{
const Vector<HTMLElement*>& items = listItems();
int listSize = static_cast<int>(items.size());
if (optionIndex < 0 || optionIndex >= listSize)
return -1;
int optionIndex2 = -1;
for (int listIndex = 0; listIndex < listSize; ++listIndex) {
if (isHTMLOptionElement(*items[listIndex])) {
++optionIndex2;
if (optionIndex2 == optionIndex)
return listIndex;
}
}
return -1;
}
int HTMLSelectElement::listToOptionIndex(int listIndex) const
{
const Vector<HTMLElement*>& items = listItems();
if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !isHTMLOptionElement(*items[listIndex]))
return -1;
int optionIndex = 0;
for (int i = 0; i < listIndex; ++i) {
if (isHTMLOptionElement(*items[i]))
++optionIndex;
}
return optionIndex;
}
void HTMLSelectElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
{
if (usesMenuList())
saveLastSelection();
HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, type);
}
void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
{
if (usesMenuList())
dispatchInputAndChangeEventForMenuList();
HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
}
void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
{
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
if (element != excludeElement && isHTMLOptionElement(*element))
toHTMLOptionElement(element)->setSelectedState(false);
}
}
FormControlState HTMLSelectElement::saveFormControlState() const
{
const Vector<HTMLElement*>& items = listItems();
size_t length = items.size();
FormControlState state;
for (unsigned i = 0; i < length; ++i) {
if (!isHTMLOptionElement(*items[i]))
continue;
HTMLOptionElement* option = toHTMLOptionElement(items[i]);
if (!option->selected())
continue;
state.append(option->value());
if (!multiple())
break;
}
return state;
}
size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
{
const Vector<HTMLElement*>& items = listItems();
size_t loopEndIndex = std::min(items.size(), listIndexEnd);
for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
if (!isHTMLOptionElement(items[i]))
continue;
if (toHTMLOptionElement(items[i])->value() == value)
return i;
}
return kNotFound;
}
void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
{
recalcListItems();
const Vector<HTMLElement*>& items = listItems();
size_t itemsSize = items.size();
if (!itemsSize)
return;
for (size_t i = 0; i < itemsSize; ++i) {
if (!isHTMLOptionElement(items[i]))
continue;
toHTMLOptionElement(items[i])->setSelectedState(false);
}
if (!multiple()) {
size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
if (foundIndex != kNotFound)
toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
} else {
size_t startIndex = 0;
for (size_t i = 0; i < state.valueSize(); ++i) {
const String& value = state[i];
size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
if (foundIndex == kNotFound)
foundIndex = searchOptionsForValue(value, 0, startIndex);
if (foundIndex == kNotFound)
continue;
toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
startIndex = foundIndex + 1;
}
}
setOptionsChangedOnRenderer();
setNeedsValidityCheck();
}
void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
{
bool oldUsesMenuList = usesMenuList();
m_multiple = !value.isNull();
setNeedsValidityCheck();
if (oldUsesMenuList != usesMenuList())
lazyReattachIfAttached();
}
bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
{
const AtomicString& name = this->name();
if (name.isEmpty())
return false;
bool successful = false;
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
if (isHTMLOptionElement(*element) && toHTMLOptionElement(*element).selected() && !toHTMLOptionElement(*element).isDisabledFormControl()) {
list.appendData(name, toHTMLOptionElement(*element).value());
successful = true;
}
}
return successful;
}
void HTMLSelectElement::resetImpl()
{
HTMLOptionElement* firstOption = 0;
HTMLOptionElement* selectedOption = 0;
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
if (!isHTMLOptionElement(*element))
continue;
if (items[i]->fastHasAttribute(selectedAttr)) {
if (selectedOption && !m_multiple)
selectedOption->setSelectedState(false);
toHTMLOptionElement(element)->setSelectedState(true);
selectedOption = toHTMLOptionElement(element);
} else
toHTMLOptionElement(element)->setSelectedState(false);
if (!firstOption)
firstOption = toHTMLOptionElement(element);
}
if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
firstOption->setSelectedState(true);
setOptionsChangedOnRenderer();
setNeedsStyleRecalc(SubtreeStyleChange);
setNeedsValidityCheck();
}
#if !OS(WIN)
bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
{
if (!RenderTheme::theme().popsMenuByArrowKeys())
return false;
if (!isSpatialNavigationEnabled(document().frame())) {
if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
focus();
if (!renderer() || isDisabledFormControl())
return true;
saveLastSelection();
if (RenderMenuList* menuList = toRenderMenuList(renderer()))
menuList->showPopup();
event->setDefaultHandled();
}
return true;
}
return false;
}
#endif
void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
{
RenderTheme& renderTheme = RenderTheme::theme();
if (event->type() == EventTypeNames::keydown) {
if (!renderer() || !event->isKeyboardEvent())
return;
if (platformHandleKeydownEvent(toKeyboardEvent(event)))
return;
if (isSpatialNavigationEnabled(document().frame())) {
if (!m_activeSelectionState)
return;
}
const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
bool handled = true;
const Vector<HTMLElement*>& listItems = this->listItems();
int listIndex = optionToListIndex(selectedIndex());
if (keyIdentifier == "Down" || keyIdentifier == "Right")
listIndex = nextValidIndex(listIndex, SkipForwards, 1);
else if (keyIdentifier == "Up" || keyIdentifier == "Left")
listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
else if (keyIdentifier == "PageDown")
listIndex = nextValidIndex(listIndex, SkipForwards, 3);
else if (keyIdentifier == "PageUp")
listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
else if (keyIdentifier == "Home")
listIndex = nextValidIndex(-1, SkipForwards, 1);
else if (keyIdentifier == "End")
listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
else
handled = false;
if (handled && static_cast<size_t>(listIndex) < listItems.size())
selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
if (handled)
event->setDefaultHandled();
}
if (event->type() == EventTypeNames::keypress) {
if (!renderer() || !event->isKeyboardEvent())
return;
int keyCode = toKeyboardEvent(event)->keyCode();
bool handled = false;
if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
m_activeSelectionState = !m_activeSelectionState;
event->setDefaultHandled();
return;
}
if (renderTheme.popsMenuBySpaceOrReturn()) {
if (keyCode == ' ' || keyCode == '\r') {
focus();
if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
return;
saveLastSelection();
if (RenderMenuList* menuList = toRenderMenuList(renderer()))
menuList->showPopup();
handled = true;
}
} else if (renderTheme.popsMenuByArrowKeys()) {
if (keyCode == ' ') {
focus();
if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
return;
saveLastSelection();
if (RenderMenuList* menuList = toRenderMenuList(renderer()))
menuList->showPopup();
handled = true;
} else if (keyCode == '\r') {
if (form())
form()->submitImplicitly(event, false);
dispatchInputAndChangeEventForMenuList();
handled = true;
}
}
if (handled)
event->setDefaultHandled();
}
if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
focus();
if (renderer() && renderer()->isMenuList() && !isDisabledFormControl()) {
if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
if (menuList->popupIsVisible())
menuList->hidePopup();
else {
saveLastSelection();
menuList->showPopup();
}
}
}
event->setDefaultHandled();
}
if (event->type() == EventTypeNames::blur) {
if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
if (menuList->popupIsVisible())
menuList->hidePopup();
}
}
}
void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
{
ASSERT(listIndex >= 0);
saveLastSelection();
m_activeSelectionState = true;
bool shiftSelect = m_multiple && shift;
bool multiSelect = m_multiple && multi && !shift;
HTMLElement* clickedElement = listItems()[listIndex];
ASSERT(clickedElement);
if (isHTMLOptionElement(*clickedElement)) {
if (toHTMLOptionElement(*clickedElement).selected() && multiSelect)
m_activeSelectionState = false;
if (!m_activeSelectionState)
toHTMLOptionElement(*clickedElement).setSelectedState(false);
}
if (!shiftSelect && !multiSelect)
deselectItemsWithoutValidation(clickedElement);
if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
setActiveSelectionAnchorIndex(selectedIndex());
if (isHTMLOptionElement(*clickedElement) && !toHTMLOptionElement(*clickedElement).isDisabledFormControl())
toHTMLOptionElement(*clickedElement).setSelectedState(true);
if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
setActiveSelectionAnchorIndex(listIndex);
setActiveSelectionEndIndex(listIndex);
updateListBoxSelection(!multiSelect);
}
void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
{
const Vector<HTMLElement*>& listItems = this->listItems();
bool dragSelection = false;
if (event->type() == EventTypeNames::gesturetap && event->isGestureEvent()) {
focus();
if (!renderer() || !renderer()->isListBox())
return;
GestureEvent& gestureEvent = toGestureEvent(*event);
IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(gestureEvent.absoluteLocation(), UseTransforms));
int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
if (listIndex >= 0) {
if (!isDisabledFormControl())
updateSelectedState(listIndex, true, gestureEvent.shiftKey());
event->setDefaultHandled();
}
} else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
focus();
if (!renderer())
return;
MouseEvent* mouseEvent = toMouseEvent(event);
IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
if (listIndex >= 0) {
if (!isDisabledFormControl()) {
#if OS(MACOSX)
updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
#else
updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
#endif
}
if (LocalFrame* frame = document().frame())
frame->eventHandler().setMouseDownMayStartAutoscroll();
event->setDefaultHandled();
}
} else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
MouseEvent* mouseEvent = toMouseEvent(event);
if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
return;
IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
if (listIndex >= 0) {
if (!isDisabledFormControl()) {
if (m_multiple) {
if (m_activeSelectionAnchorIndex < 0)
return;
setActiveSelectionEndIndex(listIndex);
updateListBoxSelection(false);
} else {
setActiveSelectionAnchorIndex(listIndex);
setActiveSelectionEndIndex(listIndex);
updateListBoxSelection(true);
}
}
dragSelection = true;
}
} else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
if (m_lastOnChangeSelection.isEmpty())
return;
if (!dragSelection) {
listBoxOnChange();
}
} else if (event->type() == EventTypeNames::keydown) {
if (!event->isKeyboardEvent())
return;
const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
bool handled = false;
int endIndex = 0;
if (m_activeSelectionEndIndex < 0) {
if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
int startIndex = lastSelectedListIndex();
handled = true;
if (keyIdentifier == "Down")
endIndex = nextSelectableListIndex(startIndex);
else
endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
} else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
int startIndex = optionToListIndex(selectedIndex());
handled = true;
if (keyIdentifier == "Up")
endIndex = previousSelectableListIndex(startIndex);
else
endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
}
} else {
if (keyIdentifier == "Down") {
endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
handled = true;
} else if (keyIdentifier == "Up") {
endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
handled = true;
} else if (keyIdentifier == "PageDown") {
endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
handled = true;
} else if (keyIdentifier == "PageUp") {
endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
handled = true;
}
}
if (keyIdentifier == "Home") {
endIndex = firstSelectableListIndex();
handled = true;
} else if (keyIdentifier == "End") {
endIndex = lastSelectableListIndex();
handled = true;
}
if (isSpatialNavigationEnabled(document().frame()))
if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
return;
if (endIndex >= 0 && handled) {
saveLastSelection();
ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
setActiveSelectionEndIndex(endIndex);
bool selectNewItem = !m_multiple || toKeyboardEvent(event)->shiftKey() || !isSpatialNavigationEnabled(document().frame());
if (selectNewItem)
m_activeSelectionState = true;
bool deselectOthers = !m_multiple || (!toKeyboardEvent(event)->shiftKey() && selectNewItem);
if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
if (deselectOthers)
deselectItemsWithoutValidation();
setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
}
toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
if (selectNewItem) {
updateListBoxSelection(deselectOthers);
listBoxOnChange();
} else
scrollToSelection();
event->setDefaultHandled();
}
} else if (event->type() == EventTypeNames::keypress) {
if (!event->isKeyboardEvent())
return;
int keyCode = toKeyboardEvent(event)->keyCode();
if (keyCode == '\r') {
if (form())
form()->submitImplicitly(event, false);
event->setDefaultHandled();
} else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
m_activeSelectionState = !m_activeSelectionState;
updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true , false );
listBoxOnChange();
event->setDefaultHandled();
}
}
}
void HTMLSelectElement::defaultEventHandler(Event* event)
{
if (!renderer())
return;
if (isDisabledFormControl()) {
HTMLFormControlElementWithState::defaultEventHandler(event);
return;
}
if (usesMenuList())
menuListDefaultEventHandler(event);
else
listBoxDefaultEventHandler(event);
if (event->defaultHandled())
return;
if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
typeAheadFind(keyboardEvent);
event->setDefaultHandled();
return;
}
}
HTMLFormControlElementWithState::defaultEventHandler(event);
}
int HTMLSelectElement::lastSelectedListIndex() const
{
const Vector<HTMLElement*>& items = listItems();
for (size_t i = items.size(); i;) {
HTMLElement* element = items[--i];
if (isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected())
return i;
}
return -1;
}
int HTMLSelectElement::indexOfSelectedOption() const
{
return optionToListIndex(selectedIndex());
}
int HTMLSelectElement::optionCount() const
{
return listItems().size();
}
String HTMLSelectElement::optionAtIndex(int index) const
{
const Vector<HTMLElement*>& items = listItems();
HTMLElement* element = items[index];
if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
return String();
return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
}
void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
{
int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
if (index < 0)
return;
selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
if (!usesMenuList())
listBoxOnChange();
}
Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
{
recalcListItems();
HTMLFormControlElementWithState::insertedInto(insertionPoint);
return InsertionDone;
}
void HTMLSelectElement::accessKeySetSelectedIndex(int index)
{
if (!focused())
accessKeyAction(false);
const Vector<HTMLElement*>& items = listItems();
int listIndex = optionToListIndex(index);
if (listIndex >= 0) {
HTMLElement* element = items[listIndex];
if (isHTMLOptionElement(*element)) {
if (toHTMLOptionElement(*element).selected())
toHTMLOptionElement(*element).setSelectedState(false);
else
selectOption(index, DispatchInputAndChangeEvent | UserDriven);
}
}
if (usesMenuList())
dispatchInputAndChangeEventForMenuList();
else
listBoxOnChange();
scrollToSelection();
}
unsigned HTMLSelectElement::length() const
{
unsigned options = 0;
const Vector<HTMLElement*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
if (isHTMLOptionElement(*items[i]))
++options;
}
return options;
}
void HTMLSelectElement::finishParsingChildren()
{
HTMLFormControlElementWithState::finishParsingChildren();
updateListItemSelectedStates();
}
bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
{
if (!value) {
remove(index);
return true;
}
setOption(index, value.get(), exceptionState);
return true;
}
bool HTMLSelectElement::isInteractiveContent() const
{
return true;
}
bool HTMLSelectElement::supportsAutofocus() const
{
return true;
}
}