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