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(¤t);
787 if (Element* nextElement = ElementTraversal::firstWithin(current)) {
788 currentElement = nextElement;
789 continue;
790 }
791 }
792
793 if (isHTMLOptionElement(current)) {
794 m_listItems.append(¤t);
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(¤t);
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