WebCore/html/HTMLSelectElement.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     3  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
       
     4  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
       
     5  *           (C) 2001 Dirk Mueller (mueller@kde.org)
       
     6  * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved.
       
     7  *           (C) 2006 Alexey Proskuryakov (ap@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) || hasTagName(keygenTag));
       
    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 bool HTMLSelectElement::checkDTD(const Node* newChild)
       
    62 {
       
    63     // Make sure to keep <optgroup> in sync with this.
       
    64     return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
       
    65            newChild->hasTagName(scriptTag);
       
    66 }
       
    67 
       
    68 void HTMLSelectElement::recalcStyle(StyleChange change)
       
    69 {
       
    70     HTMLFormControlElementWithState::recalcStyle(change);
       
    71 }
       
    72 
       
    73 const AtomicString& HTMLSelectElement::formControlType() const
       
    74 {
       
    75     DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple"));
       
    76     DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one"));
       
    77     return m_data.multiple() ? selectMultiple : selectOne;
       
    78 }
       
    79 
       
    80 int HTMLSelectElement::selectedIndex() const
       
    81 {
       
    82     return SelectElement::selectedIndex(m_data, this);
       
    83 }
       
    84 
       
    85 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
       
    86 {
       
    87     SelectElement::deselectItems(m_data, this, excludeElement);
       
    88 }
       
    89 
       
    90 void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect)
       
    91 {
       
    92     SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false);
       
    93 }
       
    94 
       
    95 void HTMLSelectElement::setSelectedIndexByUser(int optionIndex, bool deselect, bool fireOnChangeNow)
       
    96 {
       
    97     // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up
       
    98     // autofill, when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and rdar://7467917 ).
       
    99     // Perhaps this logic could be moved into SelectElement, but some callers of SelectElement::setSelectedIndex()
       
   100     // seem to expect it to fire its change event even when the index was already selected.
       
   101     if (optionIndex == selectedIndex())
       
   102         return;
       
   103     
       
   104     SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChangeNow, true);
       
   105 }
       
   106 
       
   107 void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow)
       
   108 {
       
   109     if (!multiple())
       
   110         setSelectedIndexByUser(listToOptionIndex(listIndex), true, fireOnChangeNow);
       
   111     else {
       
   112         updateSelectedState(m_data, this, listIndex, allowMultiplySelections, shift);
       
   113         if (fireOnChangeNow)
       
   114             listBoxOnChange();
       
   115     }
       
   116 }
       
   117 
       
   118 int HTMLSelectElement::activeSelectionStartListIndex() const
       
   119 {
       
   120     if (m_data.activeSelectionAnchorIndex() >= 0)
       
   121         return m_data.activeSelectionAnchorIndex();
       
   122     return optionToListIndex(selectedIndex());
       
   123 }
       
   124 
       
   125 int HTMLSelectElement::activeSelectionEndListIndex() const
       
   126 {
       
   127     if (m_data.activeSelectionEndIndex() >= 0)
       
   128         return m_data.activeSelectionEndIndex();
       
   129     return SelectElement::lastSelectedListIndex(m_data, this);
       
   130 }
       
   131 
       
   132 unsigned HTMLSelectElement::length() const
       
   133 {
       
   134     return SelectElement::optionCount(m_data, this);
       
   135 }
       
   136 
       
   137 void HTMLSelectElement::add(HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
       
   138 {
       
   139     RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
       
   140 
       
   141     if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
       
   142         return;
       
   143 
       
   144     insertBefore(element, before, ec);
       
   145 }
       
   146 
       
   147 void HTMLSelectElement::remove(int index)
       
   148 {
       
   149     int listIndex = optionToListIndex(index);
       
   150     if (listIndex < 0)
       
   151         return;
       
   152 
       
   153     Element* item = listItems()[listIndex];
       
   154     ASSERT(item->parentNode());
       
   155     ExceptionCode ec;
       
   156     item->parentNode()->removeChild(item, ec);
       
   157 }
       
   158 
       
   159 String HTMLSelectElement::value()
       
   160 {
       
   161     const Vector<Element*>& items = listItems();
       
   162     for (unsigned i = 0; i < items.size(); i++) {
       
   163         if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
       
   164             return static_cast<HTMLOptionElement*>(items[i])->value();
       
   165     }
       
   166     return "";
       
   167 }
       
   168 
       
   169 void HTMLSelectElement::setValue(const String &value)
       
   170 {
       
   171     if (value.isNull())
       
   172         return;
       
   173     // find the option with value() matching the given parameter
       
   174     // and make it the current selection.
       
   175     const Vector<Element*>& items = listItems();
       
   176     unsigned optionIndex = 0;
       
   177     for (unsigned i = 0; i < items.size(); i++) {
       
   178         if (items[i]->hasLocalName(optionTag)) {
       
   179             if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
       
   180                 setSelectedIndex(optionIndex, true);
       
   181                 return;
       
   182             }
       
   183             optionIndex++;
       
   184         }
       
   185     }
       
   186 }
       
   187 
       
   188 bool HTMLSelectElement::saveFormControlState(String& value) const
       
   189 {
       
   190     return SelectElement::saveFormControlState(m_data, this, value);
       
   191 }
       
   192 
       
   193 void HTMLSelectElement::restoreFormControlState(const String& state)
       
   194 {
       
   195     SelectElement::restoreFormControlState(m_data, this, state);
       
   196 }
       
   197 
       
   198 void HTMLSelectElement::parseMappedAttribute(Attribute* attr) 
       
   199 {
       
   200     bool oldUsesMenuList = m_data.usesMenuList();
       
   201     if (attr->name() == sizeAttr) {
       
   202         int oldSize = m_data.size();
       
   203         // Set the attribute value to a number.
       
   204         // This is important since the style rules for this attribute can determine the appearance property.
       
   205         int size = attr->value().toInt();
       
   206         String attrSize = String::number(size);
       
   207         if (attrSize != attr->value())
       
   208             attr->setValue(attrSize);
       
   209         size = max(size, 1);
       
   210 
       
   211         // Ensure that we've determined selectedness of the items at least once prior to changing the size.
       
   212         if (oldSize != size)
       
   213             recalcListItemsIfNeeded();
       
   214 
       
   215         m_data.setSize(size);
       
   216         if ((oldUsesMenuList != m_data.usesMenuList() || (!oldUsesMenuList && m_data.size() != oldSize)) && attached()) {
       
   217             detach();
       
   218             attach();
       
   219             setRecalcListItems();
       
   220         }
       
   221     } else if (attr->name() == multipleAttr)
       
   222         SelectElement::parseMultipleAttribute(m_data, this, attr);
       
   223     else if (attr->name() == accesskeyAttr) {
       
   224         // FIXME: ignore for the moment
       
   225     } else if (attr->name() == alignAttr) {
       
   226         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
       
   227         // See http://bugs.webkit.org/show_bug.cgi?id=12072
       
   228     } else if (attr->name() == onchangeAttr) {
       
   229         setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
       
   230     } else
       
   231         HTMLFormControlElementWithState::parseMappedAttribute(attr);
       
   232 }
       
   233 
       
   234 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
       
   235 {
       
   236     if (renderer())
       
   237         return isFocusable();
       
   238     return HTMLFormControlElementWithState::isKeyboardFocusable(event);
       
   239 }
       
   240 
       
   241 bool HTMLSelectElement::isMouseFocusable() const
       
   242 {
       
   243     if (renderer())
       
   244         return isFocusable();
       
   245     return HTMLFormControlElementWithState::isMouseFocusable();
       
   246 }
       
   247 
       
   248 bool HTMLSelectElement::canSelectAll() const
       
   249 {
       
   250     return !m_data.usesMenuList(); 
       
   251 }
       
   252 
       
   253 void HTMLSelectElement::selectAll()
       
   254 {
       
   255     SelectElement::selectAll(m_data, this);
       
   256 }
       
   257 
       
   258 RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*)
       
   259 {
       
   260     if (m_data.usesMenuList())
       
   261         return new (arena) RenderMenuList(this);
       
   262     return new (arena) RenderListBox(this);
       
   263 }
       
   264 
       
   265 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
       
   266 {
       
   267     return SelectElement::appendFormData(m_data, this, list);
       
   268 }
       
   269 
       
   270 int HTMLSelectElement::optionToListIndex(int optionIndex) const
       
   271 {
       
   272     return SelectElement::optionToListIndex(m_data, this, optionIndex);
       
   273 }
       
   274 
       
   275 int HTMLSelectElement::listToOptionIndex(int listIndex) const
       
   276 {
       
   277     return SelectElement::listToOptionIndex(m_data, this, listIndex);
       
   278 }
       
   279 
       
   280 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
       
   281 {
       
   282     return HTMLOptionsCollection::create(this);
       
   283 }
       
   284 
       
   285 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
       
   286 {
       
   287     SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates);
       
   288 }
       
   289 
       
   290 void HTMLSelectElement::recalcListItemsIfNeeded()
       
   291 {
       
   292     if (m_data.shouldRecalcListItems())
       
   293         recalcListItems();
       
   294 }
       
   295 
       
   296 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
       
   297 {
       
   298     setRecalcListItems();
       
   299     HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
       
   300     
       
   301     if (AXObjectCache::accessibilityEnabled() && renderer())
       
   302         renderer()->document()->axObjectCache()->childrenChanged(renderer());
       
   303 }
       
   304 
       
   305 void HTMLSelectElement::setRecalcListItems()
       
   306 {
       
   307     SelectElement::setRecalcListItems(m_data, this);
       
   308 
       
   309     if (!inDocument())
       
   310         m_collectionInfo.reset();
       
   311 }
       
   312 
       
   313 void HTMLSelectElement::reset()
       
   314 {
       
   315     SelectElement::reset(m_data, this);
       
   316 }
       
   317 
       
   318 void HTMLSelectElement::dispatchFocusEvent()
       
   319 {
       
   320     SelectElement::dispatchFocusEvent(m_data, this);
       
   321     HTMLFormControlElementWithState::dispatchFocusEvent();
       
   322 }
       
   323 
       
   324 void HTMLSelectElement::dispatchBlurEvent()
       
   325 {
       
   326     SelectElement::dispatchBlurEvent(m_data, this);
       
   327     HTMLFormControlElementWithState::dispatchBlurEvent();
       
   328 }
       
   329 
       
   330 void HTMLSelectElement::defaultEventHandler(Event* event)
       
   331 {
       
   332     SelectElement::defaultEventHandler(m_data, this, event, form());
       
   333     if (event->defaultHandled())
       
   334         return;
       
   335     HTMLFormControlElementWithState::defaultEventHandler(event);
       
   336 }
       
   337 
       
   338 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
       
   339 {
       
   340     SelectElement::setActiveSelectionAnchorIndex(m_data, this, index);
       
   341 }
       
   342 
       
   343 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
       
   344 {
       
   345     SelectElement::setActiveSelectionEndIndex(m_data, index);
       
   346 }
       
   347 
       
   348 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
       
   349 {
       
   350     SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions);
       
   351 }
       
   352 
       
   353 void HTMLSelectElement::menuListOnChange()
       
   354 {
       
   355     SelectElement::menuListOnChange(m_data, this);
       
   356 }
       
   357 
       
   358 void HTMLSelectElement::listBoxOnChange()
       
   359 {
       
   360     SelectElement::listBoxOnChange(m_data, this);
       
   361 }
       
   362 
       
   363 void HTMLSelectElement::saveLastSelection()
       
   364 {
       
   365     SelectElement::saveLastSelection(m_data, this);
       
   366 }
       
   367 
       
   368 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
       
   369 {
       
   370     focus();
       
   371     dispatchSimulatedClick(0, sendToAnyElement);
       
   372 }
       
   373 
       
   374 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
       
   375 {
       
   376     SelectElement::accessKeySetSelectedIndex(m_data, this, index);
       
   377 }
       
   378     
       
   379 void HTMLSelectElement::setMultiple(bool multiple)
       
   380 {
       
   381     setAttribute(multipleAttr, multiple ? "" : 0);
       
   382 }
       
   383 
       
   384 void HTMLSelectElement::setSize(int size)
       
   385 {
       
   386     setAttribute(sizeAttr, String::number(size));
       
   387 }
       
   388 
       
   389 Node* HTMLSelectElement::namedItem(const AtomicString& name)
       
   390 {
       
   391     return options()->namedItem(name);
       
   392 }
       
   393 
       
   394 Node* HTMLSelectElement::item(unsigned index)
       
   395 {
       
   396     return options()->item(index);
       
   397 }
       
   398 
       
   399 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
       
   400 {
       
   401     ec = 0;
       
   402     if (index > maxSelectItems - 1)
       
   403         index = maxSelectItems - 1;
       
   404     int diff = index  - length();
       
   405     HTMLElement* before = 0;
       
   406     // out of array bounds ? first insert empty dummies
       
   407     if (diff > 0) {
       
   408         setLength(index, ec);
       
   409         // replace an existing entry ?
       
   410     } else if (diff < 0) {
       
   411         before = static_cast<HTMLElement*>(options()->item(index+1));
       
   412         remove(index);
       
   413     }
       
   414     // finally add the new element
       
   415     if (!ec) {
       
   416         add(option, before, ec);
       
   417         if (diff >= 0 && option->selected())
       
   418             setSelectedIndex(index, !m_data.multiple());
       
   419     }
       
   420 }
       
   421 
       
   422 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
       
   423 {
       
   424     ec = 0;
       
   425     if (newLen > maxSelectItems)
       
   426         newLen = maxSelectItems;
       
   427     int diff = length() - newLen;
       
   428 
       
   429     if (diff < 0) { // add dummy elements
       
   430         do {
       
   431             RefPtr<Element> option = document()->createElement(optionTag, false);
       
   432             ASSERT(option);
       
   433             add(static_cast<HTMLElement*>(option.get()), 0, ec);
       
   434             if (ec)
       
   435                 break;
       
   436         } while (++diff);
       
   437     } else {
       
   438         const Vector<Element*>& items = listItems();
       
   439 
       
   440         // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list
       
   441         // of elements that we intend to remove then attempt to remove them one at a time.
       
   442         Vector<RefPtr<Element> > itemsToRemove;
       
   443         size_t optionIndex = 0;
       
   444         for (size_t i = 0; i < items.size(); ++i) {
       
   445             Element* item = items[i];
       
   446             if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) {
       
   447                 ASSERT(item->parentNode());
       
   448                 itemsToRemove.append(item);
       
   449             }
       
   450         }
       
   451 
       
   452         for (size_t i = 0; i < itemsToRemove.size(); ++i) {
       
   453             Element* item = itemsToRemove[i].get();
       
   454             if (item->parentNode()) {
       
   455                 item->parentNode()->removeChild(item, ec);
       
   456             }
       
   457         }
       
   458     }
       
   459 }
       
   460 
       
   461 void HTMLSelectElement::scrollToSelection()
       
   462 {
       
   463     SelectElement::scrollToSelection(m_data, this);
       
   464 }
       
   465 
       
   466 void HTMLSelectElement::insertedIntoTree(bool deep)
       
   467 {
       
   468     SelectElement::insertedIntoTree(m_data, this);
       
   469     HTMLFormControlElementWithState::insertedIntoTree(deep);
       
   470 }
       
   471 
       
   472 } // namespace