diff -r 000000000000 -r 4f2f89ce4247 WebCore/dom/SelectElement.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/dom/SelectElement.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,995 @@ +/* + * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * + * 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 "SelectElement.h" + +#include "Attribute.h" +#include "CharacterNames.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "Element.h" +#include "EventHandler.h" +#include "EventNames.h" +#include "FormDataList.h" +#include "Frame.h" +#include "HTMLFormElement.h" +#include "HTMLKeygenElement.h" +#include "HTMLNames.h" +#include "HTMLSelectElement.h" +#include "KeyboardEvent.h" +#include "MouseEvent.h" +#include "OptionElement.h" +#include "OptionGroupElement.h" +#include "Page.h" +#include "RenderListBox.h" +#include "RenderMenuList.h" +#include + +#if ENABLE(WML) +#include "WMLNames.h" +#include "WMLSelectElement.h" +#endif + +// Configure platform-specific behavior when focused pop-up receives arrow/space/return keystroke. +// (PLATFORM(MAC) and PLATFORM(GTK) are always false in Chromium, hence the extra tests.) +#if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN)) +#define ARROW_KEYS_POP_MENU 1 +#define SPACE_OR_RETURN_POP_MENU 0 +#elif PLATFORM(GTK) || (PLATFORM(CHROMIUM) && OS(LINUX)) +#define ARROW_KEYS_POP_MENU 0 +#define SPACE_OR_RETURN_POP_MENU 1 +#else +#define ARROW_KEYS_POP_MENU 0 +#define SPACE_OR_RETURN_POP_MENU 0 +#endif + +using std::min; +using std::max; +using namespace WTF; +using namespace Unicode; + +namespace WebCore { + +static const DOMTimeStamp typeAheadTimeout = 1000; + +void SelectElement::selectAll(SelectElementData& data, Element* element) +{ + ASSERT(!data.usesMenuList()); + if (!element->renderer() || !data.multiple()) + return; + + // Save the selection so it can be compared to the new selectAll selection when dispatching change events + saveLastSelection(data, element); + + data.setActiveSelectionState(true); + setActiveSelectionAnchorIndex(data, element, nextSelectableListIndex(data, element, -1)); + setActiveSelectionEndIndex(data, previousSelectableListIndex(data, element, -1)); + + updateListBoxSelection(data, element, false); + listBoxOnChange(data, element); +} + +void SelectElement::saveLastSelection(SelectElementData& data, Element* element) +{ + if (data.usesMenuList()) { + data.setLastOnChangeIndex(selectedIndex(data, element)); + return; + } + + Vector& lastOnChangeSelection = data.lastOnChangeSelection(); + lastOnChangeSelection.clear(); + + const Vector& items = data.listItems(element); + for (unsigned i = 0; i < items.size(); ++i) { + OptionElement* optionElement = toOptionElement(items[i]); + lastOnChangeSelection.append(optionElement && optionElement->selected()); + } +} + +int SelectElement::nextSelectableListIndex(SelectElementData& data, Element* element, int startIndex) +{ + const Vector& items = data.listItems(element); + int index = startIndex + 1; + while (index >= 0 && (unsigned) index < items.size() && (!isOptionElement(items[index]) || items[index]->disabled())) + ++index; + if ((unsigned) index == items.size()) + return startIndex; + return index; +} + +int SelectElement::previousSelectableListIndex(SelectElementData& data, Element* element, int startIndex) +{ + const Vector& items = data.listItems(element); + if (startIndex == -1) + startIndex = items.size(); + int index = startIndex - 1; + while (index >= 0 && (unsigned) index < items.size() && (!isOptionElement(items[index]) || items[index]->disabled())) + --index; + if (index == -1) + return startIndex; + return index; +} + +void SelectElement::setActiveSelectionAnchorIndex(SelectElementData& data, Element* element, int index) +{ + data.setActiveSelectionAnchorIndex(index); + + // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index + Vector& cachedStateForActiveSelection = data.cachedStateForActiveSelection(); + cachedStateForActiveSelection.clear(); + + const Vector& items = data.listItems(element); + for (unsigned i = 0; i < items.size(); ++i) { + OptionElement* optionElement = toOptionElement(items[i]); + cachedStateForActiveSelection.append(optionElement && optionElement->selected()); + } +} + +void SelectElement::setActiveSelectionEndIndex(SelectElementData& data, int index) +{ + data.setActiveSelectionEndIndex(index); +} + +void SelectElement::updateListBoxSelection(SelectElementData& data, Element* element, bool deselectOtherOptions) +{ + ASSERT(element->renderer() && (element->renderer()->isListBox() || data.multiple())); + ASSERT(data.activeSelectionAnchorIndex() >= 0); + + unsigned start = min(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex()); + unsigned end = max(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex()); + Vector& cachedStateForActiveSelection = data.cachedStateForActiveSelection(); + + const Vector& items = data.listItems(element); + for (unsigned i = 0; i < items.size(); ++i) { + OptionElement* optionElement = toOptionElement(items[i]); + if (!optionElement || items[i]->disabled()) + continue; + + if (i >= start && i <= end) + optionElement->setSelectedState(data.activeSelectionState()); + else if (deselectOtherOptions || i >= cachedStateForActiveSelection.size()) + optionElement->setSelectedState(false); + else + optionElement->setSelectedState(cachedStateForActiveSelection[i]); + } + + scrollToSelection(data, element); +} + +void SelectElement::listBoxOnChange(SelectElementData& data, Element* element) +{ + ASSERT(!data.usesMenuList() || data.multiple()); + + Vector& lastOnChangeSelection = data.lastOnChangeSelection(); + const Vector& items = data.listItems(element); + + // If the cached selection list is empty, or the size has changed, then fire dispatchFormControlChangeEvent, and return early. + if (lastOnChangeSelection.isEmpty() || lastOnChangeSelection.size() != items.size()) { + element->dispatchFormControlChangeEvent(); + return; + } + + // Update lastOnChangeSelection and fire dispatchFormControlChangeEvent + bool fireOnChange = false; + for (unsigned i = 0; i < items.size(); ++i) { + OptionElement* optionElement = toOptionElement(items[i]); + bool selected = optionElement && optionElement->selected(); + if (selected != lastOnChangeSelection[i]) + fireOnChange = true; + lastOnChangeSelection[i] = selected; + } + + if (fireOnChange) + element->dispatchFormControlChangeEvent(); +} + +void SelectElement::menuListOnChange(SelectElementData& data, Element* element) +{ + ASSERT(data.usesMenuList()); + + int selected = selectedIndex(data, element); + if (data.lastOnChangeIndex() != selected && data.userDrivenChange()) { + data.setLastOnChangeIndex(selected); + data.setUserDrivenChange(false); + element->dispatchFormControlChangeEvent(); + } +} + +void SelectElement::scrollToSelection(SelectElementData& data, Element* element) +{ + if (data.usesMenuList()) + return; + + if (RenderObject* renderer = element->renderer()) + toRenderListBox(renderer)->selectionChanged(); +} + +void SelectElement::setOptionsChangedOnRenderer(SelectElementData& data, Element* element) +{ + if (RenderObject* renderer = element->renderer()) { + if (data.usesMenuList()) + toRenderMenuList(renderer)->setOptionsChanged(true); + else + toRenderListBox(renderer)->setOptionsChanged(true); + } +} + +void SelectElement::setRecalcListItems(SelectElementData& data, Element* element) +{ + data.setShouldRecalcListItems(true); + data.setActiveSelectionAnchorIndex(-1); // Manual selection anchor is reset when manipulating the select programmatically. + setOptionsChangedOnRenderer(data, element); + element->setNeedsStyleRecalc(); +} + +void SelectElement::recalcListItems(SelectElementData& data, const Element* element, bool updateSelectedStates) +{ + Vector& listItems = data.rawListItems(); + listItems.clear(); + + data.setShouldRecalcListItems(false); + + OptionElement* foundSelected = 0; + for (Node* currentNode = element->firstChild(); currentNode;) { + if (!currentNode->isElementNode()) { + currentNode = currentNode->traverseNextSibling(element); + continue; + } + + Element* current = static_cast(currentNode); + + // optgroup tags may not nest. However, both FireFox and IE will + // flatten the tree automatically, so we follow suit. + // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6) + if (isOptionGroupElement(current)) { + listItems.append(current); + if (current->firstChild()) { + currentNode = current->firstChild(); + continue; + } + } + + if (OptionElement* optionElement = toOptionElement(current)) { + listItems.append(current); + + if (updateSelectedStates && !data.multiple()) { + if (!foundSelected && (data.size() <= 1 || optionElement->selected())) { + foundSelected = optionElement; + foundSelected->setSelectedState(true); + } else if (foundSelected && optionElement->selected()) { + foundSelected->setSelectedState(false); + foundSelected = optionElement; + } + } + } + + if (current->hasTagName(HTMLNames::hrTag)) + listItems.append(current); + + // In conforming HTML code, only and