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