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