1/* 2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 3 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 4 * (C) 1999 Antti Koivisto (koivisto@kde.org) 5 * (C) 2001 Dirk Mueller (mueller@kde.org) 6 * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010, 2011 Apple Inc. All rights reserved. 7 * (C) 2006 Alexey Proskuryakov (ap@nypop.com) 8 * Copyright (C) 2010 Google Inc. All rights reserved. 9 * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Library General Public 13 * License as published by the Free Software Foundation; either 14 * version 2 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Library General Public License for more details. 20 * 21 * You should have received a copy of the GNU Library General Public License 22 * along with this library; see the file COPYING.LIB. If not, write to 23 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 24 * Boston, MA 02110-1301, USA. 25 * 26 */ 27 28#include "config.h" 29#include "HTMLSelectElement.h" 30 31#include "AXObjectCache.h" 32#include "Attribute.h" 33#include "Chrome.h" 34#include "ChromeClient.h" 35#include "EventHandler.h" 36#include "EventNames.h" 37#include "ExceptionCodePlaceholder.h" 38#include "FormController.h" 39#include "FormDataList.h" 40#include "Frame.h" 41#include "HTMLFormElement.h" 42#include "HTMLNames.h" 43#include "HTMLOptGroupElement.h" 44#include "HTMLOptionElement.h" 45#include "HTMLOptionsCollection.h" 46#include "KeyboardEvent.h" 47#include "LocalizedStrings.h" 48#include "MouseEvent.h" 49#include "NodeRenderingContext.h" 50#include "NodeTraversal.h" 51#include "Page.h" 52#include "PlatformMouseEvent.h" 53#include "RenderListBox.h" 54#include "RenderMenuList.h" 55#include "RenderTheme.h" 56#include "ScriptEventListener.h" 57#include "Settings.h" 58#include "SpatialNavigation.h" 59#include <wtf/text/StringBuilder.h> 60#include <wtf/unicode/Unicode.h> 61 62using namespace std; 63using namespace WTF::Unicode; 64 65namespace WebCore { 66 67using namespace HTMLNames; 68 69// Upper limit agreed upon with representatives of Opera and Mozilla. 70static const unsigned maxSelectItems = 10000; 71 72HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 73 : HTMLFormControlElementWithState(tagName, document, form) 74 , m_typeAhead(this) 75 , m_size(0) 76 , m_lastOnChangeIndex(-1) 77 , m_activeSelectionAnchorIndex(-1) 78 , m_activeSelectionEndIndex(-1) 79 , m_isProcessingUserDrivenChange(false) 80 , m_multiple(false) 81 , m_activeSelectionState(false) 82 , m_shouldRecalcListItems(false) 83{ 84 ASSERT(hasTagName(selectTag)); 85} 86 87PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 88{ 89 ASSERT(tagName.matches(selectTag)); 90 return adoptRef(new HTMLSelectElement(tagName, document, form)); 91} 92 93const AtomicString& HTMLSelectElement::formControlType() const 94{ 95 DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple", AtomicString::ConstructFromLiteral)); 96 DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one", AtomicString::ConstructFromLiteral)); 97 return m_multiple ? selectMultiple : selectOne; 98} 99 100void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement) 101{ 102 deselectItemsWithoutValidation(excludeElement); 103 setNeedsValidityCheck(); 104} 105 106void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeNow, bool allowMultipleSelection) 107{ 108 // User interaction such as mousedown events can cause list box select elements to send change events. 109 // This produces that same behavior for changes triggered by other code running on behalf of the user. 110 if (!usesMenuList()) { 111 updateSelectedState(optionToListIndex(optionIndex), allowMultipleSelection, false); 112 setNeedsValidityCheck(); 113 if (fireOnChangeNow) 114 listBoxOnChange(); 115 return; 116 } 117 118 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up 119 // autofill when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and <rdar://7467917>). 120 // The selectOption function does not behave this way, possibly because other callers need a change event even 121 // in cases where the selected option is not change. 122 if (optionIndex == selectedIndex()) 123 return; 124 125 selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven); 126} 127 128bool HTMLSelectElement::hasPlaceholderLabelOption() const 129{ 130 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1. 131 // 132 // The condition "size() > 1" is not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct. 133 // Using "size() > 1" here because size() may be 0 in WebKit. 134 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887 135 // 136 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified. 137 // In this case, the display size should be assumed as the default. 138 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements. 139 // 140 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1. 141 if (multiple() || size() > 1) 142 return false; 143 144 int listIndex = optionToListIndex(0); 145 ASSERT(listIndex >= 0); 146 if (listIndex < 0) 147 return false; 148 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems()[listIndex]); 149 return !listIndex && option->value().isEmpty(); 150} 151 152String HTMLSelectElement::validationMessage() const 153{ 154 if (!willValidate()) 155 return String(); 156 157 if (customError()) 158 return customValidationMessage(); 159 160 return valueMissing() ? validationMessageValueMissingForSelectText() : String(); 161} 162 163bool HTMLSelectElement::valueMissing() const 164{ 165 if (!willValidate()) 166 return false; 167 168 if (!isRequired()) 169 return false; 170 171 int firstSelectionIndex = selectedIndex(); 172 173 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing. 174 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption()); 175} 176 177void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) 178{ 179 if (!multiple()) 180 optionSelectedByUser(listToOptionIndex(listIndex), fireOnChangeNow, false); 181 else { 182 updateSelectedState(listIndex, allowMultiplySelections, shift); 183 setNeedsValidityCheck(); 184 if (fireOnChangeNow) 185 listBoxOnChange(); 186 } 187} 188 189bool HTMLSelectElement::usesMenuList() const 190{ 191 const Page* page = document()->page(); 192 RefPtr<RenderTheme> renderTheme = page ? page->theme() : RenderTheme::defaultTheme(); 193 if (renderTheme->delegatesMenuListRendering()) 194 return true; 195 196 return !m_multiple && m_size <= 1; 197} 198 199int HTMLSelectElement::activeSelectionStartListIndex() const 200{ 201 if (m_activeSelectionAnchorIndex >= 0) 202 return m_activeSelectionAnchorIndex; 203 return optionToListIndex(selectedIndex()); 204} 205 206int HTMLSelectElement::activeSelectionEndListIndex() const 207{ 208 if (m_activeSelectionEndIndex >= 0) 209 return m_activeSelectionEndIndex; 210 return lastSelectedListIndex(); 211} 212 213void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec) 214{ 215 // Make sure the element is ref'd and deref'd so we don't leak it. 216 RefPtr<HTMLElement> protectNewChild(element); 217 218 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag))) 219 return; 220 221 insertBefore(element, before, ec); 222 setNeedsValidityCheck(); 223} 224 225void HTMLSelectElement::remove(int optionIndex) 226{ 227 int listIndex = optionToListIndex(optionIndex); 228 if (listIndex < 0) 229 return; 230 231 listItems()[listIndex]->remove(IGNORE_EXCEPTION); 232} 233 234void HTMLSelectElement::remove(HTMLOptionElement* option) 235{ 236 if (option->ownerSelectElement() != this) 237 return; 238 239 option->remove(IGNORE_EXCEPTION); 240} 241 242String HTMLSelectElement::value() const 243{ 244 const Vector<HTMLElement*>& items = listItems(); 245 for (unsigned i = 0; i < items.size(); i++) { 246 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected()) 247 return static_cast<HTMLOptionElement*>(items[i])->value(); 248 } 249 return ""; 250} 251 252void HTMLSelectElement::setValue(const String &value) 253{ 254 // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once. 255 if (value.isNull()) { 256 setSelectedIndex(-1); 257 return; 258 } 259 260 // Find the option with value() matching the given parameter and make it the current selection. 261 const Vector<HTMLElement*>& items = listItems(); 262 unsigned optionIndex = 0; 263 for (unsigned i = 0; i < items.size(); i++) { 264 if (items[i]->hasLocalName(optionTag)) { 265 if (static_cast<HTMLOptionElement*>(items[i])->value() == value) { 266 setSelectedIndex(optionIndex); 267 return; 268 } 269 optionIndex++; 270 } 271 } 272 273 setSelectedIndex(-1); 274} 275 276bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const 277{ 278 if (name == alignAttr) { 279 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. 280 // See http://bugs.webkit.org/show_bug.cgi?id=12072 281 return false; 282 } 283 284 return HTMLFormControlElementWithState::isPresentationAttribute(name); 285} 286 287void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicString& value) 288{ 289 if (name == sizeAttr) { 290 int oldSize = m_size; 291 // Set the attribute value to a number. 292 // This is important since the style rules for this attribute can determine the appearance property. 293 int size = value.toInt(); 294 String attrSize = String::number(size); 295 if (attrSize != value) { 296 // FIXME: This is horribly factored. 297 if (Attribute* sizeAttribute = ensureUniqueElementData()->getAttributeItem(sizeAttr)) 298 sizeAttribute->setValue(attrSize); 299 } 300 size = max(size, 1); 301 302 // Ensure that we've determined selectedness of the items at least once prior to changing the size. 303 if (oldSize != size) 304 updateListItemSelectedStates(); 305 306 m_size = size; 307 setNeedsValidityCheck(); 308 if (m_size != oldSize && attached()) { 309 reattach(); 310 setRecalcListItems(); 311 } 312 } else if (name == multipleAttr) 313 parseMultipleAttribute(value); 314 else if (name == accesskeyAttr) { 315 // FIXME: ignore for the moment. 316 // 317 } else 318 HTMLFormControlElementWithState::parseAttribute(name, value); 319} 320 321bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const 322{ 323 if (renderer()) 324 return isFocusable(); 325 return HTMLFormControlElementWithState::isKeyboardFocusable(event); 326} 327 328bool HTMLSelectElement::isMouseFocusable() const 329{ 330 if (renderer()) 331 return isFocusable(); 332 return HTMLFormControlElementWithState::isMouseFocusable(); 333} 334 335bool HTMLSelectElement::canSelectAll() const 336{ 337 return !usesMenuList(); 338} 339 340RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*) 341{ 342 if (usesMenuList()) 343 return new (arena) RenderMenuList(this); 344 return new (arena) RenderListBox(this); 345} 346 347bool HTMLSelectElement::childShouldCreateRenderer(const NodeRenderingContext& childContext) const 348{ 349 if (!HTMLFormControlElementWithState::childShouldCreateRenderer(childContext)) 350 return false; 351 if (!usesMenuList()) 352 return childContext.node()->hasTagName(HTMLNames::optionTag) || childContext.node()->hasTagName(HTMLNames::optgroupTag) || validationMessageShadowTreeContains(childContext.node()); 353 return validationMessageShadowTreeContains(childContext.node()); 354} 355 356PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions() 357{ 358 return ensureCachedHTMLCollection(SelectedOptions); 359} 360 361PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options() 362{ 363 return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get()); 364} 365 366void HTMLSelectElement::updateListItemSelectedStates() 367{ 368 if (m_shouldRecalcListItems) 369 recalcListItems(); 370} 371 372void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 373{ 374 setRecalcListItems(); 375 setNeedsValidityCheck(); 376 m_lastOnChangeSelection.clear(); 377 378 HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 379} 380 381void HTMLSelectElement::optionElementChildrenChanged() 382{ 383 setRecalcListItems(); 384 setNeedsValidityCheck(); 385 386 if (renderer()) { 387 if (AXObjectCache* cache = renderer()->document()->existingAXObjectCache()) 388 cache->childrenChanged(this); 389 } 390} 391 392void HTMLSelectElement::accessKeyAction(bool sendMouseEvents) 393{ 394 focus(); 395 dispatchSimulatedClick(0, sendMouseEvents ? SendMouseUpDownEvents : SendNoEvents); 396} 397 398void HTMLSelectElement::setMultiple(bool multiple) 399{ 400 bool oldMultiple = this->multiple(); 401 int oldSelectedIndex = selectedIndex(); 402 setAttribute(multipleAttr, multiple ? "" : 0); 403 404 // Restore selectedIndex after changing the multiple flag to preserve 405 // selection as single-line and multi-line has different defaults. 406 if (oldMultiple != this->multiple()) 407 setSelectedIndex(oldSelectedIndex); 408} 409 410void HTMLSelectElement::setSize(int size) 411{ 412 setAttribute(sizeAttr, String::number(size)); 413} 414 415Node* HTMLSelectElement::namedItem(const AtomicString& name) 416{ 417 return options()->namedItem(name); 418} 419 420Node* HTMLSelectElement::item(unsigned index) 421{ 422 return options()->item(index); 423} 424 425void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec) 426{ 427 ec = 0; 428 if (index > maxSelectItems - 1) 429 index = maxSelectItems - 1; 430 int diff = index - length(); 431 RefPtr<HTMLElement> before = 0; 432 // Out of array bounds? First insert empty dummies. 433 if (diff > 0) { 434 setLength(index, ec); 435 // Replace an existing entry? 436 } else if (diff < 0) { 437 before = toHTMLElement(options()->item(index+1)); 438 remove(index); 439 } 440 // Finally add the new element. 441 if (!ec) { 442 add(option, before.get(), ec); 443 if (diff >= 0 && option->selected()) 444 optionSelectionStateChanged(option, true); 445 } 446} 447 448void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec) 449{ 450 ec = 0; 451 if (newLen > maxSelectItems) 452 newLen = maxSelectItems; 453 int diff = length() - newLen; 454 455 if (diff < 0) { // Add dummy elements. 456 do { 457 RefPtr<Element> option = document()->createElement(optionTag, false); 458 ASSERT(option); 459 add(toHTMLElement(option.get()), 0, ec); 460 if (ec) 461 break; 462 } while (++diff); 463 } else { 464 const Vector<HTMLElement*>& items = listItems(); 465 466 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list 467 // of elements that we intend to remove then attempt to remove them one at a time. 468 Vector<RefPtr<Element> > itemsToRemove; 469 size_t optionIndex = 0; 470 for (size_t i = 0; i < items.size(); ++i) { 471 Element* item = items[i]; 472 if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) { 473 ASSERT(item->parentNode()); 474 itemsToRemove.append(item); 475 } 476 } 477 478 for (size_t i = 0; i < itemsToRemove.size(); ++i) { 479 Element* item = itemsToRemove[i].get(); 480 if (item->parentNode()) 481 item->parentNode()->removeChild(item, ec); 482 } 483 } 484 setNeedsValidityCheck(); 485} 486 487bool HTMLSelectElement::isRequiredFormControl() const 488{ 489 return isRequired(); 490} 491 492// Returns the 1st valid item |skip| items from |listIndex| in direction |direction| if there is one. 493// Otherwise, it returns the valid item closest to that boundary which is past |listIndex| if there is one. 494// Otherwise, it returns |listIndex|. 495// Valid means that it is enabled and an option element. 496int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const 497{ 498 ASSERT(direction == -1 || direction == 1); 499 const Vector<HTMLElement*>& listItems = this->listItems(); 500 int lastGoodIndex = listIndex; 501 int size = listItems.size(); 502 for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) { 503 --skip; 504 if (!listItems[listIndex]->isDisabledFormControl() && listItems[listIndex]->hasTagName(optionTag)) { 505 lastGoodIndex = listIndex; 506 if (skip <= 0) 507 break; 508 } 509 } 510 return lastGoodIndex; 511} 512 513int HTMLSelectElement::nextSelectableListIndex(int startIndex) const 514{ 515 return nextValidIndex(startIndex, SkipForwards, 1); 516} 517 518int HTMLSelectElement::previousSelectableListIndex(int startIndex) const 519{ 520 if (startIndex == -1) 521 startIndex = listItems().size(); 522 return nextValidIndex(startIndex, SkipBackwards, 1); 523} 524 525int HTMLSelectElement::firstSelectableListIndex() const 526{ 527 const Vector<HTMLElement*>& items = listItems(); 528 int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX); 529 if (static_cast<size_t>(index) == items.size()) 530 return -1; 531 return index; 532} 533 534int HTMLSelectElement::lastSelectableListIndex() const 535{ 536 return nextValidIndex(-1, SkipForwards, INT_MAX); 537} 538 539// Returns the index of the next valid item one page away from |startIndex| in direction |direction|. 540int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const 541{ 542 const Vector<HTMLElement*>& items = listItems(); 543 // Can't use m_size because renderer forces a minimum size. 544 int pageSize = 0; 545 if (renderer()->isListBox()) 546 pageSize = toRenderListBox(renderer())->size() - 1; // -1 so we still show context. 547 548 // One page away, but not outside valid bounds. 549 // If there is a valid option item one page away, the index is chosen. 550 // If there is no exact one page away valid option, returns startIndex or the most far index. 551 int edgeIndex = (direction == SkipForwards) ? 0 : (items.size() - 1); 552 int skipAmount = pageSize + ((direction == SkipForwards) ? startIndex : (edgeIndex - startIndex)); 553 return nextValidIndex(edgeIndex, direction, skipAmount); 554} 555 556void HTMLSelectElement::selectAll() 557{ 558 ASSERT(!usesMenuList()); 559 if (!renderer() || !m_multiple) 560 return; 561 562 // Save the selection so it can be compared to the new selectAll selection 563 // when dispatching change events. 564 saveLastSelection(); 565 566 m_activeSelectionState = true; 567 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1)); 568 setActiveSelectionEndIndex(previousSelectableListIndex(-1)); 569 570 updateListBoxSelection(false); 571 listBoxOnChange(); 572 setNeedsValidityCheck(); 573} 574 575void HTMLSelectElement::saveLastSelection() 576{ 577 if (usesMenuList()) { 578 m_lastOnChangeIndex = selectedIndex(); 579 return; 580 } 581 582 m_lastOnChangeSelection.clear(); 583 const Vector<HTMLElement*>& items = listItems(); 584 for (unsigned i = 0; i < items.size(); ++i) { 585 HTMLElement* element = items[i]; 586 m_lastOnChangeSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected()); 587 } 588} 589 590void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) 591{ 592 m_activeSelectionAnchorIndex = index; 593 594 // Cache the selection state so we can restore the old selection as the new 595 // selection pivots around this anchor index. 596 m_cachedStateForActiveSelection.clear(); 597 598 const Vector<HTMLElement*>& items = listItems(); 599 for (unsigned i = 0; i < items.size(); ++i) { 600 HTMLElement* element = items[i]; 601 m_cachedStateForActiveSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected()); 602 } 603} 604 605void HTMLSelectElement::setActiveSelectionEndIndex(int index) 606{ 607 m_activeSelectionEndIndex = index; 608} 609 610void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions) 611{ 612 ASSERT(renderer() && (renderer()->isListBox() || m_multiple)); 613 ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0); 614 615 unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex); 616 unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex); 617 618 const Vector<HTMLElement*>& items = listItems(); 619 for (unsigned i = 0; i < items.size(); ++i) { 620 HTMLElement* element = items[i]; 621 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl()) 622 continue; 623 624 if (i >= start && i <= end) 625 toHTMLOptionElement(element)->setSelectedState(m_activeSelectionState); 626 else if (deselectOtherOptions || i >= m_cachedStateForActiveSelection.size()) 627 toHTMLOptionElement(element)->setSelectedState(false); 628 else 629 toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]); 630 } 631 632 scrollToSelection(); 633 setNeedsValidityCheck(); 634 notifyFormStateChanged(); 635} 636 637void HTMLSelectElement::listBoxOnChange() 638{ 639 ASSERT(!usesMenuList() || m_multiple); 640 641 const Vector<HTMLElement*>& items = listItems(); 642 643 // If the cached selection list is empty, or the size has changed, then fire 644 // dispatchFormControlChangeEvent, and return early. 645 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) { 646 dispatchFormControlChangeEvent(); 647 return; 648 } 649 650 // Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent. 651 bool fireOnChange = false; 652 for (unsigned i = 0; i < items.size(); ++i) { 653 HTMLElement* element = items[i]; 654 bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected(); 655 if (selected != m_lastOnChangeSelection[i]) 656 fireOnChange = true; 657 m_lastOnChangeSelection[i] = selected; 658 } 659 660 if (fireOnChange) 661 dispatchFormControlChangeEvent(); 662} 663 664void HTMLSelectElement::dispatchChangeEventForMenuList() 665{ 666 ASSERT(usesMenuList()); 667 668 int selected = selectedIndex(); 669 if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) { 670 m_lastOnChangeIndex = selected; 671 m_isProcessingUserDrivenChange = false; 672 dispatchFormControlChangeEvent(); 673 } 674} 675 676void HTMLSelectElement::scrollToSelection() 677{ 678 if (usesMenuList()) 679 return; 680 681 if (RenderObject* renderer = this->renderer()) 682 toRenderListBox(renderer)->selectionChanged(); 683} 684 685void HTMLSelectElement::setOptionsChangedOnRenderer() 686{ 687 if (RenderObject* renderer = this->renderer()) { 688 if (usesMenuList()) 689 toRenderMenuList(renderer)->setOptionsChanged(true); 690 else 691 toRenderListBox(renderer)->setOptionsChanged(true); 692 } 693} 694 695const Vector<HTMLElement*>& HTMLSelectElement::listItems() const 696{ 697 if (m_shouldRecalcListItems) 698 recalcListItems(); 699 else { 700#if !ASSERT_DISABLED 701 Vector<HTMLElement*> items = m_listItems; 702 recalcListItems(false); 703 ASSERT(items == m_listItems); 704#endif 705 } 706 707 return m_listItems; 708} 709 710void HTMLSelectElement::invalidateSelectedItems() 711{ 712 if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions)) 713 collection->invalidateCache(); 714} 715 716void HTMLSelectElement::setRecalcListItems() 717{ 718 m_shouldRecalcListItems = true; 719 // Manual selection anchor is reset when manipulating the select programmatically. 720 m_activeSelectionAnchorIndex = -1; 721 setOptionsChangedOnRenderer(); 722 setNeedsStyleRecalc(); 723 if (!inDocument()) { 724 if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions)) 725 collection->invalidateCache(); 726 } 727 if (!inDocument()) 728 invalidateSelectedItems(); 729 730 if (renderer()) { 731 if (AXObjectCache* cache = renderer()->document()->existingAXObjectCache()) 732 cache->childrenChanged(this); 733 } 734} 735 736void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const 737{ 738 m_listItems.clear(); 739 740 m_shouldRecalcListItems = false; 741 742 HTMLOptionElement* foundSelected = 0; 743 HTMLOptionElement* firstOption = 0; 744 for (Element* currentElement = ElementTraversal::firstWithin(this); currentElement; ) { 745 if (!currentElement->isHTMLElement()) { 746 currentElement = ElementTraversal::nextSkippingChildren(currentElement, this); 747 continue; 748 } 749 HTMLElement* current = toHTMLElement(currentElement); 750 751 // optgroup tags may not nest. However, both FireFox and IE will 752 // flatten the tree automatically, so we follow suit. 753 // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6) 754 if (current->hasTagName(optgroupTag)) { 755 m_listItems.append(current); 756 if (Element* nextElement = ElementTraversal::firstWithin(current)) { 757 currentElement = nextElement; 758 continue; 759 } 760 } 761 762 if (current->hasTagName(optionTag)) { 763 m_listItems.append(current); 764 765 if (updateSelectedStates && !m_multiple) { 766 HTMLOptionElement* option = toHTMLOptionElement(current); 767 if (!firstOption) 768 firstOption = option; 769 if (option->selected()) { 770 if (foundSelected) 771 foundSelected->setSelectedState(false); 772 foundSelected = option; 773 } else if (m_size <= 1 && !foundSelected && !option->isDisabledFormControl()) { 774 foundSelected = option; 775 foundSelected->setSelectedState(true); 776 } 777 } 778 } 779 780 if (current->hasTagName(hrTag)) 781 m_listItems.append(current); 782 783 // In conforming HTML code, only <optgroup> and <option> will be found 784 // within a <select>. We call NodeTraversal::nextSkippingChildren so that we only step 785 // into those tags that we choose to. For web-compat, we should cope 786 // with the case where odd tags like a <div> have been added but we 787 // handle this because such tags have already been removed from the 788 // <select>'s subtree at this point. 789 currentElement = ElementTraversal::nextSkippingChildren(currentElement, this); 790 } 791 792 if (!foundSelected && m_size <= 1 && firstOption && !firstOption->selected()) 793 firstOption->setSelectedState(true); 794} 795 796int HTMLSelectElement::selectedIndex() const 797{ 798 unsigned index = 0; 799 800 // Return the number of the first option selected. 801 const Vector<HTMLElement*>& items = listItems(); 802 for (size_t i = 0; i < items.size(); ++i) { 803 HTMLElement* element = items[i]; 804 if (element->hasTagName(optionTag)) { 805 if (toHTMLOptionElement(element)->selected()) 806 return index; 807 ++index; 808 } 809 } 810 811 return -1; 812} 813 814void HTMLSelectElement::setSelectedIndex(int index) 815{ 816 selectOption(index, DeselectOtherOptions); 817} 818 819void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected) 820{ 821 ASSERT(option->ownerSelectElement() == this); 822 if (optionIsSelected) 823 selectOption(option->index()); 824 else if (!usesMenuList()) 825 selectOption(-1); 826 else 827 selectOption(nextSelectableListIndex(-1)); 828} 829 830void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags) 831{ 832 bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions); 833 834 const Vector<HTMLElement*>& items = listItems(); 835 int listIndex = optionToListIndex(optionIndex); 836 837 HTMLElement* element = 0; 838 if (listIndex >= 0) { 839 element = items[listIndex]; 840 if (element->hasTagName(optionTag)) { 841 if (m_activeSelectionAnchorIndex < 0 || shouldDeselect) 842 setActiveSelectionAnchorIndex(listIndex); 843 if (m_activeSelectionEndIndex < 0 || shouldDeselect) 844 setActiveSelectionEndIndex(listIndex); 845 toHTMLOptionElement(element)->setSelectedState(true); 846 } 847 } 848 849 if (shouldDeselect) 850 deselectItemsWithoutValidation(element); 851 852 // For the menu list case, this is what makes the selected element appear. 853 if (RenderObject* renderer = this->renderer()) 854 renderer->updateFromElement(); 855 856 scrollToSelection(); 857 858 if (usesMenuList()) { 859 m_isProcessingUserDrivenChange = flags & UserDriven; 860 if (flags & DispatchChangeEvent) 861 dispatchChangeEventForMenuList(); 862 if (RenderObject* renderer = this->renderer()) { 863 if (usesMenuList()) 864 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex); 865 else if (renderer->isListBox()) 866 toRenderListBox(renderer)->selectionChanged(); 867 } 868 } 869 870 setNeedsValidityCheck(); 871 notifyFormStateChanged(); 872} 873 874int HTMLSelectElement::optionToListIndex(int optionIndex) const 875{ 876 const Vector<HTMLElement*>& items = listItems(); 877 int listSize = static_cast<int>(items.size()); 878 if (optionIndex < 0 || optionIndex >= listSize) 879 return -1; 880 881 int optionIndex2 = -1; 882 for (int listIndex = 0; listIndex < listSize; ++listIndex) { 883 if (items[listIndex]->hasTagName(optionTag)) { 884 ++optionIndex2; 885 if (optionIndex2 == optionIndex) 886 return listIndex; 887 } 888 } 889 890 return -1; 891} 892 893int HTMLSelectElement::listToOptionIndex(int listIndex) const 894{ 895 const Vector<HTMLElement*>& items = listItems(); 896 if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !items[listIndex]->hasTagName(optionTag)) 897 return -1; 898 899 // Actual index of option not counting OPTGROUP entries that may be in list. 900 int optionIndex = 0; 901 for (int i = 0; i < listIndex; ++i) { 902 if (items[i]->hasTagName(optionTag)) 903 ++optionIndex; 904 } 905 906 return optionIndex; 907} 908 909void HTMLSelectElement::dispatchFocusEvent(PassRefPtr<Element> oldFocusedElement, FocusDirection direction) 910{ 911 // Save the selection so it can be compared to the new selection when 912 // dispatching change events during blur event dispatch. 913 if (usesMenuList()) 914 saveLastSelection(); 915 HTMLFormControlElementWithState::dispatchFocusEvent(oldFocusedElement, direction); 916} 917 918void HTMLSelectElement::dispatchBlurEvent(PassRefPtr<Element> newFocusedElement) 919{ 920 // We only need to fire change events here for menu lists, because we fire 921 // change events for list boxes whenever the selection change is actually made. 922 // This matches other browsers' behavior. 923 if (usesMenuList()) 924 dispatchChangeEventForMenuList(); 925 HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement); 926} 927 928void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement) 929{ 930 const Vector<HTMLElement*>& items = listItems(); 931 for (unsigned i = 0; i < items.size(); ++i) { 932 HTMLElement* element = items[i]; 933 if (element != excludeElement && element->hasTagName(optionTag)) 934 toHTMLOptionElement(element)->setSelectedState(false); 935 } 936} 937 938FormControlState HTMLSelectElement::saveFormControlState() const 939{ 940 const Vector<HTMLElement*>& items = listItems(); 941 size_t length = items.size(); 942 FormControlState state; 943 for (unsigned i = 0; i < length; ++i) { 944 if (!items[i]->hasTagName(optionTag)) 945 continue; 946 HTMLOptionElement* option = toHTMLOptionElement(items[i]); 947 if (!option->selected()) 948 continue; 949 state.append(option->value()); 950 if (!multiple()) 951 break; 952 } 953 return state; 954} 955 956size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const 957{ 958 const Vector<HTMLElement*>& items = listItems(); 959 size_t loopEndIndex = std::min(items.size(), listIndexEnd); 960 for (size_t i = listIndexStart; i < loopEndIndex; ++i) { 961 if (!items[i]->hasLocalName(optionTag)) 962 continue; 963 if (static_cast<HTMLOptionElement*>(items[i])->value() == value) 964 return i; 965 } 966 return notFound; 967} 968 969void HTMLSelectElement::restoreFormControlState(const FormControlState& state) 970{ 971 recalcListItems(); 972 973 const Vector<HTMLElement*>& items = listItems(); 974 size_t itemsSize = items.size(); 975 if (!itemsSize) 976 return; 977 978 for (size_t i = 0; i < itemsSize; ++i) { 979 if (!items[i]->hasLocalName(optionTag)) 980 continue; 981 static_cast<HTMLOptionElement*>(items[i])->setSelectedState(false); 982 } 983 984 if (!multiple()) { 985 size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize); 986 if (foundIndex != notFound) 987 toHTMLOptionElement(items[foundIndex])->setSelectedState(true); 988 } else { 989 size_t startIndex = 0; 990 for (size_t i = 0; i < state.valueSize(); ++i) { 991 const String& value = state[i]; 992 size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize); 993 if (foundIndex == notFound) 994 foundIndex = searchOptionsForValue(value, 0, startIndex); 995 if (foundIndex == notFound) 996 continue; 997 toHTMLOptionElement(items[foundIndex])->setSelectedState(true); 998 startIndex = foundIndex + 1; 999 } 1000 } 1001 1002 setOptionsChangedOnRenderer(); 1003 setNeedsValidityCheck(); 1004} 1005 1006void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value) 1007{ 1008 bool oldUsesMenuList = usesMenuList(); 1009 m_multiple = !value.isNull(); 1010 setNeedsValidityCheck(); 1011 if (oldUsesMenuList != usesMenuList()) 1012 reattachIfAttached(); 1013} 1014 1015bool HTMLSelectElement::appendFormData(FormDataList& list, bool) 1016{ 1017 const AtomicString& name = this->name(); 1018 if (name.isEmpty()) 1019 return false; 1020 1021 bool successful = false; 1022 const Vector<HTMLElement*>& items = listItems(); 1023 1024 for (unsigned i = 0; i < items.size(); ++i) { 1025 HTMLElement* element = items[i]; 1026 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->isDisabledFormControl()) { 1027 list.appendData(name, toHTMLOptionElement(element)->value()); 1028 successful = true; 1029 } 1030 } 1031 1032 // It's possible that this is a menulist with multiple options and nothing 1033 // will be submitted (!successful). We won't send a unselected non-disabled 1034 // option as fallback. This behavior matches to other browsers. 1035 return successful; 1036} 1037 1038void HTMLSelectElement::reset() 1039{ 1040 HTMLOptionElement* firstOption = 0; 1041 HTMLOptionElement* selectedOption = 0; 1042 1043 const Vector<HTMLElement*>& items = listItems(); 1044 for (unsigned i = 0; i < items.size(); ++i) { 1045 HTMLElement* element = items[i]; 1046 if (!element->hasTagName(optionTag)) 1047 continue; 1048 1049 if (items[i]->fastHasAttribute(selectedAttr)) { 1050 if (selectedOption && !m_multiple) 1051 selectedOption->setSelectedState(false); 1052 toHTMLOptionElement(element)->setSelectedState(true); 1053 selectedOption = toHTMLOptionElement(element); 1054 } else 1055 toHTMLOptionElement(element)->setSelectedState(false); 1056 1057 if (!firstOption) 1058 firstOption = toHTMLOptionElement(element); 1059 } 1060 1061 if (!selectedOption && firstOption && !m_multiple && m_size <= 1) 1062 firstOption->setSelectedState(true); 1063 1064 setOptionsChangedOnRenderer(); 1065 setNeedsStyleRecalc(); 1066 setNeedsValidityCheck(); 1067} 1068 1069#if !PLATFORM(WIN) 1070bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event) 1071{ 1072 const Page* page = document()->page(); 1073 RefPtr<RenderTheme> renderTheme = page ? page->theme() : RenderTheme::defaultTheme(); 1074 1075 if (!renderTheme->popsMenuByArrowKeys()) 1076 return false; 1077 1078 if (!isSpatialNavigationEnabled(document()->frame())) { 1079 if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") { 1080 focus(); 1081 // Calling focus() may cause us to lose our renderer. Return true so 1082 // that our caller doesn't process the event further, but don't set 1083 // the event as handled. 1084 if (!renderer() || !renderer()->isMenuList()) 1085 return true; 1086 1087 // Save the selection so it can be compared to the new selection 1088 // when dispatching change events during selectOption, which 1089 // gets called from RenderMenuList::valueChanged, which gets called 1090 // after the user makes a selection from the menu. 1091 saveLastSelection(); 1092 if (RenderMenuList* menuList = toRenderMenuList(renderer())) 1093 menuList->showPopup(); 1094 event->setDefaultHandled(); 1095 } 1096 return true; 1097 } 1098 1099 return false; 1100} 1101#endif 1102 1103void HTMLSelectElement::menuListDefaultEventHandler(Event* event) 1104{ 1105 const Page* page = document()->page(); 1106 RefPtr<RenderTheme> renderTheme = page ? page->theme() : RenderTheme::defaultTheme(); 1107 1108 if (event->type() == eventNames().keydownEvent) { 1109 if (!renderer() || !event->isKeyboardEvent()) 1110 return; 1111 1112 if (platformHandleKeydownEvent(static_cast<KeyboardEvent*>(event))) 1113 return; 1114 1115 // When using spatial navigation, we want to be able to navigate away 1116 // from the select element when the user hits any of the arrow keys, 1117 // instead of changing the selection. 1118 if (isSpatialNavigationEnabled(document()->frame())) { 1119 if (!m_activeSelectionState) 1120 return; 1121 } 1122 1123 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); 1124 bool handled = true; 1125 const Vector<HTMLElement*>& listItems = this->listItems(); 1126 int listIndex = optionToListIndex(selectedIndex()); 1127 1128 // When using caret browsing, we want to be able to move the focus 1129 // out of the select element when user hits a left or right arrow key. 1130 const Frame* frame = document()->frame(); 1131 if (frame && frame->settings() && frame->settings()->caretBrowsingEnabled()) { 1132 if (keyIdentifier == "Left" || keyIdentifier == "Right") 1133 return; 1134 } 1135 1136 if (keyIdentifier == "Down" || keyIdentifier == "Right") 1137 listIndex = nextValidIndex(listIndex, SkipForwards, 1); 1138 else if (keyIdentifier == "Up" || keyIdentifier == "Left") 1139 listIndex = nextValidIndex(listIndex, SkipBackwards, 1); 1140 else if (keyIdentifier == "PageDown") 1141 listIndex = nextValidIndex(listIndex, SkipForwards, 3); 1142 else if (keyIdentifier == "PageUp") 1143 listIndex = nextValidIndex(listIndex, SkipBackwards, 3); 1144 else if (keyIdentifier == "Home") 1145 listIndex = nextValidIndex(-1, SkipForwards, 1); 1146 else if (keyIdentifier == "End") 1147 listIndex = nextValidIndex(listItems.size(), SkipBackwards, 1); 1148 else 1149 handled = false; 1150 1151 if (handled && static_cast<size_t>(listIndex) < listItems.size()) 1152 selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven); 1153 1154 if (handled) 1155 event->setDefaultHandled(); 1156 } 1157 1158 // Use key press event here since sending simulated mouse events 1159 // on key down blocks the proper sending of the key press event. 1160 if (event->type() == eventNames().keypressEvent) { 1161 if (!renderer() || !event->isKeyboardEvent()) 1162 return; 1163 1164 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); 1165 bool handled = false; 1166 1167 if (keyCode == ' ' && isSpatialNavigationEnabled(document()->frame())) { 1168 // Use space to toggle arrow key handling for selection change or spatial navigation. 1169 m_activeSelectionState = !m_activeSelectionState; 1170 event->setDefaultHandled(); 1171 return; 1172 } 1173 1174 if (renderTheme->popsMenuBySpaceOrReturn()) { 1175 if (keyCode == ' ' || keyCode == '\r') { 1176 focus(); 1177 1178 // Calling focus() may remove the renderer or change the 1179 // renderer type. 1180 if (!renderer() || !renderer()->isMenuList()) 1181 return; 1182 1183 // Save the selection so it can be compared to the new selection 1184 // when dispatching change events during selectOption, which 1185 // gets called from RenderMenuList::valueChanged, which gets called 1186 // after the user makes a selection from the menu. 1187 saveLastSelection(); 1188 if (RenderMenuList* menuList = toRenderMenuList(renderer())) 1189 menuList->showPopup(); 1190 handled = true; 1191 } 1192 } else if (renderTheme->popsMenuByArrowKeys()) { 1193 if (keyCode == ' ') { 1194 focus(); 1195 1196 // Calling focus() may remove the renderer or change the 1197 // renderer type. 1198 if (!renderer() || !renderer()->isMenuList()) 1199 return; 1200 1201 // Save the selection so it can be compared to the new selection 1202 // when dispatching change events during selectOption, which 1203 // gets called from RenderMenuList::valueChanged, which gets called 1204 // after the user makes a selection from the menu. 1205 saveLastSelection(); 1206 if (RenderMenuList* menuList = toRenderMenuList(renderer())) 1207 menuList->showPopup(); 1208 handled = true; 1209 } else if (keyCode == '\r') { 1210 if (form()) 1211 form()->submitImplicitly(event, false); 1212 dispatchChangeEventForMenuList(); 1213 handled = true; 1214 } 1215 } 1216 1217 if (handled) 1218 event->setDefaultHandled(); 1219 } 1220 1221 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { 1222 focus(); 1223 if (renderer() && renderer()->isMenuList()) { 1224 if (RenderMenuList* menuList = toRenderMenuList(renderer())) { 1225 if (menuList->popupIsVisible()) 1226 menuList->hidePopup(); 1227 else { 1228 // Save the selection so it can be compared to the new 1229 // selection when we call onChange during selectOption, 1230 // which gets called from RenderMenuList::valueChanged, 1231 // which gets called after the user makes a selection from 1232 // the menu. 1233 saveLastSelection(); 1234 menuList->showPopup(); 1235 } 1236 } 1237 } 1238 event->setDefaultHandled(); 1239 } 1240 1241 if (event->type() == eventNames().blurEvent && !focused()) { 1242 if (RenderMenuList* menuList = toRenderMenuList(renderer())) { 1243 if (menuList->popupIsVisible()) 1244 menuList->hidePopup(); 1245 } 1246 } 1247} 1248 1249void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shift) 1250{ 1251 ASSERT(listIndex >= 0); 1252 1253 // Save the selection so it can be compared to the new selection when 1254 // dispatching change events during mouseup, or after autoscroll finishes. 1255 saveLastSelection(); 1256 1257 m_activeSelectionState = true; 1258 1259 bool shiftSelect = m_multiple && shift; 1260 bool multiSelect = m_multiple && multi && !shift; 1261 1262 HTMLElement* clickedElement = listItems()[listIndex]; 1263 if (clickedElement->hasTagName(optionTag)) { 1264 // Keep track of whether an active selection (like during drag 1265 // selection), should select or deselect. 1266 if (toHTMLOptionElement(clickedElement)->selected() && multiSelect) 1267 m_activeSelectionState = false; 1268 if (!m_activeSelectionState) 1269 toHTMLOptionElement(clickedElement)->setSelectedState(false); 1270 } 1271 1272 // If we're not in any special multiple selection mode, then deselect all 1273 // other items, excluding the clicked option. If no option was clicked, then 1274 // this will deselect all items in the list. 1275 if (!shiftSelect && !multiSelect) 1276 deselectItemsWithoutValidation(clickedElement); 1277 1278 // If the anchor hasn't been set, and we're doing a single selection or a 1279 // shift selection, then initialize the anchor to the first selected index. 1280 if (m_activeSelectionAnchorIndex < 0 && !multiSelect) 1281 setActiveSelectionAnchorIndex(selectedIndex()); 1282 1283 // Set the selection state of the clicked option. 1284 if (clickedElement->hasTagName(optionTag) && !toHTMLOptionElement(clickedElement)->isDisabledFormControl()) 1285 toHTMLOptionElement(clickedElement)->setSelectedState(true); 1286 1287 // If there was no selectedIndex() for the previous initialization, or If 1288 // we're doing a single selection, or a multiple selection (using cmd or 1289 // ctrl), then initialize the anchor index to the listIndex that just got 1290 // clicked. 1291 if (m_activeSelectionAnchorIndex < 0 || !shiftSelect) 1292 setActiveSelectionAnchorIndex(listIndex); 1293 1294 setActiveSelectionEndIndex(listIndex); 1295 updateListBoxSelection(!multiSelect); 1296} 1297 1298void HTMLSelectElement::listBoxDefaultEventHandler(Event* event) 1299{ 1300 const Vector<HTMLElement*>& listItems = this->listItems(); 1301 1302 if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { 1303 focus(); 1304 // Calling focus() may remove or change our renderer, in which case we don't want to handle the event further. 1305 if (!renderer() || !renderer()->isListBox()) 1306 return; 1307 1308 // Convert to coords relative to the list box if needed. 1309 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); 1310 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms)); 1311 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset)); 1312 if (listIndex >= 0) { 1313 if (!isDisabledFormControl()) { 1314#if PLATFORM(MAC) 1315 updateSelectedState(listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey()); 1316#else 1317 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey()); 1318#endif 1319 } 1320 if (Frame* frame = document()->frame()) 1321 frame->eventHandler()->setMouseDownMayStartAutoscroll(); 1322 1323 event->setDefaultHandled(); 1324 } 1325 } else if (event->type() == eventNames().mousemoveEvent && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) { 1326 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); 1327 if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown()) 1328 return; 1329 1330 IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms)); 1331 int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset)); 1332 if (listIndex >= 0) { 1333 if (!isDisabledFormControl()) { 1334 if (m_multiple) { 1335 // Only extend selection if there is something selected. 1336 if (m_activeSelectionAnchorIndex < 0) 1337 return; 1338 1339 setActiveSelectionEndIndex(listIndex); 1340 updateListBoxSelection(false); 1341 } else { 1342 setActiveSelectionAnchorIndex(listIndex); 1343 setActiveSelectionEndIndex(listIndex); 1344 updateListBoxSelection(true); 1345 } 1346 } 1347 event->setDefaultHandled(); 1348 } 1349 } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer()) { 1350 // This click or drag event was not over any of the options. 1351 if (m_lastOnChangeSelection.isEmpty()) 1352 return; 1353 // This makes sure we fire dispatchFormControlChangeEvent for a single 1354 // click. For drag selection, onChange will fire when the autoscroll 1355 // timer stops. 1356 listBoxOnChange(); 1357 } else if (event->type() == eventNames().keydownEvent) { 1358 if (!event->isKeyboardEvent()) 1359 return; 1360 const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); 1361 1362 bool handled = false; 1363 int endIndex = 0; 1364 if (m_activeSelectionEndIndex < 0) { 1365 // Initialize the end index 1366 if (keyIdentifier == "Down" || keyIdentifier == "PageDown") { 1367 int startIndex = lastSelectedListIndex(); 1368 handled = true; 1369 if (keyIdentifier == "Down") 1370 endIndex = nextSelectableListIndex(startIndex); 1371 else 1372 endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards); 1373 } else if (keyIdentifier == "Up" || keyIdentifier == "PageUp") { 1374 int startIndex = optionToListIndex(selectedIndex()); 1375 handled = true; 1376 if (keyIdentifier == "Up") 1377 endIndex = previousSelectableListIndex(startIndex); 1378 else 1379 endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards); 1380 } 1381 } else { 1382 // Set the end index based on the current end index. 1383 if (keyIdentifier == "Down") { 1384 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex); 1385 handled = true; 1386 } else if (keyIdentifier == "Up") { 1387 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex); 1388 handled = true; 1389 } else if (keyIdentifier == "PageDown") { 1390 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipForwards); 1391 handled = true; 1392 } else if (keyIdentifier == "PageUp") { 1393 endIndex = nextSelectableListIndexPageAway(m_activeSelectionEndIndex, SkipBackwards); 1394 handled = true; 1395 } 1396 } 1397 if (keyIdentifier == "Home") { 1398 endIndex = firstSelectableListIndex(); 1399 handled = true; 1400 } else if (keyIdentifier == "End") { 1401 endIndex = lastSelectableListIndex(); 1402 handled = true; 1403 } 1404 1405 if (isSpatialNavigationEnabled(document()->frame())) 1406 // Check if the selection moves to the boundary. 1407 if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == m_activeSelectionEndIndex)) 1408 return; 1409 1410 if (endIndex >= 0 && handled) { 1411 // Save the selection so it can be compared to the new selection 1412 // when dispatching change events immediately after making the new 1413 // selection. 1414 saveLastSelection(); 1415 1416 ASSERT_UNUSED(listItems, !listItems.size() || static_cast<size_t>(endIndex) < listItems.size()); 1417 setActiveSelectionEndIndex(endIndex); 1418 1419 bool selectNewItem = !m_multiple || static_cast<KeyboardEvent*>(event)->shiftKey() || !isSpatialNavigationEnabled(document()->frame()); 1420 if (selectNewItem) 1421 m_activeSelectionState = true; 1422 // If the anchor is unitialized, or if we're going to deselect all 1423 // other options, then set the anchor index equal to the end index. 1424 bool deselectOthers = !m_multiple || (!static_cast<KeyboardEvent*>(event)->shiftKey() && selectNewItem); 1425 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) { 1426 if (deselectOthers) 1427 deselectItemsWithoutValidation(); 1428 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex); 1429 } 1430 1431 toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex); 1432 if (selectNewItem) { 1433 updateListBoxSelection(deselectOthers); 1434 listBoxOnChange(); 1435 } else 1436 scrollToSelection(); 1437 1438 event->setDefaultHandled(); 1439 } 1440 } else if (event->type() == eventNames().keypressEvent) { 1441 if (!event->isKeyboardEvent()) 1442 return; 1443 int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); 1444 1445 if (keyCode == '\r') { 1446 if (form()) 1447 form()->submitImplicitly(event, false); 1448 event->setDefaultHandled(); 1449 } else if (m_multiple && keyCode == ' ' && isSpatialNavigationEnabled(document()->frame())) { 1450 // Use space to toggle selection change. 1451 m_activeSelectionState = !m_activeSelectionState; 1452 updateSelectedState(listToOptionIndex(m_activeSelectionEndIndex), true /*multi*/, false /*shift*/); 1453 listBoxOnChange(); 1454 event->setDefaultHandled(); 1455 } 1456 } 1457} 1458 1459void HTMLSelectElement::defaultEventHandler(Event* event) 1460{ 1461 if (!renderer()) 1462 return; 1463 1464 if (isDisabledFormControl()) { 1465 HTMLFormControlElementWithState::defaultEventHandler(event); 1466 return; 1467 } 1468 1469 if (usesMenuList()) 1470 menuListDefaultEventHandler(event); 1471 else 1472 listBoxDefaultEventHandler(event); 1473 if (event->defaultHandled()) 1474 return; 1475 1476 if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) { 1477 KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event); 1478 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) { 1479 typeAheadFind(keyboardEvent); 1480 event->setDefaultHandled(); 1481 return; 1482 } 1483 } 1484 HTMLFormControlElementWithState::defaultEventHandler(event); 1485} 1486 1487int HTMLSelectElement::lastSelectedListIndex() const 1488{ 1489 const Vector<HTMLElement*>& items = listItems(); 1490 for (size_t i = items.size(); i;) { 1491 HTMLElement* element = items[--i]; 1492 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected()) 1493 return i; 1494 } 1495 return -1; 1496} 1497 1498int HTMLSelectElement::indexOfSelectedOption() const 1499{ 1500 return optionToListIndex(selectedIndex()); 1501} 1502 1503int HTMLSelectElement::optionCount() const 1504{ 1505 return listItems().size(); 1506} 1507 1508String HTMLSelectElement::optionAtIndex(int index) const 1509{ 1510 const Vector<HTMLElement*>& items = listItems(); 1511 1512 HTMLElement* element = items[index]; 1513 if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl()) 1514 return String(); 1515 return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel(); 1516} 1517 1518void HTMLSelectElement::typeAheadFind(KeyboardEvent* event) 1519{ 1520 int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar); 1521 if (index < 0) 1522 return; 1523 selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven); 1524 if (!usesMenuList()) 1525 listBoxOnChange(); 1526} 1527 1528Node::InsertionNotificationRequest HTMLSelectElement::insertedInto(ContainerNode* insertionPoint) 1529{ 1530 // When the element is created during document parsing, it won't have any 1531 // items yet - but for innerHTML and related methods, this method is called 1532 // after the whole subtree is constructed. 1533 recalcListItems(); 1534 HTMLFormControlElementWithState::insertedInto(insertionPoint); 1535 return InsertionDone; 1536} 1537 1538void HTMLSelectElement::accessKeySetSelectedIndex(int index) 1539{ 1540 // First bring into focus the list box. 1541 if (!focused()) 1542 accessKeyAction(false); 1543 1544 // If this index is already selected, unselect. otherwise update the selected index. 1545 const Vector<HTMLElement*>& items = listItems(); 1546 int listIndex = optionToListIndex(index); 1547 if (listIndex >= 0) { 1548 HTMLElement* element = items[listIndex]; 1549 if (element->hasTagName(optionTag)) { 1550 if (toHTMLOptionElement(element)->selected()) 1551 toHTMLOptionElement(element)->setSelectedState(false); 1552 else 1553 selectOption(index, DispatchChangeEvent | UserDriven); 1554 } 1555 } 1556 1557 if (usesMenuList()) 1558 dispatchChangeEventForMenuList(); 1559 else 1560 listBoxOnChange(); 1561 1562 scrollToSelection(); 1563} 1564 1565unsigned HTMLSelectElement::length() const 1566{ 1567 unsigned options = 0; 1568 1569 const Vector<HTMLElement*>& items = listItems(); 1570 for (unsigned i = 0; i < items.size(); ++i) { 1571 if (items[i]->hasTagName(optionTag)) 1572 ++options; 1573 } 1574 1575 return options; 1576} 1577 1578} // namespace 1579