2 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
3 * Copyright (C) 2011 Apple Inc. All rights reserved.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
23 #include "SelectElement.h"
25 #include "Attribute.h"
27 #include "ChromeClient.h"
30 #include "EventHandler.h"
31 #include "EventNames.h"
32 #include "FormDataList.h"
34 #include "HTMLFormElement.h"
35 #include "HTMLNames.h"
36 #include "HTMLSelectElement.h"
37 #include "KeyboardEvent.h"
38 #include "MouseEvent.h"
39 #include "OptionElement.h"
40 #include "OptionGroupElement.h"
42 #include "RenderListBox.h"
43 #include "RenderMenuList.h"
44 #include "SpatialNavigation.h"
45 #include <wtf/Assertions.h>
46 #include <wtf/unicode/CharacterNames.h>
48 // Configure platform-specific behavior when focused pop-up receives arrow/space/return keystroke.
49 // (PLATFORM(MAC) and PLATFORM(GTK) are always false in Chromium, hence the extra tests.)
50 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
51 #define ARROW_KEYS_POP_MENU 1
52 #define SPACE_OR_RETURN_POP_MENU 0
53 #elif PLATFORM(GTK) || (PLATFORM(CHROMIUM) && OS(UNIX))
54 #define ARROW_KEYS_POP_MENU 0
55 #define SPACE_OR_RETURN_POP_MENU 1
57 #define ARROW_KEYS_POP_MENU 0
58 #define SPACE_OR_RETURN_POP_MENU 0
64 using namespace Unicode;
68 static const DOMTimeStamp typeAheadTimeout = 1000;
75 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
76 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
77 // Otherwise, it returns |listIndex|.
78 // Valid means that it is enabled and an option element.
79 static int nextValidIndex(const Vector<Element*>& listItems, int listIndex, SkipDirection direction, int skip)
81 ASSERT(direction == -1 || direction == 1);
82 int lastGoodIndex = listIndex;
83 int size = listItems.size();
84 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
86 if (!listItems[listIndex]->disabled() && isOptionElement(listItems[listIndex])) {
87 lastGoodIndex = listIndex;
95 static int nextSelectableListIndex(SelectElementData& data, Element* element, int startIndex)
97 return nextValidIndex(data.listItems(element), startIndex, SkipForwards, 1);
100 static int previousSelectableListIndex(SelectElementData& data, Element* element, int startIndex)
102 if (startIndex == -1)
103 startIndex = data.listItems(element).size();
104 return nextValidIndex(data.listItems(element), startIndex, SkipBackwards, 1);
107 static int firstSelectableListIndex(SelectElementData& data, Element* element)
109 const Vector<Element*>& items = data.listItems(element);
110 int index = nextValidIndex(items, items.size(), SkipBackwards, INT_MAX);
111 if (static_cast<unsigned>(index) == items.size())
116 static int lastSelectableListIndex(SelectElementData& data, Element* element)
118 return nextValidIndex(data.listItems(element), -1, SkipForwards, INT_MAX);
121 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
122 static int nextSelectableListIndexPageAway(SelectElementData& data, Element* element, int startIndex, SkipDirection direction)
124 const Vector<Element*>& items = data.listItems(element);
125 // Can't use data->size() because renderer forces a minimum size.
127 if (element->renderer()->isListBox())
128 pageSize = toRenderListBox(element->renderer())->size() - 1; // -1 so we still show context
130 // One page away, but not outside valid bounds.
131 // If there is a valid option item one page away, the index is chosen.
132 // If there is no exact one page away valid option, returns startIndex or the most far index.
133 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
134 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
135 return nextValidIndex(items, edgeIndex, direction, skipAmount);
138 void SelectElement::selectAll(SelectElementData& data, Element* element)
140 ASSERT(!data.usesMenuList());
141 if (!element->renderer() || !data.multiple())
144 // Save the selection so it can be compared to the new selectAll selection when dispatching change events
145 saveLastSelection(data, element);
147 data.setActiveSelectionState(true);
148 setActiveSelectionAnchorIndex(data, element, nextSelectableListIndex(data, element, -1));
149 setActiveSelectionEndIndex(data, previousSelectableListIndex(data, element, -1));
151 updateListBoxSelection(data, element, false);
152 listBoxOnChange(data, element);
155 void SelectElement::saveLastSelection(SelectElementData& data, Element* element)
157 if (data.usesMenuList()) {
158 data.setLastOnChangeIndex(selectedIndex(data, element));
162 Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection();
163 lastOnChangeSelection.clear();
165 const Vector<Element*>& items = data.listItems(element);
166 for (unsigned i = 0; i < items.size(); ++i) {
167 OptionElement* optionElement = toOptionElement(items[i]);
168 lastOnChangeSelection.append(optionElement && optionElement->selected());
172 void SelectElement::setActiveSelectionAnchorIndex(SelectElementData& data, Element* element, int index)
174 data.setActiveSelectionAnchorIndex(index);
176 // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
177 Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection();
178 cachedStateForActiveSelection.clear();
180 const Vector<Element*>& items = data.listItems(element);
181 for (unsigned i = 0; i < items.size(); ++i) {
182 OptionElement* optionElement = toOptionElement(items[i]);
183 cachedStateForActiveSelection.append(optionElement && optionElement->selected());
187 void SelectElement::setActiveSelectionEndIndex(SelectElementData& data, int index)
189 data.setActiveSelectionEndIndex(index);
192 void SelectElement::updateListBoxSelection(SelectElementData& data, Element* element, bool deselectOtherOptions)
194 ASSERT(element->renderer() && (element->renderer()->isListBox() || data.multiple()));
195 ASSERT(!data.listItems(element).size() || data.activeSelectionAnchorIndex() >= 0);
197 unsigned start = min(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex());
198 unsigned end = max(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex());
199 Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection();
201 const Vector<Element*>& items = data.listItems(element);
202 for (unsigned i = 0; i < items.size(); ++i) {
203 OptionElement* optionElement = toOptionElement(items[i]);
204 if (!optionElement || items[i]->disabled())
207 if (i >= start && i <= end)
208 optionElement->setSelectedState(data.activeSelectionState());
209 else if (deselectOtherOptions || i >= cachedStateForActiveSelection.size())
210 optionElement->setSelectedState(false);
212 optionElement->setSelectedState(cachedStateForActiveSelection[i]);
215 toSelectElement(element)->updateValidity();
216 scrollToSelection(data, element);
219 void SelectElement::listBoxOnChange(SelectElementData& data, Element* element)
221 ASSERT(!data.usesMenuList() || data.multiple());
223 Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection();
224 const Vector<Element*>& items = data.listItems(element);
226 // If the cached selection list is empty, or the size has changed, then fire dispatchFormControlChangeEvent, and return early.
227 if (lastOnChangeSelection.isEmpty() || lastOnChangeSelection.size() != items.size()) {
228 element->dispatchFormControlChangeEvent();
232 // Update lastOnChangeSelection and fire dispatchFormControlChangeEvent
233 bool fireOnChange = false;
234 for (unsigned i = 0; i < items.size(); ++i) {
235 OptionElement* optionElement = toOptionElement(items[i]);
236 bool selected = optionElement && optionElement->selected();
237 if (selected != lastOnChangeSelection[i])
239 lastOnChangeSelection[i] = selected;
243 element->dispatchFormControlChangeEvent();
246 void SelectElement::menuListOnChange(SelectElementData& data, Element* element)
248 ASSERT(data.usesMenuList());
250 int selected = selectedIndex(data, element);
251 if (data.lastOnChangeIndex() != selected && data.userDrivenChange()) {
252 data.setLastOnChangeIndex(selected);
253 data.setUserDrivenChange(false);
254 element->dispatchFormControlChangeEvent();
258 void SelectElement::scrollToSelection(SelectElementData& data, Element* element)
260 if (data.usesMenuList())
263 if (RenderObject* renderer = element->renderer())
264 toRenderListBox(renderer)->selectionChanged();
267 void SelectElement::setOptionsChangedOnRenderer(SelectElementData& data, Element* element)
269 if (RenderObject* renderer = element->renderer()) {
270 if (data.usesMenuList())
271 toRenderMenuList(renderer)->setOptionsChanged(true);
273 toRenderListBox(renderer)->setOptionsChanged(true);
277 void SelectElement::setRecalcListItems(SelectElementData& data, Element* element)
279 data.setShouldRecalcListItems(true);
280 data.setActiveSelectionAnchorIndex(-1); // Manual selection anchor is reset when manipulating the select programmatically.
281 setOptionsChangedOnRenderer(data, element);
282 element->setNeedsStyleRecalc();
285 void SelectElement::recalcListItems(SelectElementData& data, const Element* element, bool updateSelectedStates)
287 Vector<Element*>& listItems = data.rawListItems();
290 data.setShouldRecalcListItems(false);
292 OptionElement* foundSelected = 0;
293 for (Node* currentNode = element->firstChild(); currentNode;) {
294 if (!currentNode->isElementNode()) {
295 currentNode = currentNode->traverseNextSibling(element);
299 Element* current = static_cast<Element*>(currentNode);
301 // optgroup tags may not nest. However, both FireFox and IE will
302 // flatten the tree automatically, so we follow suit.
303 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
304 if (isOptionGroupElement(current)) {
305 listItems.append(current);
306 if (current->firstChild()) {
307 currentNode = current->firstChild();
312 if (OptionElement* optionElement = toOptionElement(current)) {
313 listItems.append(current);
315 if (updateSelectedStates && !data.multiple()) {
316 if (!foundSelected && (data.size() <= 1 || optionElement->selected())) {
317 foundSelected = optionElement;
318 foundSelected->setSelectedState(true);
319 } else if (foundSelected && optionElement->selected()) {
320 foundSelected->setSelectedState(false);
321 foundSelected = optionElement;
326 if (current->hasTagName(HTMLNames::hrTag))
327 listItems.append(current);
329 // In conforming HTML code, only <optgroup> and <option> will be found
330 // within a <select>. We call traverseNextSibling so that we only step
331 // into those tags that we choose to. For web-compat, we should cope
332 // with the case where odd tags like a <div> have been added but we
333 // handle this because such tags have already been removed from the
334 // <select>'s subtree at this point.
335 currentNode = currentNode->traverseNextSibling(element);
339 int SelectElement::selectedIndex(const SelectElementData& data, const Element* element)
343 // return the number of the first option selected
344 const Vector<Element*>& items = data.listItems(element);
345 for (size_t i = 0; i < items.size(); ++i) {
346 if (OptionElement* optionElement = toOptionElement(items[i])) {
347 if (optionElement->selected())
356 void SelectElement::setSelectedIndex(SelectElementData& data, Element* element, int optionIndex, bool deselect, bool fireOnChangeNow, bool userDrivenChange)
358 if (optionIndex == -1 && !deselect && !data.multiple())
359 optionIndex = nextSelectableListIndex(data, element, -1);
360 if (!data.multiple())
363 const Vector<Element*>& items = data.listItems(element);
364 int listIndex = optionToListIndex(data, element, optionIndex);
366 Element* excludeElement = 0;
367 if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) {
368 excludeElement = items[listIndex];
369 if (data.activeSelectionAnchorIndex() < 0 || deselect)
370 setActiveSelectionAnchorIndex(data, element, listIndex);
371 if (data.activeSelectionEndIndex() < 0 || deselect)
372 setActiveSelectionEndIndex(data, listIndex);
373 optionElement->setSelectedState(true);
377 deselectItems(data, element, excludeElement);
379 // For the menu list case, this is what makes the selected element appear.
380 if (RenderObject* renderer = element->renderer())
381 renderer->updateFromElement();
383 scrollToSelection(data, element);
385 // This only gets called with fireOnChangeNow for menu lists.
386 if (data.usesMenuList()) {
387 data.setUserDrivenChange(userDrivenChange);
389 menuListOnChange(data, element);
390 RenderObject* renderer = element->renderer();
392 if (data.usesMenuList())
393 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
394 else if (renderer->isListBox())
395 toRenderListBox(renderer)->selectionChanged();
399 toSelectElement(element)->updateValidity();
400 if (Frame* frame = element->document()->frame())
401 frame->page()->chrome()->client()->formStateDidChange(element);
404 int SelectElement::optionToListIndex(const SelectElementData& data, const Element* element, int optionIndex)
406 const Vector<Element*>& items = data.listItems(element);
407 int listSize = (int) items.size();
408 if (optionIndex < 0 || optionIndex >= listSize)
411 int optionIndex2 = -1;
412 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
413 if (isOptionElement(items[listIndex])) {
415 if (optionIndex2 == optionIndex)
423 int SelectElement::listToOptionIndex(const SelectElementData& data, const Element* element, int listIndex)
425 const Vector<Element*>& items = data.listItems(element);
426 if (listIndex < 0 || listIndex >= int(items.size()) ||
427 !isOptionElement(items[listIndex]))
430 int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
431 for (int i = 0; i < listIndex; ++i)
432 if (isOptionElement(items[i]))
438 void SelectElement::dispatchFocusEvent(SelectElementData& data, Element* element)
440 // Save the selection so it can be compared to the new selection when dispatching change events during blur event dispatchal
441 if (data.usesMenuList())
442 saveLastSelection(data, element);
445 void SelectElement::dispatchBlurEvent(SelectElementData& data, Element* element)
447 // 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.
448 // This matches other browsers' behavior.
449 if (data.usesMenuList())
450 menuListOnChange(data, element);
453 void SelectElement::deselectItems(SelectElementData& data, Element* element, Element* excludeElement)
455 const Vector<Element*>& items = data.listItems(element);
456 for (unsigned i = 0; i < items.size(); ++i) {
457 if (items[i] == excludeElement)
460 if (OptionElement* optionElement = toOptionElement(items[i]))
461 optionElement->setSelectedState(false);
465 bool SelectElement::saveFormControlState(const SelectElementData& data, const Element* element, String& value)
467 const Vector<Element*>& items = data.listItems(element);
468 int length = items.size();
470 // FIXME: Change this code to use the new StringImpl::createUninitialized code path.
471 Vector<char, 1024> characters(length);
472 for (int i = 0; i < length; ++i) {
473 OptionElement* optionElement = toOptionElement(items[i]);
474 bool selected = optionElement && optionElement->selected();
475 characters[i] = selected ? 'X' : '.';
478 value = String(characters.data(), length);
482 void SelectElement::restoreFormControlState(SelectElementData& data, Element* element, const String& state)
484 recalcListItems(data, element);
486 const Vector<Element*>& items = data.listItems(element);
487 int length = items.size();
489 for (int i = 0; i < length; ++i) {
490 if (OptionElement* optionElement = toOptionElement(items[i]))
491 optionElement->setSelectedState(state[i] == 'X');
494 setOptionsChangedOnRenderer(data, element);
497 void SelectElement::parseMultipleAttribute(SelectElementData& data, Element* element, Attribute* attribute)
499 bool oldUsesMenuList = data.usesMenuList();
500 data.setMultiple(!attribute->isNull());
501 toSelectElement(element)->updateValidity();
502 if (oldUsesMenuList != data.usesMenuList() && element->attached()) {
508 bool SelectElement::appendFormData(SelectElementData& data, Element* element, FormDataList& list)
510 const AtomicString& name = element->formControlName();
514 bool successful = false;
515 const Vector<Element*>& items = data.listItems(element);
517 for (unsigned i = 0; i < items.size(); ++i) {
518 OptionElement* optionElement = toOptionElement(items[i]);
519 if (optionElement && optionElement->selected() && !optionElement->disabled()) {
520 list.appendData(name, optionElement->value());
525 // It's possible that this is a menulist with multiple options and nothing
526 // will be submitted (!successful). We won't send a unselected non-disabled
527 // option as fallback. This behavior matches to other browsers.
531 void SelectElement::reset(SelectElementData& data, Element* element)
533 OptionElement* firstOption = 0;
534 OptionElement* selectedOption = 0;
536 const Vector<Element*>& items = data.listItems(element);
537 for (unsigned i = 0; i < items.size(); ++i) {
538 OptionElement* optionElement = toOptionElement(items[i]);
542 if (items[i]->fastHasAttribute(HTMLNames::selectedAttr)) {
543 if (selectedOption && !data.multiple())
544 selectedOption->setSelectedState(false);
545 optionElement->setSelectedState(true);
546 selectedOption = optionElement;
548 optionElement->setSelectedState(false);
551 firstOption = optionElement;
554 if (!selectedOption && firstOption && !data.multiple() && data.size() <= 1)
555 firstOption->setSelectedState(true);
557 setOptionsChangedOnRenderer(data, element);
558 element->setNeedsStyleRecalc();
561 #if !PLATFORM(WIN) || OS(WINCE)
562 bool SelectElement::platformHandleKeydownEvent(SelectElementData& data, Element* element, KeyboardEvent* event)
564 #if ARROW_KEYS_POP_MENU
565 if (!isSpatialNavigationEnabled(element->document()->frame())) {
566 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
569 // Calling focus() may cause us to lose our renderer. Return true so that our caller doesn't process the event
570 // further, but don't set the event as handled.
571 if (!element->renderer())
574 // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
575 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
576 saveLastSelection(data, element);
577 if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
578 menuList->showPopup();
579 event->setDefaultHandled();
588 void SelectElement::menuListDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
590 if (event->type() == eventNames().keydownEvent) {
591 if (!element->renderer() || !event->isKeyboardEvent())
594 if (platformHandleKeydownEvent(data, element, static_cast<KeyboardEvent*>(event)))
597 // When using spatial navigation, we want to be able to navigate away from the select element
598 // when the user hits any of the arrow keys, instead of changing the selection.
599 if (isSpatialNavigationEnabled(element->document()->frame())) {
600 if (!data.activeSelectionState())
604 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
605 bool handled = false;
607 UNUSED_PARAM(htmlForm);
608 const Vector<Element*>& listItems = data.listItems(element);
610 int listIndex = optionToListIndex(data, element, selectedIndex(data, element));
612 if (keyIdentifier == "Down" || keyIdentifier == "Right") {
613 listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 1);
615 } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
616 listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 1);
618 } else if (keyIdentifier == "PageDown") {
619 listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 3);
621 } else if (keyIdentifier == "PageUp") {
622 listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 3);
624 } else if (keyIdentifier == "Home") {
625 listIndex = nextValidIndex(listItems, -1, SkipForwards, 1);
627 } else if (keyIdentifier == "End") {
628 listIndex = nextValidIndex(listItems, listItems.size(), SkipBackwards, 1);
632 if (handled && listIndex >= 0 && static_cast<unsigned>(listIndex) < listItems.size())
633 setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex));
636 event->setDefaultHandled();
639 // Use key press event here since sending simulated mouse events
640 // on key down blocks the proper sending of the key press event.
641 if (event->type() == eventNames().keypressEvent) {
642 if (!element->renderer() || !event->isKeyboardEvent())
645 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
646 bool handled = false;
648 if (keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) {
649 // Use space to toggle arrow key handling for selection change or spatial navigation.
650 data.setActiveSelectionState(!data.activeSelectionState());
651 event->setDefaultHandled();
655 #if SPACE_OR_RETURN_POP_MENU
656 if (keyCode == ' ' || keyCode == '\r') {
659 if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
662 // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
663 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
664 saveLastSelection(data, element);
665 if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
666 menuList->showPopup();
669 #elif ARROW_KEYS_POP_MENU
670 if (keyCode == ' ') {
673 if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
676 // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
677 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
678 saveLastSelection(data, element);
679 if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
680 menuList->showPopup();
682 } else if (keyCode == '\r') {
684 htmlForm->submitImplicitly(event, false);
685 menuListOnChange(data, element);
689 int listIndex = optionToListIndex(data, element, selectedIndex(data, element));
690 if (keyCode == '\r') {
691 // listIndex should already be selected, but this will fire the onchange handler.
692 setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex), true, true);
697 event->setDefaultHandled();
700 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
702 if (element->renderer() && element->renderer()->isMenuList()) {
703 if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) {
704 if (menuList->popupIsVisible())
705 menuList->hidePopup();
707 // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
708 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
709 saveLastSelection(data, element);
710 menuList->showPopup();
714 event->setDefaultHandled();
718 void SelectElement::updateSelectedState(SelectElementData& data, Element* element, int listIndex,
719 bool multi, bool shift)
721 ASSERT(listIndex >= 0);
723 // Save the selection so it can be compared to the new selection when dispatching change events during mouseup, or after autoscroll finishes.
724 saveLastSelection(data, element);
726 data.setActiveSelectionState(true);
728 bool shiftSelect = data.multiple() && shift;
729 bool multiSelect = data.multiple() && multi && !shift;
731 Element* clickedElement = data.listItems(element)[listIndex];
732 OptionElement* option = toOptionElement(clickedElement);
734 // Keep track of whether an active selection (like during drag selection), should select or deselect
735 if (option->selected() && multi)
736 data.setActiveSelectionState(false);
738 if (!data.activeSelectionState())
739 option->setSelectedState(false);
742 // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
743 // If no option was clicked, then this will deselect all items in the list.
744 if (!shiftSelect && !multiSelect)
745 deselectItems(data, element, clickedElement);
747 // 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.
748 if (data.activeSelectionAnchorIndex() < 0 && !multiSelect)
749 setActiveSelectionAnchorIndex(data, element, selectedIndex(data, element));
751 // Set the selection state of the clicked option
752 if (option && !clickedElement->disabled())
753 option->setSelectedState(true);
755 // If there was no selectedIndex() for the previous initialization, or
756 // 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.
757 if (data.activeSelectionAnchorIndex() < 0 || !shiftSelect)
758 setActiveSelectionAnchorIndex(data, element, listIndex);
760 setActiveSelectionEndIndex(data, listIndex);
761 updateListBoxSelection(data, element, !multiSelect);
764 void SelectElement::listBoxDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
766 const Vector<Element*>& listItems = data.listItems(element);
768 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
771 if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
774 // Convert to coords relative to the list box if needed.
775 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
776 IntPoint localOffset = roundedIntPoint(element->renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
777 int listIndex = toRenderListBox(element->renderer())->listIndexAtOffset(toSize(localOffset));
778 if (listIndex >= 0) {
779 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
780 updateSelectedState(data, element, listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
782 updateSelectedState(data, element, listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
784 if (Frame* frame = element->document()->frame())
785 frame->eventHandler()->setMouseDownMayStartAutoscroll();
787 event->setDefaultHandled();
789 } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && element->document()->frame()->eventHandler()->autoscrollRenderer() != element->renderer()) {
790 // This makes sure we fire dispatchFormControlChangeEvent for a single click. For drag selection, onChange will fire when the autoscroll timer stops.
791 listBoxOnChange(data, element);
792 } else if (event->type() == eventNames().keydownEvent) {
793 if (!event->isKeyboardEvent())
795 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
797 bool handled = false;
799 if (data.activeSelectionEndIndex() < 0) {
800 // Initialize the end index
801 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
802 int startIndex = lastSelectedListIndex(data, element);
804 if (keyIdentifier == "Down")
805 endIndex = nextSelectableListIndex(data, element, startIndex);
807 endIndex = nextSelectableListIndexPageAway(data, element, startIndex, SkipForwards);
808 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
809 int startIndex = optionToListIndex(data, element, selectedIndex(data, element));
811 if (keyIdentifier == "Up")
812 endIndex = previousSelectableListIndex(data, element, startIndex);
814 endIndex = nextSelectableListIndexPageAway(data, element, startIndex, SkipBackwards);
817 // Set the end index based on the current end index
818 if (keyIdentifier == "Down") {
819 endIndex = nextSelectableListIndex(data, element, data.activeSelectionEndIndex());
821 } else if (keyIdentifier == "Up") {
822 endIndex = previousSelectableListIndex(data, element, data.activeSelectionEndIndex());
824 } else if (keyIdentifier == "PageDown") {
825 endIndex = nextSelectableListIndexPageAway(data, element, data.activeSelectionEndIndex(), SkipForwards);
827 } else if (keyIdentifier == "PageUp") {
828 endIndex = nextSelectableListIndexPageAway(data, element, data.activeSelectionEndIndex(), SkipBackwards);
832 if (keyIdentifier == "Home") {
833 endIndex = firstSelectableListIndex(data, element);
835 } else if (keyIdentifier == "End") {
836 endIndex = lastSelectableListIndex(data, element);
840 if (isSpatialNavigationEnabled(element->document()->frame()))
841 // Check if the selection moves to the boundary.
842 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == data.activeSelectionEndIndex()))
845 if (endIndex >= 0 && handled) {
846 // Save the selection so it can be compared to the new selection when dispatching change events immediately after making the new selection.
847 saveLastSelection(data, element);
849 ASSERT_UNUSED(listItems, !listItems.size() || (endIndex >= 0 && static_cast<unsigned>(endIndex) < listItems.size()));
850 setActiveSelectionEndIndex(data, endIndex);
852 bool selectNewItem = !data.multiple() || static_cast<KeyboardEvent*>(event)->shiftKey() || !isSpatialNavigationEnabled(element->document()->frame());
854 data.setActiveSelectionState(true);
855 // 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.
856 bool deselectOthers = !data.multiple() || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem);
857 if (data.activeSelectionAnchorIndex() < 0 || deselectOthers) {
859 deselectItems(data, element);
860 setActiveSelectionAnchorIndex(data, element, data.activeSelectionEndIndex());
863 toRenderListBox(element->renderer())->scrollToRevealElementAtListIndex(endIndex);
865 updateListBoxSelection(data, element, deselectOthers);
866 listBoxOnChange(data, element);
868 scrollToSelection(data, element);
870 event->setDefaultHandled();
872 } else if (event->type() == eventNames().keypressEvent) {
873 if (!event->isKeyboardEvent())
875 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
877 if (keyCode == '\r') {
879 htmlForm->submitImplicitly(event, false);
880 event->setDefaultHandled();
881 } else if (data.multiple() && keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) {
882 // Use space to toggle selection change.
883 data.setActiveSelectionState(!data.activeSelectionState());
884 updateSelectedState(data, element, listToOptionIndex(data, element, data.activeSelectionEndIndex()), true /*multi*/, false /*shift*/);
885 listBoxOnChange(data, element);
886 event->setDefaultHandled();
891 void SelectElement::defaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
893 if (!element->renderer())
896 if (data.usesMenuList())
897 menuListDefaultEventHandler(data, element, event, htmlForm);
899 listBoxDefaultEventHandler(data, element, event, htmlForm);
901 if (event->defaultHandled())
904 if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) {
905 KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event);
906 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
907 typeAheadFind(data, element, keyboardEvent);
908 event->setDefaultHandled();
914 int SelectElement::lastSelectedListIndex(const SelectElementData& data, const Element* element)
916 // return the number of the last option selected
919 const Vector<Element*>& items = data.listItems(element);
920 for (size_t i = 0; i < items.size(); ++i) {
921 if (OptionElement* optionElement = toOptionElement(items[i])) {
922 if (optionElement->selected()) {
929 return found ? (int) index : -1;
932 static String stripLeadingWhiteSpace(const String& string)
934 int length = string.length();
937 for (i = 0; i < length; ++i) {
938 if (string[i] != noBreakSpace && (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
942 return string.substring(i, length - i);
945 void SelectElement::typeAheadFind(SelectElementData& data, Element* element, KeyboardEvent* event)
947 if (event->timeStamp() < data.lastCharTime())
950 DOMTimeStamp delta = event->timeStamp() - data.lastCharTime();
951 data.setLastCharTime(event->timeStamp());
953 UChar c = event->charCode();
956 int searchStartOffset = 1;
957 if (delta > typeAheadTimeout) {
958 prefix = String(&c, 1);
959 data.setTypedString(prefix);
960 data.setRepeatingChar(c);
962 data.typedString().append(c);
964 if (c == data.repeatingChar())
965 // The user is likely trying to cycle through all the items starting with this character, so just search on the character
966 prefix = String(&c, 1);
968 data.setRepeatingChar(0);
969 prefix = data.typedString();
970 searchStartOffset = 0;
974 const Vector<Element*>& items = data.listItems(element);
975 int itemCount = items.size();
979 int selected = selectedIndex(data, element);
980 int index = (optionToListIndex(data, element, selected >= 0 ? selected : 0) + searchStartOffset) % itemCount;
983 // Compute a case-folded copy of the prefix string before beginning the search for
984 // a matching element. This code uses foldCase to work around the fact that
985 // String::startWith does not fold non-ASCII characters. This code can be changed
986 // to use startWith once that is fixed.
987 String prefixWithCaseFolded(prefix.foldCase());
988 for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) {
989 OptionElement* optionElement = toOptionElement(items[index]);
990 if (!optionElement || items[index]->disabled())
993 // Fold the option string and check if its prefix is equal to the folded prefix.
994 String text = optionElement->textIndentedToRespectGroupLabel();
995 if (stripLeadingWhiteSpace(text).foldCase().startsWith(prefixWithCaseFolded)) {
996 setSelectedIndex(data, element, listToOptionIndex(data, element, index));
997 if (!data.usesMenuList())
998 listBoxOnChange(data, element);
1000 setOptionsChangedOnRenderer(data, element);
1001 element->setNeedsStyleRecalc();
1007 void SelectElement::insertedIntoTree(SelectElementData& data, Element* element)
1009 // When the element is created during document parsing, it won't have any items yet - but for innerHTML
1010 // and related methods, this method is called after the whole subtree is constructed.
1011 recalcListItems(data, element, true);
1014 void SelectElement::accessKeySetSelectedIndex(SelectElementData& data, Element* element, int index)
1016 // first bring into focus the list box
1017 if (!element->focused())
1018 element->accessKeyAction(false);
1020 // if this index is already selected, unselect. otherwise update the selected index
1021 const Vector<Element*>& items = data.listItems(element);
1022 int listIndex = optionToListIndex(data, element, index);
1023 if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) {
1024 if (optionElement->selected())
1025 optionElement->setSelectedState(false);
1027 setSelectedIndex(data, element, index, false, true);
1030 if (data.usesMenuList())
1031 menuListOnChange(data, element);
1033 listBoxOnChange(data, element);
1035 scrollToSelection(data, element);
1038 unsigned SelectElement::optionCount(const SelectElementData& data, const Element* element)
1040 unsigned options = 0;
1042 const Vector<Element*>& items = data.listItems(element);
1043 for (unsigned i = 0; i < items.size(); ++i) {
1044 if (isOptionElement(items[i]))
1051 // SelectElementData
1052 SelectElementData::SelectElementData()
1054 , m_repeatingChar(0)
1056 , m_lastOnChangeIndex(-1)
1057 , m_activeSelectionAnchorIndex(-1)
1058 , m_activeSelectionEndIndex(-1)
1059 , m_userDrivenChange(false)
1061 , m_activeSelectionState(false)
1062 , m_recalcListItems(false)
1066 SelectElementData::~SelectElementData()
1070 void SelectElementData::checkListItems(const Element* element) const
1072 #if !ASSERT_DISABLED
1073 Vector<Element*> items = m_listItems;
1074 SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element, false);
1075 ASSERT(items == m_listItems);
1077 UNUSED_PARAM(element);
1081 Vector<Element*>& SelectElementData::listItems(const Element* element)
1083 if (m_recalcListItems)
1084 SelectElement::recalcListItems(*this, element);
1086 checkListItems(element);
1091 const Vector<Element*>& SelectElementData::listItems(const Element* element) const
1093 if (m_recalcListItems)
1094 SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element);
1096 checkListItems(element);
1101 SelectElement* toSelectElement(Element* element)
1103 if (element->isHTMLElement() && element->hasTagName(HTMLNames::selectTag))
1104 return static_cast<HTMLSelectElement*>(element);