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