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