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