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 64using std::min; 65using std::max; 66using namespace WTF; 67using namespace Unicode; 68 69namespace WebCore { 70 71static const DOMTimeStamp typeAheadTimeout = 1000; 72 73enum 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. 82static 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 98static int nextSelectableListIndex(SelectElementData& data, Element* element, int startIndex) 99{ 100 return nextValidIndex(data.listItems(element), startIndex, SkipForwards, 1); 101} 102 103static 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 110static 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 119static 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|. 125static 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 141void 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 158void 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 175void 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 190void SelectElement::setActiveSelectionEndIndex(SelectElementData& data, int index) 191{ 192 data.setActiveSelectionEndIndex(index); 193} 194 195void 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 222void 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 249void 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 261void 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 270void 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 280void 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 288void 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 342int 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 359void 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 406int 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 425int 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 440void 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 447void 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 455void 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 467bool 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 484void 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 499void 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 510bool 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 533void 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 563void 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 707void 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 753void 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 880void 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 903int 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 921static 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 934void 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 996void 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 1003void 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 1027unsigned 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 1041SelectElementData::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 1054SelectElementData::~SelectElementData() 1055{ 1056} 1057 1058void 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 1069Vector<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 1079const 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 1089SelectElement* 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