diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/html/HTMLSelectElement.cpp | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/html/HTMLSelectElement.cpp')
-rw-r--r-- | Source/WebCore/html/HTMLSelectElement.cpp | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/Source/WebCore/html/HTMLSelectElement.cpp b/Source/WebCore/html/HTMLSelectElement.cpp new file mode 100644 index 0000000..c7f47f2 --- /dev/null +++ b/Source/WebCore/html/HTMLSelectElement.cpp @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 Apple Inc. All rights reserved. + * (C) 2006 Alexey Proskuryakov (ap@nypop.com) + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" +#include "HTMLSelectElement.h" + +#include "AXObjectCache.h" +#include "Attribute.h" +#include "EventNames.h" +#include "HTMLNames.h" +#include "HTMLOptionElement.h" +#include "HTMLOptionsCollection.h" +#include "RenderListBox.h" +#include "RenderMenuList.h" +#include "ScriptEventListener.h" + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +// Upper limit agreed upon with representatives of Opera and Mozilla. +static const unsigned maxSelectItems = 10000; + +HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) + : HTMLFormControlElementWithState(tagName, document, form) +{ + ASSERT(hasTagName(selectTag) || hasTagName(keygenTag)); +} + +PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form) +{ + ASSERT(tagName.matches(selectTag)); + return adoptRef(new HTMLSelectElement(tagName, document, form)); +} + +void HTMLSelectElement::recalcStyle(StyleChange change) +{ + HTMLFormControlElementWithState::recalcStyle(change); +} + +const AtomicString& HTMLSelectElement::formControlType() const +{ + DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple")); + DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one")); + return m_data.multiple() ? selectMultiple : selectOne; +} + +int HTMLSelectElement::selectedIndex() const +{ + return SelectElement::selectedIndex(m_data, this); +} + +void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement) +{ + SelectElement::deselectItems(m_data, this, excludeElement); + setNeedsValidityCheck(); +} + +void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect) +{ + SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, false, false); + setNeedsValidityCheck(); +} + +void HTMLSelectElement::setSelectedIndexByUser(int optionIndex, bool deselect, bool fireOnChangeNow, bool allowMultipleSelection) +{ + // List box selects can fire onchange events through user interaction, such as + // mousedown events. This allows that same behavior programmatically. + if (!m_data.usesMenuList()) { + updateSelectedState(m_data, this, optionIndex, allowMultipleSelection, false); + setNeedsValidityCheck(); + if (fireOnChangeNow) + listBoxOnChange(); + return; + } + + // Bail out if this index is already the selected one, to avoid running unnecessary JavaScript that can mess up + // autofill, when there is no actual change (see https://bugs.webkit.org/show_bug.cgi?id=35256 and rdar://7467917 ). + // Perhaps this logic could be moved into SelectElement, but some callers of SelectElement::setSelectedIndex() + // seem to expect it to fire its change event even when the index was already selected. + if (optionIndex == selectedIndex()) + return; + + SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChangeNow, true); + setNeedsValidityCheck(); +} + +bool HTMLSelectElement::hasPlaceholderLabelOption() const +{ + // The select element has no placeholder label option if it has an attribute "multiple" specified or a display size of non-1. + // + // The condition "size() > 1" is actually not compliant with the HTML5 spec as of Dec 3, 2010. "size() != 1" is correct. + // Using "size() > 1" here because size() may be 0 in WebKit. + // See the discussion at https://bugs.webkit.org/show_bug.cgi?id=43887 + // + // "0 size()" happens when an attribute "size" is absent or an invalid size attribute is specified. + // In this case, the display size should be assumed as the default. + // The default display size is 1 for non-multiple select elements, and 4 for multiple select elements. + // + // Finally, if size() == 0 and non-multiple, the display size can be assumed as 1. + if (multiple() || size() > 1) + return false; + + int listIndex = optionToListIndex(0); + ASSERT(listIndex >= 0); + if (listIndex < 0) + return false; + HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems()[listIndex]); + return !option->disabled() && !listIndex && option->value().isEmpty(); +} + +bool HTMLSelectElement::valueMissing() const +{ + if (!isRequiredFormControl()) + return false; + + int firstSelectionIndex = selectedIndex(); + + // If a non-placeholer label option is selected (firstSelectionIndex > 0), it's not value-missing. + return firstSelectionIndex < 0 || (!firstSelectionIndex && hasPlaceholderLabelOption()); +} + +void HTMLSelectElement::listBoxSelectItem(int listIndex, bool allowMultiplySelections, bool shift, bool fireOnChangeNow) +{ + if (!multiple()) + setSelectedIndexByUser(listToOptionIndex(listIndex), true, fireOnChangeNow); + else { + updateSelectedState(m_data, this, listIndex, allowMultiplySelections, shift); + setNeedsValidityCheck(); + if (fireOnChangeNow) + listBoxOnChange(); + } +} + +int HTMLSelectElement::activeSelectionStartListIndex() const +{ + if (m_data.activeSelectionAnchorIndex() >= 0) + return m_data.activeSelectionAnchorIndex(); + return optionToListIndex(selectedIndex()); +} + +int HTMLSelectElement::activeSelectionEndListIndex() const +{ + if (m_data.activeSelectionEndIndex() >= 0) + return m_data.activeSelectionEndIndex(); + return SelectElement::lastSelectedListIndex(m_data, this); +} + +unsigned HTMLSelectElement::length() const +{ + return SelectElement::optionCount(m_data, this); +} + +void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionCode& ec) +{ + RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it + + if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag))) + return; + + insertBefore(element, before, ec); + setNeedsValidityCheck(); +} + +void HTMLSelectElement::remove(int optionIndex) +{ + int listIndex = optionToListIndex(optionIndex); + if (listIndex < 0) + return; + + ExceptionCode ec; + listItems()[listIndex]->remove(ec); +} + +void HTMLSelectElement::remove(HTMLOptionElement* option) +{ + if (option->ownerSelectElement() != this) + return; + + ExceptionCode ec; + option->remove(ec); +} + +String HTMLSelectElement::value() +{ + const Vector<Element*>& items = listItems(); + for (unsigned i = 0; i < items.size(); i++) { + if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected()) + return static_cast<HTMLOptionElement*>(items[i])->value(); + } + return ""; +} + +void HTMLSelectElement::setValue(const String &value) +{ + if (value.isNull()) + return; + // find the option with value() matching the given parameter + // and make it the current selection. + const Vector<Element*>& items = listItems(); + unsigned optionIndex = 0; + for (unsigned i = 0; i < items.size(); i++) { + if (items[i]->hasLocalName(optionTag)) { + if (static_cast<HTMLOptionElement*>(items[i])->value() == value) { + setSelectedIndex(optionIndex, true); + return; + } + optionIndex++; + } + } +} + +bool HTMLSelectElement::saveFormControlState(String& value) const +{ + return SelectElement::saveFormControlState(m_data, this, value); +} + +void HTMLSelectElement::restoreFormControlState(const String& state) +{ + SelectElement::restoreFormControlState(m_data, this, state); + setNeedsValidityCheck(); +} + +void HTMLSelectElement::parseMappedAttribute(Attribute* attr) +{ + bool oldUsesMenuList = m_data.usesMenuList(); + if (attr->name() == sizeAttr) { + int oldSize = m_data.size(); + // Set the attribute value to a number. + // This is important since the style rules for this attribute can determine the appearance property. + int size = attr->value().toInt(); + String attrSize = String::number(size); + if (attrSize != attr->value()) + attr->setValue(attrSize); + size = max(size, 1); + + // Ensure that we've determined selectedness of the items at least once prior to changing the size. + if (oldSize != size) + recalcListItemsIfNeeded(); + + m_data.setSize(size); + setNeedsValidityCheck(); + if ((oldUsesMenuList != m_data.usesMenuList() || (!oldUsesMenuList && m_data.size() != oldSize)) && attached()) { + detach(); + attach(); + setRecalcListItems(); + } + } else if (attr->name() == multipleAttr) + SelectElement::parseMultipleAttribute(m_data, this, attr); + else if (attr->name() == accesskeyAttr) { + // FIXME: ignore for the moment + } else if (attr->name() == alignAttr) { + // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. + // See http://bugs.webkit.org/show_bug.cgi?id=12072 + } else if (attr->name() == onchangeAttr) { + setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr)); + } else + HTMLFormControlElementWithState::parseMappedAttribute(attr); +} + +bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const +{ + if (renderer()) + return isFocusable(); + return HTMLFormControlElementWithState::isKeyboardFocusable(event); +} + +bool HTMLSelectElement::isMouseFocusable() const +{ + if (renderer()) + return isFocusable(); + return HTMLFormControlElementWithState::isMouseFocusable(); +} + +bool HTMLSelectElement::canSelectAll() const +{ + return !m_data.usesMenuList(); +} + +void HTMLSelectElement::selectAll() +{ + SelectElement::selectAll(m_data, this); + setNeedsValidityCheck(); +} + +RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*) +{ + if (m_data.usesMenuList()) + return new (arena) RenderMenuList(this); + return new (arena) RenderListBox(this); +} + +bool HTMLSelectElement::appendFormData(FormDataList& list, bool) +{ + return SelectElement::appendFormData(m_data, this, list); +} + +int HTMLSelectElement::optionToListIndex(int optionIndex) const +{ + return SelectElement::optionToListIndex(m_data, this, optionIndex); +} + +int HTMLSelectElement::listToOptionIndex(int listIndex) const +{ + return SelectElement::listToOptionIndex(m_data, this, listIndex); +} + +PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options() +{ + return HTMLOptionsCollection::create(this); +} + +void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const +{ + SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates); +} + +void HTMLSelectElement::recalcListItemsIfNeeded() +{ + if (m_data.shouldRecalcListItems()) + recalcListItems(); +} + +void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) +{ + setRecalcListItems(); + setNeedsValidityCheck(); + HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); + + if (AXObjectCache::accessibilityEnabled() && renderer()) + renderer()->document()->axObjectCache()->childrenChanged(renderer()); +} + +void HTMLSelectElement::setRecalcListItems() +{ + SelectElement::setRecalcListItems(m_data, this); + + if (!inDocument()) + m_collectionInfo.reset(); +} + +void HTMLSelectElement::reset() +{ + SelectElement::reset(m_data, this); + setNeedsValidityCheck(); +} + +void HTMLSelectElement::dispatchFocusEvent() +{ + SelectElement::dispatchFocusEvent(m_data, this); + HTMLFormControlElementWithState::dispatchFocusEvent(); +} + +void HTMLSelectElement::dispatchBlurEvent() +{ + SelectElement::dispatchBlurEvent(m_data, this); + HTMLFormControlElementWithState::dispatchBlurEvent(); +} + +void HTMLSelectElement::defaultEventHandler(Event* event) +{ + SelectElement::defaultEventHandler(m_data, this, event, form()); + if (event->defaultHandled()) + return; + HTMLFormControlElementWithState::defaultEventHandler(event); +} + +void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) +{ + SelectElement::setActiveSelectionAnchorIndex(m_data, this, index); +} + +void HTMLSelectElement::setActiveSelectionEndIndex(int index) +{ + SelectElement::setActiveSelectionEndIndex(m_data, index); +} + +void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions) +{ + SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions); + setNeedsValidityCheck(); +} + +void HTMLSelectElement::menuListOnChange() +{ + SelectElement::menuListOnChange(m_data, this); +} + +void HTMLSelectElement::listBoxOnChange() +{ + SelectElement::listBoxOnChange(m_data, this); +} + +void HTMLSelectElement::saveLastSelection() +{ + SelectElement::saveLastSelection(m_data, this); +} + +void HTMLSelectElement::accessKeyAction(bool sendToAnyElement) +{ + focus(); + dispatchSimulatedClick(0, sendToAnyElement); +} + +void HTMLSelectElement::accessKeySetSelectedIndex(int index) +{ + SelectElement::accessKeySetSelectedIndex(m_data, this, index); +} + +void HTMLSelectElement::setMultiple(bool multiple) +{ + setAttribute(multipleAttr, multiple ? "" : 0); +} + +void HTMLSelectElement::setSize(int size) +{ + setAttribute(sizeAttr, String::number(size)); +} + +Node* HTMLSelectElement::namedItem(const AtomicString& name) +{ + return options()->namedItem(name); +} + +Node* HTMLSelectElement::item(unsigned index) +{ + return options()->item(index); +} + +void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec) +{ + ec = 0; + if (index > maxSelectItems - 1) + index = maxSelectItems - 1; + int diff = index - length(); + HTMLElement* before = 0; + // out of array bounds ? first insert empty dummies + if (diff > 0) { + setLength(index, ec); + // replace an existing entry ? + } else if (diff < 0) { + before = static_cast<HTMLElement*>(options()->item(index+1)); + remove(index); + } + // finally add the new element + if (!ec) { + add(option, before, ec); + if (diff >= 0 && option->selected()) + setSelectedIndex(index, !m_data.multiple()); + } +} + +void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec) +{ + ec = 0; + if (newLen > maxSelectItems) + newLen = maxSelectItems; + int diff = length() - newLen; + + if (diff < 0) { // add dummy elements + do { + RefPtr<Element> option = document()->createElement(optionTag, false); + ASSERT(option); + add(static_cast<HTMLElement*>(option.get()), 0, ec); + if (ec) + break; + } while (++diff); + } else { + const Vector<Element*>& items = listItems(); + + // Removing children fires mutation events, which might mutate the DOM further, so we first copy out a list + // of elements that we intend to remove then attempt to remove them one at a time. + Vector<RefPtr<Element> > itemsToRemove; + size_t optionIndex = 0; + for (size_t i = 0; i < items.size(); ++i) { + Element* item = items[i]; + if (item->hasLocalName(optionTag) && optionIndex++ >= newLen) { + ASSERT(item->parentNode()); + itemsToRemove.append(item); + } + } + + for (size_t i = 0; i < itemsToRemove.size(); ++i) { + Element* item = itemsToRemove[i].get(); + if (item->parentNode()) { + item->parentNode()->removeChild(item, ec); + } + } + } + setNeedsValidityCheck(); +} + +void HTMLSelectElement::scrollToSelection() +{ + SelectElement::scrollToSelection(m_data, this); +} + +void HTMLSelectElement::insertedIntoTree(bool deep) +{ + SelectElement::insertedIntoTree(m_data, this); + HTMLFormControlElementWithState::insertedIntoTree(deep); +} + +bool HTMLSelectElement::isRequiredFormControl() const +{ + return required(); +} + +} // namespace |