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