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