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