Home | History | Annotate | Download | only in html
      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