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