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