Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
      3  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      4  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      5  *           (C) 2001 Dirk Mueller (mueller (at) kde.org)
      6  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights reserved.
      7  *           (C) 2006 Alexey Proskuryakov (ap (at) nypop.com)
      8  * Copyright (C) 2010 Google Inc. All rights reserved.
      9  * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
     10  *
     11  * This library is free software; you can redistribute it and/or
     12  * modify it under the terms of the GNU Library General Public
     13  * License as published by the Free Software Foundation; either
     14  * version 2 of the License, or (at your option) any later version.
     15  *
     16  * This library is distributed in the hope that it will be useful,
     17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     19  * Library General Public License for more details.
     20  *
     21  * You should have received a copy of the GNU Library General Public License
     22  * along with this library; see the file COPYING.LIB.  If not, write to
     23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     24  * Boston, MA 02110-1301, USA.
     25  *
     26  */
     27 
     28 #include "config.h"
     29 #include "core/html/HTMLSelectElement.h"
     30 
     31 #include "bindings/v8/ExceptionMessages.h"
     32 #include "bindings/v8/ExceptionState.h"
     33 #include "bindings/v8/ExceptionStatePlaceholder.h"
     34 #include "core/HTMLNames.h"
     35 #include "core/accessibility/AXObjectCache.h"
     36 #include "core/dom/Attribute.h"
     37 #include "core/dom/ElementTraversal.h"
     38 #include "core/dom/NodeTraversal.h"
     39 #include "core/events/GestureEvent.h"
     40 #include "core/events/KeyboardEvent.h"
     41 #include "core/events/MouseEvent.h"
     42 #include "core/frame/LocalFrame.h"
     43 #include "core/html/FormDataList.h"
     44 #include "core/html/HTMLFormElement.h"
     45 #include "core/html/HTMLOptionElement.h"
     46 #include "core/html/forms/FormController.h"
     47 #include "core/page/EventHandler.h"
     48 #include "core/page/SpatialNavigation.h"
     49 #include "core/rendering/RenderListBox.h"
     50 #include "core/rendering/RenderMenuList.h"
     51 #include "core/rendering/RenderTheme.h"
     52 #include "platform/PlatformMouseEvent.h"
     53 #include "platform/text/PlatformLocale.h"
     54 
     55 using namespace WTF::Unicode;
     56 
     57 namespace WebCore {
     58 
     59 using namespace HTMLNames;
     60 
     61 // Upper limit agreed upon with representatives of Opera and Mozilla.
     62 static const unsigned maxSelectItems = 10000;
     63 
     64 HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form)
     65     : HTMLFormControlElementWithState(selectTag, document, form)
     66     , m_typeAhead(this)
     67     , m_size(0)
     68     , m_lastOnChangeIndex(-1)
     69     , m_activeSelectionAnchorIndex(-1)
     70     , m_activeSelectionEndIndex(-1)
     71     , m_isProcessingUserDrivenChange(false)
     72     , m_multiple(false)
     73     , m_activeSelectionState(false)
     74     , m_shouldRecalcListItems(false)
     75     , m_suggestedIndex(-1)
     76 {
     77     ScriptWrappable::init(this);
     78     setHasCustomStyleCallbacks();
     79 }
     80 
     81 PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
     82 {
     83     return adoptRefWillBeNoop(new HTMLSelectElement(document, 0));
     84 }
     85 
     86 PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
     87 {
     88     return adoptRefWillBeNoop(new HTMLSelectElement(document, form));
     89 }
     90 
     91 const AtomicString& HTMLSelectElement::formControlType() const
     92 {
     93     DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral));
     94     DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral));
     95     return m_multiple ? selectMultiple : selectOne;
     96 }
     97 
     98 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
     99 {
    100     // User interaction such as mousedown events can cause list box select elements to send change events.
    101     // This produces that same behavior for changes triggered by other code running on behalf of the user.
    102     if (!usesMenuList()) {
    103         updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false);
    104         setNeedsValidityCheck();
    105         if (fireOnChangeNow)
    106             listBoxOnChange();
    107         return;
    108     }
    109 
    110     // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
    111     // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>).
    112     // The selectOption function does not behave this way, possibly because other callers need a change event even
    113     // in cases where the selected option is not change.
    114     if (optionIndex == selectedIndex())
    115         return;
    116 
    117     selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchInputAndChangeEvent : 0) | UserDriven);
    118 }
    119 
    120 bool HTMLSelectElement::hasPlaceholderLabelOption() const
    121 {
    122     // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
    123     //
    124     // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct.
    125     // Using "size() > 1" here because size() may be 0 in WebKit.
    126     // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887
    127     //
    128     // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified.
    129     // In this case, the display size should be assumed as the default.
    130     // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements.
    131     //
    132     // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
    133     if (multiple() || size() > 1)
    134         return false;
    135 
    136     int listIndex = optionToListIndex(0);
    137     ASSERT(listIndex >= 0);
    138     if (listIndex < 0)
    139         return false;
    140     return !listIndex && toHTMLOptionElement(listItems()[listIndex])->value().isEmpty();
    141 }
    142 
    143 String HTMLSelectElement::validationMessage() const
    144 {
    145     if (!willValidate())
    146         return String();
    147     if (customError())
    148         return customValidationMessage();
    149     if (valueMissing())
    150         return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForSelect);
    151     return String();
    152 }
    153 
    154 bool HTMLSelectElement::valueMissing() const
    155 {
    156     if (!willValidate())
    157         return false;
    158 
    159     if (!isRequired())
    160         return false;
    161 
    162     int firstSelectionIndex = selectedIndex();
    163 
    164     // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
    165     return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
    166 }
    167 
    168 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
    169 {
    170     if (!multiple())
    171         optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
    172     else {
    173         updateSelectedState(listIndex, allowMultiplySelections, shift);
    174         setNeedsValidityCheck();
    175         if (fireOnChangeNow)
    176             listBoxOnChange();
    177     }
    178 }
    179 
    180 bool HTMLSelectElement::usesMenuList() const
    181 {
    182     if (RenderTheme::theme().delegatesMenuListRendering())
    183         return true;
    184 
    185     return !m_multiple && m_size <= 1;
    186 }
    187 
    188 int HTMLSelectElement::activeSelectionStartListIndex() const
    189 {
    190     if (m_activeSelectionAnchorIndex >= 0)
    191         return m_activeSelectionAnchorIndex;
    192     return optionToListIndex(selectedIndex());
    193 }
    194 
    195 int HTMLSelectElement::activeSelectionEndListIndex() const
    196 {
    197     if (m_activeSelectionEndIndex >= 0)
    198         return m_activeSelectionEndIndex;
    199     return lastSelectedListIndex();
    200 }
    201 
    202 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
    203 {
    204     // Make sure the element is ref'd and deref'd so we don't leak it.
    205     RefPtrWillBeRawPtr<HTMLElement> protectNewChild(element);
    206 
    207     if (!element || !(isHTMLOptionElement(element) || isHTMLOptGroupElement(element) || isHTMLHRElement(element)))
    208         return;
    209 
    210     insertBefore(element, before, exceptionState);
    211     setNeedsValidityCheck();
    212 }
    213 
    214 void HTMLSelectElement::addBeforeOptionAtIndex(HTMLElement* element, int beforeIndex, ExceptionState& exceptionState)
    215 {
    216     HTMLElement* beforeElement = toHTMLElement(options()->item(beforeIndex));
    217     add(element, beforeElement, exceptionState);
    218 }
    219 
    220 void HTMLSelectElement::remove(int optionIndex)
    221 {
    222     int listIndex = optionToListIndex(optionIndex);
    223     if (listIndex < 0)
    224         return;
    225 
    226     listItems()[listIndex]->remove(IGNORE_EXCEPTION);
    227 }
    228 
    229 String HTMLSelectElement::value() const
    230 {
    231     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    232     for (unsigned i = 0; i < items.size(); i++) {
    233         if (isHTMLOptionElement(items[i]) && toHTMLOptionElement(items[i])->selected())
    234             return toHTMLOptionElement(items[i])->value();
    235     }
    236     return "";
    237 }
    238 
    239 void HTMLSelectElement::setValue(const String &value, bool sendEvents)
    240 {
    241     // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
    242     int optionIndex = 0;
    243     if (value.isNull()) {
    244         optionIndex = -1;
    245     } else {
    246         // Find the option with value() matching the given parameter and make it the current selection.
    247         const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    248         for (unsigned i = 0; i < items.size(); i++) {
    249             if (isHTMLOptionElement(items[i])) {
    250                 if (toHTMLOptionElement(items[i])->value() == value)
    251                     break;
    252                 optionIndex++;
    253             }
    254         }
    255         if (optionIndex >= static_cast<int>(items.size()))
    256             optionIndex = -1;
    257     }
    258 
    259     int previousSelectedIndex = selectedIndex();
    260     setSuggestedIndex(-1);
    261     setSelectedIndex(optionIndex);
    262 
    263     if (sendEvents && previousSelectedIndex != selectedIndex()) {
    264         if (usesMenuList())
    265             dispatchInputAndChangeEventForMenuList(false);
    266         else
    267             listBoxOnChange();
    268     }
    269 }
    270 
    271 String HTMLSelectElement::suggestedValue() const
    272 {
    273     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    274     for (unsigned i = 0; i < items.size(); ++i) {
    275         if (isHTMLOptionElement(items[i]) && m_suggestedIndex >= 0) {
    276             if (i == static_cast<unsigned>(m_suggestedIndex))
    277                 return toHTMLOptionElement(items[i])->value();
    278         }
    279     }
    280     return "";
    281 }
    282 
    283 void HTMLSelectElement::setSuggestedValue(const String& value)
    284 {
    285     if (value.isNull()) {
    286         setSuggestedIndex(-1);
    287         return;
    288     }
    289 
    290     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    291     unsigned optionIndex = 0;
    292     for (unsigned i = 0; i < items.size(); ++i) {
    293         if (isHTMLOptionElement(items[i])) {
    294             if (toHTMLOptionElement(items[i])->value() == value) {
    295                 setSuggestedIndex(optionIndex);
    296                 return;
    297             }
    298             optionIndex++;
    299         }
    300     }
    301 
    302     setSuggestedIndex(-1);
    303 }
    304 
    305 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
    306 {
    307     if (name == alignAttr) {
    308         // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
    309         // See http://bugs.webkit.org/show_bug.cgi?id=12072
    310         return false;
    311     }
    312 
    313     return HTMLFormControlElementWithState::isPresentationAttribute(name);
    314 }
    315 
    316 void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    317 {
    318     if (name == sizeAttr) {
    319         int oldSize = m_size;
    320         // Set the attribute value to a number.
    321         // This is important since the style rules for this attribute can determine the appearance property.
    322         int size = value.toInt();
    323         AtomicString attrSize = AtomicString::number(size);
    324         if (attrSize != value) {
    325             // FIXME: This is horribly factored.
    326             if (Attribute* sizeAttribute = ensureUniqueElementData().findAttributeByName(sizeAttr))
    327                 sizeAttribute->setValue(attrSize);
    328         }
    329         size = std::max(size, 1);
    330 
    331         // Ensure that we've determined selectedness of the items at least once prior to changing the size.
    332         if (oldSize != size)
    333             updateListItemSelectedStates();
    334 
    335         m_size = size;
    336         setNeedsValidityCheck();
    337         if (m_size != oldSize && inActiveDocument()) {
    338             lazyReattachIfAttached();
    339             setRecalcListItems();
    340         }
    341     } else if (name == multipleAttr)
    342         parseMultipleAttribute(value);
    343     else if (name == accesskeyAttr) {
    344         // FIXME: ignore for the moment.
    345         //
    346     } else if (name == disabledAttr) {
    347         HTMLFormControlElementWithState::parseAttribute(name, value);
    348         if (renderer() && renderer()->isMenuList()) {
    349             if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
    350                 if (menuList->popupIsVisible())
    351                     menuList->hidePopup();
    352             }
    353         }
    354 
    355     } else
    356         HTMLFormControlElementWithState::parseAttribute(name, value);
    357 }
    358 
    359 bool HTMLSelectElement::shouldShowFocusRingOnMouseFocus() const
    360 {
    361     return true;
    362 }
    363 
    364 bool HTMLSelectElement::canSelectAll() const
    365 {
    366     return !usesMenuList();
    367 }
    368 
    369 RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
    370 {
    371     if (usesMenuList())
    372         return new RenderMenuList(this);
    373     return new RenderListBox(this);
    374 }
    375 
    376 PassRefPtrWillBeRawPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
    377 {
    378     updateListItemSelectedStates();
    379     return ensureCachedHTMLCollection(SelectedOptions);
    380 }
    381 
    382 PassRefPtrWillBeRawPtr<HTMLOptionsCollection> HTMLSelectElement::options()
    383 {
    384     return toHTMLOptionsCollection(ensureCachedHTMLCollection(SelectOptions).get());
    385 }
    386 
    387 void HTMLSelectElement::updateListItemSelectedStates()
    388 {
    389     if (!m_shouldRecalcListItems)
    390         return;
    391     recalcListItems();
    392     setNeedsValidityCheck();
    393 }
    394 
    395 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    396 {
    397     setRecalcListItems();
    398     setNeedsValidityCheck();
    399     m_lastOnChangeSelection.clear();
    400 
    401     HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    402 }
    403 
    404 void HTMLSelectElement::optionElementChildrenChanged()
    405 {
    406     setRecalcListItems();
    407     setNeedsValidityCheck();
    408 
    409     if (renderer()) {
    410         if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
    411             cache->childrenChanged(this);
    412     }
    413 }
    414 
    415 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
    416 {
    417     focus();
    418     dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
    419 }
    420 
    421 void HTMLSelectElement::setMultiple(bool multiple)
    422 {
    423     bool oldMultiple = this->multiple();
    424     int oldSelectedIndex = selectedIndex();
    425     setAttribute(multipleAttr, multiple ? emptyAtom : nullAtom);
    426 
    427     // Restore selectedIndex after changing the multiple flag to preserve
    428     // selection as single-line and multi-line has different defaults.
    429     if (oldMultiple != this->multiple())
    430         setSelectedIndex(oldSelectedIndex);
    431 }
    432 
    433 void HTMLSelectElement::setSize(int size)
    434 {
    435     setIntegralAttribute(sizeAttr, size);
    436 }
    437 
    438 Element* HTMLSelectElement::namedItem(const AtomicString& name)
    439 {
    440     return options()->namedItem(name);
    441 }
    442 
    443 Element* HTMLSelectElement::item(unsigned index)
    444 {
    445     return options()->item(index);
    446 }
    447 
    448 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionState& exceptionState)
    449 {
    450     if (index > maxSelectItems - 1)
    451         index = maxSelectItems - 1;
    452     int diff = index - length();
    453     RefPtrWillBeRawPtr<HTMLElement> before = nullptr;
    454     // Out of array bounds? First insert empty dummies.
    455     if (diff > 0) {
    456         setLength(index, exceptionState);
    457         // Replace an existing entry?
    458     } else if (diff < 0) {
    459         before = toHTMLElement(options()->item(index+1));
    460         remove(index);
    461     }
    462     // Finally add the new element.
    463     if (!exceptionState.hadException()) {
    464         add(option, before.get(), exceptionState);
    465         if (diff >= 0 && option->selected())
    466             optionSelectionStateChanged(option, true);
    467     }
    468 }
    469 
    470 void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionState)
    471 {
    472     if (newLen > maxSelectItems)
    473         newLen = maxSelectItems;
    474     int diff = length() - newLen;
    475 
    476     if (diff < 0) { // Add dummy elements.
    477         do {
    478             RefPtrWillBeRawPtr<Element> option = document().createElement(optionTag, false);
    479             ASSERT(option);
    480             add(toHTMLElement(option), 0, exceptionState);
    481             if (exceptionState.hadException())
    482                 break;
    483         } while (++diff);
    484     } else {
    485         const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    486 
    487         // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
    488         // of elements that we intend to remove then attempt to remove them one at a time.
    489         WillBeHeapVector<RefPtrWillBeMember<Element> > itemsToRemove;
    490         size_t optionIndex = 0;
    491         for (size_t i = 0; i < items.size(); ++i) {
    492             Element* item = items[i];
    493             if (isHTMLOptionElement(items[i]) && optionIndex++ >= newLen) {
    494                 ASSERT(item->parentNode());
    495                 itemsToRemove.append(item);
    496             }
    497         }
    498 
    499         for (size_t i = 0; i < itemsToRemove.size(); ++i) {
    500             Element* item = itemsToRemove[i].get();
    501             if (item->parentNode())
    502                 item->parentNode()->removeChild(item, exceptionState);
    503         }
    504     }
    505     setNeedsValidityCheck();
    506 }
    507 
    508 bool HTMLSelectElement::isRequiredFormControl() const
    509 {
    510     return isRequired();
    511 }
    512 
    513 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
    514 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
    515 // Otherwise, it returns |listIndex|.
    516 // Valid means that it is enabled and an option element.
    517 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
    518 {
    519     ASSERT(direction == -1 || direction == 1);
    520     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = this->listItems();
    521     int lastGoodIndex = listIndex;
    522     int size = listItems.size();
    523     for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
    524         --skip;
    525         HTMLElement* element = listItems[listIndex];
    526         if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || toHTMLOptionElement(element)->isDisplayNone())
    527             continue;
    528         lastGoodIndex = listIndex;
    529         if (skip <= 0)
    530             break;
    531     }
    532     return lastGoodIndex;
    533 }
    534 
    535 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
    536 {
    537     return nextValidIndex(startIndex, SkipForwards, 1);
    538 }
    539 
    540 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
    541 {
    542     if (startIndex == -1)
    543         startIndex = listItems().size();
    544     return nextValidIndex(startIndex, SkipBackwards, 1);
    545 }
    546 
    547 int HTMLSelectElement::firstSelectableListIndex() const
    548 {
    549     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    550     int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
    551     if (static_cast<size_t>(index) == items.size())
    552         return -1;
    553     return index;
    554 }
    555 
    556 int HTMLSelectElement::lastSelectableListIndex() const
    557 {
    558     return nextValidIndex(-1, SkipForwards, INT_MAX);
    559 }
    560 
    561 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
    562 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
    563 {
    564     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    565     // Can't use m_size because renderer forces a minimum size.
    566     int pageSize = 0;
    567     if (renderer()->isListBox())
    568         pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
    569 
    570     // One page away, but not outside valid bounds.
    571     // If there is a valid option item one page away, the index is chosen.
    572     // If there is no exact one page away valid option, returns startIndex or the most far index.
    573     int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
    574     int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
    575     return nextValidIndex(edgeIndex, direction, skipAmount);
    576 }
    577 
    578 void HTMLSelectElement::selectAll()
    579 {
    580     ASSERT(!usesMenuList());
    581     if (!renderer() || !m_multiple)
    582         return;
    583 
    584     // Save the selection so it can be compared to the new selectAll selection
    585     // when dispatching change events.
    586     saveLastSelection();
    587 
    588     m_activeSelectionState = true;
    589     setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
    590     setActiveSelectionEndIndex(previousSelectableListIndex(-1));
    591 
    592     updateListBoxSelection(false);
    593     listBoxOnChange();
    594     setNeedsValidityCheck();
    595 }
    596 
    597 void HTMLSelectElement::saveLastSelection()
    598 {
    599     if (usesMenuList()) {
    600         m_lastOnChangeIndex = selectedIndex();
    601         return;
    602     }
    603 
    604     m_lastOnChangeSelection.clear();
    605     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    606     for (unsigned i = 0; i < items.size(); ++i) {
    607         HTMLElement* element = items[i];
    608         m_lastOnChangeSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
    609     }
    610 }
    611 
    612 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
    613 {
    614     m_activeSelectionAnchorIndex = index;
    615 
    616     // Cache the selection state so we can restore the old selection as the new
    617     // selection pivots around this anchor index.
    618     m_cachedStateForActiveSelection.clear();
    619 
    620     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    621     for (unsigned i = 0; i < items.size(); ++i) {
    622         HTMLElement* element = items[i];
    623         m_cachedStateForActiveSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
    624     }
    625 }
    626 
    627 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
    628 {
    629     m_activeSelectionEndIndex = index;
    630 }
    631 
    632 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
    633 {
    634     ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
    635     ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
    636 
    637     unsigned start = std::min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
    638     unsigned end = std::max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
    639 
    640     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    641     for (unsigned i = 0; i < items.size(); ++i) {
    642         HTMLElement* element = items[i];
    643         if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || toHTMLOptionElement(element)->isDisplayNone())
    644             continue;
    645 
    646         if (i >= start && i <= end)
    647             toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
    648         else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
    649             toHTMLOptionElement(element)->setSelectedState(false);
    650         else
    651             toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
    652     }
    653 
    654     scrollToSelection();
    655     setNeedsValidityCheck();
    656     notifyFormStateChanged();
    657 }
    658 
    659 void HTMLSelectElement::listBoxOnChange()
    660 {
    661     ASSERT(!usesMenuList() || m_multiple);
    662 
    663     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    664 
    665     // If the cached selection list is empty, or the size has changed, then fire
    666     // dispatchFormControlChangeEvent, and return early.
    667     // FIXME: Why? This looks unreasonable.
    668     if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
    669         dispatchFormControlChangeEvent();
    670         return;
    671     }
    672 
    673     // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
    674     bool fireOnChange = false;
    675     for (unsigned i = 0; i < items.size(); ++i) {
    676         HTMLElement* element = items[i];
    677         bool selected = isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected();
    678         if (selected != m_lastOnChangeSelection[i])
    679             fireOnChange = true;
    680         m_lastOnChangeSelection[i] = selected;
    681     }
    682 
    683     if (fireOnChange) {
    684         RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
    685         dispatchInputEvent();
    686         dispatchFormControlChangeEvent();
    687     }
    688 }
    689 
    690 void HTMLSelectElement::dispatchInputAndChangeEventForMenuList(bool requiresUserGesture)
    691 {
    692     ASSERT(usesMenuList());
    693 
    694     int selected = selectedIndex();
    695     if (m_lastOnChangeIndex != selected && (!requiresUserGesture || m_isProcessingUserDrivenChange)) {
    696         m_lastOnChangeIndex = selected;
    697         m_isProcessingUserDrivenChange = false;
    698         RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
    699         dispatchInputEvent();
    700         dispatchFormControlChangeEvent();
    701     }
    702 }
    703 
    704 void HTMLSelectElement::scrollToSelection()
    705 {
    706     if (usesMenuList())
    707         return;
    708 
    709     if (RenderObject* renderer = this->renderer())
    710         toRenderListBox(renderer)->selectionChanged();
    711 }
    712 
    713 void HTMLSelectElement::setOptionsChangedOnRenderer()
    714 {
    715     if (RenderObject* renderer = this->renderer()) {
    716         if (usesMenuList())
    717             toRenderMenuList(renderer)->setOptionsChanged(true);
    718         else
    719             toRenderListBox(renderer)->setOptionsChanged(true);
    720     }
    721 }
    722 
    723 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& HTMLSelectElement::listItems() const
    724 {
    725     if (m_shouldRecalcListItems)
    726         recalcListItems();
    727     else {
    728 #if ASSERT_ENABLED
    729         WillBeHeapVector<RawPtrWillBeMember<HTMLElement> > items = m_listItems;
    730         recalcListItems(false);
    731         ASSERT(items == m_listItems);
    732 #endif
    733     }
    734 
    735     return m_listItems;
    736 }
    737 
    738 void HTMLSelectElement::invalidateSelectedItems()
    739 {
    740     if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
    741         collection->invalidateCache();
    742 }
    743 
    744 void HTMLSelectElement::setRecalcListItems()
    745 {
    746     // FIXME: This function does a bunch of confusing things depending on if it
    747     // is in the document or not.
    748 
    749     m_shouldRecalcListItems = true;
    750     // Manual selection anchor is reset when manipulating the select programmatically.
    751     m_activeSelectionAnchorIndex = -1;
    752     setOptionsChangedOnRenderer();
    753     setNeedsStyleRecalc(SubtreeStyleChange);
    754     if (!inDocument()) {
    755         if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
    756             collection->invalidateCache();
    757     }
    758     if (!inDocument())
    759         invalidateSelectedItems();
    760 
    761     if (renderer()) {
    762         if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
    763             cache->childrenChanged(this);
    764     }
    765 }
    766 
    767 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
    768 {
    769     m_listItems.clear();
    770 
    771     m_shouldRecalcListItems = false;
    772 
    773     HTMLOptionElement* foundSelected = 0;
    774     HTMLOptionElement* firstOption = 0;
    775     for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
    776         if (!currentElement->isHTMLElement()) {
    777             currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
    778             continue;
    779         }
    780         HTMLElement& current = toHTMLElement(*currentElement);
    781 
    782         // optgroup tags may not nest. However, both FireFox and IE will
    783         // flatten the tree automatically, so we follow suit.
    784         // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
    785         if (isHTMLOptGroupElement(current)) {
    786             m_listItems.append(&current);
    787             if (Element* nextElement = ElementTraversal::firstWithin(current)) {
    788                 currentElement = nextElement;
    789                 continue;
    790             }
    791         }
    792 
    793         if (isHTMLOptionElement(current)) {
    794             m_listItems.append(&current);
    795 
    796             if (updateSelectedStates && !m_multiple) {
    797                 HTMLOptionElement& option = toHTMLOptionElement(current);
    798                 if (!firstOption)
    799                     firstOption = &option;
    800                 if (option.selected()) {
    801                     if (foundSelected)
    802                         foundSelected->setSelectedState(false);
    803                     foundSelected = &option;
    804                 } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
    805                     foundSelected = &option;
    806                     foundSelected->setSelectedState(true);
    807                 }
    808             }
    809         }
    810 
    811         if (isHTMLHRElement(current))
    812             m_listItems.append(&current);
    813 
    814         // In conforming HTML code, only <optgroup> and <option> will be found
    815         // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step
    816         // into those tags that we choose to. For web-compat, we should cope
    817         // with the case where odd tags like a <div> have been added but we
    818         // handle this because such tags have already been removed from the
    819         // <select>'s subtree at this point.
    820         currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
    821     }
    822 
    823     if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
    824         firstOption->setSelectedState(true);
    825 }
    826 
    827 int HTMLSelectElement::selectedIndex() const
    828 {
    829     unsigned index = 0;
    830 
    831     // Return the number of the first option selected.
    832     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    833     for (size_t i = 0; i < items.size(); ++i) {
    834         HTMLElement* element = items[i];
    835         if (isHTMLOptionElement(*element)) {
    836             if (toHTMLOptionElement(*element).selected())
    837                 return index;
    838             ++index;
    839         }
    840     }
    841 
    842     return -1;
    843 }
    844 
    845 void HTMLSelectElement::setSelectedIndex(int index)
    846 {
    847     selectOption(index, DeselectOtherOptions);
    848 }
    849 
    850 int HTMLSelectElement::suggestedIndex() const
    851 {
    852     return m_suggestedIndex;
    853 }
    854 
    855 void HTMLSelectElement::setSuggestedIndex(int suggestedIndex)
    856 {
    857     m_suggestedIndex = suggestedIndex;
    858 
    859     if (RenderObject* renderer = this->renderer())  {
    860         renderer->updateFromElement();
    861         if (renderer->isListBox())
    862             toRenderListBox(renderer)->scrollToRevealElementAtListIndex(suggestedIndex);
    863     }
    864 }
    865 
    866 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
    867 {
    868     ASSERT(option->ownerSelectElement() == this);
    869     if (optionIsSelected)
    870         selectOption(option->index());
    871     else if (!usesMenuList() || multiple())
    872         selectOption(-1);
    873     else
    874         selectOption(nextSelectableListIndex(-1));
    875 }
    876 
    877 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
    878 {
    879     bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
    880 
    881     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    882     int listIndex = optionToListIndex(optionIndex);
    883 
    884     HTMLElement* element = 0;
    885     if (listIndex >= 0) {
    886         element = items[listIndex];
    887         if (isHTMLOptionElement(*element)) {
    888             if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
    889                 setActiveSelectionAnchorIndex(listIndex);
    890             if (m_activeSelectionEndIndex < 0 || shouldDeselect)
    891                 setActiveSelectionEndIndex(listIndex);
    892             toHTMLOptionElement(*element).setSelectedState(true);
    893         }
    894     }
    895 
    896     if (shouldDeselect)
    897         deselectItemsWithoutValidation(element);
    898 
    899     // For the menu list case, this is what makes the selected element appear.
    900     if (RenderObject* renderer = this->renderer())
    901         renderer->updateFromElement();
    902 
    903     scrollToSelection();
    904 
    905     setNeedsValidityCheck();
    906 
    907     if (usesMenuList()) {
    908         m_isProcessingUserDrivenChange = flags & UserDriven;
    909         if (flags & DispatchInputAndChangeEvent)
    910             dispatchInputAndChangeEventForMenuList();
    911         if (RenderObject* renderer = this->renderer()) {
    912             if (usesMenuList())
    913                 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
    914             else if (renderer->isListBox())
    915                 toRenderListBox(renderer)->selectionChanged();
    916         }
    917     }
    918 
    919     notifyFormStateChanged();
    920 }
    921 
    922 int HTMLSelectElement::optionToListIndex(int optionIndex) const
    923 {
    924     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    925     int listSize = static_cast<int>(items.size());
    926     if (optionIndex < 0 || optionIndex >= listSize)
    927         return -1;
    928 
    929     int optionIndex2 = -1;
    930     for (int listIndex = 0; listIndex < listSize; ++listIndex) {
    931         if (isHTMLOptionElement(*items[listIndex])) {
    932             ++optionIndex2;
    933             if (optionIndex2 == optionIndex)
    934                 return listIndex;
    935         }
    936     }
    937 
    938     return -1;
    939 }
    940 
    941 int HTMLSelectElement::listToOptionIndex(int listIndex) const
    942 {
    943     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    944     if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !isHTMLOptionElement(*items[listIndex]))
    945         return -1;
    946 
    947     // Actual index of option not counting OPTGROUP entries that may be in list.
    948     int optionIndex = 0;
    949     for (int i = 0; i < listIndex; ++i) {
    950         if (isHTMLOptionElement(*items[i]))
    951             ++optionIndex;
    952     }
    953 
    954     return optionIndex;
    955 }
    956 
    957 void HTMLSelectElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
    958 {
    959     // Save the selection so it can be compared to the new selection when
    960     // dispatching change events during blur event dispatch.
    961     if (usesMenuList())
    962         saveLastSelection();
    963     HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, type);
    964 }
    965 
    966 void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
    967 {
    968     // We only need to fire change events here for menu lists, because we fire
    969     // change events for list boxes whenever the selection change is actually made.
    970     // This matches other browsers' behavior.
    971     if (usesMenuList())
    972         dispatchInputAndChangeEventForMenuList();
    973     HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
    974 }
    975 
    976 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
    977 {
    978     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    979     for (unsigned i = 0; i < items.size(); ++i) {
    980         HTMLElement* element = items[i];
    981         if (element != excludeElement && isHTMLOptionElement(*element))
    982             toHTMLOptionElement(element)->setSelectedState(false);
    983     }
    984 }
    985 
    986 FormControlState HTMLSelectElement::saveFormControlState() const
    987 {
    988     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
    989     size_t length = items.size();
    990     FormControlState state;
    991     for (unsigned i = 0; i < length; ++i) {
    992         if (!isHTMLOptionElement(*items[i]))
    993             continue;
    994         HTMLOptionElement* option = toHTMLOptionElement(items[i]);
    995         if (!option->selected())
    996             continue;
    997         state.append(option->value());
    998         if (!multiple())
    999             break;
   1000     }
   1001     return state;
   1002 }
   1003 
   1004 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
   1005 {
   1006     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1007     size_t loopEndIndex = std::min(items.size(), listIndexEnd);
   1008     for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
   1009         if (!isHTMLOptionElement(items[i]))
   1010             continue;
   1011         if (toHTMLOptionElement(items[i])->value() == value)
   1012             return i;
   1013     }
   1014     return kNotFound;
   1015 }
   1016 
   1017 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
   1018 {
   1019     recalcListItems();
   1020 
   1021     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1022     size_t itemsSize = items.size();
   1023     if (!itemsSize)
   1024         return;
   1025 
   1026     for (size_t i = 0; i < itemsSize; ++i) {
   1027         if (!isHTMLOptionElement(items[i]))
   1028             continue;
   1029         toHTMLOptionElement(items[i])->setSelectedState(false);
   1030     }
   1031 
   1032     if (!multiple()) {
   1033         size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
   1034         if (foundIndex != kNotFound)
   1035             toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
   1036     } else {
   1037         size_t startIndex = 0;
   1038         for (size_t i = 0; i < state.valueSize(); ++i) {
   1039             const String& value = state[i];
   1040             size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
   1041             if (foundIndex == kNotFound)
   1042                 foundIndex = searchOptionsForValue(value, 0, startIndex);
   1043             if (foundIndex == kNotFound)
   1044                 continue;
   1045             toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
   1046             startIndex = foundIndex + 1;
   1047         }
   1048     }
   1049 
   1050     setOptionsChangedOnRenderer();
   1051     setNeedsValidityCheck();
   1052 }
   1053 
   1054 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
   1055 {
   1056     bool oldUsesMenuList = usesMenuList();
   1057     m_multiple = !value.isNull();
   1058     setNeedsValidityCheck();
   1059     if (oldUsesMenuList != usesMenuList())
   1060         lazyReattachIfAttached();
   1061 }
   1062 
   1063 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
   1064 {
   1065     const AtomicString& name = this->name();
   1066     if (name.isEmpty())
   1067         return false;
   1068 
   1069     bool successful = false;
   1070     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1071 
   1072     for (unsigned i = 0; i < items.size(); ++i) {
   1073         HTMLElement* element = items[i];
   1074         if (isHTMLOptionElement(*element) && toHTMLOptionElement(*element).selected() && !toHTMLOptionElement(*element).isDisabledFormControl()) {
   1075             list.appendData(name, toHTMLOptionElement(*element).value());
   1076             successful = true;
   1077         }
   1078     }
   1079 
   1080     // It's possible that this is a menulist with multiple options and nothing
   1081     // will be submitted (!successful). We won't send a unselected non-disabled
   1082     // option as fallback. This behavior matches to other browsers.
   1083     return successful;
   1084 }
   1085 
   1086 void HTMLSelectElement::resetImpl()
   1087 {
   1088     HTMLOptionElement* firstOption = 0;
   1089     HTMLOptionElement* selectedOption = 0;
   1090 
   1091     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1092     for (unsigned i = 0; i < items.size(); ++i) {
   1093         HTMLElement* element = items[i];
   1094         if (!isHTMLOptionElement(*element))
   1095             continue;
   1096 
   1097         if (items[i]->fastHasAttribute(selectedAttr)) {
   1098             if (selectedOption && !m_multiple)
   1099                 selectedOption->setSelectedState(false);
   1100             toHTMLOptionElement(element)->setSelectedState(true);
   1101             selectedOption = toHTMLOptionElement(element);
   1102         } else
   1103             toHTMLOptionElement(element)->setSelectedState(false);
   1104 
   1105         if (!firstOption)
   1106             firstOption = toHTMLOptionElement(element);
   1107     }
   1108 
   1109     if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
   1110         firstOption->setSelectedState(true);
   1111 
   1112     setOptionsChangedOnRenderer();
   1113     setNeedsStyleRecalc(SubtreeStyleChange);
   1114     setNeedsValidityCheck();
   1115 }
   1116 
   1117 #if !OS(WIN)
   1118 bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
   1119 {
   1120     if (!RenderTheme::theme().popsMenuByArrowKeys())
   1121         return false;
   1122 
   1123     if (!isSpatialNavigationEnabled(document().frame())) {
   1124         if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
   1125             focus();
   1126             // Calling focus() may cause us to lose our renderer. Return true so
   1127             // that our caller doesn't process the event further, but don't set
   1128             // the event as handled.
   1129             if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
   1130                 return true;
   1131 
   1132             // Save the selection so it can be compared to the new selection
   1133             // when dispatching change events during selectOption, which
   1134             // gets called from RenderMenuList::valueChanged, which gets called
   1135             // after the user makes a selection from the menu.
   1136             saveLastSelection();
   1137             if (RenderMenuList* menuList = toRenderMenuList(renderer()))
   1138                 menuList->showPopup();
   1139             event->setDefaultHandled();
   1140         }
   1141         return true;
   1142     }
   1143 
   1144     return false;
   1145 }
   1146 #endif
   1147 
   1148 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
   1149 {
   1150     RenderTheme& renderTheme = RenderTheme::theme();
   1151 
   1152     if (event->type() == EventTypeNames::keydown) {
   1153         if (!renderer() || !event->isKeyboardEvent())
   1154             return;
   1155 
   1156         if (platformHandleKeydownEvent(toKeyboardEvent(event)))
   1157             return;
   1158 
   1159         // When using spatial navigation, we want to be able to navigate away
   1160         // from the select element when the user hits any of the arrow keys,
   1161         // instead of changing the selection.
   1162         if (isSpatialNavigationEnabled(document().frame())) {
   1163             if (!m_activeSelectionState)
   1164                 return;
   1165         }
   1166 
   1167         const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
   1168         bool handled = true;
   1169         const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = this->listItems();
   1170         int listIndex = optionToListIndex(selectedIndex());
   1171 
   1172         if (keyIdentifier == "Down" || keyIdentifier == "Right")
   1173             listIndex = nextValidIndex(listIndex, SkipForwards, 1);
   1174         else if (keyIdentifier == "Up" || keyIdentifier == "Left")
   1175             listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
   1176         else if (keyIdentifier == "PageDown")
   1177             listIndex = nextValidIndex(listIndex, SkipForwards, 3);
   1178         else if (keyIdentifier == "PageUp")
   1179             listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
   1180         else if (keyIdentifier == "Home")
   1181             listIndex = nextValidIndex(-1, SkipForwards, 1);
   1182         else if (keyIdentifier == "End")
   1183             listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
   1184         else
   1185             handled = false;
   1186 
   1187         if (handled && static_cast<size_t>(listIndex) < listItems.size())
   1188             selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
   1189 
   1190         if (handled)
   1191             event->setDefaultHandled();
   1192     }
   1193 
   1194     // Use key press event here since sending simulated mouse events
   1195     // on key down blocks the proper sending of the key press event.
   1196     if (event->type() == EventTypeNames::keypress) {
   1197         if (!renderer() || !event->isKeyboardEvent())
   1198             return;
   1199 
   1200         int keyCode = toKeyboardEvent(event)->keyCode();
   1201         bool handled = false;
   1202 
   1203         if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
   1204             // Use space to toggle arrow key handling for selection change or spatial navigation.
   1205             m_activeSelectionState = !m_activeSelectionState;
   1206             event->setDefaultHandled();
   1207             return;
   1208         }
   1209 
   1210         if (renderTheme.popsMenuBySpaceOrReturn()) {
   1211             if (keyCode == ' ' || keyCode == '\r') {
   1212                 focus();
   1213 
   1214                 // Calling focus() may remove the renderer or change the
   1215                 // renderer type.
   1216                 if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
   1217                     return;
   1218 
   1219                 // Save the selection so it can be compared to the new selection
   1220                 // when dispatching change events during selectOption, which
   1221                 // gets called from RenderMenuList::valueChanged, which gets called
   1222                 // after the user makes a selection from the menu.
   1223                 saveLastSelection();
   1224                 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
   1225                     menuList->showPopup();
   1226                 handled = true;
   1227             }
   1228         } else if (renderTheme.popsMenuByArrowKeys()) {
   1229             if (keyCode == ' ') {
   1230                 focus();
   1231 
   1232                 // Calling focus() may remove the renderer or change the
   1233                 // renderer type.
   1234                 if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
   1235                     return;
   1236 
   1237                 // Save the selection so it can be compared to the new selection
   1238                 // when dispatching change events during selectOption, which
   1239                 // gets called from RenderMenuList::valueChanged, which gets called
   1240                 // after the user makes a selection from the menu.
   1241                 saveLastSelection();
   1242                 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
   1243                     menuList->showPopup();
   1244                 handled = true;
   1245             } else if (keyCode == '\r') {
   1246                 if (form())
   1247                     form()->submitImplicitly(event, false);
   1248                 dispatchInputAndChangeEventForMenuList();
   1249                 handled = true;
   1250             }
   1251         }
   1252 
   1253         if (handled)
   1254             event->setDefaultHandled();
   1255     }
   1256 
   1257     if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
   1258         focus();
   1259         if (renderer() && renderer()->isMenuList() && !isDisabledFormControl()) {
   1260             if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
   1261                 if (menuList->popupIsVisible())
   1262                     menuList->hidePopup();
   1263                 else {
   1264                     // Save the selection so it can be compared to the new
   1265                     // selection when we call onChange during selectOption,
   1266                     // which gets called from RenderMenuList::valueChanged,
   1267                     // which gets called after the user makes a selection from
   1268                     // the menu.
   1269                     saveLastSelection();
   1270                     menuList->showPopup();
   1271                 }
   1272             }
   1273         }
   1274         event->setDefaultHandled();
   1275     }
   1276 
   1277     if (event->type() == EventTypeNames::blur) {
   1278         if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
   1279             if (menuList->popupIsVisible())
   1280                 menuList->hidePopup();
   1281         }
   1282     }
   1283 }
   1284 
   1285 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
   1286 {
   1287     ASSERT(listIndex >= 0);
   1288 
   1289     HTMLElement* clickedElement = listItems()[listIndex];
   1290     ASSERT(clickedElement);
   1291     if (isHTMLOptGroupElement(clickedElement))
   1292         return;
   1293 
   1294     // Save the selection so it can be compared to the new selection when
   1295     // dispatching change events during mouseup, or after autoscroll finishes.
   1296     saveLastSelection();
   1297 
   1298     m_activeSelectionState = true;
   1299 
   1300     bool shiftSelect = m_multiple && shift;
   1301     bool multiSelect = m_multiple && multi && !shift;
   1302 
   1303     if (isHTMLOptionElement(*clickedElement)) {
   1304         // Keep track of whether an active selection (like during drag
   1305         // selection), should select or deselect.
   1306         if (toHTMLOptionElement(*clickedElement).selected() && multiSelect)
   1307             m_activeSelectionState = false;
   1308         if (!m_activeSelectionState)
   1309             toHTMLOptionElement(*clickedElement).setSelectedState(false);
   1310     }
   1311 
   1312     // If we're not in any special multiple selection mode, then deselect all
   1313     // other items, excluding the clicked option. If no option was clicked, then
   1314     // this will deselect all items in the list.
   1315     if (!shiftSelect && !multiSelect)
   1316         deselectItemsWithoutValidation(clickedElement);
   1317 
   1318     // If the anchor hasn't been set, and we're doing a single selection or a
   1319     // shift selection, then initialize the anchor to the first selected index.
   1320     if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
   1321         setActiveSelectionAnchorIndex(selectedIndex());
   1322 
   1323     // Set the selection state of the clicked option.
   1324     if (isHTMLOptionElement(*clickedElement) && !toHTMLOptionElement(*clickedElement).isDisabledFormControl())
   1325         toHTMLOptionElement(*clickedElement).setSelectedState(true);
   1326 
   1327     // If there was no selectedIndex() for the previous initialization, or If
   1328     // we're doing a single selection, or a multiple selection (using cmd or
   1329     // ctrl), then initialize the anchor index to the listIndex that just got
   1330     // clicked.
   1331     if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
   1332         setActiveSelectionAnchorIndex(listIndex);
   1333 
   1334     setActiveSelectionEndIndex(listIndex);
   1335     updateListBoxSelection(!multiSelect);
   1336 }
   1337 
   1338 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
   1339 {
   1340     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = this->listItems();
   1341     if (event->type() == EventTypeNames::gesturetap && event->isGestureEvent()) {
   1342         focus();
   1343         // Calling focus() may cause us to lose our renderer or change the render type, in which case do not want to handle the event.
   1344         if (!renderer() || !renderer()->isListBox())
   1345             return;
   1346 
   1347         // Convert to coords relative to the list box if needed.
   1348         GestureEvent& gestureEvent = toGestureEvent(*event);
   1349         IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(gestureEvent.absoluteLocation(), UseTransforms));
   1350         int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
   1351         if (listIndex >= 0) {
   1352             if (!isDisabledFormControl())
   1353                 updateSelectedState(listIndex, true, gestureEvent.shiftKey());
   1354             event->setDefaultHandled();
   1355         }
   1356     } else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
   1357         focus();
   1358         // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
   1359         if (!renderer() || !renderer()->isListBox() || isDisabledFormControl())
   1360             return;
   1361 
   1362         // Convert to coords relative to the list box if needed.
   1363         MouseEvent* mouseEvent = toMouseEvent(event);
   1364         IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
   1365         int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
   1366         if (listIndex >= 0) {
   1367             if (!isDisabledFormControl()) {
   1368 #if OS(MACOSX)
   1369                 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
   1370 #else
   1371                 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
   1372 #endif
   1373             }
   1374             if (LocalFrame* frame = document().frame())
   1375                 frame->eventHandler().setMouseDownMayStartAutoscroll();
   1376 
   1377             event->setDefaultHandled();
   1378         }
   1379     } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
   1380         MouseEvent* mouseEvent = toMouseEvent(event);
   1381         if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
   1382             return;
   1383 
   1384         IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
   1385         int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
   1386         if (listIndex >= 0) {
   1387             if (!isDisabledFormControl()) {
   1388                 if (m_multiple) {
   1389                     // Only extend selection if there is something selected.
   1390                     if (m_activeSelectionAnchorIndex < 0)
   1391                         return;
   1392 
   1393                     setActiveSelectionEndIndex(listIndex);
   1394                     updateListBoxSelection(false);
   1395                 } else {
   1396                     setActiveSelectionAnchorIndex(listIndex);
   1397                     setActiveSelectionEndIndex(listIndex);
   1398                     updateListBoxSelection(true);
   1399                 }
   1400             }
   1401         }
   1402     } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
   1403         // We didn't start this click/drag on any options.
   1404         if (m_lastOnChangeSelection.isEmpty())
   1405             return;
   1406         listBoxOnChange();
   1407     } else if (event->type() == EventTypeNames::keydown) {
   1408         if (!event->isKeyboardEvent())
   1409             return;
   1410         const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
   1411 
   1412         bool handled = false;
   1413         int endIndex = 0;
   1414         if (m_activeSelectionEndIndex < 0) {
   1415             // Initialize the end index
   1416             if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
   1417                 int startIndex = lastSelectedListIndex();
   1418                 handled = true;
   1419                 if (keyIdentifier == "Down")
   1420                     endIndex = nextSelectableListIndex(startIndex);
   1421                 else
   1422                     endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
   1423             } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
   1424                 int startIndex = optionToListIndex(selectedIndex());
   1425                 handled = true;
   1426                 if (keyIdentifier == "Up")
   1427                     endIndex = previousSelectableListIndex(startIndex);
   1428                 else
   1429                     endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
   1430             }
   1431         } else {
   1432             // Set the end index based on the current end index.
   1433             if (keyIdentifier == "Down") {
   1434                 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
   1435                 handled = true;
   1436             } else if (keyIdentifier == "Up") {
   1437                 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
   1438                 handled = true;
   1439             } else if (keyIdentifier == "PageDown") {
   1440                 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
   1441                 handled = true;
   1442             } else if (keyIdentifier == "PageUp") {
   1443                 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
   1444                 handled = true;
   1445             }
   1446         }
   1447         if (keyIdentifier == "Home") {
   1448             endIndex = firstSelectableListIndex();
   1449             handled = true;
   1450         } else if (keyIdentifier == "End") {
   1451             endIndex = lastSelectableListIndex();
   1452             handled = true;
   1453         }
   1454 
   1455         if (isSpatialNavigationEnabled(document().frame()))
   1456             // Check if the selection moves to the boundary.
   1457             if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
   1458                 return;
   1459 
   1460         if (endIndex >= 0 && handled) {
   1461             // Save the selection so it can be compared to the new selection
   1462             // when dispatching change events immediately after making the new
   1463             // selection.
   1464             saveLastSelection();
   1465 
   1466             ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
   1467             setActiveSelectionEndIndex(endIndex);
   1468 
   1469             bool selectNewItem = !m_multiple || toKeyboardEvent(event)->shiftKey() || !isSpatialNavigationEnabled(document().frame());
   1470             if (selectNewItem)
   1471                 m_activeSelectionState = true;
   1472             // If the anchor is unitialized, or if we're going to deselect all
   1473             // other options, then set the anchor index equal to the end index.
   1474             bool deselectOthers = !m_multiple || (!toKeyboardEvent(event)->shiftKey() && selectNewItem);
   1475             if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
   1476                 if (deselectOthers)
   1477                     deselectItemsWithoutValidation();
   1478                 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
   1479             }
   1480 
   1481             toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
   1482             if (selectNewItem) {
   1483                 updateListBoxSelection(deselectOthers);
   1484                 listBoxOnChange();
   1485             } else
   1486                 scrollToSelection();
   1487 
   1488             event->setDefaultHandled();
   1489         }
   1490     } else if (event->type() == EventTypeNames::keypress) {
   1491         if (!event->isKeyboardEvent())
   1492             return;
   1493         int keyCode = toKeyboardEvent(event)->keyCode();
   1494 
   1495         if (keyCode == '\r') {
   1496             if (form())
   1497                 form()->submitImplicitly(event, false);
   1498             event->setDefaultHandled();
   1499         } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
   1500             // Use space to toggle selection change.
   1501             m_activeSelectionState = !m_activeSelectionState;
   1502             updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
   1503             listBoxOnChange();
   1504             event->setDefaultHandled();
   1505         }
   1506     }
   1507 }
   1508 
   1509 void HTMLSelectElement::defaultEventHandler(Event* event)
   1510 {
   1511     if (!renderer())
   1512         return;
   1513 
   1514     if (isDisabledFormControl()) {
   1515         HTMLFormControlElementWithState::defaultEventHandler(event);
   1516         return;
   1517     }
   1518 
   1519     if (usesMenuList())
   1520         menuListDefaultEventHandler(event);
   1521     else
   1522         listBoxDefaultEventHandler(event);
   1523     if (event->defaultHandled())
   1524         return;
   1525 
   1526     if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
   1527         KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
   1528         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
   1529             typeAheadFind(keyboardEvent);
   1530             event->setDefaultHandled();
   1531             return;
   1532         }
   1533     }
   1534     HTMLFormControlElementWithState::defaultEventHandler(event);
   1535 }
   1536 
   1537 int HTMLSelectElement::lastSelectedListIndex() const
   1538 {
   1539     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1540     for (size_t i = items.size(); i;) {
   1541         HTMLElement* element = items[--i];
   1542         if (isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected())
   1543             return i;
   1544     }
   1545     return -1;
   1546 }
   1547 
   1548 int HTMLSelectElement::indexOfSelectedOption() const
   1549 {
   1550     return optionToListIndex(selectedIndex());
   1551 }
   1552 
   1553 int HTMLSelectElement::optionCount() const
   1554 {
   1555     return listItems().size();
   1556 }
   1557 
   1558 String HTMLSelectElement::optionAtIndex(int index) const
   1559 {
   1560     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1561 
   1562     HTMLElement* element = items[index];
   1563     if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
   1564         return String();
   1565     return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
   1566 }
   1567 
   1568 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
   1569 {
   1570     int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
   1571     if (index < 0)
   1572         return;
   1573     selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
   1574     if (!usesMenuList())
   1575         listBoxOnChange();
   1576 }
   1577 
   1578 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
   1579 {
   1580     // When the element is created during document parsing, it won't have any
   1581     // items yet - but for innerHTML and related methods, this method is called
   1582     // after the whole subtree is constructed.
   1583     recalcListItems();
   1584     HTMLFormControlElementWithState::insertedInto(insertionPoint);
   1585     return InsertionDone;
   1586 }
   1587 
   1588 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
   1589 {
   1590     // First bring into focus the list box.
   1591     if (!focused())
   1592         accessKeyAction(false);
   1593 
   1594     // If this index is already selected, unselect. otherwise update the selected index.
   1595     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1596     int listIndex = optionToListIndex(index);
   1597     if (listIndex >= 0) {
   1598         HTMLElement* element = items[listIndex];
   1599         if (isHTMLOptionElement(*element)) {
   1600             if (toHTMLOptionElement(*element).selected())
   1601                 toHTMLOptionElement(*element).setSelectedState(false);
   1602             else
   1603                 selectOption(index, DispatchInputAndChangeEvent | UserDriven);
   1604         }
   1605     }
   1606 
   1607     if (usesMenuList())
   1608         dispatchInputAndChangeEventForMenuList();
   1609     else
   1610         listBoxOnChange();
   1611 
   1612     scrollToSelection();
   1613 }
   1614 
   1615 unsigned HTMLSelectElement::length() const
   1616 {
   1617     unsigned options = 0;
   1618 
   1619     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
   1620     for (unsigned i = 0; i < items.size(); ++i) {
   1621         if (isHTMLOptionElement(*items[i]))
   1622             ++options;
   1623     }
   1624 
   1625     return options;
   1626 }
   1627 
   1628 void HTMLSelectElement::finishParsingChildren()
   1629 {
   1630     HTMLFormControlElementWithState::finishParsingChildren();
   1631     updateListItemSelectedStates();
   1632 }
   1633 
   1634 bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtrWillBeRawPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
   1635 {
   1636     if (!value) { // undefined or null
   1637         remove(index);
   1638         return true;
   1639     }
   1640     setOption(index, value.get(), exceptionState);
   1641     return true;
   1642 }
   1643 
   1644 bool HTMLSelectElement::isInteractiveContent() const
   1645 {
   1646     return true;
   1647 }
   1648 
   1649 bool HTMLSelectElement::supportsAutofocus() const
   1650 {
   1651     return true;
   1652 }
   1653 
   1654 void HTMLSelectElement::updateListOnRenderer()
   1655 {
   1656     setOptionsChangedOnRenderer();
   1657 }
   1658 
   1659 void HTMLSelectElement::trace(Visitor* visitor)
   1660 {
   1661 #if ENABLE(OILPAN)
   1662     visitor->trace(m_listItems);
   1663 #endif
   1664     HTMLFormControlElementWithState::trace(visitor);
   1665 }
   1666 
   1667 } // namespace
   1668