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