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