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/)
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.
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.
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.
29 #include "core/html/HTMLSelectElement.h"
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"
64 using namespace WTF::Unicode;
68 using namespace HTMLNames;
70 // Upper limit agreed upon with representatives of Opera and Mozilla.
71 static const unsigned maxSelectItems = 10000;
73 HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form)
74 : HTMLFormControlElementWithState(selectTag, document, form)
77 , m_lastOnChangeIndex(-1)
78 , m_activeSelectionAnchorIndex(-1)
79 , m_activeSelectionEndIndex(-1)
80 , m_isProcessingUserDrivenChange(false)
82 , m_activeSelectionState(false)
83 , m_shouldRecalcListItems(false)
84 , m_suggestedIndex(-1)
85 , m_isAutofilledByPreview(false)
89 PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
91 RefPtrWillBeRawPtr<HTMLSelectElement> select = adoptRefWillBeNoop(new HTMLSelectElement(document, 0));
92 select->ensureUserAgentShadowRoot();
93 return select.release();
96 PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
98 RefPtrWillBeRawPtr<HTMLSelectElement> select = adoptRefWillBeNoop(new HTMLSelectElement(document, form));
99 select->ensureUserAgentShadowRoot();
100 return select.release();
103 const AtomicString& HTMLSelectElement::formControlType() const
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;
110 void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection)
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();
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())
129 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchInputAndChangeEvent : 0) | UserDriven);
132 bool HTMLSelectElement::hasPlaceholderLabelOption() const
134 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1.
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
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.
144 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1.
145 if (multiple() || size() > 1)
148 int listIndex = optionToListIndex(0);
149 ASSERT(listIndex >= 0);
152 return !listIndex && toHTMLOptionElement(listItems()[listIndex])->value().isEmpty();
155 String HTMLSelectElement::validationMessage() const
160 return customValidationMessage();
162 return locale().queryString(blink::WebLocalizedString::ValidationValueMissingForSelect);
166 bool HTMLSelectElement::valueMissing() const
174 int firstSelectionIndex = selectedIndex();
176 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing.
177 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption());
180 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
183 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false);
185 updateSelectedState(listIndex, allowMultiplySelections, shift);
186 setNeedsValidityCheck();
192 bool HTMLSelectElement::usesMenuList() const
194 if (RenderTheme::theme().delegatesMenuListRendering())
197 return !m_multiple && m_size <= 1;
200 int HTMLSelectElement::activeSelectionStartListIndex() const
202 if (m_activeSelectionAnchorIndex >= 0)
203 return m_activeSelectionAnchorIndex;
204 return optionToListIndex(selectedIndex());
207 int HTMLSelectElement::activeSelectionEndListIndex() const
209 if (m_activeSelectionEndIndex >= 0)
210 return m_activeSelectionEndIndex;
211 return lastSelectedListIndex();
214 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
216 // Make sure the element is ref'd and deref'd so we don't leak it.
217 RefPtrWillBeRawPtr<HTMLElement> protectNewChild(element);
219 if (!element || !(isHTMLOptionElement(element) || isHTMLOptGroupElement(element) || isHTMLHRElement(element)))
222 insertBefore(element, before, exceptionState);
223 setNeedsValidityCheck();
226 void HTMLSelectElement::addBeforeOptionAtIndex(HTMLElement* element, int beforeIndex, ExceptionState& exceptionState)
228 HTMLOptionElement* beforeElement = options()->item(beforeIndex);
229 add(element, beforeElement, exceptionState);
232 void HTMLSelectElement::remove(int optionIndex)
234 int listIndex = optionToListIndex(optionIndex);
238 listItems()[listIndex]->remove(IGNORE_EXCEPTION);
241 String HTMLSelectElement::value() const
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();
251 void HTMLSelectElement::setValue(const String &value, bool sendEvents)
253 // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
255 if (value.isNull()) {
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)
267 if (optionIndex >= static_cast<int>(items.size()))
271 int previousSelectedIndex = selectedIndex();
272 setSuggestedIndex(-1);
273 if (m_isAutofilledByPreview)
274 setAutofilled(false);
275 setSelectedIndex(optionIndex);
277 if (sendEvents && previousSelectedIndex != selectedIndex()) {
279 dispatchInputAndChangeEventForMenuList(false);
285 String HTMLSelectElement::suggestedValue() const
287 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
288 for (unsigned i = 0; i < items.size(); ++i) {
289 if (isHTMLOptionElement(items[i]) && m_suggestedIndex >= 0) {
290 if (i == static_cast<unsigned>(m_suggestedIndex))
291 return toHTMLOptionElement(items[i])->value();
297 void HTMLSelectElement::setSuggestedValue(const String& value)
299 if (value.isNull()) {
300 setSuggestedIndex(-1);
304 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
305 unsigned optionIndex = 0;
306 for (unsigned i = 0; i < items.size(); ++i) {
307 if (isHTMLOptionElement(items[i])) {
308 if (toHTMLOptionElement(items[i])->value() == value) {
309 setSuggestedIndex(optionIndex);
310 m_isAutofilledByPreview = true;
317 setSuggestedIndex(-1);
320 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
322 if (name == alignAttr) {
323 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
324 // See http://bugs.webkit.org/show_bug.cgi?id=12072
328 return HTMLFormControlElementWithState::isPresentationAttribute(name);
331 void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
333 if (name == sizeAttr) {
334 int oldSize = m_size;
335 // Set the attribute value to a number.
336 // This is important since the style rules for this attribute can determine the appearance property.
337 int size = value.toInt();
338 AtomicString attrSize = AtomicString::number(size);
339 if (attrSize != value) {
340 // FIXME: This is horribly factored.
341 if (Attribute* sizeAttribute = ensureUniqueElementData().attributes().find(sizeAttr))
342 sizeAttribute->setValue(attrSize);
344 size = std::max(size, 0);
346 // Ensure that we've determined selectedness of the items at least once prior to changing the size.
348 updateListItemSelectedStates();
351 setNeedsValidityCheck();
352 if (m_size != oldSize && inActiveDocument()) {
353 lazyReattachIfAttached();
354 setRecalcListItems();
356 } else if (name == multipleAttr)
357 parseMultipleAttribute(value);
358 else if (name == accesskeyAttr) {
359 // FIXME: ignore for the moment.
361 } else if (name == disabledAttr) {
362 HTMLFormControlElementWithState::parseAttribute(name, value);
363 if (renderer() && renderer()->isMenuList()) {
364 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
365 if (menuList->popupIsVisible())
366 menuList->hidePopup();
371 HTMLFormControlElementWithState::parseAttribute(name, value);
374 bool HTMLSelectElement::shouldShowFocusRingOnMouseFocus() const
379 bool HTMLSelectElement::canSelectAll() const
381 return !usesMenuList();
384 RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
387 return new RenderMenuList(this);
388 return new RenderListBox(this);
391 PassRefPtrWillBeRawPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
393 updateListItemSelectedStates();
394 return ensureCachedCollection<HTMLCollection>(SelectedOptions);
397 PassRefPtrWillBeRawPtr<HTMLOptionsCollection> HTMLSelectElement::options()
399 return ensureCachedCollection<HTMLOptionsCollection>(SelectOptions);
402 void HTMLSelectElement::updateListItemSelectedStates()
404 if (!m_shouldRecalcListItems)
407 setNeedsValidityCheck();
410 void HTMLSelectElement::childrenChanged(const ChildrenChange& change)
412 setRecalcListItems();
413 setNeedsValidityCheck();
414 m_lastOnChangeSelection.clear();
416 HTMLFormControlElementWithState::childrenChanged(change);
419 void HTMLSelectElement::optionElementChildrenChanged()
421 setRecalcListItems();
422 setNeedsValidityCheck();
425 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
426 cache->childrenChanged(this);
430 void HTMLSelectElement::accessKeyAction(bool sendMouseEvents)
433 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents);
436 void HTMLSelectElement::setMultiple(bool multiple)
438 bool oldMultiple = this->multiple();
439 int oldSelectedIndex = selectedIndex();
440 setAttribute(multipleAttr, multiple ? emptyAtom : nullAtom);
442 // Restore selectedIndex after changing the multiple flag to preserve
443 // selection as single-line and multi-line has different defaults.
444 if (oldMultiple != this->multiple())
445 setSelectedIndex(oldSelectedIndex);
448 void HTMLSelectElement::setSize(int size)
450 setIntegralAttribute(sizeAttr, size);
453 Element* HTMLSelectElement::namedItem(const AtomicString& name)
455 return options()->namedItem(name);
458 HTMLOptionElement* HTMLSelectElement::item(unsigned index)
460 return options()->item(index);
463 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionState& exceptionState)
465 if (index > maxSelectItems - 1)
466 index = maxSelectItems - 1;
467 int diff = index - length();
468 RefPtrWillBeRawPtr<HTMLOptionElement> before = nullptr;
469 // Out of array bounds? First insert empty dummies.
471 setLength(index, exceptionState);
472 // Replace an existing entry?
473 } else if (diff < 0) {
474 before = options()->item(index + 1);
477 // Finally add the new element.
478 if (!exceptionState.hadException()) {
479 add(option, before.get(), exceptionState);
480 if (diff >= 0 && option->selected())
481 optionSelectionStateChanged(option, true);
485 void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionState)
487 if (newLen > maxSelectItems)
488 newLen = maxSelectItems;
489 int diff = length() - newLen;
491 if (diff < 0) { // Add dummy elements.
493 RefPtrWillBeRawPtr<Element> option = document().createElement(optionTag, false);
495 add(toHTMLElement(option), 0, exceptionState);
496 if (exceptionState.hadException())
500 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
502 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
503 // of elements that we intend to remove then attempt to remove them one at a time.
504 WillBeHeapVector<RefPtrWillBeMember<Element>> itemsToRemove;
505 size_t optionIndex = 0;
506 for (size_t i = 0; i < items.size(); ++i) {
507 Element* item = items[i];
508 if (isHTMLOptionElement(items[i]) && optionIndex++ >= newLen) {
509 ASSERT(item->parentNode());
510 itemsToRemove.append(item);
514 for (size_t i = 0; i < itemsToRemove.size(); ++i) {
515 Element* item = itemsToRemove[i].get();
516 if (item->parentNode())
517 item->parentNode()->removeChild(item, exceptionState);
520 setNeedsValidityCheck();
523 bool HTMLSelectElement::isRequiredFormControl() const
528 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
529 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
530 // Otherwise, it returns |listIndex|.
531 // Valid means that it is enabled and an option element.
532 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
534 ASSERT(direction == -1 || direction == 1);
535 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = this->listItems();
536 int lastGoodIndex = listIndex;
537 int size = listItems.size();
538 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
540 HTMLElement* element = listItems[listIndex];
541 if (!isHTMLOptionElement(*element))
543 if (toHTMLOptionElement(*element).isDisplayNone())
545 if (element->isDisabledFormControl())
547 if (!usesMenuList() && !element->renderer())
549 lastGoodIndex = listIndex;
553 return lastGoodIndex;
556 int HTMLSelectElement::nextSelectableListIndex(int startIndex) const
558 return nextValidIndex(startIndex, SkipForwards, 1);
561 int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
563 if (startIndex == -1)
564 startIndex = listItems().size();
565 return nextValidIndex(startIndex, SkipBackwards, 1);
568 int HTMLSelectElement::firstSelectableListIndex() const
570 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
571 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
572 if (static_cast<size_t>(index) == items.size())
577 int HTMLSelectElement::lastSelectableListIndex() const
579 return nextValidIndex(-1, SkipForwards, INT_MAX);
582 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
583 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
585 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
586 // Can't use m_size because renderer forces a minimum size.
588 if (renderer()->isListBox())
589 pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context.
591 // One page away, but not outside valid bounds.
592 // If there is a valid option item one page away, the index is chosen.
593 // If there is no exact one page away valid option, returns startIndex or the most far index.
594 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
595 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
596 return nextValidIndex(edgeIndex, direction, skipAmount);
599 void HTMLSelectElement::selectAll()
601 ASSERT(!usesMenuList());
602 if (!renderer() || !m_multiple)
605 // Save the selection so it can be compared to the new selectAll selection
606 // when dispatching change events.
609 m_activeSelectionState = true;
610 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
611 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
613 updateListBoxSelection(false, false);
615 setNeedsValidityCheck();
618 void HTMLSelectElement::saveLastSelection()
620 if (usesMenuList()) {
621 m_lastOnChangeIndex = selectedIndex();
625 m_lastOnChangeSelection.clear();
626 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
627 for (unsigned i = 0; i < items.size(); ++i) {
628 HTMLElement* element = items[i];
629 m_lastOnChangeSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
633 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
635 m_activeSelectionAnchorIndex = index;
637 // Cache the selection state so we can restore the old selection as the new
638 // selection pivots around this anchor index.
639 m_cachedStateForActiveSelection.clear();
641 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
642 for (unsigned i = 0; i < items.size(); ++i) {
643 HTMLElement* element = items[i];
644 m_cachedStateForActiveSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
648 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
650 if (index == m_activeSelectionEndIndex)
652 m_activeSelectionEndIndex = index;
653 setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::Control));
656 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions, bool scroll)
658 ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
659 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
661 unsigned start = std::min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
662 unsigned end = std::max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
664 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
665 for (unsigned i = 0; i < items.size(); ++i) {
666 HTMLElement* element = items[i];
667 if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || !toHTMLOptionElement(element)->renderer())
670 if (i >= start && i <= end)
671 toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState);
672 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size())
673 toHTMLOptionElement(element)->setSelectedState(false);
675 toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
678 setNeedsValidityCheck();
681 notifyFormStateChanged();
684 void HTMLSelectElement::listBoxOnChange()
686 ASSERT(!usesMenuList() || m_multiple);
688 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
690 // If the cached selection list is empty, or the size has changed, then fire
691 // dispatchFormControlChangeEvent, and return early.
692 // FIXME: Why? This looks unreasonable.
693 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
694 dispatchFormControlChangeEvent();
698 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
699 bool fireOnChange = false;
700 for (unsigned i = 0; i < items.size(); ++i) {
701 HTMLElement* element = items[i];
702 bool selected = isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected();
703 if (selected != m_lastOnChangeSelection[i])
705 m_lastOnChangeSelection[i] = selected;
709 RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
710 dispatchInputEvent();
711 dispatchFormControlChangeEvent();
715 void HTMLSelectElement::dispatchInputAndChangeEventForMenuList(bool requiresUserGesture)
717 ASSERT(usesMenuList());
719 int selected = selectedIndex();
720 if (m_lastOnChangeIndex != selected && (!requiresUserGesture || m_isProcessingUserDrivenChange)) {
721 m_lastOnChangeIndex = selected;
722 m_isProcessingUserDrivenChange = false;
723 RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
724 dispatchInputEvent();
725 dispatchFormControlChangeEvent();
729 void HTMLSelectElement::scrollToSelection()
731 if (!isFinishedParsingChildren())
735 scrollTo(activeSelectionEndListIndex());
736 if (AXObjectCache* cache = document().existingAXObjectCache())
737 cache->selectedChildrenChanged(this);
740 void HTMLSelectElement::setOptionsChangedOnRenderer()
742 if (RenderObject* renderer = this->renderer()) {
744 toRenderMenuList(renderer)->setOptionsChanged(true);
748 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& HTMLSelectElement::listItems() const
750 if (m_shouldRecalcListItems)
754 WillBeHeapVector<RawPtrWillBeMember<HTMLElement>> items = m_listItems;
755 recalcListItems(false);
756 ASSERT(items == m_listItems);
763 void HTMLSelectElement::invalidateSelectedItems()
765 if (HTMLCollection* collection = cachedCollection<HTMLCollection>(SelectedOptions))
766 collection->invalidateCache();
769 void HTMLSelectElement::setRecalcListItems()
771 // FIXME: This function does a bunch of confusing things depending on if it
772 // is in the document or not.
774 m_shouldRecalcListItems = true;
775 // Manual selection anchor is reset when manipulating the select programmatically.
776 m_activeSelectionAnchorIndex = -1;
777 setOptionsChangedOnRenderer();
778 setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::ControlValue));
780 if (HTMLOptionsCollection* collection = cachedCollection<HTMLOptionsCollection>(SelectOptions))
781 collection->invalidateCache();
784 invalidateSelectedItems();
787 if (AXObjectCache* cache = renderer()->document().existingAXObjectCache())
788 cache->childrenChanged(this);
792 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
796 m_shouldRecalcListItems = false;
798 HTMLOptionElement* foundSelected = 0;
799 HTMLOptionElement* firstOption = 0;
800 for (Element* currentElement = ElementTraversal::firstWithin(*this); currentElement; ) {
801 if (!currentElement->isHTMLElement()) {
802 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
805 HTMLElement& current = toHTMLElement(*currentElement);
807 // optgroup tags may not nest. However, both FireFox and IE will
808 // flatten the tree automatically, so we follow suit.
809 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
810 if (isHTMLOptGroupElement(current)) {
811 m_listItems.append(¤t);
812 if (Element* nextElement = ElementTraversal::firstWithin(current)) {
813 currentElement = nextElement;
818 if (isHTMLOptionElement(current)) {
819 m_listItems.append(¤t);
821 if (updateSelectedStates && !m_multiple) {
822 HTMLOptionElement& option = toHTMLOptionElement(current);
824 firstOption = &option;
825 if (option.selected()) {
827 foundSelected->setSelectedState(false);
828 foundSelected = &option;
829 } else if (m_size <= 1 && !foundSelected && !option.isDisabledFormControl()) {
830 foundSelected = &option;
831 foundSelected->setSelectedState(true);
836 if (isHTMLHRElement(current))
837 m_listItems.append(¤t);
839 // In conforming HTML code, only <optgroup> and <option> will be found
840 // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step
841 // into those tags that we choose to. For web-compat, we should cope
842 // with the case where odd tags like a <div> have been added but we
843 // handle this because such tags have already been removed from the
844 // <select>'s subtree at this point.
845 currentElement = ElementTraversal::nextSkippingChildren(*currentElement, this);
848 if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected())
849 firstOption->setSelectedState(true);
852 int HTMLSelectElement::selectedIndex() const
856 // Return the number of the first option selected.
857 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
858 for (size_t i = 0; i < items.size(); ++i) {
859 HTMLElement* element = items[i];
860 if (isHTMLOptionElement(*element)) {
861 if (toHTMLOptionElement(*element).selected())
870 void HTMLSelectElement::setSelectedIndex(int index)
872 selectOption(index, DeselectOtherOptions);
875 int HTMLSelectElement::suggestedIndex() const
877 return m_suggestedIndex;
880 void HTMLSelectElement::setSuggestedIndex(int suggestedIndex)
882 m_suggestedIndex = suggestedIndex;
884 if (RenderObject* renderer = this->renderer()) {
885 renderer->updateFromElement();
886 scrollTo(suggestedIndex);
890 void HTMLSelectElement::scrollTo(int listIndex)
896 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
897 int listSize = static_cast<int>(items.size());
898 if (listIndex >= listSize)
900 document().updateLayoutIgnorePendingStylesheets();
901 if (!renderer() || !renderer()->isListBox())
903 LayoutRect bounds = items[listIndex]->boundingBox();
904 toRenderListBox(renderer())->scrollToRect(bounds);
907 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
909 ASSERT(option->ownerSelectElement() == this);
910 if (optionIsSelected)
911 selectOption(option->index());
912 else if (!usesMenuList() || multiple())
915 selectOption(nextSelectableListIndex(-1));
918 void HTMLSelectElement::optionRemoved(const HTMLOptionElement& option)
920 if (m_activeSelectionAnchorIndex < 0 && m_activeSelectionEndIndex < 0)
922 int listIndex = optionToListIndex(option.index());
923 if (listIndex <= m_activeSelectionAnchorIndex)
924 m_activeSelectionAnchorIndex--;
925 if (listIndex <= m_activeSelectionEndIndex)
926 m_activeSelectionEndIndex--;
927 if (listIndex == selectedIndex())
928 setAutofilled(false);
931 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
933 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
935 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
936 int listIndex = optionToListIndex(optionIndex);
938 if (selectedIndex() != optionIndex && isAutofilled())
939 setAutofilled(false);
941 HTMLElement* element = 0;
942 if (listIndex >= 0) {
943 element = items[listIndex];
944 if (isHTMLOptionElement(*element)) {
945 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
946 setActiveSelectionAnchorIndex(listIndex);
947 if (m_activeSelectionEndIndex < 0 || shouldDeselect)
948 setActiveSelectionEndIndex(listIndex);
949 toHTMLOptionElement(*element).setSelectedState(true);
954 deselectItemsWithoutValidation(element);
956 // For the menu list case, this is what makes the selected element appear.
957 if (RenderObject* renderer = this->renderer())
958 renderer->updateFromElement();
962 setNeedsValidityCheck();
964 if (usesMenuList()) {
965 m_isProcessingUserDrivenChange = flags & UserDriven;
966 if (flags & DispatchInputAndChangeEvent)
967 dispatchInputAndChangeEventForMenuList();
968 if (RenderObject* renderer = this->renderer()) {
969 if (usesMenuList()) {
970 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
971 } else if (renderer->isListBox()) {
972 if (AXObjectCache* cache = document().existingAXObjectCache())
973 cache->selectedChildrenChanged(this);
978 notifyFormStateChanged();
981 int HTMLSelectElement::optionToListIndex(int optionIndex) const
983 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
984 int listSize = static_cast<int>(items.size());
985 if (optionIndex < 0 || optionIndex >= listSize)
988 int optionIndex2 = -1;
989 for (int listIndex = 0; listIndex < listSize; ++listIndex) {
990 if (isHTMLOptionElement(*items[listIndex])) {
992 if (optionIndex2 == optionIndex)
1000 int HTMLSelectElement::listToOptionIndex(int listIndex) const
1002 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1003 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !isHTMLOptionElement(*items[listIndex]))
1006 // Actual index of option not counting OPTGROUP entries that may be in list.
1007 int optionIndex = 0;
1008 for (int i = 0; i < listIndex; ++i) {
1009 if (isHTMLOptionElement(*items[i]))
1016 void HTMLSelectElement::dispatchFocusEvent(Element* oldFocusedElement, FocusType type)
1018 // Save the selection so it can be compared to the new selection when
1019 // dispatching change events during blur event dispatch.
1021 saveLastSelection();
1022 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, type);
1025 void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
1027 // We only need to fire change events here for menu lists, because we fire
1028 // change events for list boxes whenever the selection change is actually made.
1029 // This matches other browsers' behavior.
1031 dispatchInputAndChangeEventForMenuList();
1032 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
1035 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
1037 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1038 for (unsigned i = 0; i < items.size(); ++i) {
1039 HTMLElement* element = items[i];
1040 if (element != excludeElement && isHTMLOptionElement(*element))
1041 toHTMLOptionElement(element)->setSelectedState(false);
1045 FormControlState HTMLSelectElement::saveFormControlState() const
1047 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1048 size_t length = items.size();
1049 FormControlState state;
1050 for (unsigned i = 0; i < length; ++i) {
1051 if (!isHTMLOptionElement(*items[i]))
1053 HTMLOptionElement* option = toHTMLOptionElement(items[i]);
1054 if (!option->selected())
1056 state.append(option->value());
1057 state.append(String::number(i));
1064 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
1066 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1067 size_t loopEndIndex = std::min(items.size(), listIndexEnd);
1068 for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
1069 if (!isHTMLOptionElement(items[i]))
1071 if (toHTMLOptionElement(items[i])->value() == value)
1077 void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
1081 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1082 size_t itemsSize = items.size();
1086 for (size_t i = 0; i < itemsSize; ++i) {
1087 if (!isHTMLOptionElement(items[i]))
1089 toHTMLOptionElement(items[i])->setSelectedState(false);
1092 // The saved state should have at least one value and an index.
1093 ASSERT(state.valueSize() >= 2);
1095 size_t index = state[1].toUInt();
1096 if (index < itemsSize && isHTMLOptionElement(items[index]) && toHTMLOptionElement(items[index])->value() == state[0]) {
1097 toHTMLOptionElement(items[index])->setSelectedState(true);
1099 size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
1100 if (foundIndex != kNotFound)
1101 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
1104 size_t startIndex = 0;
1105 for (size_t i = 0; i < state.valueSize(); i+= 2) {
1106 const String& value = state[i];
1107 const size_t index = state[i + 1].toUInt();
1108 if (index < itemsSize && isHTMLOptionElement(items[index]) && toHTMLOptionElement(items[index])->value() == value) {
1109 toHTMLOptionElement(items[index])->setSelectedState(true);
1110 startIndex = index + 1;
1112 size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
1113 if (foundIndex == kNotFound)
1114 foundIndex = searchOptionsForValue(value, 0, startIndex);
1115 if (foundIndex == kNotFound)
1117 toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
1118 startIndex = foundIndex + 1;
1123 setOptionsChangedOnRenderer();
1124 setNeedsValidityCheck();
1127 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
1129 m_multiple = !value.isNull();
1130 setNeedsValidityCheck();
1132 lazyReattachIfAttached();
1135 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
1137 const AtomicString& name = this->name();
1141 bool successful = false;
1142 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1144 for (unsigned i = 0; i < items.size(); ++i) {
1145 HTMLElement* element = items[i];
1146 if (isHTMLOptionElement(*element) && toHTMLOptionElement(*element).selected() && !toHTMLOptionElement(*element).isDisabledFormControl()) {
1147 list.appendData(name, toHTMLOptionElement(*element).value());
1152 // It's possible that this is a menulist with multiple options and nothing
1153 // will be submitted (!successful). We won't send a unselected non-disabled
1154 // option as fallback. This behavior matches to other browsers.
1158 void HTMLSelectElement::resetImpl()
1160 HTMLOptionElement* firstOption = 0;
1161 HTMLOptionElement* selectedOption = 0;
1163 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1164 for (unsigned i = 0; i < items.size(); ++i) {
1165 HTMLElement* element = items[i];
1166 if (!isHTMLOptionElement(*element))
1169 if (items[i]->fastHasAttribute(selectedAttr)) {
1170 if (selectedOption && !m_multiple)
1171 selectedOption->setSelectedState(false);
1172 toHTMLOptionElement(element)->setSelectedState(true);
1173 selectedOption = toHTMLOptionElement(element);
1175 toHTMLOptionElement(element)->setSelectedState(false);
1178 firstOption = toHTMLOptionElement(element);
1181 if (!selectedOption && firstOption && !m_multiple && m_size <= 1)
1182 firstOption->setSelectedState(true);
1184 setOptionsChangedOnRenderer();
1185 setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::ControlValue));
1186 setNeedsValidityCheck();
1189 void HTMLSelectElement::handlePopupOpenKeyboardEvent(Event* event)
1192 // Calling focus() may cause us to lose our renderer. Return true so
1193 // that our caller doesn't process the event further, but don't set
1194 // the event as handled.
1195 if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
1197 // Save the selection so it can be compared to the new selection
1198 // when dispatching change events during selectOption, which
1199 // gets called from RenderMenuList::valueChanged, which gets called
1200 // after the user makes a selection from the menu.
1201 saveLastSelection();
1202 if (RenderMenuList* menuList = toRenderMenuList(renderer()))
1203 menuList->showPopup();
1204 int index = selectedIndex();
1206 ASSERT_WITH_SECURITY_IMPLICATION(index < static_cast<int>(listItems().size()));
1207 setSelectedIndex(index);
1208 event->setDefaultHandled();
1212 bool HTMLSelectElement::shouldOpenPopupForKeyDownEvent(KeyboardEvent* keyEvent)
1214 const String& keyIdentifier = keyEvent->keyIdentifier();
1215 RenderTheme& renderTheme = RenderTheme::theme();
1217 if (isSpatialNavigationEnabled(document().frame()))
1220 return ((renderTheme.popsMenuByArrowKeys() && (keyIdentifier == "Down" || keyIdentifier == "Up"))
1221 || (renderTheme.popsMenuByAltDownUpOrF4Key() && (keyIdentifier == "Down" || keyIdentifier == "Up") && keyEvent->altKey())
1222 || (renderTheme.popsMenuByAltDownUpOrF4Key() && (!keyEvent->altKey() && !keyEvent->ctrlKey() && keyIdentifier == "F4")));
1225 bool HTMLSelectElement::shouldOpenPopupForKeyPressEvent(KeyboardEvent *event)
1227 RenderTheme& renderTheme = RenderTheme::theme();
1228 int keyCode = event->keyCode();
1230 return ((renderTheme.popsMenuBySpaceKey() && event->keyCode() == ' ')
1231 || (renderTheme.popsMenuByReturnKey() && keyCode == '\r'));
1234 void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
1236 if (event->type() == EventTypeNames::keydown) {
1237 if (!renderer() || !event->isKeyboardEvent())
1240 KeyboardEvent* keyEvent = toKeyboardEvent(event);
1241 if (shouldOpenPopupForKeyDownEvent(keyEvent)) {
1242 handlePopupOpenKeyboardEvent(event);
1246 // When using spatial navigation, we want to be able to navigate away
1247 // from the select element when the user hits any of the arrow keys,
1248 // instead of changing the selection.
1249 if (isSpatialNavigationEnabled(document().frame())) {
1250 if (!m_activeSelectionState)
1254 // The key handling below shouldn't be used for non spatial navigation mode Mac
1255 if (RenderTheme::theme().popsMenuByArrowKeys() && !isSpatialNavigationEnabled(document().frame()))
1258 const String& keyIdentifier = keyEvent->keyIdentifier();
1259 bool handled = true;
1260 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = this->listItems();
1261 int listIndex = optionToListIndex(selectedIndex());
1263 if (keyIdentifier == "Down" || keyIdentifier == "Right")
1264 listIndex = nextValidIndex(listIndex, SkipForwards, 1);
1265 else if (keyIdentifier == "Up" || keyIdentifier == "Left")
1266 listIndex = nextValidIndex(listIndex, SkipBackwards, 1);
1267 else if (keyIdentifier == "PageDown")
1268 listIndex = nextValidIndex(listIndex, SkipForwards, 3);
1269 else if (keyIdentifier == "PageUp")
1270 listIndex = nextValidIndex(listIndex, SkipBackwards, 3);
1271 else if (keyIdentifier == "Home")
1272 listIndex = nextValidIndex(-1, SkipForwards, 1);
1273 else if (keyIdentifier == "End")
1274 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1);
1278 if (handled && static_cast<size_t>(listIndex) < listItems.size())
1279 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
1282 event->setDefaultHandled();
1285 if (event->type() == EventTypeNames::keypress) {
1286 if (!renderer() || !event->isKeyboardEvent())
1289 int keyCode = toKeyboardEvent(event)->keyCode();
1290 if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1291 // Use space to toggle arrow key handling for selection change or spatial navigation.
1292 m_activeSelectionState = !m_activeSelectionState;
1293 event->setDefaultHandled();
1297 KeyboardEvent* keyEvent = toKeyboardEvent(event);
1298 if (shouldOpenPopupForKeyPressEvent(keyEvent)) {
1299 handlePopupOpenKeyboardEvent(event);
1303 if (!RenderTheme::theme().popsMenuByReturnKey() && keyCode == '\r') {
1305 form()->submitImplicitly(event, false);
1306 dispatchInputAndChangeEventForMenuList();
1307 event->setDefaultHandled();
1311 if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
1313 if (renderer() && renderer()->isMenuList() && !isDisabledFormControl()) {
1314 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1315 if (menuList->popupIsVisible())
1316 menuList->hidePopup();
1318 // Save the selection so it can be compared to the new
1319 // selection when we call onChange during selectOption,
1320 // which gets called from RenderMenuList::valueChanged,
1321 // which gets called after the user makes a selection from
1323 saveLastSelection();
1324 menuList->showPopup();
1328 event->setDefaultHandled();
1331 if (event->type() == EventTypeNames::blur) {
1332 if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
1333 if (menuList->popupIsVisible())
1334 menuList->hidePopup();
1339 void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift)
1341 ASSERT(listIndex >= 0);
1343 HTMLElement* clickedElement = listItems()[listIndex];
1344 ASSERT(clickedElement);
1345 if (isHTMLOptGroupElement(clickedElement))
1348 // Save the selection so it can be compared to the new selection when
1349 // dispatching change events during mouseup, or after autoscroll finishes.
1350 saveLastSelection();
1352 m_activeSelectionState = true;
1354 bool shiftSelect = m_multiple && shift;
1355 bool multiSelect = m_multiple && multi && !shift;
1357 if (isHTMLOptionElement(*clickedElement)) {
1358 // Keep track of whether an active selection (like during drag
1359 // selection), should select or deselect.
1360 if (toHTMLOptionElement(*clickedElement).selected() && multiSelect)
1361 m_activeSelectionState = false;
1362 if (!m_activeSelectionState)
1363 toHTMLOptionElement(*clickedElement).setSelectedState(false);
1366 // If we're not in any special multiple selection mode, then deselect all
1367 // other items, excluding the clicked option. If no option was clicked, then
1368 // this will deselect all items in the list.
1369 if (!shiftSelect && !multiSelect)
1370 deselectItemsWithoutValidation(clickedElement);
1372 // If the anchor hasn't been set, and we're doing a single selection or a
1373 // shift selection, then initialize the anchor to the first selected index.
1374 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
1375 setActiveSelectionAnchorIndex(selectedIndex());
1377 // Set the selection state of the clicked option.
1378 if (isHTMLOptionElement(*clickedElement) && !toHTMLOptionElement(*clickedElement).isDisabledFormControl())
1379 toHTMLOptionElement(*clickedElement).setSelectedState(true);
1381 // If there was no selectedIndex() for the previous initialization, or If
1382 // we're doing a single selection, or a multiple selection (using cmd or
1383 // ctrl), then initialize the anchor index to the listIndex that just got
1385 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect)
1386 setActiveSelectionAnchorIndex(listIndex);
1388 setActiveSelectionEndIndex(listIndex);
1389 updateListBoxSelection(!multiSelect);
1392 int HTMLSelectElement::listIndexForEventTargetOption(const Event& event)
1394 Node* targetNode = event.target()->toNode();
1395 if (!targetNode || !isHTMLOptionElement(*targetNode))
1397 return listIndexForOption(toHTMLOptionElement(*targetNode));
1400 int HTMLSelectElement::listIndexForOption(const HTMLOptionElement& option)
1402 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = this->listItems();
1403 size_t length = items.size();
1404 for (size_t i = 0; i < length; ++i) {
1405 if (items[i].get() == &option)
1411 AutoscrollController* HTMLSelectElement::autoscrollController() const
1413 if (Page* page = document().page())
1414 return &page->autoscrollController();
1418 void HTMLSelectElement::handleMouseRelease()
1420 // We didn't start this click/drag on any options.
1421 if (m_lastOnChangeSelection.isEmpty())
1426 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
1428 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = this->listItems();
1429 if (event->type() == EventTypeNames::gesturetap && event->isGestureEvent()) {
1431 // Calling focus() may cause us to lose our renderer or change the render type, in which case do not want to handle the event.
1432 if (!renderer() || !renderer()->isListBox())
1435 // Convert to coords relative to the list box if needed.
1436 GestureEvent& gestureEvent = toGestureEvent(*event);
1437 int listIndex = listIndexForEventTargetOption(gestureEvent);
1438 if (listIndex >= 0) {
1439 if (!isDisabledFormControl()) {
1440 updateSelectedState(listIndex, true, gestureEvent.shiftKey());
1443 event->setDefaultHandled();
1445 } else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
1447 // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
1448 if (!renderer() || !renderer()->isListBox() || isDisabledFormControl())
1451 // Convert to coords relative to the list box if needed.
1452 MouseEvent* mouseEvent = toMouseEvent(event);
1453 int listIndex = listIndexForEventTargetOption(*mouseEvent);
1454 if (listIndex >= 0) {
1455 if (!isDisabledFormControl()) {
1457 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
1459 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
1462 if (LocalFrame* frame = document().frame())
1463 frame->eventHandler().setMouseDownMayStartAutoscroll();
1465 event->setDefaultHandled();
1467 } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent()) {
1468 MouseEvent* mouseEvent = toMouseEvent(event);
1469 if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
1472 if (Page* page = document().page())
1473 page->autoscrollController().startAutoscrollForSelection(renderer());
1475 int listIndex = listIndexForEventTargetOption(*mouseEvent);
1476 if (listIndex >= 0) {
1477 if (!isDisabledFormControl()) {
1479 // Only extend selection if there is something selected.
1480 if (m_activeSelectionAnchorIndex < 0)
1483 setActiveSelectionEndIndex(listIndex);
1484 updateListBoxSelection(false);
1486 setActiveSelectionAnchorIndex(listIndex);
1487 setActiveSelectionEndIndex(listIndex);
1488 updateListBoxSelection(true);
1492 } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer()) {
1493 if (document().page() && document().page()->autoscrollController().autoscrollInProgress(toRenderBox(renderer())))
1494 document().page()->autoscrollController().stopAutoscroll();
1496 handleMouseRelease();
1497 } else if (event->type() == EventTypeNames::keydown) {
1498 if (!event->isKeyboardEvent())
1500 const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
1502 bool handled = false;
1504 if (m_activeSelectionEndIndex < 0) {
1505 // Initialize the end index
1506 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
1507 int startIndex = lastSelectedListIndex();
1509 if (keyIdentifier == "Down")
1510 endIndex = nextSelectableListIndex(startIndex);
1512 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
1513 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
1514 int startIndex = optionToListIndex(selectedIndex());
1516 if (keyIdentifier == "Up")
1517 endIndex = previousSelectableListIndex(startIndex);
1519 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
1522 // Set the end index based on the current end index.
1523 if (keyIdentifier == "Down") {
1524 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
1526 } else if (keyIdentifier == "Up") {
1527 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
1529 } else if (keyIdentifier == "PageDown") {
1530 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards);
1532 } else if (keyIdentifier == "PageUp") {
1533 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards);
1537 if (keyIdentifier == "Home") {
1538 endIndex = firstSelectableListIndex();
1540 } else if (keyIdentifier == "End") {
1541 endIndex = lastSelectableListIndex();
1545 if (isSpatialNavigationEnabled(document().frame()))
1546 // Check if the selection moves to the boundary.
1547 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex))
1550 if (endIndex >= 0 && handled) {
1551 // Save the selection so it can be compared to the new selection
1552 // when dispatching change events immediately after making the new
1554 saveLastSelection();
1556 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size());
1557 setActiveSelectionEndIndex(endIndex);
1559 bool selectNewItem = !m_multiple || toKeyboardEvent(event)->shiftKey() || !isSpatialNavigationEnabled(document().frame());
1561 m_activeSelectionState = true;
1562 // If the anchor is unitialized, or if we're going to deselect all
1563 // other options, then set the anchor index equal to the end index.
1564 bool deselectOthers = !m_multiple || (!toKeyboardEvent(event)->shiftKey() && selectNewItem);
1565 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
1567 deselectItemsWithoutValidation();
1568 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
1572 if (selectNewItem) {
1573 updateListBoxSelection(deselectOthers);
1576 scrollToSelection();
1578 event->setDefaultHandled();
1580 } else if (event->type() == EventTypeNames::keypress) {
1581 if (!event->isKeyboardEvent())
1583 int keyCode = toKeyboardEvent(event)->keyCode();
1585 if (keyCode == '\r') {
1587 form()->submitImplicitly(event, false);
1588 event->setDefaultHandled();
1589 } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
1590 // Use space to toggle selection change.
1591 m_activeSelectionState = !m_activeSelectionState;
1592 updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/);
1594 event->setDefaultHandled();
1599 void HTMLSelectElement::defaultEventHandler(Event* event)
1604 if (isDisabledFormControl()) {
1605 HTMLFormControlElementWithState::defaultEventHandler(event);
1610 menuListDefaultEventHandler(event);
1612 listBoxDefaultEventHandler(event);
1613 if (event->defaultHandled())
1616 if (event->type() == EventTypeNames::keypress && event->isKeyboardEvent()) {
1617 KeyboardEvent* keyboardEvent = toKeyboardEvent(event);
1618 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
1619 typeAheadFind(keyboardEvent);
1620 event->setDefaultHandled();
1624 HTMLFormControlElementWithState::defaultEventHandler(event);
1627 int HTMLSelectElement::lastSelectedListIndex() const
1629 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1630 for (size_t i = items.size(); i;) {
1631 HTMLElement* element = items[--i];
1632 if (isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected())
1638 int HTMLSelectElement::indexOfSelectedOption() const
1640 return optionToListIndex(selectedIndex());
1643 int HTMLSelectElement::optionCount() const
1645 return listItems().size();
1648 String HTMLSelectElement::optionAtIndex(int index) const
1650 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1652 HTMLElement* element = items[index];
1653 if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
1655 return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
1658 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
1660 int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
1663 selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
1664 if (!usesMenuList())
1668 Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint)
1670 // When the element is created during document parsing, it won't have any
1671 // items yet - but for innerHTML and related methods, this method is called
1672 // after the whole subtree is constructed.
1674 HTMLFormControlElementWithState::insertedInto(insertionPoint);
1675 return InsertionDone;
1678 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1680 // First bring into focus the list box.
1682 accessKeyAction(false);
1684 // If this index is already selected, unselect. otherwise update the selected index.
1685 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1686 int listIndex = optionToListIndex(index);
1687 if (listIndex >= 0) {
1688 HTMLElement* element = items[listIndex];
1689 if (isHTMLOptionElement(*element)) {
1690 if (toHTMLOptionElement(*element).selected())
1691 toHTMLOptionElement(*element).setSelectedState(false);
1693 selectOption(index, DispatchInputAndChangeEvent | UserDriven);
1698 dispatchInputAndChangeEventForMenuList();
1702 scrollToSelection();
1705 unsigned HTMLSelectElement::length() const
1707 unsigned options = 0;
1709 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
1710 for (unsigned i = 0; i < items.size(); ++i) {
1711 if (isHTMLOptionElement(*items[i]))
1718 void HTMLSelectElement::finishParsingChildren()
1720 HTMLFormControlElementWithState::finishParsingChildren();
1721 updateListItemSelectedStates();
1722 if (!usesMenuList())
1723 scrollToSelection();
1726 bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtrWillBeRawPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
1728 if (!value) { // undefined or null
1732 setOption(index, value.get(), exceptionState);
1736 bool HTMLSelectElement::isInteractiveContent() const
1741 bool HTMLSelectElement::supportsAutofocus() const
1746 void HTMLSelectElement::updateListOnRenderer()
1748 setOptionsChangedOnRenderer();
1751 void HTMLSelectElement::trace(Visitor* visitor)
1754 visitor->trace(m_listItems);
1756 HTMLFormControlElementWithState::trace(visitor);
1759 void HTMLSelectElement::didAddUserAgentShadowRoot(ShadowRoot& root)
1761 RefPtrWillBeRawPtr<HTMLContentElement> content = HTMLContentElement::create(document());
1762 content->setAttribute(selectAttr, "option,optgroup");
1763 root.appendChild(content);
1766 HTMLOptionElement* HTMLSelectElement::spatialNavigationFocusedOption()
1768 if (!isSpatialNavigationEnabled(document().frame()))
1770 int focusedIndex = activeSelectionEndListIndex();
1771 if (focusedIndex < 0)
1772 focusedIndex = firstSelectableListIndex();
1773 if (focusedIndex < 0)
1775 HTMLElement* focused = listItems()[focusedIndex];
1776 return isHTMLOptionElement(focused) ? toHTMLOptionElement(focused) : nullptr;