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