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 Apple Inc. All rights reserved. 7 * (C) 2006 Alexey Proskuryakov (ap (at) nypop.com) 8 * Copyright (C) 2010 Google Inc. All rights reserved. 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Library General Public 12 * License as published by the Free Software Foundation; either 13 * version 2 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Library General Public License for more details. 19 * 20 * You should have received a copy of the GNU Library General Public License 21 * along with this library; see the file COPYING.LIB. If not, write to 22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 23 * Boston, MA 02110-1301, USA. 24 * 25 */ 26 27 #include "config.h" 28 #include "HTMLSelectElement.h" 29 30 #include "AXObjectCache.h" 31 #include "Attribute.h" 32 #include "EventNames.h" 33 #include "HTMLNames.h" 34 #include "HTMLOptionElement.h" 35 #include "HTMLOptionsCollection.h" 36 #include "RenderListBox.h" 37 #include "RenderMenuList.h" 38 #include "ScriptEventListener.h" 39 40 using namespace std; 41 42 namespace WebCore { 43 44 using namespace HTMLNames; 45 46 // Upper limit agreed upon with representatives of Opera and Mozilla. 47 static const unsigned maxSelectItems = 10000; 48 49 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 50 : HTMLFormControlElementWithState(tagName, document, form) 51 { 52 ASSERT(hasTagName(selectTag)); 53 } 54 55 PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 56 { 57 ASSERT(tagName.matches(selectTag)); 58 return adoptRef(new HTMLSelectElement(tagName, document, form)); 59 } 60 61 void HTMLSelectElement::recalcStyle(StyleChange change) 62 { 63 HTMLFormControlElementWithState::recalcStyle(change); 64 } 65 66 const AtomicString& HTMLSelectElement::formControlType() const 67 { 68 DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple")); 69 DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one")); 70 return m_data.multiple() ? selectMultiple : selectOne; 71 } 72 73 int HTMLSelectElement::selectedIndex() const 74 { 75 return SelectElement::selectedIndex(m_data, this); 76 } 77 78 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement) 79 { 80 SelectElement::deselectItems(m_data, this, excludeElement); 81 setNeedsValidityCheck(); 82 } 83 84 void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect) 85 { 86 SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false); 87 setNeedsValidityCheck(); 88 } 89 90 void HTMLSelectElement::setSelectedIndexByUser(int optionIndex, bool deselect, bool fireOnChangeNow, bool allowMultipleSelection) 91 { 92 // List box selects can fire onchange events through user interaction, such as 93 // mousedown events. This allows that same behavior programmatically. 94 if (!m_data.usesMenuList()) { 95 updateSelectedState(m_data, this, optionIndex, allowMultipleSelection, false); 96 setNeedsValidityCheck(); 97 if (fireOnChangeNow) 98 listBoxOnChange(); 99 return; 100 } 101 102 // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up 103 // autofill, when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and rdar://7467917 ). 104 // Perhaps this logic could be moved into SelectElement, but some callers of SelectElement::setSelectedIndex() 105 // seem to expect it to fire its change event even when the index was already selected. 106 if (optionIndex == selectedIndex()) 107 return; 108 109 SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChangeNow, true); 110 setNeedsValidityCheck(); 111 } 112 113 bool HTMLSelectElement::hasPlaceholderLabelOption() const 114 { 115 // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1. 116 // 117 // The condition "size() > 1" is actually not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct. 118 // Using "size() > 1" here because size() may be 0 in WebKit. 119 // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887 120 // 121 // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified. 122 // In this case, the display size should be assumed as the default. 123 // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements. 124 // 125 // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1. 126 if (multiple() || size() > 1) 127 return false; 128 129 int listIndex = optionToListIndex(0); 130 ASSERT(listIndex >= 0); 131 if (listIndex < 0) 132 return false; 133 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems()[listIndex]); 134 return !option->disabled() && !listIndex && option->value().isEmpty(); 135 } 136 137 bool HTMLSelectElement::valueMissing() const 138 { 139 if (!isRequiredFormControl()) 140 return false; 141 142 int firstSelectionIndex = selectedIndex(); 143 144 // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing. 145 return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption()); 146 } 147 148 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) 149 { 150 if (!multiple()) 151 setSelectedIndexByUser(listToOptionIndex(listIndex), true, fireOnChangeNow); 152 else { 153 updateSelectedState(m_data, this, listIndex, allowMultiplySelections, shift); 154 setNeedsValidityCheck(); 155 if (fireOnChangeNow) 156 listBoxOnChange(); 157 } 158 } 159 160 int HTMLSelectElement::activeSelectionStartListIndex() const 161 { 162 if (m_data.activeSelectionAnchorIndex() >= 0) 163 return m_data.activeSelectionAnchorIndex(); 164 return optionToListIndex(selectedIndex()); 165 } 166 167 int HTMLSelectElement::activeSelectionEndListIndex() const 168 { 169 if (m_data.activeSelectionEndIndex() >= 0) 170 return m_data.activeSelectionEndIndex(); 171 return SelectElement::lastSelectedListIndex(m_data, this); 172 } 173 174 unsigned HTMLSelectElement::length() const 175 { 176 return SelectElement::optionCount(m_data, this); 177 } 178 179 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec) 180 { 181 RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it 182 183 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag))) 184 return; 185 186 insertBefore(element, before, ec); 187 setNeedsValidityCheck(); 188 } 189 190 void HTMLSelectElement::remove(int optionIndex) 191 { 192 int listIndex = optionToListIndex(optionIndex); 193 if (listIndex < 0) 194 return; 195 196 ExceptionCode ec; 197 listItems()[listIndex]->remove(ec); 198 } 199 200 void HTMLSelectElement::remove(HTMLOptionElement* option) 201 { 202 if (option->ownerSelectElement() != this) 203 return; 204 205 ExceptionCode ec; 206 option->remove(ec); 207 } 208 209 String HTMLSelectElement::value() const 210 { 211 const Vector<Element*>& items = listItems(); 212 for (unsigned i = 0; i < items.size(); i++) { 213 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected()) 214 return static_cast<HTMLOptionElement*>(items[i])->value(); 215 } 216 return ""; 217 } 218 219 void HTMLSelectElement::setValue(const String &value) 220 { 221 if (value.isNull()) 222 return; 223 // find the option with value() matching the given parameter 224 // and make it the current selection. 225 const Vector<Element*>& items = listItems(); 226 unsigned optionIndex = 0; 227 for (unsigned i = 0; i < items.size(); i++) { 228 if (items[i]->hasLocalName(optionTag)) { 229 if (static_cast<HTMLOptionElement*>(items[i])->value() == value) { 230 setSelectedIndex(optionIndex, true); 231 return; 232 } 233 optionIndex++; 234 } 235 } 236 } 237 238 bool HTMLSelectElement::saveFormControlState(String& value) const 239 { 240 return SelectElement::saveFormControlState(m_data, this, value); 241 } 242 243 void HTMLSelectElement::restoreFormControlState(const String& state) 244 { 245 SelectElement::restoreFormControlState(m_data, this, state); 246 setNeedsValidityCheck(); 247 } 248 249 void HTMLSelectElement::parseMappedAttribute(Attribute* attr) 250 { 251 bool oldUsesMenuList = m_data.usesMenuList(); 252 if (attr->name() == sizeAttr) { 253 int oldSize = m_data.size(); 254 // Set the attribute value to a number. 255 // This is important since the style rules for this attribute can determine the appearance property. 256 int size = attr->value().toInt(); 257 String attrSize = String::number(size); 258 if (attrSize != attr->value()) 259 attr->setValue(attrSize); 260 size = max(size, 1); 261 262 // Ensure that we've determined selectedness of the items at least once prior to changing the size. 263 if (oldSize != size) 264 recalcListItemsIfNeeded(); 265 266 m_data.setSize(size); 267 setNeedsValidityCheck(); 268 if ((oldUsesMenuList != m_data.usesMenuList() || (!oldUsesMenuList && m_data.size() != oldSize)) && attached()) { 269 detach(); 270 attach(); 271 setRecalcListItems(); 272 } 273 } else if (attr->name() == multipleAttr) 274 SelectElement::parseMultipleAttribute(m_data, this, attr); 275 else if (attr->name() == accesskeyAttr) { 276 // FIXME: ignore for the moment 277 } else if (attr->name() == alignAttr) { 278 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. 279 // See http://bugs.webkit.org/show_bug.cgi?id=12072 280 } else if (attr->name() == onchangeAttr) { 281 setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr)); 282 } else 283 HTMLFormControlElementWithState::parseMappedAttribute(attr); 284 } 285 286 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const 287 { 288 if (renderer()) 289 return isFocusable(); 290 return HTMLFormControlElementWithState::isKeyboardFocusable(event); 291 } 292 293 bool HTMLSelectElement::isMouseFocusable() const 294 { 295 if (renderer()) 296 return isFocusable(); 297 return HTMLFormControlElementWithState::isMouseFocusable(); 298 } 299 300 bool HTMLSelectElement::canSelectAll() const 301 { 302 return !m_data.usesMenuList(); 303 } 304 305 void HTMLSelectElement::selectAll() 306 { 307 SelectElement::selectAll(m_data, this); 308 setNeedsValidityCheck(); 309 } 310 311 RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*) 312 { 313 if (m_data.usesMenuList()) 314 return new (arena) RenderMenuList(this); 315 return new (arena) RenderListBox(this); 316 } 317 318 bool HTMLSelectElement::appendFormData(FormDataList& list, bool) 319 { 320 return SelectElement::appendFormData(m_data, this, list); 321 } 322 323 int HTMLSelectElement::optionToListIndex(int optionIndex) const 324 { 325 return SelectElement::optionToListIndex(m_data, this, optionIndex); 326 } 327 328 int HTMLSelectElement::listToOptionIndex(int listIndex) const 329 { 330 return SelectElement::listToOptionIndex(m_data, this, listIndex); 331 } 332 333 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options() 334 { 335 return HTMLOptionsCollection::create(this); 336 } 337 338 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const 339 { 340 SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates); 341 } 342 343 void HTMLSelectElement::recalcListItemsIfNeeded() 344 { 345 if (m_data.shouldRecalcListItems()) 346 recalcListItems(); 347 } 348 349 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 350 { 351 setRecalcListItems(); 352 setNeedsValidityCheck(); 353 HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 354 355 if (AXObjectCache::accessibilityEnabled() && renderer()) 356 renderer()->document()->axObjectCache()->childrenChanged(renderer()); 357 } 358 359 void HTMLSelectElement::setRecalcListItems() 360 { 361 SelectElement::setRecalcListItems(m_data, this); 362 363 if (!inDocument()) 364 m_collectionInfo.reset(); 365 } 366 367 void HTMLSelectElement::reset() 368 { 369 SelectElement::reset(m_data, this); 370 setNeedsValidityCheck(); 371 } 372 373 void HTMLSelectElement::dispatchFocusEvent() 374 { 375 SelectElement::dispatchFocusEvent(m_data, this); 376 HTMLFormControlElementWithState::dispatchFocusEvent(); 377 } 378 379 void HTMLSelectElement::dispatchBlurEvent() 380 { 381 SelectElement::dispatchBlurEvent(m_data, this); 382 HTMLFormControlElementWithState::dispatchBlurEvent(); 383 } 384 385 void HTMLSelectElement::defaultEventHandler(Event* event) 386 { 387 SelectElement::defaultEventHandler(m_data, this, event, form()); 388 if (event->defaultHandled()) 389 return; 390 HTMLFormControlElementWithState::defaultEventHandler(event); 391 } 392 393 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) 394 { 395 SelectElement::setActiveSelectionAnchorIndex(m_data, this, index); 396 } 397 398 void HTMLSelectElement::setActiveSelectionEndIndex(int index) 399 { 400 SelectElement::setActiveSelectionEndIndex(m_data, index); 401 } 402 403 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions) 404 { 405 SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions); 406 setNeedsValidityCheck(); 407 } 408 409 void HTMLSelectElement::menuListOnChange() 410 { 411 SelectElement::menuListOnChange(m_data, this); 412 } 413 414 void HTMLSelectElement::listBoxOnChange() 415 { 416 SelectElement::listBoxOnChange(m_data, this); 417 } 418 419 void HTMLSelectElement::saveLastSelection() 420 { 421 SelectElement::saveLastSelection(m_data, this); 422 } 423 424 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement) 425 { 426 focus(); 427 dispatchSimulatedClick(0, sendToAnyElement); 428 } 429 430 void HTMLSelectElement::accessKeySetSelectedIndex(int index) 431 { 432 SelectElement::accessKeySetSelectedIndex(m_data, this, index); 433 } 434 435 void HTMLSelectElement::setMultiple(bool multiple) 436 { 437 int oldSelectedIndex = selectedIndex(); 438 setAttribute(multipleAttr, multiple ? "" : 0); 439 440 // Restore selectedIndex after changing the multiple flag to preserve 441 // selection as single-line and multi-line has different defaults. 442 setSelectedIndex(oldSelectedIndex); 443 } 444 445 void HTMLSelectElement::setSize(int size) 446 { 447 setAttribute(sizeAttr, String::number(size)); 448 } 449 450 Node* HTMLSelectElement::namedItem(const AtomicString& name) 451 { 452 return options()->namedItem(name); 453 } 454 455 Node* HTMLSelectElement::item(unsigned index) 456 { 457 return options()->item(index); 458 } 459 460 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec) 461 { 462 ec = 0; 463 if (index > maxSelectItems - 1) 464 index = maxSelectItems - 1; 465 int diff = index - length(); 466 HTMLElement* before = 0; 467 // out of array bounds ? first insert empty dummies 468 if (diff > 0) { 469 setLength(index, ec); 470 // replace an existing entry ? 471 } else if (diff < 0) { 472 before = toHTMLElement(options()->item(index+1)); 473 remove(index); 474 } 475 // finally add the new element 476 if (!ec) { 477 add(option, before, ec); 478 if (diff >= 0 && option->selected()) 479 setSelectedIndex(index, !m_data.multiple()); 480 } 481 } 482 483 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec) 484 { 485 ec = 0; 486 if (newLen > maxSelectItems) 487 newLen = maxSelectItems; 488 int diff = length() - newLen; 489 490 if (diff < 0) { // add dummy elements 491 do { 492 RefPtr<Element> option = document()->createElement(optionTag, false); 493 ASSERT(option); 494 add(toHTMLElement(option.get()), 0, ec); 495 if (ec) 496 break; 497 } while (++diff); 498 } else { 499 const Vector<Element*>& items = listItems(); 500 501 // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list 502 // of elements that we intend to remove then attempt to remove them one at a time. 503 Vector<RefPtr<Element> > itemsToRemove; 504 size_t optionIndex = 0; 505 for (size_t i = 0; i < items.size(); ++i) { 506 Element* item = items[i]; 507 if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) { 508 ASSERT(item->parentNode()); 509 itemsToRemove.append(item); 510 } 511 } 512 513 for (size_t i = 0; i < itemsToRemove.size(); ++i) { 514 Element* item = itemsToRemove[i].get(); 515 if (item->parentNode()) { 516 item->parentNode()->removeChild(item, ec); 517 } 518 } 519 } 520 setNeedsValidityCheck(); 521 } 522 523 void HTMLSelectElement::scrollToSelection() 524 { 525 SelectElement::scrollToSelection(m_data, this); 526 } 527 528 void HTMLSelectElement::insertedIntoTree(bool deep) 529 { 530 SelectElement::insertedIntoTree(m_data, this); 531 HTMLFormControlElementWithState::insertedIntoTree(deep); 532 } 533 534 bool HTMLSelectElement::isRequiredFormControl() const 535 { 536 return required(); 537 } 538 539 } // namespace 540