• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20 
21 #include "config.h"
22 #include "SelectElement.h"
23 
24 #include "Attribute.h"
25 #include "Chrome.h"
26 #include "ChromeClient.h"
27 #include "Element.h"
28 #include "EventHandler.h"
29 #include "EventNames.h"
30 #include "FormDataList.h"
31 #include "Frame.h"
32 #include "HTMLFormElement.h"
33 #include "HTMLNames.h"
34 #include "HTMLSelectElement.h"
35 #include "KeyboardEvent.h"
36 #include "MouseEvent.h"
37 #include "OptionElement.h"
38 #include "OptionGroupElement.h"
39 #include "Page.h"
40 #include "RenderListBox.h"
41 #include "RenderMenuList.h"
42 #include "SpatialNavigation.h"
43 #include <wtf/Assertions.h>
44 #include <wtf/unicode/CharacterNames.h>
45 
46 #if ENABLE(WML)
47 #include "WMLNames.h"
48 #include "WMLSelectElement.h"
49 #endif
50 
51 // Configure platform-specific behavior when focused pop-up receives arrow/space/return keystroke.
52 // (PLATFORM(MAC) and PLATFORM(GTK) are always false in Chromium, hence the extra tests.)
53 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
54 #define ARROW_KEYS_POP_MENU 1
55 #define SPACE_OR_RETURN_POP_MENU 0
56 #elif PLATFORM(GTK) || (PLATFORM(CHROMIUM) && (OS(LINUX) || OS(FREEBSD)))
57 #define ARROW_KEYS_POP_MENU 0
58 #define SPACE_OR_RETURN_POP_MENU 1
59 #else
60 #define ARROW_KEYS_POP_MENU 0
61 #define SPACE_OR_RETURN_POP_MENU 0
62 #endif
63 
64 using std::min;
65 using std::max;
66 using namespace WTF;
67 using namespace Unicode;
68 
69 namespace WebCore {
70 
71 static const DOMTimeStamp typeAheadTimeout = 1000;
72 
73 enum SkipDirection {
74     SkipBackwards = -1,
75     SkipForwards = 1
76 };
77 
78 // Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one.
79 // Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one.
80 // Otherwise, it returns |listIndex|.
81 // Valid means that it is enabled and an option element.
nextValidIndex(const Vector<Element * > & listItems,int listIndex,SkipDirection direction,int skip)82 static int nextValidIndex(const Vector<Element*>& listItems, int listIndex, SkipDirection direction, int skip)
83 {
84     ASSERT(direction == -1 || direction == 1);
85     int lastGoodIndex = listIndex;
86     int size = listItems.size();
87     for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
88         --skip;
89         if (!listItems[listIndex]->disabled() && isOptionElement(listItems[listIndex])) {
90             lastGoodIndex = listIndex;
91             if (skip <= 0)
92                 break;
93         }
94     }
95     return lastGoodIndex;
96 }
97 
nextSelectableListIndex(SelectElementData & data,Element * element,int startIndex)98 static int nextSelectableListIndex(SelectElementData& data, Element* element, int startIndex)
99 {
100     return nextValidIndex(data.listItems(element), startIndex, SkipForwards, 1);
101 }
102 
previousSelectableListIndex(SelectElementData & data,Element * element,int startIndex)103 static int previousSelectableListIndex(SelectElementData& data, Element* element, int startIndex)
104 {
105     if (startIndex == -1)
106         startIndex = data.listItems(element).size();
107     return nextValidIndex(data.listItems(element), startIndex, SkipBackwards, 1);
108 }
109 
firstSelectableListIndex(SelectElementData & data,Element * element)110 static int firstSelectableListIndex(SelectElementData& data, Element* element)
111 {
112     const Vector<Element*>& items = data.listItems(element);
113     int index = nextValidIndex(items, items.size(), SkipBackwards, INT_MAX);
114     if (static_cast<unsigned>(index) == items.size())
115         return -1;
116     return index;
117 }
118 
lastSelectableListIndex(SelectElementData & data,Element * element)119 static int lastSelectableListIndex(SelectElementData& data, Element* element)
120 {
121     return nextValidIndex(data.listItems(element), -1, SkipForwards, INT_MAX);
122 }
123 
124 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
nextSelectableListIndexPageAway(SelectElementData & data,Element * element,int startIndex,SkipDirection direction)125 static int nextSelectableListIndexPageAway(SelectElementData& data, Element* element, int startIndex, SkipDirection direction)
126 {
127     const Vector<Element*>& items = data.listItems(element);
128     // Can't use data->size() because renderer forces a minimum size.
129     int pageSize = 0;
130     if (element->renderer()->isListBox())
131         pageSize = toRenderListBox(element->renderer())->size() - 1; // -1 so we still show context
132 
133     // One page away, but not outside valid bounds.
134     // If there is a valid option item one page away, the index is chosen.
135     // If there is no exact one page away valid option, returns startIndex or the most far index.
136     int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1);
137     int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex));
138     return nextValidIndex(items, edgeIndex, direction, skipAmount);
139 }
140 
selectAll(SelectElementData & data,Element * element)141 void SelectElement::selectAll(SelectElementData& data, Element* element)
142 {
143     ASSERT(!data.usesMenuList());
144     if (!element->renderer() || !data.multiple())
145         return;
146 
147     // Save the selection so it can be compared to the new selectAll selection when dispatching change events
148     saveLastSelection(data, element);
149 
150     data.setActiveSelectionState(true);
151     setActiveSelectionAnchorIndex(data, element, nextSelectableListIndex(data, element, -1));
152     setActiveSelectionEndIndex(data, previousSelectableListIndex(data, element, -1));
153 
154     updateListBoxSelection(data, element, false);
155     listBoxOnChange(data, element);
156 }
157 
saveLastSelection(SelectElementData & data,Element * element)158 void SelectElement::saveLastSelection(SelectElementData& data, Element* element)
159 {
160     if (data.usesMenuList()) {
161         data.setLastOnChangeIndex(selectedIndex(data, element));
162         return;
163     }
164 
165     Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection();
166     lastOnChangeSelection.clear();
167 
168     const Vector<Element*>& items = data.listItems(element);
169     for (unsigned i = 0; i < items.size(); ++i) {
170         OptionElement* optionElement = toOptionElement(items[i]);
171         lastOnChangeSelection.append(optionElement && optionElement->selected());
172     }
173 }
174 
setActiveSelectionAnchorIndex(SelectElementData & data,Element * element,int index)175 void SelectElement::setActiveSelectionAnchorIndex(SelectElementData& data, Element* element, int index)
176 {
177     data.setActiveSelectionAnchorIndex(index);
178 
179     // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
180     Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection();
181     cachedStateForActiveSelection.clear();
182 
183     const Vector<Element*>& items = data.listItems(element);
184     for (unsigned i = 0; i < items.size(); ++i) {
185         OptionElement* optionElement = toOptionElement(items[i]);
186         cachedStateForActiveSelection.append(optionElement && optionElement->selected());
187     }
188 }
189 
setActiveSelectionEndIndex(SelectElementData & data,int index)190 void SelectElement::setActiveSelectionEndIndex(SelectElementData& data, int index)
191 {
192     data.setActiveSelectionEndIndex(index);
193 }
194 
updateListBoxSelection(SelectElementData & data,Element * element,bool deselectOtherOptions)195 void SelectElement::updateListBoxSelection(SelectElementData& data, Element* element, bool deselectOtherOptions)
196 {
197     ASSERT(element->renderer() && (element->renderer()->isListBox() || data.multiple()));
198     ASSERT(!data.listItems(element).size() || data.activeSelectionAnchorIndex() >= 0);
199 
200     unsigned start = min(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex());
201     unsigned end = max(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex());
202     Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection();
203 
204     const Vector<Element*>& items = data.listItems(element);
205     for (unsigned i = 0; i < items.size(); ++i) {
206         OptionElement* optionElement = toOptionElement(items[i]);
207         if (!optionElement || items[i]->disabled())
208             continue;
209 
210         if (i >= start && i <= end)
211             optionElement->setSelectedState(data.activeSelectionState());
212         else if (deselectOtherOptions || i >= cachedStateForActiveSelection.size())
213             optionElement->setSelectedState(false);
214         else
215             optionElement->setSelectedState(cachedStateForActiveSelection[i]);
216     }
217 
218     toSelectElement(element)->updateValidity();
219     scrollToSelection(data, element);
220 }
221 
listBoxOnChange(SelectElementData & data,Element * element)222 void SelectElement::listBoxOnChange(SelectElementData& data, Element* element)
223 {
224     ASSERT(!data.usesMenuList() || data.multiple());
225 
226     Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection();
227     const Vector<Element*>& items = data.listItems(element);
228 
229     // If the cached selection list is empty, or the size has changed, then fire dispatchFormControlChangeEvent, and return early.
230     if (lastOnChangeSelection.isEmpty() || lastOnChangeSelection.size() != items.size()) {
231         element->dispatchFormControlChangeEvent();
232         return;
233     }
234 
235     // Update lastOnChangeSelection and fire dispatchFormControlChangeEvent
236     bool fireOnChange = false;
237     for (unsigned i = 0; i < items.size(); ++i) {
238         OptionElement* optionElement = toOptionElement(items[i]);
239         bool selected = optionElement &&  optionElement->selected();
240         if (selected != lastOnChangeSelection[i])
241             fireOnChange = true;
242         lastOnChangeSelection[i] = selected;
243     }
244 
245     if (fireOnChange)
246         element->dispatchFormControlChangeEvent();
247 }
248 
menuListOnChange(SelectElementData & data,Element * element)249 void SelectElement::menuListOnChange(SelectElementData& data, Element* element)
250 {
251     ASSERT(data.usesMenuList());
252 
253     int selected = selectedIndex(data, element);
254     if (data.lastOnChangeIndex() != selected && data.userDrivenChange()) {
255         data.setLastOnChangeIndex(selected);
256         data.setUserDrivenChange(false);
257         element->dispatchFormControlChangeEvent();
258     }
259 }
260 
scrollToSelection(SelectElementData & data,Element * element)261 void SelectElement::scrollToSelection(SelectElementData& data, Element* element)
262 {
263     if (data.usesMenuList())
264         return;
265 
266     if (RenderObject* renderer = element->renderer())
267         toRenderListBox(renderer)->selectionChanged();
268 }
269 
setOptionsChangedOnRenderer(SelectElementData & data,Element * element)270 void SelectElement::setOptionsChangedOnRenderer(SelectElementData& data, Element* element)
271 {
272     if (RenderObject* renderer = element->renderer()) {
273         if (data.usesMenuList())
274             toRenderMenuList(renderer)->setOptionsChanged(true);
275         else
276             toRenderListBox(renderer)->setOptionsChanged(true);
277     }
278 }
279 
setRecalcListItems(SelectElementData & data,Element * element)280 void SelectElement::setRecalcListItems(SelectElementData& data, Element* element)
281 {
282     data.setShouldRecalcListItems(true);
283     data.setActiveSelectionAnchorIndex(-1); // Manual selection anchor is reset when manipulating the select programmatically.
284     setOptionsChangedOnRenderer(data, element);
285     element->setNeedsStyleRecalc();
286 }
287 
recalcListItems(SelectElementData & data,const Element * element,bool updateSelectedStates)288 void SelectElement::recalcListItems(SelectElementData& data, const Element* element, bool updateSelectedStates)
289 {
290     Vector<Element*>& listItems = data.rawListItems();
291     listItems.clear();
292 
293     data.setShouldRecalcListItems(false);
294 
295     OptionElement* foundSelected = 0;
296     for (Node* currentNode = element->firstChild(); currentNode;) {
297         if (!currentNode->isElementNode()) {
298             currentNode = currentNode->traverseNextSibling(element);
299             continue;
300         }
301 
302         Element* current = static_cast<Element*>(currentNode);
303 
304         // optgroup tags may not nest. However, both FireFox and IE will
305         // flatten the tree automatically, so we follow suit.
306         // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6)
307         if (isOptionGroupElement(current)) {
308             listItems.append(current);
309             if (current->firstChild()) {
310                 currentNode = current->firstChild();
311                 continue;
312             }
313         }
314 
315         if (OptionElement* optionElement = toOptionElement(current)) {
316             listItems.append(current);
317 
318             if (updateSelectedStates && !data.multiple()) {
319                 if (!foundSelected && (data.size() <= 1 || optionElement->selected())) {
320                     foundSelected = optionElement;
321                     foundSelected->setSelectedState(true);
322                 } else if (foundSelected && optionElement->selected()) {
323                     foundSelected->setSelectedState(false);
324                     foundSelected = optionElement;
325                 }
326             }
327         }
328 
329         if (current->hasTagName(HTMLNames::hrTag))
330             listItems.append(current);
331 
332         // In conforming HTML code, only <optgroup> and <option> will be found
333         // within a <select>. We call traverseNextSibling so that we only step
334         // into those tags that we choose to. For web-compat, we should cope
335         // with the case where odd tags like a <div> have been added but we
336         // handle this because such tags have already been removed from the
337         // <select>'s subtree at this point.
338         currentNode = currentNode->traverseNextSibling(element);
339     }
340 }
341 
selectedIndex(const SelectElementData & data,const Element * element)342 int SelectElement::selectedIndex(const SelectElementData& data, const Element* element)
343 {
344     unsigned index = 0;
345 
346     // return the number of the first option selected
347     const Vector<Element*>& items = data.listItems(element);
348     for (size_t i = 0; i < items.size(); ++i) {
349         if (OptionElement* optionElement = toOptionElement(items[i])) {
350             if (optionElement->selected())
351                 return index;
352             ++index;
353         }
354     }
355 
356     return -1;
357 }
358 
setSelectedIndex(SelectElementData & data,Element * element,int optionIndex,bool deselect,bool fireOnChangeNow,bool userDrivenChange)359 void SelectElement::setSelectedIndex(SelectElementData& data, Element* element, int optionIndex, bool deselect, bool fireOnChangeNow, bool userDrivenChange)
360 {
361     if (optionIndex == -1 && !deselect && !data.multiple())
362         optionIndex = nextSelectableListIndex(data, element, -1);
363     if (!data.multiple())
364         deselect = true;
365 
366     const Vector<Element*>& items = data.listItems(element);
367     int listIndex = optionToListIndex(data, element, optionIndex);
368 
369     Element* excludeElement = 0;
370     if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) {
371         excludeElement = items[listIndex];
372         if (data.activeSelectionAnchorIndex() < 0 || deselect)
373             setActiveSelectionAnchorIndex(data, element, listIndex);
374         if (data.activeSelectionEndIndex() < 0 || deselect)
375             setActiveSelectionEndIndex(data, listIndex);
376         optionElement->setSelectedState(true);
377     }
378 
379     if (deselect)
380         deselectItems(data, element, excludeElement);
381 
382     // For the menu list case, this is what makes the selected element appear.
383     if (RenderObject* renderer = element->renderer())
384         renderer->updateFromElement();
385 
386     scrollToSelection(data, element);
387 
388     // This only gets called with fireOnChangeNow for menu lists.
389     if (data.usesMenuList()) {
390         data.setUserDrivenChange(userDrivenChange);
391         if (fireOnChangeNow)
392             menuListOnChange(data, element);
393         RenderObject* renderer = element->renderer();
394         if (renderer) {
395             if (data.usesMenuList())
396                 toRenderMenuList(renderer)->didSetSelectedIndex();
397             else if (renderer->isListBox())
398                 toRenderListBox(renderer)->selectionChanged();
399         }
400     }
401 
402     if (Frame* frame = element->document()->frame())
403         frame->page()->chrome()->client()->formStateDidChange(element);
404 }
405 
optionToListIndex(const SelectElementData & data,const Element * element,int optionIndex)406 int SelectElement::optionToListIndex(const SelectElementData& data, const Element* element, int optionIndex)
407 {
408     const Vector<Element*>& items = data.listItems(element);
409     int listSize = (int) items.size();
410     if (optionIndex < 0 || optionIndex >= listSize)
411         return -1;
412 
413     int optionIndex2 = -1;
414     for (int listIndex = 0; listIndex < listSize; ++listIndex) {
415         if (isOptionElement(items[listIndex])) {
416             ++optionIndex2;
417             if (optionIndex2 == optionIndex)
418                 return listIndex;
419         }
420     }
421 
422     return -1;
423 }
424 
listToOptionIndex(const SelectElementData & data,const Element * element,int listIndex)425 int SelectElement::listToOptionIndex(const SelectElementData& data, const Element* element, int listIndex)
426 {
427     const Vector<Element*>& items = data.listItems(element);
428     if (listIndex < 0 || listIndex >= int(items.size()) ||
429         !isOptionElement(items[listIndex]))
430         return -1;
431 
432     int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
433     for (int i = 0; i < listIndex; ++i)
434         if (isOptionElement(items[i]))
435             ++optionIndex;
436 
437     return optionIndex;
438 }
439 
dispatchFocusEvent(SelectElementData & data,Element * element)440 void SelectElement::dispatchFocusEvent(SelectElementData& data, Element* element)
441 {
442     // Save the selection so it can be compared to the new selection when dispatching change events during blur event dispatchal
443     if (data.usesMenuList())
444         saveLastSelection(data, element);
445 }
446 
dispatchBlurEvent(SelectElementData & data,Element * element)447 void SelectElement::dispatchBlurEvent(SelectElementData& data, Element* element)
448 {
449     // We only need to fire change events here for menu lists, because we fire change events for list boxes whenever the selection change is actually made.
450     // This matches other browsers' behavior.
451     if (data.usesMenuList())
452         menuListOnChange(data, element);
453 }
454 
deselectItems(SelectElementData & data,Element * element,Element * excludeElement)455 void SelectElement::deselectItems(SelectElementData& data, Element* element, Element* excludeElement)
456 {
457     const Vector<Element*>& items = data.listItems(element);
458     for (unsigned i = 0; i < items.size(); ++i) {
459         if (items[i] == excludeElement)
460             continue;
461 
462         if (OptionElement* optionElement = toOptionElement(items[i]))
463             optionElement->setSelectedState(false);
464     }
465 }
466 
saveFormControlState(const SelectElementData & data,const Element * element,String & value)467 bool SelectElement::saveFormControlState(const SelectElementData& data, const Element* element, String& value)
468 {
469     const Vector<Element*>& items = data.listItems(element);
470     int length = items.size();
471 
472     // FIXME: Change this code to use the new StringImpl::createUninitialized code path.
473     Vector<char, 1024> characters(length);
474     for (int i = 0; i < length; ++i) {
475         OptionElement* optionElement = toOptionElement(items[i]);
476         bool selected = optionElement && optionElement->selected();
477         characters[i] = selected ? 'X' : '.';
478     }
479 
480     value = String(characters.data(), length);
481     return true;
482 }
483 
restoreFormControlState(SelectElementData & data,Element * element,const String & state)484 void SelectElement::restoreFormControlState(SelectElementData& data, Element* element, const String& state)
485 {
486     recalcListItems(data, element);
487 
488     const Vector<Element*>& items = data.listItems(element);
489     int length = items.size();
490 
491     for (int i = 0; i < length; ++i) {
492         if (OptionElement* optionElement = toOptionElement(items[i]))
493             optionElement->setSelectedState(state[i] == 'X');
494     }
495 
496     setOptionsChangedOnRenderer(data, element);
497 }
498 
parseMultipleAttribute(SelectElementData & data,Element * element,Attribute * attribute)499 void SelectElement::parseMultipleAttribute(SelectElementData& data, Element* element, Attribute* attribute)
500 {
501     bool oldUsesMenuList = data.usesMenuList();
502     data.setMultiple(!attribute->isNull());
503     toSelectElement(element)->updateValidity();
504     if (oldUsesMenuList != data.usesMenuList() && element->attached()) {
505         element->detach();
506         element->attach();
507     }
508 }
509 
appendFormData(SelectElementData & data,Element * element,FormDataList & list)510 bool SelectElement::appendFormData(SelectElementData& data, Element* element, FormDataList& list)
511 {
512     const AtomicString& name = element->formControlName();
513     if (name.isEmpty())
514         return false;
515 
516     bool successful = false;
517     const Vector<Element*>& items = data.listItems(element);
518 
519     for (unsigned i = 0; i < items.size(); ++i) {
520         OptionElement* optionElement = toOptionElement(items[i]);
521         if (optionElement && optionElement->selected() && !optionElement->disabled()) {
522             list.appendData(name, optionElement->value());
523             successful = true;
524         }
525     }
526 
527     // It's possible that this is a menulist with multiple options and nothing
528     // will be submitted (!successful). We won't send a unselected non-disabled
529     // option as fallback. This behavior matches to other browsers.
530     return successful;
531 }
532 
reset(SelectElementData & data,Element * element)533 void SelectElement::reset(SelectElementData& data, Element* element)
534 {
535     OptionElement* firstOption = 0;
536     OptionElement* selectedOption = 0;
537 
538     const Vector<Element*>& items = data.listItems(element);
539     for (unsigned i = 0; i < items.size(); ++i) {
540         OptionElement* optionElement = toOptionElement(items[i]);
541         if (!optionElement)
542             continue;
543 
544         if (items[i]->fastHasAttribute(HTMLNames::selectedAttr)) {
545             if (selectedOption && !data.multiple())
546                 selectedOption->setSelectedState(false);
547             optionElement->setSelectedState(true);
548             selectedOption = optionElement;
549         } else
550             optionElement->setSelectedState(false);
551 
552         if (!firstOption)
553             firstOption = optionElement;
554     }
555 
556     if (!selectedOption && firstOption && !data.multiple() && data.size() <= 1)
557         firstOption->setSelectedState(true);
558 
559     setOptionsChangedOnRenderer(data, element);
560     element->setNeedsStyleRecalc();
561 }
562 
menuListDefaultEventHandler(SelectElementData & data,Element * element,Event * event,HTMLFormElement * htmlForm)563 void SelectElement::menuListDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
564 {
565     if (event->type() == eventNames().keydownEvent) {
566         if (!element->renderer() || !event->isKeyboardEvent())
567             return;
568 
569         const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
570         bool handled = false;
571 
572 #if ARROW_KEYS_POP_MENU
573         if (!isSpatialNavigationEnabled(element->document()->frame())) {
574             if (keyIdentifier == "Down" || keyIdentifier == "Up") {
575                 element->focus();
576 
577                 if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
578                     return;
579 
580                 // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
581                 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
582                 saveLastSelection(data, element);
583                 if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
584                     menuList->showPopup();
585 
586                 event->setDefaultHandled();
587             }
588             return;
589         }
590 #endif
591         // When using spatial navigation, we want to be able to navigate away from the select element
592         // when the user hits any of the arrow keys, instead of changing the selection.
593         if (isSpatialNavigationEnabled(element->document()->frame()))
594             if (!data.activeSelectionState())
595                 return;
596 
597         UNUSED_PARAM(htmlForm);
598         const Vector<Element*>& listItems = data.listItems(element);
599 
600         int listIndex = optionToListIndex(data, element, selectedIndex(data, element));
601         if (keyIdentifier == "Down" || keyIdentifier == "Right") {
602             listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 1);
603             handled = true;
604         } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
605             listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 1);
606             handled = true;
607         } else if (keyIdentifier == "PageDown") {
608             listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 3);
609             handled = true;
610         } else if (keyIdentifier == "PageUp") {
611             listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 3);
612             handled = true;
613         } else if (keyIdentifier == "Home") {
614             listIndex = nextValidIndex(listItems, -1, SkipForwards, 1);
615             handled = true;
616         } else if (keyIdentifier == "End") {
617             listIndex = nextValidIndex(listItems, listItems.size(), SkipBackwards, 1);
618             handled = true;
619         }
620 
621         if (handled && listIndex >= 0 && static_cast<unsigned>(listIndex) < listItems.size())
622             setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex));
623 
624         if (handled)
625             event->setDefaultHandled();
626     }
627 
628     // Use key press event here since sending simulated mouse events
629     // on key down blocks the proper sending of the key press event.
630     if (event->type() == eventNames().keypressEvent) {
631         if (!element->renderer() || !event->isKeyboardEvent())
632             return;
633 
634         int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
635         bool handled = false;
636 
637         if (keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) {
638             // Use space to toggle arrow key handling for selection change or spatial navigation.
639             data.setActiveSelectionState(!data.activeSelectionState());
640             event->setDefaultHandled();
641             return;
642         }
643 
644 #if SPACE_OR_RETURN_POP_MENU
645         if (keyCode == ' ' || keyCode == '\r') {
646             element->focus();
647 
648             if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
649                 return;
650 
651             // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
652             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
653             saveLastSelection(data, element);
654             if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
655                 menuList->showPopup();
656             handled = true;
657         }
658 #elif ARROW_KEYS_POP_MENU
659         if (keyCode == ' ') {
660             element->focus();
661 
662             if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
663                 return;
664 
665             // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex,
666             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
667             saveLastSelection(data, element);
668             if (RenderMenuList* menuList = toRenderMenuList(element->renderer()))
669                 menuList->showPopup();
670             handled = true;
671         } else if (keyCode == '\r') {
672             if (htmlForm)
673                 htmlForm->submitImplicitly(event, false);
674             menuListOnChange(data, element);
675             handled = true;
676         }
677 #else
678         int listIndex = optionToListIndex(data, element, selectedIndex(data, element));
679         if (keyCode == '\r') {
680             // listIndex should already be selected, but this will fire the onchange handler.
681             setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex), true, true);
682             handled = true;
683         }
684 #endif
685         if (handled)
686             event->setDefaultHandled();
687     }
688 
689     if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
690         element->focus();
691         if (element->renderer() && element->renderer()->isMenuList()) {
692             if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) {
693                 if (menuList->popupIsVisible())
694                     menuList->hidePopup();
695                 else {
696                     // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
697                     // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
698                     saveLastSelection(data, element);
699                     menuList->showPopup();
700                 }
701             }
702         }
703         event->setDefaultHandled();
704     }
705 }
706 
updateSelectedState(SelectElementData & data,Element * element,int listIndex,bool multi,bool shift)707 void SelectElement::updateSelectedState(SelectElementData& data, Element* element, int listIndex,
708                                         bool multi, bool shift)
709 {
710     ASSERT(listIndex >= 0);
711 
712     // Save the selection so it can be compared to the new selection when dispatching change events during mouseup, or after autoscroll finishes.
713     saveLastSelection(data, element);
714 
715     data.setActiveSelectionState(true);
716 
717     bool shiftSelect = data.multiple() && shift;
718     bool multiSelect = data.multiple() && multi && !shift;
719 
720     Element* clickedElement = data.listItems(element)[listIndex];
721     OptionElement* option = toOptionElement(clickedElement);
722     if (option) {
723         // Keep track of whether an active selection (like during drag selection), should select or deselect
724         if (option->selected() && multi)
725             data.setActiveSelectionState(false);
726 
727         if (!data.activeSelectionState())
728             option->setSelectedState(false);
729     }
730 
731     // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
732     // If no option was clicked, then this will deselect all items in the list.
733     if (!shiftSelect && !multiSelect)
734         deselectItems(data, element, clickedElement);
735 
736     // If the anchor hasn't been set, and we're doing a single selection or a shift selection, then initialize the anchor to the first selected index.
737     if (data.activeSelectionAnchorIndex() < 0 && !multiSelect)
738         setActiveSelectionAnchorIndex(data, element, selectedIndex(data, element));
739 
740     // Set the selection state of the clicked option
741     if (option && !clickedElement->disabled())
742         option->setSelectedState(true);
743 
744     // If there was no selectedIndex() for the previous initialization, or
745     // If we're doing a single selection, or a multiple selection (using cmd or ctrl), then initialize the anchor index to the listIndex that just got clicked.
746     if (data.activeSelectionAnchorIndex() < 0 || !shiftSelect)
747         setActiveSelectionAnchorIndex(data, element, listIndex);
748 
749     setActiveSelectionEndIndex(data, listIndex);
750     updateListBoxSelection(data, element, !multiSelect);
751 }
752 
listBoxDefaultEventHandler(SelectElementData & data,Element * element,Event * event,HTMLFormElement * htmlForm)753 void SelectElement::listBoxDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
754 {
755     const Vector<Element*>& listItems = data.listItems(element);
756 
757     if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) {
758         element->focus();
759 
760         if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
761             return;
762 
763         // Convert to coords relative to the list box if needed.
764         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
765         IntPoint localOffset = roundedIntPoint(element->renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true));
766         int listIndex = toRenderListBox(element->renderer())->listIndexAtOffset(localOffset.x(), localOffset.y());
767         if (listIndex >= 0) {
768 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
769             updateSelectedState(data, element, listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey());
770 #else
771             updateSelectedState(data, element, listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
772 #endif
773             if (Frame* frame = element->document()->frame())
774                 frame->eventHandler()->setMouseDownMayStartAutoscroll();
775 
776             event->setDefaultHandled();
777         }
778     } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && element->document()->frame()->eventHandler()->autoscrollRenderer() != element->renderer()) {
779         // This makes sure we fire dispatchFormControlChangeEvent for a single click.  For drag selection, onChange will fire when the autoscroll timer stops.
780         listBoxOnChange(data, element);
781     } else if (event->type() == eventNames().keydownEvent) {
782         if (!event->isKeyboardEvent())
783             return;
784         const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier();
785 
786         bool handled = false;
787         int endIndex = 0;
788         if (data.activeSelectionEndIndex() < 0) {
789             // Initialize the end index
790             if (keyIdentifier == "Down" || keyIdentifier == "PageDown") {
791                 int startIndex = lastSelectedListIndex(data, element);
792                 handled = true;
793                 if (keyIdentifier == "Down")
794                     endIndex = nextSelectableListIndex(data, element, startIndex);
795                 else
796                     endIndex = nextSelectableListIndexPageAway(data, element, startIndex, SkipForwards);
797             } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") {
798                 int startIndex = optionToListIndex(data, element, selectedIndex(data, element));
799                 handled = true;
800                 if (keyIdentifier == "Up")
801                     endIndex = previousSelectableListIndex(data, element, startIndex);
802                 else
803                     endIndex = nextSelectableListIndexPageAway(data, element, startIndex, SkipBackwards);
804             }
805         } else {
806             // Set the end index based on the current end index
807             if (keyIdentifier == "Down") {
808                 endIndex = nextSelectableListIndex(data, element, data.activeSelectionEndIndex());
809                 handled = true;
810             } else if (keyIdentifier == "Up") {
811                 endIndex = previousSelectableListIndex(data, element, data.activeSelectionEndIndex());
812                 handled = true;
813             } else if (keyIdentifier == "PageDown") {
814                 endIndex = nextSelectableListIndexPageAway(data, element, data.activeSelectionEndIndex(), SkipForwards);
815                 handled = true;
816             } else if (keyIdentifier == "PageUp") {
817                 endIndex = nextSelectableListIndexPageAway(data, element, data.activeSelectionEndIndex(), SkipBackwards);
818                 handled = true;
819             }
820         }
821         if (keyIdentifier == "Home") {
822             endIndex = firstSelectableListIndex(data, element);
823             handled = true;
824         } else if (keyIdentifier == "End") {
825             endIndex = lastSelectableListIndex(data, element);
826             handled = true;
827         }
828 
829         if (isSpatialNavigationEnabled(element->document()->frame()))
830             // Check if the selection moves to the boundary.
831             if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == data.activeSelectionEndIndex()))
832                 return;
833 
834         if (endIndex >= 0 && handled) {
835             // Save the selection so it can be compared to the new selection when dispatching change events immediately after making the new selection.
836             saveLastSelection(data, element);
837 
838             ASSERT_UNUSED(listItems, !listItems.size() || (endIndex >= 0 && static_cast<unsigned>(endIndex) < listItems.size()));
839             setActiveSelectionEndIndex(data, endIndex);
840 
841             bool selectNewItem = !data.multiple() || static_cast<KeyboardEvent*>(event)->shiftKey() || !isSpatialNavigationEnabled(element->document()->frame());
842             if (selectNewItem)
843                 data.setActiveSelectionState(true);
844             // If the anchor is unitialized, or if we're going to deselect all other options, then set the anchor index equal to the end index.
845             bool deselectOthers = !data.multiple() || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem);
846             if (data.activeSelectionAnchorIndex() < 0 || deselectOthers) {
847                 if (deselectOthers)
848                     deselectItems(data, element);
849                 setActiveSelectionAnchorIndex(data, element, data.activeSelectionEndIndex());
850             }
851 
852             toRenderListBox(element->renderer())->scrollToRevealElementAtListIndex(endIndex);
853             if (selectNewItem) {
854                 updateListBoxSelection(data, element, deselectOthers);
855                 listBoxOnChange(data, element);
856             } else
857                 scrollToSelection(data, element);
858 
859             event->setDefaultHandled();
860         }
861     } else if (event->type() == eventNames().keypressEvent) {
862         if (!event->isKeyboardEvent())
863             return;
864         int keyCode = static_cast<KeyboardEvent*>(event)->keyCode();
865 
866         if (keyCode == '\r') {
867             if (htmlForm)
868                 htmlForm->submitImplicitly(event, false);
869             event->setDefaultHandled();
870         } else if (data.multiple() && keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) {
871             // Use space to toggle selection change.
872             data.setActiveSelectionState(!data.activeSelectionState());
873             updateSelectedState(data, element, listToOptionIndex(data, element, data.activeSelectionEndIndex()), true /*multi*/, false /*shift*/);
874             listBoxOnChange(data, element);
875             event->setDefaultHandled();
876         }
877     }
878 }
879 
defaultEventHandler(SelectElementData & data,Element * element,Event * event,HTMLFormElement * htmlForm)880 void SelectElement::defaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm)
881 {
882     if (!element->renderer())
883         return;
884 
885     if (data.usesMenuList())
886         menuListDefaultEventHandler(data, element, event, htmlForm);
887     else
888         listBoxDefaultEventHandler(data, element, event, htmlForm);
889 
890     if (event->defaultHandled())
891         return;
892 
893     if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) {
894         KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event);
895         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) {
896             typeAheadFind(data, element, keyboardEvent);
897             event->setDefaultHandled();
898             return;
899         }
900     }
901 }
902 
lastSelectedListIndex(const SelectElementData & data,const Element * element)903 int SelectElement::lastSelectedListIndex(const SelectElementData& data, const Element* element)
904 {
905     // return the number of the last option selected
906     unsigned index = 0;
907     bool found = false;
908     const Vector<Element*>& items = data.listItems(element);
909     for (size_t i = 0; i < items.size(); ++i) {
910         if (OptionElement* optionElement = toOptionElement(items[i])) {
911             if (optionElement->selected()) {
912                 index = i;
913                 found = true;
914             }
915         }
916     }
917 
918     return found ? (int) index : -1;
919 }
920 
stripLeadingWhiteSpace(const String & string)921 static String stripLeadingWhiteSpace(const String& string)
922 {
923     int length = string.length();
924 
925     int i;
926     for (i = 0; i < length; ++i) {
927         if (string[i] != noBreakSpace && (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
928             break;
929     }
930 
931     return string.substring(i, length - i);
932 }
933 
typeAheadFind(SelectElementData & data,Element * element,KeyboardEvent * event)934 void SelectElement::typeAheadFind(SelectElementData& data, Element* element, KeyboardEvent* event)
935 {
936     if (event->timeStamp() < data.lastCharTime())
937         return;
938 
939     DOMTimeStamp delta = event->timeStamp() - data.lastCharTime();
940     data.setLastCharTime(event->timeStamp());
941 
942     UChar c = event->charCode();
943 
944     String prefix;
945     int searchStartOffset = 1;
946     if (delta > typeAheadTimeout) {
947         prefix = String(&c, 1);
948         data.setTypedString(prefix);
949         data.setRepeatingChar(c);
950     } else {
951         data.typedString().append(c);
952 
953         if (c == data.repeatingChar())
954             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
955             prefix = String(&c, 1);
956         else {
957             data.setRepeatingChar(0);
958             prefix = data.typedString();
959             searchStartOffset = 0;
960         }
961     }
962 
963     const Vector<Element*>& items = data.listItems(element);
964     int itemCount = items.size();
965     if (itemCount < 1)
966         return;
967 
968     int selected = selectedIndex(data, element);
969     int index = (optionToListIndex(data, element, selected >= 0 ? selected : 0) + searchStartOffset) % itemCount;
970     ASSERT(index >= 0);
971 
972     // Compute a case-folded copy of the prefix string before beginning the search for
973     // a matching element. This code uses foldCase to work around the fact that
974     // String::startWith does not fold non-ASCII characters. This code can be changed
975     // to use startWith once that is fixed.
976     String prefixWithCaseFolded(prefix.foldCase());
977     for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) {
978         OptionElement* optionElement = toOptionElement(items[index]);
979         if (!optionElement || items[index]->disabled())
980             continue;
981 
982         // Fold the option string and check if its prefix is equal to the folded prefix.
983         String text = optionElement->textIndentedToRespectGroupLabel();
984         if (stripLeadingWhiteSpace(text).foldCase().startsWith(prefixWithCaseFolded)) {
985             setSelectedIndex(data, element, listToOptionIndex(data, element, index));
986             if (!data.usesMenuList())
987                 listBoxOnChange(data, element);
988 
989             setOptionsChangedOnRenderer(data, element);
990             element->setNeedsStyleRecalc();
991             return;
992         }
993     }
994 }
995 
insertedIntoTree(SelectElementData & data,Element * element)996 void SelectElement::insertedIntoTree(SelectElementData& data, Element* element)
997 {
998     // When the element is created during document parsing, it won't have any items yet - but for innerHTML
999     // and related methods, this method is called after the whole subtree is constructed.
1000     recalcListItems(data, element, true);
1001 }
1002 
accessKeySetSelectedIndex(SelectElementData & data,Element * element,int index)1003 void SelectElement::accessKeySetSelectedIndex(SelectElementData& data, Element* element, int index)
1004 {
1005     // first bring into focus the list box
1006     if (!element->focused())
1007         element->accessKeyAction(false);
1008 
1009     // if this index is already selected, unselect. otherwise update the selected index
1010     const Vector<Element*>& items = data.listItems(element);
1011     int listIndex = optionToListIndex(data, element, index);
1012     if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) {
1013         if (optionElement->selected())
1014             optionElement->setSelectedState(false);
1015         else
1016             setSelectedIndex(data, element, index, false, true);
1017     }
1018 
1019     if (data.usesMenuList())
1020         menuListOnChange(data, element);
1021     else
1022         listBoxOnChange(data, element);
1023 
1024     scrollToSelection(data, element);
1025 }
1026 
optionCount(const SelectElementData & data,const Element * element)1027 unsigned SelectElement::optionCount(const SelectElementData& data, const Element* element)
1028 {
1029     unsigned options = 0;
1030 
1031     const Vector<Element*>& items = data.listItems(element);
1032     for (unsigned i = 0; i < items.size(); ++i) {
1033         if (isOptionElement(items[i]))
1034             ++options;
1035     }
1036 
1037     return options;
1038 }
1039 
1040 // SelectElementData
SelectElementData()1041 SelectElementData::SelectElementData()
1042     : m_multiple(false)
1043     , m_size(0)
1044     , m_lastOnChangeIndex(-1)
1045     , m_activeSelectionState(false)
1046     , m_activeSelectionAnchorIndex(-1)
1047     , m_activeSelectionEndIndex(-1)
1048     , m_recalcListItems(false)
1049     , m_repeatingChar(0)
1050     , m_lastCharTime(0)
1051 {
1052 }
1053 
~SelectElementData()1054 SelectElementData::~SelectElementData()
1055 {
1056 }
1057 
checkListItems(const Element * element) const1058 void SelectElementData::checkListItems(const Element* element) const
1059 {
1060 #if !ASSERT_DISABLED
1061     Vector<Element*> items = m_listItems;
1062     SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element, false);
1063     ASSERT(items == m_listItems);
1064 #else
1065     UNUSED_PARAM(element);
1066 #endif
1067 }
1068 
listItems(const Element * element)1069 Vector<Element*>& SelectElementData::listItems(const Element* element)
1070 {
1071     if (m_recalcListItems)
1072         SelectElement::recalcListItems(*this, element);
1073     else
1074         checkListItems(element);
1075 
1076     return m_listItems;
1077 }
1078 
listItems(const Element * element) const1079 const Vector<Element*>& SelectElementData::listItems(const Element* element) const
1080 {
1081     if (m_recalcListItems)
1082         SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element);
1083     else
1084         checkListItems(element);
1085 
1086     return m_listItems;
1087 }
1088 
toSelectElement(Element * element)1089 SelectElement* toSelectElement(Element* element)
1090 {
1091     if (element->isHTMLElement() && element->hasTagName(HTMLNames::selectTag))
1092         return static_cast<HTMLSelectElement*>(element);
1093 
1094 #if ENABLE(WML)
1095     if (element->isWMLElement() && element->hasTagName(WMLNames::selectTag))
1096         return static_cast<WMLSelectElement*>(element);
1097 #endif
1098 
1099     return 0;
1100 }
1101 
1102 }
1103