Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  *
     19  */
     20 
     21 #include "config.h"
     22 #include "SelectElement.h"
     23 
     24 #include "Attribute.h"
     25 #include "Chrome.h"
     26 #include "ChromeClient.h"
     27 #include "Element.h"
     28 #include "EventHandler.h"
     29 #include "EventNames.h"
     30 #include "FormDataList.h"
     31 #include "Frame.h"
     32 #include "HTMLFormElement.h"
     33 #include "HTMLNames.h"
     34 #include "HTMLSelectElement.h"
     35 #include "KeyboardEvent.h"
     36 #include "MouseEvent.h"
     37 #include "OptionElement.h"
     38 #include "OptionGroupElement.h"
     39 #include "Page.h"
     40 #include "RenderListBox.h"
     41 #include "RenderMenuList.h"
     42 #include "SpatialNavigation.h"
     43 #include <wtf/Assertions.h>
     44 #include <wtf/unicode/CharacterNames.h>
     45 
     46 #if ENABLE(WML)
     47 #include "WMLNames.h"
     48 #include "WMLSelectElement.h"
     49 #endif
     50 
     51 // Configure platform-specific behavior when focused pop-up receives arrow/space/return keystroke.
     52 // (PLATFORM(MAC) and PLATFORM(GTK) are always false in Chromium, hence the extra tests.)
     53 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
     54 #define ARROW_KEYS_POP_MENU 1
     55 #define SPACE_OR_RETURN_POP_MENU 0
     56 #elif PLATFORM(GTK) || (PLATFORM(CHROMIUM) && (OS(LINUX) || OS(FREEBSD)))
     57 #define ARROW_KEYS_POP_MENU 0
     58 #define SPACE_OR_RETURN_POP_MENU 1
     59 #else
     60 #define ARROW_KEYS_POP_MENU 0
     61 #define SPACE_OR_RETURN_POP_MENU 0
     62 #endif
     63 
     64 using std::min;
     65 using std::max;
     66 using namespace WTF;
     67 using namespace Unicode;
     68 
     69 namespace WebCore {
     70 
     71 static const DOMTimeStamp typeAheadTimeout = 1000;
     72 
     73 enum SkipDirection {
     74     SkipBackwards = -1,
     75     SkipForwards = 1
     76 };
     77 
     78 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
     79 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
     80 // Otherwise, it returns |listIndex|.
     81 // Valid means that it is enabled and an option element.
     82 static int nextValidIndex(const Vector<Element*>& listItems, int listIndex, SkipDirection direction, int skip)
     83 {
     84     ASSERT(direction == -1 || direction == 1);
     85     int lastGoodIndex = listIndex;
     86     int size = listItems.size();
     87     for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
     88         --skip;
     89         if (!listItems[listIndex]->disabled() && isOptionElement(listItems[listIndex])) {
     90             lastGoodIndex = listIndex;
     91             if (skip <= 0)
     92                 break;
     93         }
     94     }
     95     return lastGoodIndex;
     96 }
     97 
     98 static int nextSelectableListIndex(SelectElementData& data, Element* element, int startIndex)
     99 {
    100     return nextValidIndex(data.listItems(element), startIndex, SkipForwards, 1);
    101 }
    102 
    103 static int previousSelectableListIndex(SelectElementData& data, Element* element, int startIndex)
    104 {
    105     if (startIndex == -1)
    106         startIndex = data.listItems(element).size();
    107     return nextValidIndex(data.listItems(element), startIndex, SkipBackwards, 1);
    108 }
    109 
    110 static int firstSelectableListIndex(SelectElementData& data, Element* element)
    111 {
    112     const Vector<Element*>& items = data.listItems(element);
    113     int index = nextValidIndex(items, items.size(), SkipBackwards, INT_MAX);
    114     if (static_cast<unsigned>(index) == items.size())
    115         return -1;
    116     return index;
    117 }
    118 
    119 static int lastSelectableListIndex(SelectElementData& data, Element* element)
    120 {
    121     return nextValidIndex(data.listItems(element), -1, SkipForwards, INT_MAX);
    122 }
    123 
    124 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
    125 static int nextSelectableListIndexPageAway(SelectElementData& data, Element* element, int startIndex, SkipDirection direction)
    126 {
    127     const Vector<Element*>& items = data.listItems(element);
    128     // Can't use data->size() because renderer forces a minimum size.
    129     int pageSize = 0;
    130     if (element->renderer()->isListBox())
    131         pageSize = toRenderListBox(element->renderer())->size() - 1; // -1 so we still show context
    132 
    133     // One page away, but not outside valid bounds.
    134     // If there is a valid option item one page away, the index is chosen.
    135     // If there is no exact one page away valid option, returns startIndex or the most far index.
    136     int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
    137     int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
    138     return nextValidIndex(items, edgeIndex, direction, skipAmount);
    139 }
    140 
    141 void SelectElement::selectAll(SelectElementData& data, Element* element)
    142 {
    143     ASSERT(!data.usesMenuList());
    144     if (!element->renderer() || !data.multiple())
    145         return;
    146 
    147     // Save the selection so it can be compared to the new selectAll selection when dispatching change events
    148     saveLastSelection(data, element);
    149 
    150     data.setActiveSelectionState(true);
    151     setActiveSelectionAnchorIndex(data, element, nextSelectableListIndex(data, element, -1));
    152     setActiveSelectionEndIndex(data, previousSelectableListIndex(data, element, -1));
    153 
    154     updateListBoxSelection(data, element, false);
    155     listBoxOnChange(data, element);
    156 }
    157 
    158 void SelectElement::saveLastSelection(SelectElementData& data, Element* element)
    159 {
    160     if (data.usesMenuList()) {
    161         data.setLastOnChangeIndex(selectedIndex(data, element));
    162         return;
    163     }
    164 
    165     Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection();
    166     lastOnChangeSelection.clear();
    167 
    168     const Vector<Element*>& items = data.listItems(element);
    169     for (unsigned i = 0; i < items.size(); ++i) {
    170         OptionElement* optionElement = toOptionElement(items[i]);
    171         lastOnChangeSelection.append(optionElement && optionElement->selected());
    172     }
    173 }
    174 
    175 void SelectElement::setActiveSelectionAnchorIndex(SelectElementData& data, Element* element, int index)
    176 {
    177     data.setActiveSelectionAnchorIndex(index);
    178 
    179     // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
    180     Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection();
    181     cachedStateForActiveSelection.clear();
    182 
    183     const Vector<Element*>& items = data.listItems(element);
    184     for (unsigned i = 0; i < items.size(); ++i) {
    185         OptionElement* optionElement = toOptionElement(items[i]);
    186         cachedStateForActiveSelection.append(optionElement && optionElement->selected());
    187     }
    188 }
    189 
    190 void SelectElement::setActiveSelectionEndIndex(SelectElementData& data, int index)
    191 {
    192     data.setActiveSelectionEndIndex(index);
    193 }
    194 
    195 void SelectElement::updateListBoxSelection(SelectElementData& data, Element* element, bool deselectOtherOptions)
    196 {
    197     ASSERT(element->renderer() && (element->renderer()->isListBox() || data.multiple()));
    198     ASSERT(!data.listItems(element).size() || data.activeSelectionAnchorIndex() >= 0);
    199 
    200     unsigned start = min(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex());
    201     unsigned end = max(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex());
    202     Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection();
    203 
    204     const Vector<Element*>& items = data.listItems(element);
    205     for (unsigned i = 0; i < items.size(); ++i) {
    206         OptionElement* optionElement = toOptionElement(items[i]);
    207         if (!optionElement || items[i]->disabled())
    208             continue;
    209 
    210         if (i >= start && i <= end)
    211             optionElement->setSelectedState(data.activeSelectionState());
    212         else if (deselectOtherOptions || i >= cachedStateForActiveSelection.size())
    213             optionElement->setSelectedState(false);
    214         else
    215             optionElement->setSelectedState(cachedStateForActiveSelection[i]);
    216     }
    217 
    218     toSelectElement(element)->updateValidity();
    219     scrollToSelection(data, element);
    220 }
    221 
    222 void SelectElement::listBoxOnChange(SelectElementData& data, Element* element)
    223 {
    224     ASSERT(!data.usesMenuList() || data.multiple());
    225 
    226     Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection();
    227     const Vector<Element*>& items = data.listItems(element);
    228 
    229     // If the cached selection list is empty, or the size has changed, then fire dispatchFormControlChangeEvent, and return early.
    230     if (lastOnChangeSelection.isEmpty() || lastOnChangeSelection.size() != items.size()) {
    231         element->dispatchFormControlChangeEvent();
    232         return;
    233     }
    234 
    235     // Update lastOnChangeSelection and fire dispatchFormControlChangeEvent
    236     bool fireOnChange = false;
    237     for (unsigned i = 0; i < items.size(); ++i) {
    238         OptionElement* optionElement = toOptionElement(items[i]);
    239         bool selected = optionElement &&  optionElement->selected();
    240         if (selected != lastOnChangeSelection[i])
    241             fireOnChange = true;
    242         lastOnChangeSelection[i] = selected;
    243     }
    244 
    245     if (fireOnChange)
    246         element->dispatchFormControlChangeEvent();
    247 }
    248 
    249 void SelectElement::menuListOnChange(SelectElementData& data, Element* element)
    250 {
    251     ASSERT(data.usesMenuList());
    252 
    253     int selected = selectedIndex(data, element);
    254     if (data.lastOnChangeIndex() != selected && data.userDrivenChange()) {
    255         data.setLastOnChangeIndex(selected);
    256         data.setUserDrivenChange(false);
    257         element->dispatchFormControlChangeEvent();
    258     }
    259 }
    260 
    261 void SelectElement::scrollToSelection(SelectElementData& data, Element* element)
    262 {
    263     if (data.usesMenuList())
    264         return;
    265 
    266     if (RenderObject* renderer = element->renderer())
    267         toRenderListBox(renderer)->selectionChanged();
    268 }
    269 
    270 void SelectElement::setOptionsChangedOnRenderer(SelectElementData& data, Element* element)
    271 {
    272     if (RenderObject* renderer = element->renderer()) {
    273         if (data.usesMenuList())
    274             toRenderMenuList(renderer)->setOptionsChanged(true);
    275         else
    276             toRenderListBox(renderer)->setOptionsChanged(true);
    277     }
    278 }
    279 
    280 void SelectElement::setRecalcListItems(SelectElementData& data, Element* element)
    281 {
    282     data.setShouldRecalcListItems(true);
    283     data.setActiveSelectionAnchorIndex(-1); // Manual selection anchor is reset when manipulating the select programmatically.
    284     setOptionsChangedOnRenderer(data, element);
    285     element->setNeedsStyleRecalc();
    286 }
    287 
    288 void SelectElement::recalcListItems(SelectElementData& data, const Element* element, bool updateSelectedStates)
    289 {
    290     Vector<Element*>& listItems = data.rawListItems();
    291     listItems.clear();
    292 
    293     data.setShouldRecalcListItems(false);
    294 
    295     OptionElement* foundSelected = 0;
    296     for (Node* currentNode = element->firstChild(); currentNode;) {
    297         if (!currentNode->isElementNode()) {
    298             currentNode = currentNode->traverseNextSibling(element);
    299             continue;
    300         }
    301 
    302         Element* current = static_cast<Element*>(currentNode);
    303 
    304         // optgroup tags may not nest. However, both FireFox and IE will
    305         // flatten the tree automatically, so we follow suit.
    306         // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
    307         if (isOptionGroupElement(current)) {
    308             listItems.append(current);
    309             if (current->firstChild()) {
    310                 currentNode = current->firstChild();
    311                 continue;
    312             }
    313         }
    314 
    315         if (OptionElement* optionElement = toOptionElement(current)) {
    316             listItems.append(current);
    317 
    318             if (updateSelectedStates && !data.multiple()) {
    319                 if (!foundSelected && (data.size() <= 1 || optionElement->selected())) {
    320                     foundSelected = optionElement;
    321                     foundSelected->setSelectedState(true);
    322                 } else if (foundSelected && optionElement->selected()) {
    323                     foundSelected->setSelectedState(false);
    324                     foundSelected = optionElement;
    325                 }
    326             }
    327         }
    328 
    329         if (current->hasTagName(HTMLNames::hrTag))
    330             listItems.append(current);
    331 
    332         // In conforming HTML code, only <optgroup> and <option> will be found
    333         // within a <select>. We call traverseNextSibling so that we only step
    334         // into those tags that we choose to. For web-compat, we should cope
    335         // with the case where odd tags like a <div> have been added but we
    336         // handle this because such tags have already been removed from the
    337         // <select>'s subtree at this point.
    338         currentNode = currentNode->traverseNextSibling(element);
    339     }
    340 }
    341 
    342 int SelectElement::selectedIndex(const SelectElementData& data, const Element* element)
    343 {
    344     unsigned index = 0;
    345 
    346     // return the number of the first option selected
    347     const Vector<Element*>& items = data.listItems(element);
    348     for (size_t i = 0; i < items.size(); ++i) {
    349         if (OptionElement* optionElement = toOptionElement(items[i])) {
    350             if (optionElement->selected())
    351                 return index;
    352             ++index;
    353         }
    354     }
    355 
    356     return -1;
    357 }
    358 
    359 void SelectElement::setSelectedIndex(SelectElementData& data, Element* element, int optionIndex, bool deselect, bool fireOnChangeNow, bool userDrivenChange)
    360 {
    361     if (optionIndex == -1 && !deselect && !data.multiple())
    362         optionIndex = nextSelectableListIndex(data, element, -1);
    363     if (!data.multiple())
    364         deselect = true;
    365 
    366     const Vector<Element*>& items = data.listItems(element);
    367     int listIndex = optionToListIndex(data, element, optionIndex);
    368 
    369     Element* excludeElement = 0;
    370     if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) {
    371         excludeElement = items[listIndex];
    372         if (data.activeSelectionAnchorIndex() < 0 || deselect)
    373             setActiveSelectionAnchorIndex(data, element, listIndex);
    374         if (data.activeSelectionEndIndex() < 0 || deselect)
    375             setActiveSelectionEndIndex(data, listIndex);
    376         optionElement->setSelectedState(true);
    377     }
    378 
    379     if (deselect)
    380         deselectItems(data, element, excludeElement);
    381 
    382     // For the menu list case, this is what makes the selected element appear.
    383     if (RenderObject* renderer = element->renderer())
    384         renderer->updateFromElement();
    385 
    386     scrollToSelection(data, element);
    387 
    388     // This only gets called with fireOnChangeNow for menu lists.
    389     if (data.usesMenuList()) {
    390         data.setUserDrivenChange(userDrivenChange);
    391         if (fireOnChangeNow)
    392             menuListOnChange(data, element);
    393         RenderObject* renderer = element->renderer();
    394         if (renderer) {
    395             if (data.usesMenuList())
    396                 toRenderMenuList(renderer)->didSetSelectedIndex();
    397             else if (renderer->isListBox())
    398                 toRenderListBox(renderer)->selectionChanged();
    399         }
    400     }
    401 
    402     if (Frame* frame = element->document()->frame())
    403         frame->page()->chrome()->client()->formStateDidChange(element);
    404 }
    405 
    406 int SelectElement::optionToListIndex(const SelectElementData& data, const Element* element, int optionIndex)
    407 {
    408     const Vector<Element*>& items = data.listItems(element);
    409     int listSize = (int) items.size();
    410     if (optionIndex < 0 || optionIndex >= listSize)
    411         return -1;
    412 
    413     int optionIndex2 = -1;
    414     for (int listIndex = 0; listIndex < listSize; ++listIndex) {
    415         if (isOptionElement(items[listIndex])) {
    416             ++optionIndex2;
    417             if (optionIndex2 == optionIndex)
    418                 return listIndex;
    419         }
    420     }
    421 
    422     return -1;
    423 }
    424 
    425 int SelectElement::listToOptionIndex(const SelectElementData& data, const Element* element, int listIndex)
    426 {
    427     const Vector<Element*>& items = data.listItems(element);
    428     if (listIndex < 0 || listIndex >= int(items.size()) ||
    429         !isOptionElement(items[listIndex]))
    430         return -1;
    431 
    432     int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
    433     for (int i = 0; i < listIndex; ++i)
    434         if (isOptionElement(items[i]))
    435             ++optionIndex;
    436 
    437     return optionIndex;
    438 }
    439 
    440 void SelectElement::dispatchFocusEvent(SelectElementData& data, Element* element)
    441 {
    442     // Save the selection so it can be compared to the new selection when dispatching change events during blur event dispatchal
    443     if (data.usesMenuList())
    444         saveLastSelection(data, element);
    445 }
    446 
    447 void SelectElement::dispatchBlurEvent(SelectElementData& data, Element* element)
    448 {
    449     // We only need to fire change events here for menu lists, because we fire change events for list boxes whenever the selection change is actually made.
    450     // This matches other browsers' behavior.
    451     if (data.usesMenuList())
    452         menuListOnChange(data, element);
    453 }
    454 
    455 void SelectElement::deselectItems(SelectElementData& data, Element* element, Element* excludeElement)
    456 {
    457     const Vector<Element*>& items = data.listItems(element);
    458     for (unsigned i = 0; i < items.size(); ++i) {
    459         if (items[i] == excludeElement)
    460             continue;
    461 
    462         if (OptionElement* optionElement = toOptionElement(items[i]))
    463             optionElement->setSelectedState(false);
    464     }
    465 }
    466 
    467 bool SelectElement::saveFormControlState(const SelectElementData& data, const Element* element, String& value)
    468 {
    469     const Vector<Element*>& items = data.listItems(element);
    470     int length = items.size();
    471 
    472     // FIXME: Change this code to use the new StringImpl::createUninitialized code path.
    473     Vector<char, 1024> characters(length);
    474     for (int i = 0; i < length; ++i) {
    475         OptionElement* optionElement = toOptionElement(items[i]);
    476         bool selected = optionElement && optionElement->selected();
    477         characters[i] = selected ? 'X' : '.';
    478     }
    479 
    480     value = String(characters.data(), length);
    481     return true;
    482 }
    483 
    484 void SelectElement::restoreFormControlState(SelectElementData& data, Element* element, const String& state)
    485 {
    486     recalcListItems(data, element);
    487 
    488     const Vector<Element*>& items = data.listItems(element);
    489     int length = items.size();
    490 
    491     for (int i = 0; i < length; ++i) {
    492         if (OptionElement* optionElement = toOptionElement(items[i]))
    493             optionElement->setSelectedState(state[i] == 'X');
    494     }
    495 
    496     setOptionsChangedOnRenderer(data, element);
    497 }
    498 
    499 void SelectElement::parseMultipleAttribute(SelectElementData& data, Element* element, Attribute* attribute)
    500 {
    501     bool oldUsesMenuList = data.usesMenuList();
    502     data.setMultiple(!attribute->isNull());
    503     toSelectElement(element)->updateValidity();
    504     if (oldUsesMenuList != data.usesMenuList() && element->attached()) {
    505         element->detach();
    506         element->attach();
    507     }
    508 }
    509 
    510 bool SelectElement::appendFormData(SelectElementData& data, Element* element, FormDataList& list)
    511 {
    512     const AtomicString& name = element->formControlName();
    513     if (name.isEmpty())
    514         return false;
    515 
    516     bool successful = false;
    517     const Vector<Element*>& items = data.listItems(element);
    518 
    519     for (unsigned i = 0; i < items.size(); ++i) {
    520         OptionElement* optionElement = toOptionElement(items[i]);
    521         if (optionElement && optionElement->selected() && !optionElement->disabled()) {
    522             list.appendData(name, optionElement->value());
    523             successful = true;
    524         }
    525     }
    526 
    527     // It's possible that this is a menulist with multiple options and nothing
    528     // will be submitted (!successful). We won't send a unselected non-disabled
    529     // option as fallback. This behavior matches to other browsers.
    530     return successful;
    531 }
    532 
    533 void SelectElement::reset(SelectElementData& data, Element* element)
    534 {
    535     OptionElement* firstOption = 0;
    536     OptionElement* selectedOption = 0;
    537 
    538     const Vector<Element*>& items = data.listItems(element);
    539     for (unsigned i = 0; i < items.size(); ++i) {
    540         OptionElement* optionElement = toOptionElement(items[i]);
    541         if (!optionElement)
    542             continue;
    543 
    544         if (items[i]->fastHasAttribute(HTMLNames::selectedAttr)) {
    545             if (selectedOption && !data.multiple())
    546                 selectedOption->setSelectedState(false);
    547             optionElement->setSelectedState(true);
    548             selectedOption = optionElement;
    549         } else
    550             optionElement->setSelectedState(false);
    551 
    552         if (!firstOption)
    553             firstOption = optionElement;
    554     }
    555 
    556     if (!selectedOption && firstOption && !data.multiple() && data.size() <= 1)
    557         firstOption->setSelectedState(true);
    558 
    559     setOptionsChangedOnRenderer(data, element);
    560     element->setNeedsStyleRecalc();
    561 }
    562 
    563 void SelectElement::menuListDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
    564 {
    565     if (event->type() == eventNames().keydownEvent) {
    566         if (!element->renderer() || !event->isKeyboardEvent())
    567             return;
    568 
    569         const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
    570         bool handled = false;
    571 
    572 #if ARROW_KEYS_POP_MENU
    573         if (!isSpatialNavigationEnabled(element->document()->frame())) {
    574             if (keyIdentifier == "Down" || keyIdentifier == "Up") {
    575                 element->focus();
    576 
    577                 if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
    578                     return;
    579 
    580                 // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
    581                 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
    582                 saveLastSelection(data, element);
    583                 if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
    584                     menuList->showPopup();
    585 
    586                 event->setDefaultHandled();
    587             }
    588             return;
    589         }
    590 #endif
    591         // When using spatial navigation, we want to be able to navigate away from the select element
    592         // when the user hits any of the arrow keys, instead of changing the selection.
    593         if (isSpatialNavigationEnabled(element->document()->frame()))
    594             if (!data.activeSelectionState())
    595                 return;
    596 
    597         UNUSED_PARAM(htmlForm);
    598         const Vector<Element*>& listItems = data.listItems(element);
    599 
    600         int listIndex = optionToListIndex(data, element, selectedIndex(data, element));
    601         if (keyIdentifier == "Down" || keyIdentifier == "Right") {
    602             listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 1);
    603             handled = true;
    604         } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
    605             listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 1);
    606             handled = true;
    607         } else if (keyIdentifier == "PageDown") {
    608             listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 3);
    609             handled = true;
    610         } else if (keyIdentifier == "PageUp") {
    611             listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 3);
    612             handled = true;
    613         } else if (keyIdentifier == "Home") {
    614             listIndex = nextValidIndex(listItems, -1, SkipForwards, 1);
    615             handled = true;
    616         } else if (keyIdentifier == "End") {
    617             listIndex = nextValidIndex(listItems, listItems.size(), SkipBackwards, 1);
    618             handled = true;
    619         }
    620 
    621         if (handled && listIndex >= 0 && static_cast<unsigned>(listIndex) < listItems.size())
    622             setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex));
    623 
    624         if (handled)
    625             event->setDefaultHandled();
    626     }
    627 
    628     // Use key press event here since sending simulated mouse events
    629     // on key down blocks the proper sending of the key press event.
    630     if (event->type() == eventNames().keypressEvent) {
    631         if (!element->renderer() || !event->isKeyboardEvent())
    632             return;
    633 
    634         int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
    635         bool handled = false;
    636 
    637         if (keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) {
    638             // Use space to toggle arrow key handling for selection change or spatial navigation.
    639             data.setActiveSelectionState(!data.activeSelectionState());
    640             event->setDefaultHandled();
    641             return;
    642         }
    643 
    644 #if SPACE_OR_RETURN_POP_MENU
    645         if (keyCode == ' ' || keyCode == '\r') {
    646             element->focus();
    647 
    648             if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
    649                 return;
    650 
    651             // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
    652             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
    653             saveLastSelection(data, element);
    654             if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
    655                 menuList->showPopup();
    656             handled = true;
    657         }
    658 #elif ARROW_KEYS_POP_MENU
    659         if (keyCode == ' ') {
    660             element->focus();
    661 
    662             if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
    663                 return;
    664 
    665             // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
    666             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
    667             saveLastSelection(data, element);
    668             if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
    669                 menuList->showPopup();
    670             handled = true;
    671         } else if (keyCode == '\r') {
    672             if (htmlForm)
    673                 htmlForm->submitImplicitly(event, false);
    674             menuListOnChange(data, element);
    675             handled = true;
    676         }
    677 #else
    678         int listIndex = optionToListIndex(data, element, selectedIndex(data, element));
    679         if (keyCode == '\r') {
    680             // listIndex should already be selected, but this will fire the onchange handler.
    681             setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex), true, true);
    682             handled = true;
    683         }
    684 #endif
    685         if (handled)
    686             event->setDefaultHandled();
    687     }
    688 
    689     if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
    690         element->focus();
    691         if (element->renderer() && element->renderer()->isMenuList()) {
    692             if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) {
    693                 if (menuList->popupIsVisible())
    694                     menuList->hidePopup();
    695                 else {
    696                     // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
    697                     // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
    698                     saveLastSelection(data, element);
    699                     menuList->showPopup();
    700                 }
    701             }
    702         }
    703         event->setDefaultHandled();
    704     }
    705 }
    706 
    707 void SelectElement::updateSelectedState(SelectElementData& data, Element* element, int listIndex,
    708                                         bool multi, bool shift)
    709 {
    710     ASSERT(listIndex >= 0);
    711 
    712     // Save the selection so it can be compared to the new selection when dispatching change events during mouseup, or after autoscroll finishes.
    713     saveLastSelection(data, element);
    714 
    715     data.setActiveSelectionState(true);
    716 
    717     bool shiftSelect = data.multiple() && shift;
    718     bool multiSelect = data.multiple() && multi && !shift;
    719 
    720     Element* clickedElement = data.listItems(element)[listIndex];
    721     OptionElement* option = toOptionElement(clickedElement);
    722     if (option) {
    723         // Keep track of whether an active selection (like during drag selection), should select or deselect
    724         if (option->selected() && multi)
    725             data.setActiveSelectionState(false);
    726 
    727         if (!data.activeSelectionState())
    728             option->setSelectedState(false);
    729     }
    730 
    731     // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
    732     // If no option was clicked, then this will deselect all items in the list.
    733     if (!shiftSelect && !multiSelect)
    734         deselectItems(data, element, clickedElement);
    735 
    736     // If the anchor hasn't been set, and we're doing a single selection or a shift selection, then initialize the anchor to the first selected index.
    737     if (data.activeSelectionAnchorIndex() < 0 && !multiSelect)
    738         setActiveSelectionAnchorIndex(data, element, selectedIndex(data, element));
    739 
    740     // Set the selection state of the clicked option
    741     if (option && !clickedElement->disabled())
    742         option->setSelectedState(true);
    743 
    744     // If there was no selectedIndex() for the previous initialization, or
    745     // If we're doing a single selection, or a multiple selection (using cmd or ctrl), then initialize the anchor index to the listIndex that just got clicked.
    746     if (data.activeSelectionAnchorIndex() < 0 || !shiftSelect)
    747         setActiveSelectionAnchorIndex(data, element, listIndex);
    748 
    749     setActiveSelectionEndIndex(data, listIndex);
    750     updateListBoxSelection(data, element, !multiSelect);
    751 }
    752 
    753 void SelectElement::listBoxDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
    754 {
    755     const Vector<Element*>& listItems = data.listItems(element);
    756 
    757     if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
    758         element->focus();
    759 
    760         if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
    761             return;
    762 
    763         // Convert to coords relative to the list box if needed.
    764         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
    765         IntPoint localOffset = roundedIntPoint(element->renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
    766         int listIndex = toRenderListBox(element->renderer())->listIndexAtOffset(localOffset.x(), localOffset.y());
    767         if (listIndex >= 0) {
    768 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
    769             updateSelectedState(data, element, listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
    770 #else
    771             updateSelectedState(data, element, listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
    772 #endif
    773             if (Frame* frame = element->document()->frame())
    774                 frame->eventHandler()->setMouseDownMayStartAutoscroll();
    775 
    776             event->setDefaultHandled();
    777         }
    778     } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && element->document()->frame()->eventHandler()->autoscrollRenderer() != element->renderer()) {
    779         // This makes sure we fire dispatchFormControlChangeEvent for a single click.  For drag selection, onChange will fire when the autoscroll timer stops.
    780         listBoxOnChange(data, element);
    781     } else if (event->type() == eventNames().keydownEvent) {
    782         if (!event->isKeyboardEvent())
    783             return;
    784         const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
    785 
    786         bool handled = false;
    787         int endIndex = 0;
    788         if (data.activeSelectionEndIndex() < 0) {
    789             // Initialize the end index
    790             if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
    791                 int startIndex = lastSelectedListIndex(data, element);
    792                 handled = true;
    793                 if (keyIdentifier == "Down")
    794                     endIndex = nextSelectableListIndex(data, element, startIndex);
    795                 else
    796                     endIndex = nextSelectableListIndexPageAway(data, element, startIndex, SkipForwards);
    797             } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
    798                 int startIndex = optionToListIndex(data, element, selectedIndex(data, element));
    799                 handled = true;
    800                 if (keyIdentifier == "Up")
    801                     endIndex = previousSelectableListIndex(data, element, startIndex);
    802                 else
    803                     endIndex = nextSelectableListIndexPageAway(data, element, startIndex, SkipBackwards);
    804             }
    805         } else {
    806             // Set the end index based on the current end index
    807             if (keyIdentifier == "Down") {
    808                 endIndex = nextSelectableListIndex(data, element, data.activeSelectionEndIndex());
    809                 handled = true;
    810             } else if (keyIdentifier == "Up") {
    811                 endIndex = previousSelectableListIndex(data, element, data.activeSelectionEndIndex());
    812                 handled = true;
    813             } else if (keyIdentifier == "PageDown") {
    814                 endIndex = nextSelectableListIndexPageAway(data, element, data.activeSelectionEndIndex(), SkipForwards);
    815                 handled = true;
    816             } else if (keyIdentifier == "PageUp") {
    817                 endIndex = nextSelectableListIndexPageAway(data, element, data.activeSelectionEndIndex(), SkipBackwards);
    818                 handled = true;
    819             }
    820         }
    821         if (keyIdentifier == "Home") {
    822             endIndex = firstSelectableListIndex(data, element);
    823             handled = true;
    824         } else if (keyIdentifier == "End") {
    825             endIndex = lastSelectableListIndex(data, element);
    826             handled = true;
    827         }
    828 
    829         if (isSpatialNavigationEnabled(element->document()->frame()))
    830             // Check if the selection moves to the boundary.
    831             if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == data.activeSelectionEndIndex()))
    832                 return;
    833 
    834         if (endIndex >= 0 && handled) {
    835             // Save the selection so it can be compared to the new selection when dispatching change events immediately after making the new selection.
    836             saveLastSelection(data, element);
    837 
    838             ASSERT_UNUSED(listItems, !listItems.size() || (endIndex >= 0 && static_cast<unsigned>(endIndex) < listItems.size()));
    839             setActiveSelectionEndIndex(data, endIndex);
    840 
    841             bool selectNewItem = !data.multiple() || static_cast<KeyboardEvent*>(event)->shiftKey() || !isSpatialNavigationEnabled(element->document()->frame());
    842             if (selectNewItem)
    843                 data.setActiveSelectionState(true);
    844             // If the anchor is unitialized, or if we're going to deselect all other options, then set the anchor index equal to the end index.
    845             bool deselectOthers = !data.multiple() || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem);
    846             if (data.activeSelectionAnchorIndex() < 0 || deselectOthers) {
    847                 if (deselectOthers)
    848                     deselectItems(data, element);
    849                 setActiveSelectionAnchorIndex(data, element, data.activeSelectionEndIndex());
    850             }
    851 
    852             toRenderListBox(element->renderer())->scrollToRevealElementAtListIndex(endIndex);
    853             if (selectNewItem) {
    854                 updateListBoxSelection(data, element, deselectOthers);
    855                 listBoxOnChange(data, element);
    856             } else
    857                 scrollToSelection(data, element);
    858 
    859             event->setDefaultHandled();
    860         }
    861     } else if (event->type() == eventNames().keypressEvent) {
    862         if (!event->isKeyboardEvent())
    863             return;
    864         int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
    865 
    866         if (keyCode == '\r') {
    867             if (htmlForm)
    868                 htmlForm->submitImplicitly(event, false);
    869             event->setDefaultHandled();
    870         } else if (data.multiple() && keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) {
    871             // Use space to toggle selection change.
    872             data.setActiveSelectionState(!data.activeSelectionState());
    873             updateSelectedState(data, element, listToOptionIndex(data, element, data.activeSelectionEndIndex()), true /*multi*/, false /*shift*/);
    874             listBoxOnChange(data, element);
    875             event->setDefaultHandled();
    876         }
    877     }
    878 }
    879 
    880 void SelectElement::defaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
    881 {
    882     if (!element->renderer())
    883         return;
    884 
    885     if (data.usesMenuList())
    886         menuListDefaultEventHandler(data, element, event, htmlForm);
    887     else
    888         listBoxDefaultEventHandler(data, element, event, htmlForm);
    889 
    890     if (event->defaultHandled())
    891         return;
    892 
    893     if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) {
    894         KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event);
    895         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
    896             typeAheadFind(data, element, keyboardEvent);
    897             event->setDefaultHandled();
    898             return;
    899         }
    900     }
    901 }
    902 
    903 int SelectElement::lastSelectedListIndex(const SelectElementData& data, const Element* element)
    904 {
    905     // return the number of the last option selected
    906     unsigned index = 0;
    907     bool found = false;
    908     const Vector<Element*>& items = data.listItems(element);
    909     for (size_t i = 0; i < items.size(); ++i) {
    910         if (OptionElement* optionElement = toOptionElement(items[i])) {
    911             if (optionElement->selected()) {
    912                 index = i;
    913                 found = true;
    914             }
    915         }
    916     }
    917 
    918     return found ? (int) index : -1;
    919 }
    920 
    921 static String stripLeadingWhiteSpace(const String& string)
    922 {
    923     int length = string.length();
    924 
    925     int i;
    926     for (i = 0; i < length; ++i) {
    927         if (string[i] != noBreakSpace && (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
    928             break;
    929     }
    930 
    931     return string.substring(i, length - i);
    932 }
    933 
    934 void SelectElement::typeAheadFind(SelectElementData& data, Element* element, KeyboardEvent* event)
    935 {
    936     if (event->timeStamp() < data.lastCharTime())
    937         return;
    938 
    939     DOMTimeStamp delta = event->timeStamp() - data.lastCharTime();
    940     data.setLastCharTime(event->timeStamp());
    941 
    942     UChar c = event->charCode();
    943 
    944     String prefix;
    945     int searchStartOffset = 1;
    946     if (delta > typeAheadTimeout) {
    947         prefix = String(&c, 1);
    948         data.setTypedString(prefix);
    949         data.setRepeatingChar(c);
    950     } else {
    951         data.typedString().append(c);
    952 
    953         if (c == data.repeatingChar())
    954             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
    955             prefix = String(&c, 1);
    956         else {
    957             data.setRepeatingChar(0);
    958             prefix = data.typedString();
    959             searchStartOffset = 0;
    960         }
    961     }
    962 
    963     const Vector<Element*>& items = data.listItems(element);
    964     int itemCount = items.size();
    965     if (itemCount < 1)
    966         return;
    967 
    968     int selected = selectedIndex(data, element);
    969     int index = (optionToListIndex(data, element, selected >= 0 ? selected : 0) + searchStartOffset) % itemCount;
    970     ASSERT(index >= 0);
    971 
    972     // Compute a case-folded copy of the prefix string before beginning the search for
    973     // a matching element. This code uses foldCase to work around the fact that
    974     // String::startWith does not fold non-ASCII characters. This code can be changed
    975     // to use startWith once that is fixed.
    976     String prefixWithCaseFolded(prefix.foldCase());
    977     for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) {
    978         OptionElement* optionElement = toOptionElement(items[index]);
    979         if (!optionElement || items[index]->disabled())
    980             continue;
    981 
    982         // Fold the option string and check if its prefix is equal to the folded prefix.
    983         String text = optionElement->textIndentedToRespectGroupLabel();
    984         if (stripLeadingWhiteSpace(text).foldCase().startsWith(prefixWithCaseFolded)) {
    985             setSelectedIndex(data, element, listToOptionIndex(data, element, index));
    986             if (!data.usesMenuList())
    987                 listBoxOnChange(data, element);
    988 
    989             setOptionsChangedOnRenderer(data, element);
    990             element->setNeedsStyleRecalc();
    991             return;
    992         }
    993     }
    994 }
    995 
    996 void SelectElement::insertedIntoTree(SelectElementData& data, Element* element)
    997 {
    998     // When the element is created during document parsing, it won't have any items yet - but for innerHTML
    999     // and related methods, this method is called after the whole subtree is constructed.
   1000     recalcListItems(data, element, true);
   1001 }
   1002 
   1003 void SelectElement::accessKeySetSelectedIndex(SelectElementData& data, Element* element, int index)
   1004 {
   1005     // first bring into focus the list box
   1006     if (!element->focused())
   1007         element->accessKeyAction(false);
   1008 
   1009     // if this index is already selected, unselect. otherwise update the selected index
   1010     const Vector<Element*>& items = data.listItems(element);
   1011     int listIndex = optionToListIndex(data, element, index);
   1012     if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) {
   1013         if (optionElement->selected())
   1014             optionElement->setSelectedState(false);
   1015         else
   1016             setSelectedIndex(data, element, index, false, true);
   1017     }
   1018 
   1019     if (data.usesMenuList())
   1020         menuListOnChange(data, element);
   1021     else
   1022         listBoxOnChange(data, element);
   1023 
   1024     scrollToSelection(data, element);
   1025 }
   1026 
   1027 unsigned SelectElement::optionCount(const SelectElementData& data, const Element* element)
   1028 {
   1029     unsigned options = 0;
   1030 
   1031     const Vector<Element*>& items = data.listItems(element);
   1032     for (unsigned i = 0; i < items.size(); ++i) {
   1033         if (isOptionElement(items[i]))
   1034             ++options;
   1035     }
   1036 
   1037     return options;
   1038 }
   1039 
   1040 // SelectElementData
   1041 SelectElementData::SelectElementData()
   1042     : m_multiple(false)
   1043     , m_size(0)
   1044     , m_lastOnChangeIndex(-1)
   1045     , m_activeSelectionState(false)
   1046     , m_activeSelectionAnchorIndex(-1)
   1047     , m_activeSelectionEndIndex(-1)
   1048     , m_recalcListItems(false)
   1049     , m_repeatingChar(0)
   1050     , m_lastCharTime(0)
   1051 {
   1052 }
   1053 
   1054 SelectElementData::~SelectElementData()
   1055 {
   1056 }
   1057 
   1058 void SelectElementData::checkListItems(const Element* element) const
   1059 {
   1060 #if !ASSERT_DISABLED
   1061     Vector<Element*> items = m_listItems;
   1062     SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element, false);
   1063     ASSERT(items == m_listItems);
   1064 #else
   1065     UNUSED_PARAM(element);
   1066 #endif
   1067 }
   1068 
   1069 Vector<Element*>& SelectElementData::listItems(const Element* element)
   1070 {
   1071     if (m_recalcListItems)
   1072         SelectElement::recalcListItems(*this, element);
   1073     else
   1074         checkListItems(element);
   1075 
   1076     return m_listItems;
   1077 }
   1078 
   1079 const Vector<Element*>& SelectElementData::listItems(const Element* element) const
   1080 {
   1081     if (m_recalcListItems)
   1082         SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element);
   1083     else
   1084         checkListItems(element);
   1085 
   1086     return m_listItems;
   1087 }
   1088 
   1089 SelectElement* toSelectElement(Element* element)
   1090 {
   1091     if (element->isHTMLElement() && element->hasTagName(HTMLNames::selectTag))
   1092         return static_cast<HTMLSelectElement*>(element);
   1093 
   1094 #if ENABLE(WML)
   1095     if (element->isWMLElement() && element->hasTagName(WMLNames::selectTag))
   1096         return static_cast<WMLSelectElement*>(element);
   1097 #endif
   1098 
   1099     return 0;
   1100 }
   1101 
   1102 }
   1103