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